diff --git a/example/listener.dart b/example/listener.dart new file mode 100644 index 0000000..04849f0 --- /dev/null +++ b/example/listener.dart @@ -0,0 +1,54 @@ +import 'dart:convert'; +import 'package:lite_agent_core_dart/lite_agent_core.dart'; +import 'package:opentool_dart/opentool_dart.dart'; + +void listen(AgentMessageDto agentMessageDto) { + String system = "🖥SYSTEM"; + String user = "👤USER"; + String agent = "🤖AGENT"; + String llm = "💡LLM"; + String tool = "🔧TOOL"; + String client = "🔗CLIENT"; + + String message = ""; + if (agentMessageDto.type == ToolMessageType.TEXT) + message = agentMessageDto.message as String; + if (agentMessageDto.type == ToolMessageType.IMAGE_URL) + message = agentMessageDto.message as String; + if (agentMessageDto.type == ToolMessageType.FUNCTION_CALL_LIST) { + List originalFunctionCallList = + agentMessageDto.message as List; + List functionCallList = + originalFunctionCallList.map((dynamic json) { + return FunctionCall.fromJson(json); + }).toList(); + message = jsonEncode(functionCallList); + } + if (agentMessageDto.type == ToolMessageType.TOOL_RETURN) { + message = jsonEncode(ToolReturn.fromJson(agentMessageDto.message)); + } + ; + + String from = ""; + if (agentMessageDto.from == ToolRoleType.SYSTEM) { + from = system; + message = "\n$message"; + } + if (agentMessageDto.from == ToolRoleType.USER) from = user; + if (agentMessageDto.from == ToolRoleType.AGENT) from = agent; + if (agentMessageDto.from == ToolRoleType.LLM) from = llm; + if (agentMessageDto.from == ToolRoleType.TOOL) from = tool; + if (agentMessageDto.from == ToolRoleType.CLIENT) from = client; + + String to = ""; + if (agentMessageDto.to == ToolRoleType.SYSTEM) to = system; + if (agentMessageDto.to == ToolRoleType.USER) to = user; + if (agentMessageDto.to == ToolRoleType.AGENT) to = agent; + if (agentMessageDto.to == ToolRoleType.LLM) to = llm; + if (agentMessageDto.to == ToolRoleType.TOOL) to = tool; + if (agentMessageDto.to == ToolRoleType.CLIENT) to = client; + + if (from.isNotEmpty && to.isNotEmpty) { + print("#${agentMessageDto.sessionId}:${agentMessageDto.taskId}# $from -> $to: [${agentMessageDto.type}] $message"); + } +} \ No newline at end of file diff --git a/example/mock/mock_client_example.dart b/example/mock/mock_client_example.dart index 1d93c0b..55b16a7 100644 --- a/example/mock/mock_client_example.dart +++ b/example/mock/mock_client_example.dart @@ -4,7 +4,7 @@ import 'package:dio/dio.dart'; import 'package:lite_agent_core_dart/lite_agent_core.dart'; import 'package:dotenv/dotenv.dart'; import 'package:lite_agent_core_dart_server/src/config.dart'; -import 'package:opentool_dart/opentool_dart.dart'; +import '../listener.dart'; String prompt = "List count of the storage"; @@ -29,7 +29,7 @@ Future main() async { if (sessionDto != null) { print("[sessionDto] " + sessionDto.toJson().toString()); - WebSocket webSocket = await connectChat(sessionDto.id, (agentMessageDto) => printAgentMessage(agentMessageDto)); + WebSocket webSocket = await connectChat(sessionDto.id, (agentMessageDto) => listen(agentMessageDto)); print("[webSocket] " + webSocket.toString()); @@ -75,9 +75,8 @@ Future initChat(CapabilityDto capabilityDto) async { } Future connectChat( - String sessionId, onReceive(AgentMessageDto)) async { - final String url = - 'ws://127.0.0.1:${config.server.port}${config.server.apiPathPrefix}/chat?id=$sessionId'; + String sessionId, onReceive(AgentMessageDto)) async { + final String url = 'ws://127.0.0.1:${config.server.port}${config.server.apiPathPrefix}/chat?id=$sessionId'; final WebSocket socket = await WebSocket.connect( url, @@ -109,7 +108,7 @@ Future connectChat( Future sendUserMessage(WebSocket socket, String prompt) async { UserMessageDto userMessageDto = UserMessageDto(type: UserMessageDtoType.text, message: prompt); - UserTaskDto userTaskDto = UserTaskDto(taskId: "0", contentList: [userMessageDto]); + UserTaskDto userTaskDto = UserTaskDto(contentList: [userMessageDto]); socket.add(jsonEncode(userTaskDto.toJson())); } @@ -146,58 +145,6 @@ Future clearChat(String sessionId, WebSocket socket) async { return null; } -void printAgentMessage(AgentMessageDto agentMessageDto) { - String system = "🖥SYSTEM"; - String user = "👤USER"; - String agent = "🤖AGENT"; - String llm = "💡LLM"; - String tool = "🔧TOOL"; - String client = "🔗CLIENT"; - - String message = ""; - if (agentMessageDto.type == ToolMessageType.TEXT) - message = agentMessageDto.message as String; - if (agentMessageDto.type == ToolMessageType.IMAGE_URL) - message = agentMessageDto.message as String; - if (agentMessageDto.type == ToolMessageType.FUNCTION_CALL_LIST) { - List originalFunctionCallList = - agentMessageDto.message as List; - List functionCallList = - originalFunctionCallList.map((dynamic json) { - return FunctionCall.fromJson(json); - }).toList(); - message = jsonEncode(functionCallList); - } - if (agentMessageDto.type == ToolMessageType.TOOL_RETURN) { - message = jsonEncode(ToolReturn.fromJson(agentMessageDto.message)); - } - ; - - String from = ""; - if (agentMessageDto.from == ToolRoleType.SYSTEM) { - from = system; - message = "\n$message"; - } - if (agentMessageDto.from == ToolRoleType.USER) from = user; - if (agentMessageDto.from == ToolRoleType.AGENT) from = agent; - if (agentMessageDto.from == ToolRoleType.LLM) from = llm; - if (agentMessageDto.from == ToolRoleType.TOOL) from = tool; - if (agentMessageDto.from == ToolRoleType.CLIENT) from = client; - - String to = ""; - if (agentMessageDto.to == ToolRoleType.SYSTEM) to = system; - if (agentMessageDto.to == ToolRoleType.USER) to = user; - if (agentMessageDto.to == ToolRoleType.AGENT) to = agent; - if (agentMessageDto.to == ToolRoleType.LLM) to = llm; - if (agentMessageDto.to == ToolRoleType.TOOL) to = tool; - if (agentMessageDto.to == ToolRoleType.CLIENT) to = client; - - if (from.isNotEmpty && to.isNotEmpty) { - print( - "#${agentMessageDto.sessionId}# $from -> $to: [${agentMessageDto.type}] $message"); - } -} - LLMConfigDto _buildLLMConfigDto() { DotEnv env = DotEnv(); env.load(['example/.env']); @@ -217,15 +164,15 @@ Future> _buildOpenSpecList() async { "mock_openapi.json" ]; - List OpenSpecDtoList = []; + List openSpecDtoList = []; for (String openAPIFileName in openAPIFileNameList) { String jsonPath = "$openAPIFolder${Platform.pathSeparator}$openAPIFileName"; File file = File(jsonPath); String jsonString = await file.readAsString(); OpenSpecDto openSpecDto = OpenSpecDto(openSpec: jsonString, protocol: Protocol.OPENAPI); - OpenSpecDtoList.add(openSpecDto); + openSpecDtoList.add(openSpecDto); } - return OpenSpecDtoList; + return openSpecDtoList; } diff --git a/example/mock/server/mock_openapi.json b/example/mock/server/mock_openapi.json index 7f16bd7..68850df 100644 --- a/example/mock/server/mock_openapi.json +++ b/example/mock/server/mock_openapi.json @@ -40,17 +40,15 @@ "/create": { "post": { "summary": "Add a new text to storage", - "parameters": [ - { - "name": "text", - "in": "path", - "required": true, - "description": "Text should be added", - "schema": { - "$ref": "#/components/schemas/Text" + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Text" + } } } - ], + }, "responses": { "200": { "description": "Add successfully", @@ -68,17 +66,15 @@ "/read": { "post": { "summary": "Read text by Id", - "parameters": [ - { - "name": "Id", - "in": "path", - "required": true, - "description": "Read text in storage by Id", - "schema": { - "$ref": "#/components/schemas/Id" + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Id" + } } } - ], + }, "responses": { "200": { "description": "Text in storage", @@ -96,17 +92,15 @@ "/update": { "post": { "summary": "Update text in storage by Id", - "parameters": [ - { - "name": "update", - "in": "path", - "required": true, - "description": "The update info include text and Id", - "schema": { - "$ref": "#/components/schemas/Update" + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Update" + } } } - ], + }, "responses": { "200": { "description": "Update result", @@ -124,17 +118,15 @@ "/delete": { "post": { "summary": "Delete text in storage by Id", - "parameters": [ - { - "name": "Id", - "in": "path", - "required": true, - "description": "The Id of the text be deleted", - "schema": { - "$ref": "#/components/schemas/Id" + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Id" + } } } - ], + }, "responses": { "200": { "description": "Delete result", @@ -166,6 +158,7 @@ }, "Id": { "type": "object", + "description": "Text in storage by Id", "required": [ "id" ], diff --git a/example/multi_agent_example.dart b/example/multi_agent_example.dart new file mode 100644 index 0000000..8dc40e4 --- /dev/null +++ b/example/multi_agent_example.dart @@ -0,0 +1,188 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:dotenv/dotenv.dart'; +import 'package:lite_agent_core_dart/lite_agent_core.dart'; +import 'package:lite_agent_core_dart_server/src/config.dart'; +import 'listener.dart'; + +Config config = initConfig(); + +Dio dio = Dio(BaseOptions( + baseUrl: + "http://127.0.0.1:${config.server.port}${config.server.apiPathPrefix}", + // headers: {"Authorization": "Bearer "} +)); + + + +Future main() async { + + DotEnv env = DotEnv();env.load(['example/.env']);LLMConfigDto llmConfig = LLMConfigDto(baseUrl: env["baseUrl"]!, apiKey: env["apiKey"]!, model: "gpt-4o-mini"); + + String systemPrompt = "You play the role of a tool caller. You can help me decide which tool to call to complete the task according to my requirements. You can only call one tool at a time. \n\nYou support the following tools:\n\n 1. Translation\n 2. Add, delete, modify and query tools"; + String prompt = "Find the text with ID 0 and translate it into Chinese."; + + SessionDto? sessionDto1 = await _buildTextAgent(); + SessionDto? sessionDto2 = await _buildToolAgent(); + + if(sessionDto1 == null || sessionDto2 == null) { + print("Init sub session failed."); + return; + } + + CapabilityDto capabilityDto = CapabilityDto(llmConfig: llmConfig, systemPrompt: systemPrompt, + sessionList: [sessionDto1, sessionDto2] + ); + + SessionDto? sessionDto = await initChat(capabilityDto); + + if (sessionDto != null) { + print("[sessionDto] " + sessionDto.toJson().toString()); + WebSocket webSocket = await connectChat(sessionDto.id, (agentMessageDto) => listen(agentMessageDto)); + + print("[webSocket] " + webSocket.toString()); + + await sendUserMessage(webSocket, prompt); + print("[prompt] " + prompt); + + await sleep(30); + + await sendPing(webSocket); + print("[ping]"); + + await sleep(2); + + SessionDto? stopSessionDto = await stopChat(sessionDto.id); + print("[stopSessionDto] " + stopSessionDto!.toJson().toString()); + + await sleep(5); + + SessionDto? clearSessionDto = await clearChat(sessionDto.id, webSocket); + print("[clearSessionDto] " + clearSessionDto!.toJson().toString()); + } +} + +Future _buildTextAgent() async { + DotEnv env = DotEnv();env.load(['example/.env']);LLMConfigDto llmConfig = LLMConfigDto(baseUrl: env["baseUrl"]!, apiKey: env["apiKey"]!, model: "gpt-4o-mini"); + + String systemPrompt = "Playing as a translator, knowing how to translate between languages."; + + CapabilityDto capabilityDto = CapabilityDto(llmConfig: llmConfig, systemPrompt: systemPrompt); + return await initChat(capabilityDto); +} + +Future _buildToolAgent() async { + DotEnv env = DotEnv();env.load(['example/.env']);LLMConfigDto llmConfig = LLMConfigDto(baseUrl: env["baseUrl"]!, apiKey: env["apiKey"]!, model: "gpt-4o-mini"); + + String openAPIFolder = "${Directory.current.path}${Platform.pathSeparator}example${Platform.pathSeparator}mock${Platform.pathSeparator}server"; + List openAPIFileNameList = [ + "mock_openapi.json" + ]; + + List openSpecDtoList = []; + for (String openAPIFileName in openAPIFileNameList) { + String jsonPath = "$openAPIFolder${Platform.pathSeparator}$openAPIFileName"; + File file = File(jsonPath); + String jsonString = await file.readAsString(); + OpenSpecDto openSpecDto = OpenSpecDto(openSpec: jsonString, protocol: Protocol.OPENAPI); + openSpecDtoList.add(openSpecDto); + } + + String systemPrompt = "A storage management tool that knows how to add, delete, modify, and query my texts."; + + CapabilityDto capabilityDto = CapabilityDto(llmConfig: llmConfig, systemPrompt: systemPrompt, openSpecList: openSpecDtoList); + + return await initChat(capabilityDto); +} + +Future sleep(int seconds) async { + for (int i = seconds; i > 0; i--) { + print(i); + await Future.delayed(Duration(seconds: 1)); + } +} + +Future initChat(CapabilityDto capabilityDto) async { + try { + Response response = await dio.post('/init', data: capabilityDto.toJson()); + final payload = response.data as String; + final data = jsonDecode(payload); + SessionDto sessionDto = SessionDto.fromJson(data); + print("[initChat->RES] " + sessionDto.toJson().toString()); + return sessionDto; + } catch (e) { + print(e); + } + return null; +} + +Future connectChat( + String sessionId, onReceive(AgentMessageDto)) async { + final String url = 'ws://127.0.0.1:${config.server.port}${config.server.apiPathPrefix}/chat?id=$sessionId'; + + final WebSocket socket = await WebSocket.connect( + url, + // headers: {"Authorization": "Bearer "} + ); + + socket.listen((message) { + final payload = message as String; + if (payload != "pong") { + final data = jsonDecode(payload); + AgentMessageDto agentMessageDto = AgentMessageDto.fromJson(data); + onReceive(agentMessageDto); + } else { + print("[pong]"); + } + }, + onDone: () { + print('WebSocket connection closed'); + }, + onError: (error) { + print('WebSocket error: $error'); + }, + cancelOnError: true, + ); + + return socket; +} + +Future sendUserMessage(WebSocket socket, String prompt) async { + UserMessageDto userMessageDto = UserMessageDto(type: UserMessageDtoType.text, message: prompt); + UserTaskDto userTaskDto = UserTaskDto(contentList: [userMessageDto]); + socket.add(jsonEncode(userTaskDto.toJson())); +} + +Future sendPing(WebSocket socket) async { + socket.add("ping"); +} + +Future stopChat(String sessionId) async { + try { + Response response = + await dio.get('/stop', queryParameters: {"id": sessionId}); + final payload = response.data as String; + final data = jsonDecode(payload); + SessionDto sessionDto = SessionDto.fromJson(data); + return sessionDto; + } catch (e) { + print(e); + } + return null; +} + +Future clearChat(String sessionId, WebSocket socket) async { + try { + Response response = + await dio.get('/clear', queryParameters: {"id": sessionId}); + final payload = response.data as String; + final data = jsonDecode(payload); + SessionDto sessionDto = SessionDto.fromJson(data); + await socket.close(); + return sessionDto; + } catch (e) { + print(e); + } + return null; +} \ No newline at end of file diff --git a/example/client_example.dart b/example/single_agent_example.dart similarity index 67% rename from example/client_example.dart rename to example/single_agent_example.dart index 4077b2c..9a3e112 100644 --- a/example/client_example.dart +++ b/example/single_agent_example.dart @@ -4,9 +4,10 @@ import 'package:dio/dio.dart'; import 'package:lite_agent_core_dart/lite_agent_core.dart'; import 'package:dotenv/dotenv.dart'; import 'package:lite_agent_core_dart_server/src/config.dart'; -import 'package:opentool_dart/opentool_dart.dart'; -String prompt = "Get some tool status."; +import 'listener.dart'; + +String prompt = "Find the text with ID 0."; Config config = initConfig(); @@ -16,6 +17,11 @@ Dio dio = Dio(BaseOptions( // headers: {"Authorization": "Bearer "} )); + +/// [IMPORTANT] Prepare: +/// 1. HTTP Server, according to `/example/mock/server/mock_http_server`, which server is running. +/// 2. OneAPI JSON file, which is described the HTTP Server API. +/// 3. Add LLM baseUrl and apiKey to `.env` file Future main() async { CapabilityDto capabilityDto = CapabilityDto( llmConfig: _buildLLMConfigDto(), @@ -30,14 +36,14 @@ Future main() async { if (sessionDto != null) { print("[sessionDto] " + sessionDto.toJson().toString()); - WebSocket webSocket = await connectChat(sessionDto.id, (agentMessageDto) => printAgentMessage(agentMessageDto)); + WebSocket webSocket = await connectChat(sessionDto.id, (agentMessageDto) => listen(agentMessageDto)); print("[webSocket] " + webSocket.toString()); await sendUserMessage(webSocket, prompt); print("[prompt] " + prompt); - await sleep(10); + await sleep(20); await sendPing(webSocket); print("[ping]"); @@ -77,8 +83,7 @@ Future initChat(CapabilityDto capabilityDto) async { Future connectChat( String sessionId, onReceive(AgentMessageDto)) async { - final String url = - 'ws://127.0.0.1:${config.server.port}${config.server.apiPathPrefix}/chat?id=$sessionId'; + final String url = 'ws://127.0.0.1:${config.server.port}${config.server.apiPathPrefix}/chat?id=$sessionId'; final WebSocket socket = await WebSocket.connect( url, @@ -111,7 +116,7 @@ Future connectChat( Future sendUserMessage(WebSocket socket, String prompt) async { UserMessageDto userMessageDto = UserMessageDto(type: UserMessageDtoType.text, message: prompt); UserTaskDto userTaskDto = UserTaskDto(contentList: [userMessageDto]); - socket.add(jsonEncode([userTaskDto.toJson()])); + socket.add(jsonEncode(userTaskDto.toJson())); } Future sendPing(WebSocket socket) async { @@ -147,58 +152,6 @@ Future clearChat(String sessionId, WebSocket socket) async { return null; } -void printAgentMessage(AgentMessageDto agentMessageDto) { - String system = "🖥SYSTEM"; - String user = "👤USER"; - String agent = "🤖AGENT"; - String llm = "💡LLM"; - String tool = "🔧TOOL"; - String client = "🔗CLIENT"; - - String message = ""; - if (agentMessageDto.type == ToolMessageType.TEXT) - message = agentMessageDto.message as String; - if (agentMessageDto.type == ToolMessageType.IMAGE_URL) - message = agentMessageDto.message as String; - if (agentMessageDto.type == ToolMessageType.FUNCTION_CALL_LIST) { - List originalFunctionCallList = - agentMessageDto.message as List; - List functionCallList = - originalFunctionCallList.map((dynamic json) { - return FunctionCall.fromJson(json); - }).toList(); - message = jsonEncode(functionCallList); - } - if (agentMessageDto.type == ToolMessageType.TOOL_RETURN) { - message = jsonEncode(ToolReturn.fromJson(agentMessageDto.message)); - } - ; - - String from = ""; - if (agentMessageDto.from == ToolRoleType.SYSTEM) { - from = system; - message = "\n$message"; - } - if (agentMessageDto.from == ToolRoleType.USER) from = user; - if (agentMessageDto.from == ToolRoleType.AGENT) from = agent; - if (agentMessageDto.from == ToolRoleType.LLM) from = llm; - if (agentMessageDto.from == ToolRoleType.TOOL) from = tool; - if (agentMessageDto.from == ToolRoleType.CLIENT) from = client; - - String to = ""; - if (agentMessageDto.to == ToolRoleType.SYSTEM) to = system; - if (agentMessageDto.to == ToolRoleType.USER) to = user; - if (agentMessageDto.to == ToolRoleType.AGENT) to = agent; - if (agentMessageDto.to == ToolRoleType.LLM) to = llm; - if (agentMessageDto.to == ToolRoleType.TOOL) to = tool; - if (agentMessageDto.to == ToolRoleType.CLIENT) to = client; - - if (from.isNotEmpty && to.isNotEmpty) { - print( - "#${agentMessageDto.sessionId}# $from -> $to: [${agentMessageDto.type}] $message"); - } -} - LLMConfigDto _buildLLMConfigDto() { DotEnv env = DotEnv(); env.load(['example/.env']); @@ -209,17 +162,14 @@ LLMConfigDto _buildLLMConfigDto() { /// Use Prompt engineering to design SystemPrompt /// https://platform.openai.com/docs/guides/prompt-engineering String _buildSystemPrompt() { - return 'You are a tools caller, who can call book system tools to help me manage my books.'; + return 'A storage management tool that knows how to add, delete, modify, and query my texts.'; } Future> _buildOpenSpecList() async { - String openAPIFolder = - "${Directory.current.path}${Platform.pathSeparator}example${Platform.pathSeparator}json${Platform.pathSeparator}openrpc"; + String openAPIFolder = "${Directory.current.path}${Platform.pathSeparator}example${Platform.pathSeparator}mock${Platform.pathSeparator}server"; List openAPIFileNameList = [ - "json-rpc-book.json" - + "mock_openapi.json" /// you can add more tool spec json file. - // "json-rpc-food.json" ]; List OpenSpecDtoList = []; @@ -227,7 +177,7 @@ Future> _buildOpenSpecList() async { String jsonPath = "$openAPIFolder${Platform.pathSeparator}$openAPIFileName"; File file = File(jsonPath); String jsonString = await file.readAsString(); - OpenSpecDto openSpecDto = OpenSpecDto(openSpec: jsonString, protocol: Protocol.JSONRPCHTTP); + OpenSpecDto openSpecDto = OpenSpecDto(openSpec: jsonString, protocol: Protocol.OPENAPI); OpenSpecDtoList.add(openSpecDto); } diff --git a/lib/src/controller.dart b/lib/src/controller.dart index d3ee854..80dd139 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -74,7 +74,7 @@ class AgentController { final data = jsonDecode(payload); UserTaskDto userTaskDto = UserTaskDto.fromJson(data); logger.log(LogModule.ws, "Receive message", detail: payload); - agentService.startChat(sessionDto.id, userTaskDto); + agentService.startChat(sessionDto.id, userTaskDto); } }, onDone: () { logger.log(LogModule.ws, "onDone", detail: jsonEncode(data));