diff --git a/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs index 6c1adcf..8e2ce0a 100644 --- a/client/tool/ArcGisPro.cs +++ b/client/tool/ArcGisPro.cs @@ -100,7 +100,7 @@ public class ArcGisPro return result; } - [McpServerTool, Description("列出gdb数据库中的所有数据名称")] + [McpServerTool, Description("列出gdb数据库中的所有数据的名称")] public static async Task ListData(string gdbPath) { var datasets = new List(); @@ -137,6 +137,80 @@ public class ArcGisPro return result; } + [McpServerTool, Description("获取要素类的属性表内容,可以查看属性表中的至多前20条记录的内容")] + public static async Task GetFeatureDatasetAttributeTable(string datasetPath, string dataName, string rowsLimit) + { + JsonRpcResultEntity result = new JsonRpcResultEntity(); + await QueuedTask.Run(async () => + { + try + { + using Geodatabase gdb = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(datasetPath))); + FeatureClass featureClass = gdb.OpenDataset(dataName); + List> attributeTable = await GetAttributeTableAsync(featureClass,Convert.ToInt32(rowsLimit)); + result = new JsonRpcSuccessEntity() + { + Id = 1, + Result = JsonConvert.SerializeObject(attributeTable) + }; + return result; + }catch (Exception ex) + { + result = new JsonRpcErrorEntity() + { + Error = new Error() + { + Message = ex.Message, + Code = "500" + } + }; + return result; + } + }); + return result; + } + + private static async Task>> GetAttributeTableAsync(FeatureClass featureClass,int limit = 5) + { + if (limit > 20) + limit = 20; + return await QueuedTask.Run(() => + { + var result = new List>(); + + using (var cursor = featureClass.Search()) + { + int i = 0; + while (cursor.MoveNext()) + { + i++; + if (i >= limit && limit != -1) + break; + var feature = cursor.Current as Feature; + if (feature == null) + continue; + + var record = new Dictionary(); + + foreach (var field in featureClass.GetDefinition().GetFields()) + { + var value = feature[field.Name]; + + // 处理 DBNull 值 + if (value is DBNull) + value = null; + + record[field.Name] = value.ToString(); + } + + result.Add(record); + } + } + + return result; + }); + } + private static string GetMessagesString(IEnumerable messages) { StringBuilder messagesStr = new StringBuilder(); diff --git a/host/Gateway.cs b/host/Gateway.cs index 8ce17fb..7359391 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -12,9 +12,11 @@ using System.Windows; using System.Windows.Documents; using System.Xml; using System.Xml.Linq; +using ArcGIS.Desktop.Internal.Mapping; using ArcGIS.Desktop.Internal.Mapping.Locate; using LinkToolAddin.client; using LinkToolAddin.client.prompt; +using LinkToolAddin.client.tool; using LinkToolAddin.host.llm; using LinkToolAddin.host.llm.entity; using LinkToolAddin.host.mcp; @@ -265,6 +267,8 @@ public class Gateway PromptServerList promptServerList = new PromptServerList(); int loop = 0; string messageContent = ""; //一次请求下完整的response + bool queriedKnowledge = false; + bool executedTool = false; while (goOn) { loop++; @@ -398,21 +402,24 @@ public class Gateway { callback?.Invoke(chatMessageListItem); }); - MessageListItem toolMessageListItem = new ToolMessageItem() + if (!executedTool) { - content = "", - result = "", - toolName = toolName, - toolParams = toolParams, - role = "user", - type = MessageType.TOOL_MESSAGE, - id = (timestamp+1).ToString(), - status = "loading" - }; - Application.Current.Dispatcher.Invoke(() => - { - callback?.Invoke(toolMessageListItem); - }); + MessageListItem toolMessageListItem = new ToolMessageItem() + { + content = toolName, + result = "工具运行中" + toolName, + toolName = toolName, + toolParams = toolParams, + role = "user", + type = MessageType.TOOL_MESSAGE, + id = (timestamp+1).ToString(), + status = "loading" + }; + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(toolMessageListItem); + }); + } mcpToolRequests = new List(); McpToolRequest mcpToolRequest = new McpToolRequest() { @@ -434,7 +441,6 @@ public class Gateway log.Error(e.Message); MessageBox.Show(e.Message, "请求大模型出错"); } - if (messageContent != "") { messages.Add(new Message @@ -446,6 +452,7 @@ public class Gateway /*统一处理本次请求中的MCP工具调用需求*/ foreach (McpToolRequest mcpToolRequest in mcpToolRequests) { + executedTool = true; try { McpServer mcpServer = mcpToolRequest.McpServer; @@ -456,7 +463,7 @@ public class Gateway SseMcpServer sseMcpServer = mcpServer as SseMcpServer; SseMcpClient client = new SseMcpClient(sseMcpServer.BaseUrl); CallToolResponse toolResponse = await client.CallToolAsync(toolName, toolParams); - MessageListItem toolMessageItem = new ToolMessageItem + MessageListItem toolMessageItem1 = new ToolMessageItem { toolName = toolName, toolParams = toolParams, @@ -476,7 +483,7 @@ public class Gateway }); Application.Current.Dispatcher.Invoke(() => { - callback?.Invoke(toolMessageItem); + callback?.Invoke(toolMessageItem1); }); } else if (mcpServer is StdioMcpServer) @@ -484,7 +491,7 @@ public class Gateway StdioMcpServer stdioMcpServer = mcpServer as StdioMcpServer; StdioMcpClient client = new StdioMcpClient(stdioMcpServer.Command, stdioMcpServer.Args); CallToolResponse toolResponse = await client.CallToolAsync(toolName, toolParams); - MessageListItem toolMessageItem = new ToolMessageItem + MessageListItem toolMessageItem1 = new ToolMessageItem { toolName = toolName, toolParams = toolParams, @@ -504,11 +511,67 @@ public class Gateway }); Application.Current.Dispatcher.Invoke(() => { - callback?.Invoke(toolMessageItem); + callback?.Invoke(toolMessageItem1); }); } else if (mcpServer is InnerMcpServer) { + InnerMcpServer innerMcpServer = mcpServer as InnerMcpServer; + string mcpServerName = innerMcpServer.Name; + if (toolName == "ArcGisProTool" && queriedKnowledge == false) + { + JsonRpcResultEntity knowledgeResult = await KnowledgeBase.QueryArcgisToolDoc(toolParams["toolName"].ToString()); + if (knowledgeResult is JsonRpcSuccessEntity) + { + JsonRpcSuccessEntity knowledgeSuccessResult = knowledgeResult as JsonRpcSuccessEntity; + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = "QueryArcgisToolDoc", + toolParams = new Dictionary(){{"query",toolParams["toolName"].ToString()}}, + type = MessageType.TOOL_MESSAGE, + status = "success", + content = JsonConvert.SerializeObject(knowledgeSuccessResult.Result), + result = JsonConvert.SerializeObject(knowledgeSuccessResult.Result), + id = (timestamp + 4).ToString(), + role = "user" + }; + messages.Add(new Message + { + Role = "user", + Content = SystemPrompt.ToolContinuePrompt(JsonConvert.SerializeObject(knowledgeSuccessResult)) + }); + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(toolMessageItem); + }); + queriedKnowledge = true; + } + else + { + JsonRpcErrorEntity knowledgeErrorResult = knowledgeResult as JsonRpcErrorEntity; + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = "QueryArcgisToolDoc", + toolParams = new Dictionary(){{"query",toolParams["toolName"].ToString()}}, + type = MessageType.TOOL_MESSAGE, + status = "fail", + content = JsonConvert.SerializeObject(knowledgeErrorResult.Error), + result = JsonConvert.SerializeObject(knowledgeErrorResult.Error), + id = (timestamp + 4).ToString(), + role = "user" + }; + messages.Add(new Message + { + Role = "user", + Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(knowledgeErrorResult)) + }); + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(toolMessageItem); + }); + } + continue; + } 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(); @@ -534,9 +597,10 @@ public class Gateway { displayToolName = "【GP】"+toolParams["toolName"].ToString(); } + queriedKnowledge = false; if (innerResult is JsonRpcErrorEntity) { - MessageListItem toolMessageItem = new ToolMessageItem + MessageListItem toolMessageItem1 = new ToolMessageItem { toolName = displayToolName, toolParams = toolParams, @@ -550,16 +614,16 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolMessageItem)) + Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(innerResult)) }); Application.Current.Dispatcher.Invoke(() => { - callback?.Invoke(toolMessageItem); + callback?.Invoke(toolMessageItem1); }); } else if (innerResult is JsonRpcSuccessEntity) { - MessageListItem toolMessageItem = new ToolMessageItem + MessageListItem toolMessageItem1 = new ToolMessageItem { toolName = displayToolName, toolParams = toolParams, @@ -573,11 +637,11 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolMessageItem)) + Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(innerResult)) }); Application.Current.Dispatcher.Invoke(() => { - callback?.Invoke(toolMessageItem); + callback?.Invoke(toolMessageItem1); }); } } @@ -603,7 +667,7 @@ public class Gateway MessageListItem toolMessageItem = new ToolMessageItem { toolName = "调用提示词", - toolParams = null, + toolParams = new Dictionary(), type = MessageType.TOOL_MESSAGE, status = "success", content = "成功调用提示词:"+promptRequest.PromptName, @@ -638,6 +702,7 @@ public class Gateway StringBuilder toolInfos = new StringBuilder(); int i = 0; List failedMcp = new List(); + List failedMcpString = new List(); foreach (McpServer mcpServer in mcpServerList.GetAllServers()) { i++; @@ -717,6 +782,7 @@ public class Gateway { log.Error(e.Message); failedMcp.Add(i); + failedMcpString.Add(mcpServerList.GetAllServerNames()[i]); } } @@ -734,7 +800,11 @@ public class Gateway log.Error(e.Message); } } - MessageBox.Show($"读取失败的MCP序号:{JsonConvert.SerializeObject(failedMcp)}","MCP读取错误"); + + if (!failedMcp.IsNullOrEmpty()) + { + MessageBox.Show($"读取失败的MCP服务有:{JsonConvert.SerializeObject(failedMcpString)}","MCP读取错误"); + } return toolInfos.ToString(); } diff --git a/host/McpServerList.cs b/host/McpServerList.cs index b925a40..38f14ba 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -105,4 +105,14 @@ public class McpServerList } return serverList; } + + public List GetAllServerNames() + { + List serverList = new List(); + foreach (var server in servers) + { + serverList.Add(server.Key); + } + return serverList; + } } \ No newline at end of file diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 2295d68..b6db27c 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -49,4 +49,11 @@ public class SystemPrompt errPrompt = errPrompt.Replace("{{toolResult}}", toolResult); return errPrompt; } + + public static string ToolContinuePrompt(string toolResult) + { + string continuePrompt = "根据你需要调用的工具查询到如下帮助文档内容,请严格按照帮助文档中的参数和名称要求再次生成准确的工具调用请求以确保工具调用成功。\n{{toolResult}}"; + continuePrompt = continuePrompt.Replace("{{toolResult}}", toolResult); + return continuePrompt; + } } \ No newline at end of file diff --git a/ui/VersionButton.cs b/ui/VersionButton.cs index be464f7..bec6767 100644 --- a/ui/VersionButton.cs +++ b/ui/VersionButton.cs @@ -22,7 +22,7 @@ namespace LinkToolAddin { internal class VersionButton : Button { - private string version = "0.1.3"; + private string version = "0.1.4"; protected override void OnClick() { MessageBox.Show($"当前LinkTool版本为{version}", "版本信息"); diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index 907dd2f..68b55b0 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -122,6 +122,7 @@ namespace LinkToolAddin.ui.dockpane { model = ShowInputBox("自定义模型", "请输入模型名称:", ""); } + ScrollViewer.ScrollToBottom(); Gateway.SendMessageStream(question,model,defaultGdbPath,NewMessage_Recall); } @@ -218,6 +219,13 @@ namespace LinkToolAddin.ui.dockpane StatusTextBlock.Text = "回答生成中"; } } + else if (msg.role == "system") + { + if (msg.type == MessageType.WARNING) + { + + } + } } else { @@ -241,7 +249,10 @@ namespace LinkToolAddin.ui.dockpane Border borderItem = borderItemsDict[msgId]; Grid grid = borderItem.Child as Grid; TextBlock textBlock = grid.Children[1] as TextBlock; - textBlock.Text = (msg as ToolMessageItem).toolName; + ToolMessageItem msgItem = msg as ToolMessageItem; + textBlock.Text = msgItem.toolName + " | " + msgItem.status; + Button resButton = grid.Children[3] as Button; + resButton.Tag = msg; StatusTextBlock.Text = "正在执行工具"; }else if (msg.type == MessageType.CHAT_MESSAGE) { diff --git a/ui/dockpane/TestDockpane.xaml b/ui/dockpane/TestDockpane.xaml index 6d4ae00..0306e36 100644 --- a/ui/dockpane/TestDockpane.xaml +++ b/ui/dockpane/TestDockpane.xaml @@ -25,6 +25,7 @@ + @@ -41,5 +42,6 @@ + \ No newline at end of file diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 458d99f..5985146 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -9,6 +9,7 @@ using System.Windows; using System.Windows.Controls; using System.Xml.Linq; using LinkToolAddin.client; +using LinkToolAddin.client.tool; using LinkToolAddin.common; using LinkToolAddin.host; using LinkToolAddin.host.llm; @@ -311,5 +312,13 @@ namespace LinkToolAddin.ui.dockpane string content = LocalResource.ReadFileByResource("LinkToolAddin.resource.SystemPrompt.txt"); MessageBox.Show(content); } + + private async void TestAttrTable_OnClick(object sender, RoutedEventArgs e) + { + JsonRpcResultEntity result = await ArcGisPro.GetFeatureDatasetAttributeTable( + "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb", + "LandUse_2005_Copy", "30"); + log.Info("finish"); + } } } diff --git a/ui/message/MessageListItem.cs b/ui/message/MessageListItem.cs index 3b0ce37..31610a5 100644 --- a/ui/message/MessageListItem.cs +++ b/ui/message/MessageListItem.cs @@ -5,7 +5,9 @@ public enum MessageType TOOL_MESSAGE, CHAT_MESSAGE, REASON_MESSAGE, - END_TAG + END_TAG, + WARNING, + ERROR } public interface MessageListItem