diff --git a/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs index da2e604..163f253 100644 --- a/client/tool/ArcGisPro.cs +++ b/client/tool/ArcGisPro.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Threading.Tasks; using ArcGIS.Core.Data; using ArcGIS.Core.Data.Raster; @@ -9,6 +10,7 @@ using ArcGIS.Desktop.Framework.Threading.Tasks; using LinkToolAddin.server; using ModelContextProtocol.Server; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace LinkToolAddin.client.tool; diff --git a/client/tool/KnowledgeBase.cs b/client/tool/KnowledgeBase.cs index 9d2b162..38fe949 100644 --- a/client/tool/KnowledgeBase.cs +++ b/client/tool/KnowledgeBase.cs @@ -22,4 +22,28 @@ public class KnowledgeBase }; return result; } + + [McpServerTool, Description("查询ArcGIS Pro调用工具的标准调用名和参数要求知识库")] + public static async Task QueryArcgisToolDoc(string query) + { + DocDb docDb = new DocDb("sk-db177155677e438f832860e7f4da6afc", DocDb.KnowledgeBase.ArcGISProToolDoc); + KnowledgeResult knowledgeResult = await docDb.Retrieve(query); + JsonRpcResultEntity result = new JsonRpcSuccessEntity() + { + Result = JsonConvert.SerializeObject(knowledgeResult.ChunkList), + }; + return result; + } + + [McpServerTool, Description("查询使用ArcGIS Pro进行任务规划和解决实际问题的案例知识库")] + public static async Task QueryArcgisExampleDoc(string query) + { + DocDb docDb = new DocDb("sk-db177155677e438f832860e7f4da6afc", DocDb.KnowledgeBase.ArcGISProApplicantExample); + KnowledgeResult knowledgeResult = await docDb.Retrieve(query); + JsonRpcResultEntity result = new JsonRpcSuccessEntity() + { + Result = JsonConvert.SerializeObject(knowledgeResult.ChunkList), + }; + return result; + } } \ No newline at end of file diff --git a/host/Gateway.cs b/host/Gateway.cs index fca37cc..28935f0 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -8,6 +8,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using System.Windows.Documents; using System.Xml; using System.Xml.Linq; using ArcGIS.Desktop.Framework.Dialogs; @@ -37,6 +38,13 @@ namespace LinkToolAddin.host; public class Gateway { private static ILog log = LogManager.GetLogger(typeof(Gateway)); + private static bool goOn = true; + + public static void StopConversation() + { + goOn = false; + } + public static async void SendMessage(string message, string model, string gdbPath, Action callback) { Llm bailian = new Bailian @@ -240,12 +248,19 @@ public class Gateway Role = "user", Content = message }); - bool goOn = true; + goOn = true; string toolPattern = "^[\\s\\S]*?<\\/tool_use>$"; string promptPattern = "^[\\s\\S]*?<\\/prompt>$"; McpServerList mcpServerList = new McpServerList(); + int loop = 0; while (goOn) { + loop++; + if (loop > 20) + { + MessageBox.Show("达到最大循环次数", "退出循环"); + break; + } LlmJsonContent jsonContent = new LlmJsonContent() { Model = model, @@ -266,6 +281,11 @@ public class Gateway if (Regex.IsMatch(chunk, toolPattern)) { //返回工具卡片 + messages.Add(new Message + { + Role = "assistant", + Content = chunk + }); XElement toolUse = XElement.Parse(chunk); string fullToolName = toolUse.Element("name")?.Value; string toolArgs = toolUse.Element("arguments")?.Value; @@ -290,13 +310,14 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(toolResponse) + // Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate + Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); + // messages.Add(new Message + // { + // Role = "user", + // Content = JsonConvert.SerializeObject(toolResponse) + // }); callback?.Invoke(toolMessageItem); }else if (mcpServer is StdioMcpServer) { @@ -315,19 +336,35 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(toolResponse) + // Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate + Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); + // messages.Add(new Message + // { + // Role = "user", + // Content = JsonConvert.SerializeObject(toolResponse) + // }); callback?.Invoke(toolMessageItem); }else if (mcpServer is InnerMcpServer) { Type type = Type.GetType("LinkToolAddin.client.tool."+serverName); MethodInfo method = type.GetMethod(toolName,BindingFlags.Public | BindingFlags.Static); - var task = method.Invoke(null, toolParams.Values.ToArray()) as Task; + var methodParams = toolParams.Values.ToArray(); + object[] args = new object[methodParams.Length]; + for (int i = 0; i < methodParams.Length; i++) + { + if (methodParams[i].GetType() == typeof(JArray)) + { + List list = new List(); + list = (methodParams[i] as JArray).Select(token => token.ToString()).ToList(); + args[i] = list; + } + else + { + args[i] = methodParams[i]; + } + } + var task = method.Invoke(null, args) as Task; JsonRpcResultEntity innerResult = await task; if (innerResult is JsonRpcErrorEntity) { @@ -365,13 +402,14 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = SystemPrompt.ContinuePromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(innerResult) + // Content = SystemPrompt.ContinuePromptTemplate + Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(innerResult)) }); + // messages.Add(new Message + // { + // Role = "user", + // Content = JsonConvert.SerializeObject(innerResult) + // }); callback?.Invoke(toolMessageItem); } } @@ -451,16 +489,9 @@ public class Gateway private static async Task GetToolInfos(McpServerList mcpServerList) { - int loop = 0; StringBuilder toolInfos = new StringBuilder(); foreach (McpServer mcpServer in mcpServerList.GetAllServers()) { - loop++; - if (loop > 3) - { - MessageBox.Show("达到最大循环次数", "退出循环"); - break; - } if (mcpServer is InnerMcpServer) { InnerMcpServer innerMcpServer = (InnerMcpServer)mcpServer; diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 2c03a41..68f83b4 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -28,6 +28,13 @@ public class McpServerList Description = "可以调用arcgis的地理处理工具或执行python代码等", IsActive = true }); + servers.Add("KnowledgeBase", new InnerMcpServer + { + Name = "KnowledgeBase", + Type = "inner", + Description = "可以调用进行查询知识库,获取相关参考信息。", + IsActive = true + }); } public McpServer GetServer(string name) diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 912fd6f..69f180a 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -7,20 +7,21 @@ public class SystemPrompt "指令:您可以使用一组工具来回答用户的问题。还可以通过调用用户提示词,从而使你更好地理解和完成用户的任务。整个流程结束之后单独输出一条内容为'[DONE]'的消息,前后不要有任何说明文字。" + "调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过的方式来调用用户提示词,你能更好地理解和解决用户的问题。" + "工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。" + - "输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化。如果需要进行文字说明,请确保输出内容不采用Markdown格式或其他特殊文本标记;文字说明应以普通段落形式呈现,并且不含工具调用或prompt的XML标签。" + - + "输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化以单独信息格式输出,前后没有文字信息。" + + "如果需要进行文字说明,请只有文字内容,以普通段落形式呈现没有其他格式。" + "工具调用格式:工具名称包含在一对标签内,每个参数也需用对应的标签包裹。结构如下:\n {tool_name}\n {json_arguments}\n。" + "工具名称:需与所使用工具的精确名称一致。" + "参数:应为包含工具所需参数的 JSON 对象。例如:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n" + - "注意事项:1.无论是调用工具还是调用提示词,必须是单独的一条消息,只输出调用格式的内容,前后不能有描述性文字。内容为相应格式的XML文本,调用消息中不能加任何其它文字说明," + - "只有单独一条的调用请求文本程序才能识别并调用,从而将结果反馈给你。2.如果需要进行文字说明,请先进行文字说明,其中不含工具调用和prompt的XML,也不要用markdown格式" + - "3.用户时间宝贵,成功调用过的工具不应重复调用,应该尽快执行下一个工具。" + - "4.整个工具流程调用结束后,一定要单独输出一条内容为'[DONE]'的消息表示工具调用结束。不要在文本的末尾。" + + "你必须严格遵守以下输出规则:" + + "1.XML工具调用格式必须在下一条单独输出,如果前面有文字将永远无法调用工具。" + + //"只有单独一条的调用请求文本程序才能识别并调用,从而将结果反馈给你。" + + "2.文字说明如果紧跟着工具调用的XML将暴露程序,XML会在下一条单独输出,因此文字描述后面一定不能输出工具调用的XML格式。" + + "3.用户时间宝贵,不得重复调用上一次已成功执行的工具调用,除非有新的参数或上下文变化。" + + "4.如果目前工作已经完成无法知道用户其他需求时,一定要单独输出一条内容为'[DONE]'的消息表示工具调用结束。不要在文本的末尾。" + + "5.不得在同一消息中混合说明文字与工具调用。" + "结果:用户将以以下格式返回工具调用结果:\n {tool_name}\n {result}\n。应为字符串类型,可以表示文件或其他输出类型。" + - "例如,若工具返回.shp 文件,可在下一步操作中这样使用:\n ArcGIS_Pro:GP\n {\"output_file\": \"source.shp\"}\n" + - "请始终遵循此格式以确保工具调用被正确解析和执行。" + - "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n\n"; - + "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n"; + //"现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。\r\n\r\n指令:\r\n你需要使用一组工具来回答用户的问题。也可以通过 标签调用用户提示词,以帮助你更好地理解任务。所有操作完成后,在最后一条输出中包含 '[DONE]',但不要单独发送这条消息。\r\n\r\n调用工具要求:\r\n1. 每次只能调用一个工具,并等待用户的反馈结果。\r\n2. 所有工具调用都必须基于前一步的结果进行推理和决策。\r\n3. 工具调用必须以 XML 格式输出,并且必须作为**独立的一条消息输出**,前后不得有任何解释性文字或说明内容。\r\n4. 如果需要提供解释、说明或引导信息,请先单独输出一段普通文本,**其中不能包含任何 XML 标签或格式**。\r\n5. 不得重复调用已经成功执行过的工具,除非有新的参数或上下文需要重新调用。\r\n\r\n工具调用背景:\r\n你有以下工具可以调用:{{toolInfos}} \r\n用户的数据库路径是:{{gdbPath}}\r\n\r\n输出风格:\r\n- 所有工具调用都必须使用标准 XML 格式输出,并且每条消息只包含一次调用。\r\n- 文字说明必须单独输出,且为普通段落格式,不含任何 XML 或 Markdown 标记。\r\n- 输出顺序应为:【可选的文字说明】→【必选的工具调用】,或者仅输出工具调用。\r\n\r\n工具调用格式示例:\r\n\r\n {tool_name}\r\n {json_arguments}\r\n\r\n\r\n例如:\r\n\r\n gaode:maps_geo\r\n {\"address\":\"广州市政府, 广州市\", \"city\":\"广州\"}\r\n\r\n\r\n注意事项:\r\n1. 工具名称必须与实际工具完全一致。\r\n2. 参数必须为合法 JSON 格式,且符合工具要求。\r\n3. 用户时间宝贵,请高效调用工具,尽快完成任务。\r\n4. 最终完成时应在最后一次响应中包含 [DONE],而不是单独输出。\r\n\r\n工具调用结果返回格式:\r\n用户将以如下格式返回工具调用结果:\r\n\r\n {tool_name}\r\n {result}\r\n\r\n\r\n例如:\r\n\r\n ArcGIS_Pro:GP\r\n {\"output_file\": \"source.shp\"}\r\n\r\n\r\n请始终遵循此格式以确保工具调用被正确解析和执行。"; public static string ContinuePromptTemplate = "这是上述工具调用的结果。{{toolResult}}\n请根据以下执行结果,清晰解释执行结果并执行下一步操作。" + "执行下一步工具的要求:1. 解析工具输出结果2. 调用下一个工具时确保参数继承前序输出。请据此继续执行"; diff --git a/resource/DocDb.cs b/resource/DocDb.cs index 1487b5e..f4937fe 100644 --- a/resource/DocDb.cs +++ b/resource/DocDb.cs @@ -21,13 +21,14 @@ public class DocDb { ArcGISProHelpDoc, ArcGISProToolDoc, - TaskPlanningDoc, ArcGISProApplicantExample } public Dictionary knowledgeBase = new Dictionary { - {KnowledgeBase.ArcGISProHelpDoc,"6a77c5a68de64f469b79fcdcde9d5001"} + {KnowledgeBase.ArcGISProHelpDoc,"6a77c5a68de64f469b79fcdcde9d5001"}, + {KnowledgeBase.ArcGISProToolDoc,"080f8925318247ea822a9e12db5cb5cd"}, + {KnowledgeBase.ArcGISProApplicantExample,"eef60f7c879b4e8597138c261578d2a5"} }; public async Task Retrieve(string query) diff --git a/server/CallArcGISPro.cs b/server/CallArcGISPro.cs index 049fd3f..4d5c611 100644 --- a/server/CallArcGISPro.cs +++ b/server/CallArcGISPro.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using ArcGIS.Desktop.Core.Geoprocessing; using ArcGIS.Desktop.Framework.Dialogs; using ArcGIS.Desktop.Framework.Threading.Tasks; +using Newtonsoft.Json; namespace LinkToolAddin.server; @@ -22,7 +23,7 @@ public class CallArcGISPro Error = new Error() { Code = results.ErrorCode, - Message = results.ErrorMessages.ToString() + Message = JsonConvert.SerializeObject(results.ErrorMessages) } }; } @@ -30,7 +31,7 @@ public class CallArcGISPro { jsonRpcResultEntity = new JsonRpcSuccessEntity { - Result = results.Messages.ToString() + Result = JsonConvert.SerializeObject(results.Messages) }; } return jsonRpcResultEntity; diff --git a/ui/dockpane/TestDockpane.xaml b/ui/dockpane/TestDockpane.xaml index fd7d8c8..53c1501 100644 --- a/ui/dockpane/TestDockpane.xaml +++ b/ui/dockpane/TestDockpane.xaml @@ -20,7 +20,9 @@ - + + + @@ -31,9 +33,11 @@ - + - + + + \ No newline at end of file diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 34fa96d..0286d87 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -1,12 +1,18 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Xml.Linq; using LinkToolAddin.client; using LinkToolAddin.host; using LinkToolAddin.host.llm; using LinkToolAddin.host.llm.entity; +using LinkToolAddin.host.mcp; +using LinkToolAddin.host.prompt; using LinkToolAddin.message; using LinkToolAddin.resource; using LinkToolAddin.server; @@ -231,5 +237,42 @@ namespace LinkToolAddin.ui.dockpane { Request_Bailian_Stream_Test(); } + + private void StopConversation_OnClick(object sender, RoutedEventArgs e) + { + Gateway.StopConversation(); + } + + private async void TestArcGisTool_OnClick(object sender, RoutedEventArgs e) + { + string xmlStr = + "\nArcGisPro:ArcGisProTool\n{\"toolName\": \"Buffer\", \"toolParams\": [\"D:\\\\01_Project\\\\20250305_LinkTool\\\\20250420_AiDemoProject\\\\20250420_AiDemoProject.gdb\\\\LandUse_2005_Copy\", \"D:\\\\01_Project\\\\20250305_LinkTool\\\\20250420_AiDemoProject\\\\20250420_AiDemoProject.gdb\\\\LandUse_2005_Buffer30m\", \"30 Meters\", \"NONE\", \"ROUND\", \"ALL\"]}\n"; + XElement toolUse = XElement.Parse(xmlStr); + 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; + McpServerList mcpServerList = new McpServerList(); + McpServer mcpServer = mcpServerList.GetServer(serverName); + if (mcpServer is InnerMcpServer) + { + Type type = Type.GetType("LinkToolAddin.client.tool." + serverName); + var toolParamsValues = toolParams.Values.ToArray(); + MethodInfo method = type.GetMethod(toolName, BindingFlags.Public | BindingFlags.Static); + var task = method.Invoke(null, toolParams.Values.ToArray()) as Task; + JsonRpcResultEntity innerResult = await task; + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = toolName, + toolParams = toolParams, + type = MessageType.TOOL_MESSAGE, + status = "fail", + content = JsonConvert.SerializeObject(innerResult), + id = "1test" + }; + AddReply(toolMessageItem); + } + } } }