1. 修复工具调用前不查知识库问题

2. 完善MCP报错机制
3. 增加ArcGIS Pro查看属性表工具
This commit is contained in:
PeterZhong 2025-06-11 00:50:46 +08:00
parent 86b800c477
commit 3f3ecadec9
9 changed files with 216 additions and 31 deletions

View File

@ -100,7 +100,7 @@ public class ArcGisPro
return result;
}
[McpServerTool, Description("列出gdb数据库中的所有数据名称")]
[McpServerTool, Description("列出gdb数据库中的所有数据名称")]
public static async Task<JsonRpcResultEntity> ListData(string gdbPath)
{
var datasets = new List<string>();
@ -137,6 +137,80 @@ public class ArcGisPro
return result;
}
[McpServerTool, Description("获取要素类的属性表内容可以查看属性表中的至多前20条记录的内容")]
public static async Task<JsonRpcResultEntity> 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<FeatureClass>(dataName);
List<Dictionary<string, string>> 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<List<Dictionary<string, string>>> GetAttributeTableAsync(FeatureClass featureClass,int limit = 5)
{
if (limit > 20)
limit = 20;
return await QueuedTask.Run(() =>
{
var result = new List<Dictionary<string, string>>();
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<string, string>();
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<IGPMessage> messages)
{
StringBuilder messagesStr = new StringBuilder();

View File

@ -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 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<string, object>(){{"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<string, object>(){{"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<string, object>(),
type = MessageType.TOOL_MESSAGE,
status = "success",
content = "成功调用提示词:"+promptRequest.PromptName,
@ -638,6 +702,7 @@ public class Gateway
StringBuilder toolInfos = new StringBuilder();
int i = 0;
List<int> failedMcp = new List<int>();
List<string> failedMcpString = new List<string>();
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();
}

View File

@ -105,4 +105,14 @@ public class McpServerList
}
return serverList;
}
public List<string> GetAllServerNames()
{
List<string> serverList = new List<string>();
foreach (var server in servers)
{
serverList.Add(server.Key);
}
return serverList;
}
}

View File

@ -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;
}
}

View File

@ -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}", "版本信息");

View File

@ -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)
{

View File

@ -25,6 +25,7 @@
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Test Workflow" Name="TestServer" Click="TestWorkflow_OnClick"></Button>
<Grid Grid.Row="1">
@ -41,5 +42,6 @@
<Button Grid.Row="4" Content="Test Stream" Name="TestStream" Click="TestStream_OnClick"></Button>
<Button Grid.Row="5" Content="Test Arcgis Tool" Name="TestArcGisTool" Click="TestArcGisTool_OnClick"></Button>
<Button Grid.Row="6" Content="Test Resource" Name="TestResource" Click="TestResource_OnClick"></Button>
<Button Grid.Row="7" Content="测试获取属性表" Name="TestAttrTable" Click="TestAttrTable_OnClick"></Button>
</Grid>
</UserControl>

View File

@ -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");
}
}
}

View File

@ -5,7 +5,9 @@ public enum MessageType
TOOL_MESSAGE,
CHAT_MESSAGE,
REASON_MESSAGE,
END_TAG
END_TAG,
WARNING,
ERROR
}
public interface MessageListItem