diff --git a/doc/TodoList.md b/doc/TodoList.md index b4fcad0..9812b21 100644 --- a/doc/TodoList.md +++ b/doc/TodoList.md @@ -5,11 +5,18 @@ ## 核心程序 - [x] 提示词调用完整实现:以面向对象形式取代字典形式,加入参数和描述 -- [ ] 接入本地文件系统等基础性MCP服务 +- [x] 接入本地文件系统等基础性MCP服务 - [ ] Python代码执行的实现 -- [ ] 适配qwen3、deepseek等推理模型并迁移 +- [x] 适配qwen3、deepseek等推理模型并迁移 - [x] 工具执行错误消息合并为一条 ## 提示词工程 +- [ ] 将原来的单独XML规则修改为内嵌XML规则 +- [ ] 系统提示词明确提示调用动态Prompt和知识库 +- [ ] 错误和继续提示词预留工具消息占位符 +- [ ] 错误和继续提示词明示 +- [ ] 系统提示词泛化增强 +- [ ] 针对qwen3适当调整 + ## 前端交互 \ No newline at end of file diff --git a/host/Gateway.cs b/host/Gateway.cs index 58dc92e..406d789 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -259,7 +259,6 @@ public class Gateway }; List messages = new List(); string toolInfos = await GetToolInfos(new McpServerList()); - log.Info(SystemPrompt.SysPrompt(gdbPath, toolInfos)); messages.Add(new Message { Role = "system", @@ -311,86 +310,94 @@ public class Gateway break; } - string chunk = llmStreamChat.Choices[0].Delta.Content; - MessageListItem reasonMessageListItem = new ChatMessageItem() + try { - content = llmStreamChat.Choices[0].Delta.ResoningContent, - role = "assistant", - type = MessageType.REASON_MESSAGE, - id = (timestamp-1).ToString() - }; - callback?.Invoke(reasonMessageListItem); - messageContent = chunk; - var (matched, remaining) = ExtractMatchedPart(chunk, toolPattern); - if (matched == "") - { - var (matchedPrompt, remainingPrompt) = ExtractMatchedPart(chunk, promptPattern); - if (matchedPrompt == "") + string chunk = llmStreamChat.Choices[0].Delta.Content; + MessageListItem reasonMessageListItem = new ChatMessageItem() { - //普通消息文本 - MessageListItem chatMessageListItem = new ChatMessageItem() + content = llmStreamChat.Choices[0].Delta.ResoningContent, + role = "assistant", + type = MessageType.REASON_MESSAGE, + id = (timestamp-1).ToString() + }; + callback?.Invoke(reasonMessageListItem); + messageContent = chunk; + var (matched, remaining) = ExtractMatchedPart(chunk, toolPattern); + if (matched == "") + { + var (matchedPrompt, remainingPrompt) = ExtractMatchedPart(chunk, promptPattern); + if (matchedPrompt == "") { - content = remainingPrompt, - role = "assistant", - type = MessageType.CHAT_MESSAGE, - id = timestamp.ToString() - }; - callback?.Invoke(chatMessageListItem); + //普通消息文本 + MessageListItem chatMessageListItem = new ChatMessageItem() + { + content = remainingPrompt, + role = "assistant", + type = MessageType.CHAT_MESSAGE, + id = timestamp.ToString() + }; + callback?.Invoke(chatMessageListItem); + } + else + { + //包含Prompt调用请求的消息 + MessageListItem chatMessageListItem = new ChatMessageItem() + { + content = remainingPrompt, + role = "assistant", + type = MessageType.CHAT_MESSAGE, + id = timestamp.ToString() + }; + callback?.Invoke(chatMessageListItem); + XElement promptUse = XElement.Parse(matchedPrompt); + string promptKey = promptUse.Element("name")?.Value; + string promptArgs = promptUse.Element("arguments")?.Value; + Dictionary promptParams = JsonConvert.DeserializeObject>(promptArgs); + promptRequests = new List(); + promptRequests.Add(new PromptRequest() + { + PromptName = promptKey, + PromptArgs = promptParams, + PromptServer = promptServerList.GetPromptServer(promptKey) + }); + } } else { - //包含Prompt调用请求的消息 + //包含工具调用请求的消息 MessageListItem chatMessageListItem = new ChatMessageItem() { - content = remainingPrompt, + content = remaining, role = "assistant", type = MessageType.CHAT_MESSAGE, id = timestamp.ToString() }; callback?.Invoke(chatMessageListItem); - XElement promptUse = XElement.Parse(matchedPrompt); - string promptKey = promptUse.Element("name")?.Value; - string promptArgs = promptUse.Element("arguments")?.Value; - Dictionary promptParams = JsonConvert.DeserializeObject>(promptArgs); - promptRequests = new List(); - promptRequests.Add(new PromptRequest() + XElement toolUse = XElement.Parse(matched); + string fullToolName = toolUse.Element("name")?.Value; + string toolArgs = toolUse.Element("arguments")?.Value; + Dictionary toolParams = JsonConvert.DeserializeObject>(toolArgs); + string serverName = fullToolName.Contains(":") ? fullToolName.Split(':')[0] : fullToolName; + string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName; + McpServer mcpServer = mcpServerList.GetServer(serverName); + //将工具调用请求添加至列表中待一次回答完整后再统一进行调用 + mcpToolRequests = new List(); + McpToolRequest mcpToolRequest = new McpToolRequest() { - PromptName = promptKey, - PromptArgs = promptParams, - PromptServer = promptServerList.GetPromptServer(promptKey) - }); + McpServer = mcpServer, + ToolName = toolName, + ToolArgs = toolParams, + }; + mcpToolRequests.Add(mcpToolRequest); } } - else + catch (Exception e) { - //包含工具调用请求的消息 - MessageListItem chatMessageListItem = new ChatMessageItem() - { - content = remaining, - role = "assistant", - type = MessageType.CHAT_MESSAGE, - id = timestamp.ToString() - }; - callback?.Invoke(chatMessageListItem); - XElement toolUse = XElement.Parse(matched); - string fullToolName = toolUse.Element("name")?.Value; - string toolArgs = toolUse.Element("arguments")?.Value; - Dictionary toolParams = JsonConvert.DeserializeObject>(toolArgs); - string serverName = fullToolName.Contains(":") ? fullToolName.Split(':')[0] : fullToolName; - string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName; - McpServer mcpServer = mcpServerList.GetServer(serverName); - //将工具调用请求添加至列表中待一次回答完整后再统一进行调用 - mcpToolRequests = new List(); - McpToolRequest mcpToolRequest = new McpToolRequest() - { - McpServer = mcpServer, - ToolName = toolName, - ToolArgs = toolParams, - }; - mcpToolRequests.Add(mcpToolRequest); + Console.WriteLine(e); + log.Error(e.Message); } + } - if (messageContent != "") { messages.Add(new Message @@ -402,14 +409,16 @@ public class Gateway /*统一处理本次请求中的MCP工具调用需求*/ foreach (McpToolRequest mcpToolRequest in mcpToolRequests) { - McpServer mcpServer = mcpToolRequest.McpServer; - string toolName = mcpToolRequest.ToolName; - Dictionary toolParams = mcpToolRequest.ToolArgs; - if (mcpServer is SseMcpServer) + try + { + McpServer mcpServer = mcpToolRequest.McpServer; + string toolName = mcpToolRequest.ToolName; + Dictionary toolParams = mcpToolRequest.ToolArgs; + if (mcpServer is SseMcpServer) { SseMcpServer sseMcpServer = mcpServer as SseMcpServer; SseMcpClient client = new SseMcpClient(sseMcpServer.BaseUrl); - CallToolResponse toolResponse = await client.CallToolAsync(toolName,toolParams); + CallToolResponse toolResponse = await client.CallToolAsync(toolName, toolParams); MessageListItem toolMessageItem = new ToolMessageItem { toolName = toolName, @@ -417,19 +426,22 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = toolResponse.IsError ? "fail" : "success", content = JsonConvert.SerializeObject(toolResponse), - id = (timestamp+1).ToString() + id = (timestamp + 1).ToString() }; messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) + Content = toolResponse.IsError + ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) + : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); callback?.Invoke(toolMessageItem); - }else if (mcpServer is StdioMcpServer) + } + else if (mcpServer is StdioMcpServer) { StdioMcpServer stdioMcpServer = mcpServer as StdioMcpServer; StdioMcpClient client = new StdioMcpClient(stdioMcpServer.Command, stdioMcpServer.Args); - CallToolResponse toolResponse = await client.CallToolAsync(toolName,toolParams); + CallToolResponse toolResponse = await client.CallToolAsync(toolName, toolParams); MessageListItem toolMessageItem = new ToolMessageItem { toolName = toolName, @@ -437,18 +449,21 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = toolResponse.IsError ? "fail" : "success", content = JsonConvert.SerializeObject(toolResponse), - id = (timestamp+1).ToString() + id = (timestamp + 1).ToString() }; messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) + Content = toolResponse.IsError + ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) + : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); callback?.Invoke(toolMessageItem); - }else if (mcpServer is InnerMcpServer) + } + else if (mcpServer is InnerMcpServer) { - Type type = Type.GetType("LinkToolAddin.client.tool."+(mcpServer as InnerMcpServer).Name); - MethodInfo method = type.GetMethod(toolName,BindingFlags.Public | BindingFlags.Static); + Type type = Type.GetType("LinkToolAddin.client.tool." + (mcpServer as InnerMcpServer).Name); + MethodInfo method = type.GetMethod(toolName, BindingFlags.Public | BindingFlags.Static); var methodParams = toolParams.Values.ToArray(); object[] args = new object[methodParams.Length]; for (int i = 0; i < methodParams.Length; i++) @@ -464,6 +479,7 @@ public class Gateway args[i] = methodParams[i]; } } + var task = method.Invoke(null, args) as Task; JsonRpcResultEntity innerResult = await task; if (innerResult is JsonRpcErrorEntity) @@ -475,7 +491,7 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = "fail", content = JsonConvert.SerializeObject(innerResult), - id = (timestamp+1).ToString() + id = (timestamp + 1).ToString() }; messages.Add(new Message { @@ -483,7 +499,8 @@ public class Gateway Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolMessageItem)) }); callback?.Invoke(toolMessageItem); - }else if (innerResult is JsonRpcSuccessEntity) + } + else if (innerResult is JsonRpcSuccessEntity) { MessageListItem toolMessageItem = new ToolMessageItem { @@ -492,7 +509,7 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = "success", content = JsonConvert.SerializeObject(innerResult), - id = (timestamp+1).ToString() + id = (timestamp + 1).ToString() }; messages.Add(new Message { @@ -502,26 +519,41 @@ public class Gateway callback?.Invoke(toolMessageItem); } } + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + log.Error(ex.Message); + } + } /*统一处理本次请求中的Prompt调用需求*/ foreach (PromptRequest promptRequest in promptRequests) { - string promptContent = DynamicPrompt.GetPrompt(promptRequest.PromptName, promptRequest.PromptArgs); - messages.Add(new Message + try { - Role = "user", - Content = promptContent - }); - MessageListItem toolMessageItem = new ToolMessageItem + string promptContent = DynamicPrompt.GetPrompt(promptRequest.PromptName, promptRequest.PromptArgs); + messages.Add(new Message + { + Role = "user", + Content = promptContent + }); + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = "调用提示词", + toolParams = null, + type = MessageType.TOOL_MESSAGE, + status = "success", + content = "成功调用提示词:"+promptRequest.PromptName, + id = (timestamp+1).ToString() + }; + callback?.Invoke(toolMessageItem); + } + catch (Exception e) { - toolName = "调用提示词", - toolParams = null, - type = MessageType.TOOL_MESSAGE, - status = "success", - content = "成功调用提示词:"+promptRequest.PromptName, - id = (timestamp+1).ToString() - }; - callback?.Invoke(toolMessageItem); + Console.WriteLine(e); + log.Error(e.Message); + } } } } @@ -531,6 +563,7 @@ public class Gateway StringBuilder toolInfos = new StringBuilder(); foreach (McpServer mcpServer in mcpServerList.GetAllServers()) { + log.Info($"正在列出{mcpServer.Name}中的工具"); if (mcpServer is InnerMcpServer) { InnerMcpServer innerMcpServer = (InnerMcpServer)mcpServer; diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 68f83b4..2752204 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -35,6 +35,53 @@ public class McpServerList Description = "可以调用进行查询知识库,获取相关参考信息。", IsActive = true }); + servers.Add("filesystem", new StdioMcpServer() + { + Name = "filesystem", + Type = "stdio", + Command = "npx", + Args = new List() + { + "-y", + "@modelcontextprotocol/server-filesystem", + "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData" + } + }); + servers.Add("fetch", new StdioMcpServer() + { + Name = "fetch", + Type = "stdio", + Command = "uvx", + Args = new List() + { + "mcp-server-fetch" + } + }); + servers.Add("bing-search", new StdioMcpServer() + { + Name = "bing-search", + Type = "stdio", + Command = "npx", + Args = new List() + { + "bing-cn-mcp" + } + }); + // servers.Add("mcp-python-interpreter", new StdioMcpServer() + // { + // Name = "mcp-python-interpreter", + // Type = "stdio", + // Command = "uvx", + // Args = new List() + // { + // "--native-tls", + // "mcp-python-interpreter", + // "--dir", + // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData", + // "--python-path", + // "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe" + // } + // }); } public McpServer GetServer(string name) diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 1b2c1ea..435ec72 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -199,7 +199,14 @@ namespace LinkToolAddin.ui.dockpane { string userPrompt = PromptTestTextBox.Text; // model可选值:qwen3-235b-a22b,qwen-max,deepseek-r1 - Gateway.SendMessageStream(userPrompt,"qwen3-235b-a22b", "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb", AddReplyStream); + try + { + Gateway.SendMessageStream(userPrompt,"qwen3-235b-a22b", "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb", AddReplyStream); + } + catch (Exception exception) + { + log.Error(exception.Message); + } } public void AddReplyStream(MessageListItem msg) @@ -215,20 +222,27 @@ namespace LinkToolAddin.ui.dockpane messageDict.Add(msg.id, msg); } ReplyTextBox.Clear(); - StringBuilder builder = new StringBuilder(); - foreach (KeyValuePair pair in messageDict) + try { - MessageListItem msgItem = pair.Value; - string content = msgItem.content; - if (msgItem.type == MessageType.REASON_MESSAGE) + StringBuilder builder = new StringBuilder(); + foreach (KeyValuePair pair in messageDict) { - content = "" + content + ""; + MessageListItem msgItem = pair.Value; + string content = msgItem.content; + if (msgItem.type == MessageType.REASON_MESSAGE) + { + content = "" + content + ""; + } + builder.AppendLine(content); + builder.AppendLine(); + ReplyTextBox.Text = builder.ToString(); + ReplyTextBox.ScrollToEnd(); } - builder.AppendLine(content); - builder.AppendLine(); - ReplyTextBox.Text = builder.ToString(); - ReplyTextBox.ScrollToEnd(); + }catch (Exception exception) + { + log.Error(exception.Message); } + } public void AddReply(MessageListItem msg)