diff --git a/Config.daml b/Config.daml
index 9147f6f..18a04f2 100644
--- a/Config.daml
+++ b/Config.daml
@@ -23,9 +23,10 @@
-
-
-
+
+
+
+
@@ -40,11 +41,17 @@
+
+
+
+
diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj
index 818ba95..26846fa 100644
--- a/LinkToolAddin.csproj
+++ b/LinkToolAddin.csproj
@@ -1,11 +1,18 @@
- net8.0-windows
true
win-x64
false
true
CA1416
+ net8.0-windows
+ 0.1.3
+ LinkToolAddin
+ LinkTool团队
+ LinkTool以大模型赋能让您只需一两句话便能完成复杂的空间分析与时空大数据处理任务。
+ 华南农业大学
+ 校AI大赛提交版本
+ 华南农业大学
@@ -22,85 +29,124 @@
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\ArcGIS.Desktop.Framework.dll
+ C:\Program Files\ArcGIS\Pro\bin\ArcGIS.Desktop.Framework.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\ArcGIS.Core.dll
+ C:\Program Files\ArcGIS\Pro\bin\ArcGIS.Core.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\Extensions\Core\ArcGIS.Desktop.Core.dll
+ C:\Program Files\ArcGIS\Pro\bin\Extensions\Core\ArcGIS.Desktop.Core.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\Extensions\Mapping\ArcGIS.Desktop.Mapping.dll
+ C:\Program Files\ArcGIS\Pro\bin\Extensions\Mapping\ArcGIS.Desktop.Mapping.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\Extensions\Catalog\ArcGIS.Desktop.Catalog.dll
+ C:\Program Files\ArcGIS\Pro\bin\Extensions\Catalog\ArcGIS.Desktop.Catalog.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\Extensions\Editing\ArcGIS.Desktop.Editing.dll
+ C:\Program Files\ArcGIS\Pro\bin\Extensions\Editing\ArcGIS.Desktop.Editing.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\Extensions\DesktopExtensions\ArcGIS.Desktop.Extensions.dll
+ C:\Program Files\ArcGIS\Pro\bin\Extensions\DesktopExtensions\ArcGIS.Desktop.Extensions.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\Extensions\GeoProcessing\ArcGIS.Desktop.GeoProcessing.dll
+ C:\Program Files\ArcGIS\Pro\bin\Extensions\GeoProcessing\ArcGIS.Desktop.GeoProcessing.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\Extensions\Layout\ArcGIS.Desktop.Layouts.dll
+ C:\Program Files\ArcGIS\Pro\bin\Extensions\Layout\ArcGIS.Desktop.Layouts.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\Extensions\KnowledgeGraph\ArcGIS.Desktop.KnowledgeGraph.dll
+ C:\Program Files\ArcGIS\Pro\bin\Extensions\KnowledgeGraph\ArcGIS.Desktop.KnowledgeGraph.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\ArcGIS.Desktop.Shared.Wpf.dll
+ C:\Program Files\ArcGIS\Pro\bin\ArcGIS.Desktop.Shared.Wpf.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\ArcGIS.Desktop.Ribbon.Wpf.dll
+ C:\Program Files\ArcGIS\Pro\bin\ArcGIS.Desktop.Ribbon.Wpf.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\ArcGIS.Desktop.DataGrid.Contrib.Wpf.dll
+ C:\Program Files\ArcGIS\Pro\bin\ArcGIS.Desktop.DataGrid.Contrib.Wpf.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\ArcGIS.Desktop.Resources.dll
+ C:\Program Files\ArcGIS\Pro\bin\ArcGIS.Desktop.Resources.dll
False
False
- C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\ESRI.ArcGIS.ItemIndex.dll
+ C:\Program Files\ArcGIS\Pro\bin\ESRI.ArcGIS.ItemIndex.dll
False
False
-
-
-
+
+
+
+
+
+
-
+
+
+
+
+
+ Always
+
+
+
+ Always
+
+
+
+ Always
+
+
+
+ Always
+
+
+
+ Always
+
+
+
+ Always
+
+
+
+ Always
+
+
+
+ Always
+
+
+
diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json
index f8e1bfb..165b000 100644
--- a/Properties/launchSettings.json
+++ b/Properties/launchSettings.json
@@ -1,8 +1,12 @@
{
"profiles": {
"LinkToolAddin": {
- "commandName": "Executable",
- "executablePath": "C:\\Users\\PeterZhong\\AppData\\Local\\Programs\\ArcGIS\\Pro\\bin\\ArcGISPro.exe"
+ "commandName": "Executable",
+ "executablePath": "C:\\Users\\86158\\AppData\\Local\\Programs\\ArcGIS\\Pro\\bin\\ArcGISPro.exe",
+ "applicationUrl": "https://localhost:5001",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
}
}
}
\ No newline at end of file
diff --git a/README.md b/README.md
index f0d5845..71d45b5 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,7 @@ git clone xxx.git
5. 部分情况下可能还需要替换LinkToolAddin.csproj中的所有相关安装路径
6. 点击运行进行测试,看是否能正常打开ArcGIS Pro和LinkTool插件
7. 确认无误建议commit到本地的master分支以备后续使用(但请勿推送至远端)
+8. 修改McpServerList里面filesystem的白名单目录
#### 创建分支
diff --git a/client/CallArcGISPro.cs b/client/CallArcGISPro.cs
new file mode 100644
index 0000000..a3c00e4
--- /dev/null
+++ b/client/CallArcGISPro.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using LinkToolAddin.server;
+using Newtonsoft.Json;
+
+namespace LinkToolAddin.client;
+
+public class CallArcGISPro
+{
+ public async static Task CallArcGISProTool(Dictionary parameters)
+ {
+ // Call the ArcGIS Pro method and get the result
+ var result = await server.CallArcGISPro.CallArcGISProTool(parameters["toolName"], JsonConvert.DeserializeObject>(parameters["toolParams"]));
+
+ // Serialize the result back to a JSON string
+ return JsonConvert.SerializeObject(result);
+ }
+}
\ No newline at end of file
diff --git a/client/McpClient.cs b/client/McpClient.cs
new file mode 100644
index 0000000..c03734b
--- /dev/null
+++ b/client/McpClient.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using ModelContextProtocol.Client;
+using ModelContextProtocol.Protocol.Types;
+
+namespace LinkToolAddin.client;
+
+public interface McpClient
+{
+ public Task> GetToolListAsync();
+
+ public Task CallToolAsync(string toolName, Dictionary parameters = null);
+}
\ No newline at end of file
diff --git a/client/SseMcpClient.cs b/client/SseMcpClient.cs
new file mode 100644
index 0000000..6af5c55
--- /dev/null
+++ b/client/SseMcpClient.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using ModelContextProtocol.Client;
+using ModelContextProtocol.Protocol.Transport;
+using ModelContextProtocol.Protocol.Types;
+using Newtonsoft.Json;
+
+namespace LinkToolAddin.client;
+
+public class SseMcpClient : McpClient
+{
+ private SseClientTransportOptions options;
+ private IClientTransport transport;
+ public SseMcpClient(string url)
+ {
+ options = new SseClientTransportOptions
+ {
+ Endpoint = new Uri(url),
+ };
+ transport = new SseClientTransport(options);
+ }
+ public async Task> GetToolListAsync()
+ {
+ // 创建 MCP Client
+ IMcpClient client = await McpClientFactory.CreateAsync(transport);
+ var tools = await client.ListToolsAsync();
+ return tools;
+ }
+
+ public async Task CallToolAsync(string toolName,Dictionary parameters = null)
+ {
+ IMcpClient client = await McpClientFactory.CreateAsync(transport);
+ CallToolResponse result = await client.CallToolAsync(toolName,parameters);
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/client/StdioMcpClient.cs b/client/StdioMcpClient.cs
new file mode 100644
index 0000000..64933ad
--- /dev/null
+++ b/client/StdioMcpClient.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Windows.Documents;
+using ModelContextProtocol.Protocol.Types;
+using Newtonsoft.Json;
+
+namespace LinkToolAddin.client;
+
+using ModelContextProtocol.Client;
+using ModelContextProtocol.Protocol.Transport;
+
+public class StdioMcpClient : McpClient
+{
+ private List arguments;
+ private StdioClientTransportOptions transportOptions;
+
+ public StdioMcpClient(string command,List arguments)
+ {
+ this.arguments = arguments;
+ transportOptions = new StdioClientTransportOptions()
+ {
+ Command = command,
+ Arguments = this.arguments
+ };
+ }
+
+ public static async Task StdioMcpTest()
+ {
+ List arguments = new List();
+ arguments.Add("mcp-server-time");
+ arguments.Add("--local-timezone=America/New_York");
+ StdioClientTransportOptions transportOptions = new StdioClientTransportOptions()
+ {
+ Command = "uvx", // 运行服务器的命令
+ Arguments = arguments
+ };
+ var client = await McpClientFactory.CreateAsync(new StdioClientTransport(transportOptions));
+ var tools = await client.ListToolsAsync();
+ Console.WriteLine("Available Tools:");
+ foreach (var tool in tools)
+ {
+ Console.WriteLine($"- {tool.Name}");
+ }
+
+ var result = await client.CallToolAsync("get_current_time",
+ new Dictionary { { "timezone", "America/New_York" } });
+ Console.WriteLine(JsonConvert.SerializeObject(result));
+ return tools[0].Name;
+ }
+
+ public async Task> GetToolListAsync()
+ {
+ try
+ {
+ IMcpClient client = await McpClientFactory.CreateAsync(new StdioClientTransport(transportOptions));
+ IList tools = await client.ListToolsAsync();
+ return tools;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ return null;
+ }
+
+ }
+
+ public async Task CallToolAsync(string toolName, Dictionary parameters = null)
+ {
+ IMcpClient client = await McpClientFactory.CreateAsync(new StdioClientTransport(transportOptions));
+ CallToolResponse result = await client.CallToolAsync(toolName,parameters);
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/client/prompt/DynamicPrompt.cs b/client/prompt/DynamicPrompt.cs
new file mode 100644
index 0000000..c63c9fa
--- /dev/null
+++ b/client/prompt/DynamicPrompt.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using LinkToolAddin.host;
+using LinkToolAddin.host.prompt;
+
+namespace LinkToolAddin.client.prompt;
+
+public class DynamicPrompt
+{
+ public static string GetPrompt(string name,Dictionary args = null)
+ {
+ PromptServerList promptServerList = new PromptServerList();
+ string template = promptServerList.GetPromptServer(name).Content;
+ if (args == null)
+ {
+ return template;
+ }
+ foreach (KeyValuePair pair in args)
+ {
+ string replaceKey = "{{"+pair.Key+"}}";
+ template = template.Replace(replaceKey, pair.Value.ToString());
+ }
+ return template;
+ }
+
+ public static List GetAllPrompts()
+ {
+ PromptServerList promptServerList = new PromptServerList();
+ List prompts = new List();
+ Dictionary promptDefinitions = promptServerList.GetPromptsDict();
+ foreach (KeyValuePair pair in promptDefinitions)
+ {
+ prompts.Add(new UserPrompt()
+ {
+ Name = pair.Value.Name,
+ Description = pair.Value.Description,
+ Arguments = pair.Value.Arguments
+ });
+ }
+ return prompts;
+ }
+}
\ No newline at end of file
diff --git a/client/prompt/PromptTemplates.cs b/client/prompt/PromptTemplates.cs
new file mode 100644
index 0000000..6f9eaa1
--- /dev/null
+++ b/client/prompt/PromptTemplates.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using LinkToolAddin.host.prompt;
+
+namespace LinkToolAddin.client.prompt;
+
+public class PromptTemplates
+{
+ private Dictionary prompts = new Dictionary();
+
+ public PromptTemplates()
+ {
+ prompts.Add("plan", "请根据用户所提问题进行工具规划,输出格式为'1.工具2.工具’,如果是ArcGIS Pro的工具,根据用户的具体需求和提供的数据类型," +
+ "判断并列出所有必要的分析步骤和工具,同时严格遵守知识库中“ArcGIS Pro工具调用大全”里“工具调用名称”一列的工具命名规则(例如:analysis.Erase)," +
+ "确保工具名的准确无误,工具与所属工具箱的对应关系正确无误,严格遵循文档规定的格式和大小写。工具的组合顺序优先参考知识库中“ArcGIS Pro的帮助文档”。" +
+ "有一些与分析无关的数据能够进行排除,在选择工具时不受其干扰。");
+ prompts.Add("param", "根据帮助文档填写工具参数,请你根据“所需调用工具”,参照知识库“ArcGIS Pro工具调用大全”里工具所需的参数顺序进行陈列。" +
+ "列出所需调用工具的名称及其按照“ArcGIS Pro工具调用大全”里“的该工具所需的参数顺序陈列对应的参数。如果跳过了可选参数需要用空字符表示。" +
+ "不能更改所需调用工具的名称。例如:arcpy.analysis.Buffer,不能只写成Buffer。确保格式、参数的完整性和准确性,避免任何遗漏或错误。" +
+ "特别注意,所有参数均应视为字符串类型,即使它们可能代表数字或文件路径。例如问题为:使用地理处理中的\"擦除分析\"工具(Erase),将圆形要素(circle.shp)与方形要素(square.shp)进行空间叠加运算。" +
+ "输出: \"in_features\":\"circle.shp\",\r\n \"erase_features\":\"sqaure.shp\",\r\n \"out_feature_class\":\"res.shp\",\r\n \"cluster_tolerance\":\"1\"");
+ prompts.Add("code", "根据你在多种编程语言、框架、设计模式和最佳实践方面拥有的广泛知识。现在需要根据用户需求生成高质量的代码,并确保语法正确。" +
+ "编写Arcpy代码时必须符合ArcGIS官方文档要求。参考官方文档的方法参数,确保编写正确。");
+ }
+
+ public string GetPrompt(string name)
+ {
+ return prompts[name];
+ }
+
+ public Dictionary GetPromptsDict()
+ {
+ return prompts;
+ }
+}
\ No newline at end of file
diff --git a/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs
new file mode 100644
index 0000000..8e2ce0a
--- /dev/null
+++ b/client/tool/ArcGisPro.cs
@@ -0,0 +1,223 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ArcGIS.Core.Data;
+using ArcGIS.Core.Data.Raster;
+using ArcGIS.Core.Geometry;
+using ArcGIS.Desktop.Core.Geoprocessing;
+using ArcGIS.Desktop.Framework.Threading.Tasks;
+using LinkToolAddin.server;
+using LinkToolAddin.ui.dockpane;
+using log4net;
+using ModelContextProtocol.Server;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace LinkToolAddin.client.tool;
+
+public class ArcGisPro
+{
+ private static ILog log = LogManager.GetLogger(typeof(ArcGisPro));
+ [McpServerTool, Description("可以通过调用ArcGIS Pro的地理处理工具实现一些数据处理功能,传入参数必须严格遵循ArcGIS Pro调用工具的标准调用名和参数要求知识库。")]
+ public static async Task ArcGisProTool(string toolName, List toolParams)
+ {
+ IGPResult results = await Geoprocessing.ExecuteToolAsync(toolName, toolParams,null,null,null,GPExecuteToolFlags.InheritGPOptions|GPExecuteToolFlags.GPThread);
+ JsonRpcResultEntity jsonRpcResultEntity;
+ if (results.IsFailed)
+ {
+ log.Error(results.ErrorMessages);
+ jsonRpcResultEntity = new JsonRpcErrorEntity()
+ {
+ Error = new Error()
+ {
+ Code = results.ErrorCode.ToString(),
+ Message = GetMessagesString(results.ErrorMessages)+"\n"+GetMessagesString(results.Messages)
+ }
+ };
+ }else if(results.HasWarnings)
+ {
+ log.Warn(results.Messages);
+ jsonRpcResultEntity = new JsonRpcSuccessEntity
+ {
+ Result = GetMessagesString(results.Messages)
+ };
+ }
+ else
+ {
+ log.Info("success gp tool");
+ jsonRpcResultEntity = new JsonRpcSuccessEntity
+ {
+ Result = GetMessagesString(results.Messages)
+ };
+ }
+ return jsonRpcResultEntity;
+ }
+
+ [McpServerTool, Description("查看指定数据的坐标系、范围、几何类型、是否有Z坐标和M坐标,获取字段列表等")]
+ public static async Task DataProperty(string datasetPath,string dataName)
+ {
+ JsonRpcResultEntity result = new JsonRpcResultEntity();
+ await QueuedTask.Run(() =>
+ {
+ try
+ {
+ using Geodatabase gdb = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(datasetPath)));
+ FeatureClass featureClass = gdb.OpenDataset(dataName);
+ FeatureClassDefinition featureClassDefinition = featureClass.GetDefinition();
+ SpatialReference spatialReference = featureClassDefinition.GetSpatialReference();
+ GeometryType geometryType = featureClassDefinition.GetShapeType();
+ result = new JsonRpcSuccessEntity()
+ {
+ Id = 1,
+ Result = JsonConvert.SerializeObject(new Dictionary()
+ {
+ {"spatialReference", spatialReference.Name+"(WKID:"+spatialReference.Wkid+")"},
+ {"dataName", dataName},
+ {"geometryType", geometryType.ToString()},
+ {"hasZValue", featureClassDefinition.HasZ()},
+ {"hasMValue", featureClassDefinition.HasM()},
+ {"fields",featureClassDefinition.GetFields()}
+ })
+ };
+ return result;
+ }
+ catch (Exception ex)
+ {
+ result = new JsonRpcErrorEntity()
+ {
+ Error = new Error()
+ {
+ Message = ex.Message
+ },
+ Id = 1
+ };
+ return result;
+ }
+ });
+ return result;
+ }
+
+ [McpServerTool, Description("列出gdb数据库中的所有数据的名称")]
+ public static async Task ListData(string gdbPath)
+ {
+ var datasets = new List();
+ await QueuedTask.Run(() =>
+ {
+ using (Geodatabase gdb = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(gdbPath))))
+ {
+ // 获取所有要素类(Feature Classes)
+ var featureClasses = gdb.GetDefinitions();
+ foreach (var fc in featureClasses)
+ datasets.Add($"要素类: {fc.GetName()}");
+
+ // 获取所有表格(Tables)
+ var tables = gdb.GetDefinitions();
+ foreach (var table in tables)
+ datasets.Add($"表格: {table.GetName()}");
+
+ // 获取所有要素数据集(Feature Datasets)
+ var featureDatasets = gdb.GetDefinitions();
+ foreach (var fd in featureDatasets)
+ datasets.Add($"要素数据集: {fd.GetName()}");
+
+ // 获取所有栅格数据集(Raster Datasets)
+ var rasterDatasets = gdb.GetDefinitions();
+ foreach (var raster in rasterDatasets)
+ datasets.Add($"栅格数据: {raster.GetName()}");
+ }
+ });
+ JsonRpcResultEntity result = new JsonRpcSuccessEntity()
+ {
+ Id = 1,
+ Result = JsonConvert.SerializeObject(datasets)
+ };
+ 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();
+ foreach (var gpMessage in messages)
+ {
+ messagesStr.AppendLine(gpMessage.Text);
+ }
+ return messagesStr.ToString();
+ }
+}
\ No newline at end of file
diff --git a/client/tool/KnowledgeBase.cs b/client/tool/KnowledgeBase.cs
new file mode 100644
index 0000000..38fe949
--- /dev/null
+++ b/client/tool/KnowledgeBase.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using LinkToolAddin.host.llm.entity;
+using LinkToolAddin.resource;
+using LinkToolAddin.server;
+using ModelContextProtocol.Server;
+using Newtonsoft.Json;
+
+namespace LinkToolAddin.client.tool;
+
+public class KnowledgeBase
+{
+ [McpServerTool, Description("可以查询ArcGIS Pro的帮助文档获取关于地理处理工具使用参数的说明")]
+ public static async Task QueryArcgisHelpDoc(string query)
+ {
+ DocDb docDb = new DocDb("sk-db177155677e438f832860e7f4da6afc", DocDb.KnowledgeBase.ArcGISProHelpDoc);
+ 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 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/common/HttpRequest.cs b/common/HttpRequest.cs
new file mode 100644
index 0000000..5d42052
--- /dev/null
+++ b/common/HttpRequest.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+using LinkToolAddin.host.llm.entity;
+using LinkToolAddin.host.llm.entity.stream;
+using Newtonsoft.Json;
+
+namespace LinkToolAddin.common;
+
+public class HttpRequest
+{
+ public static async Task SendPostRequestAsync(string url, string jsonContent, string apiKey)
+ {
+ using var httpClient = new HttpClient();
+ httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
+ var response = await httpClient.PostAsync(url, new StringContent(jsonContent, Encoding.UTF8, "application/json"));
+ response.EnsureSuccessStatusCode();
+ var responseBody = await response.Content.ReadAsStringAsync();
+ return responseBody;
+ }
+
+ public static async IAsyncEnumerable SendStreamPostRequestAsync(string url, string jsonContent, string apiKey)
+ {
+ using var client = new HttpClient();
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
+
+ // 发送 POST 请求并获取响应流
+ var response = await client.PostAsync(url,new StringContent(jsonContent, Encoding.UTF8, "application/json"));
+
+ // 验证响应状态
+ response.EnsureSuccessStatusCode();
+
+ // 获取响应流
+ using var stream = await response.Content.ReadAsStreamAsync();
+ using var reader = new StreamReader(stream);
+
+ // 流式读取
+ string line;
+ while ((line = await reader.ReadLineAsync()) != null)
+ {
+ string content = ProcessPartialResponse(line);
+ yield return content;
+ }
+ }
+
+ private static string ProcessPartialResponse(string rawData)
+ {
+ try
+ {
+ var lines = rawData.Split(new[] { "data: " }, StringSplitOptions.RemoveEmptyEntries);
+ foreach (var line in lines)
+ {
+ var trimmedLine = line.Trim();
+ if (!string.IsNullOrEmpty(trimmedLine))
+ {
+ var result = JsonConvert.DeserializeObject(trimmedLine);
+ return result.Choices[0].Delta.Content;
+ }
+ }
+ }
+ catch { /* 处理解析异常 */ }
+ return null;
+ }
+
+ public static async IAsyncEnumerable PostWithStreamingResponseAsync(
+ string url,
+ string body,
+ string apiKey,
+ string contentType = "application/json",
+ Action configureHeaders = null)
+ {
+ using (var client = new HttpClient())
+ {
+ // 设置超时时间为30分钟
+ client.Timeout = TimeSpan.FromMinutes(30);
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
+ using (var request = new HttpRequestMessage(HttpMethod.Post, url))
+ {
+ // 设置请求头和Body
+ Console.WriteLine("开始请求...");
+ configureHeaders?.Invoke(request);
+ request.Content = new StringContent(body, Encoding.UTF8, contentType);
+
+ // 发送请求并立即开始读取响应流
+ using (var response = await client.SendAsync(
+ request,
+ HttpCompletionOption.ResponseHeadersRead))
+ {
+ response.EnsureSuccessStatusCode();
+
+ // 获取响应流
+ using (var stream = await response.Content.ReadAsStreamAsync())
+ using (var reader = new StreamReader(stream))
+ {
+ string line;
+
+ StringBuilder incompleteJsonBuffer = new StringBuilder();
+
+ // 流式读取并输出到控制台
+ while ((line = await reader.ReadLineAsync()) != null)
+ {
+ foreach (var chunk in line.Split(new[] { "data: " }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ LlmStreamChat dataObj = null;
+ try
+ {
+ dataObj = JsonConvert.DeserializeObject(chunk);
+ }catch{/*process exception*/}
+
+ if (dataObj is not null)
+ {
+ yield return dataObj;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/JsonSchemaGenerator.cs b/common/JsonSchemaGenerator.cs
new file mode 100644
index 0000000..ff799c4
--- /dev/null
+++ b/common/JsonSchemaGenerator.cs
@@ -0,0 +1,150 @@
+using System.Collections.ObjectModel;
+
+namespace LinkToolAddin.common;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text.Json;
+
+public static class JsonSchemaGenerator
+{
+ public static string GenerateJsonSchema(MethodInfo methodInfo)
+ {
+ var parameters = methodInfo.GetParameters();
+ var properties = new Dictionary();
+ var required = new List();
+
+ foreach (var param in parameters)
+ {
+ var paramName = param.Name ?? throw new InvalidOperationException("参数没有名称。");
+ var paramSchema = GenerateSchemaForType(param.ParameterType);
+ properties[paramName] = paramSchema;
+
+ if (!param.IsOptional)
+ {
+ required.Add(paramName);
+ }
+ }
+
+ var schemaRoot = new Dictionary
+ {
+ { "$schema", "http://json-schema.org/draft-07/schema#" },
+ { "type", "object" },
+ { "properties", properties }
+ };
+
+ if (required.Count > 0)
+ {
+ schemaRoot["required"] = required;
+ }
+
+ var options = new JsonSerializerOptions { WriteIndented = true };
+ return JsonSerializer.Serialize(schemaRoot, options);
+ }
+
+ private static object GenerateSchemaForType(Type type)
+ {
+ // 处理可空类型
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
+ {
+ var underlyingType = Nullable.GetUnderlyingType(type);
+ return new[] { GenerateSchemaForType(underlyingType), "null" };
+ }
+
+ // 处理集合类型(数组或IEnumerable)
+ if (IsCollectionType(type, out Type elementType))
+ {
+ return new Dictionary
+ {
+ { "type", "array" },
+ { "items", GenerateSchemaForType(elementType) }
+ };
+ }
+
+ // 处理基本类型(int, string, bool, etc.)
+ if (IsPrimitiveType(type))
+ {
+ string jsonType = MapClrTypeToJsonType(type);
+ var schema = new Dictionary { { "type", jsonType } };
+
+ if (type == typeof(DateTime))
+ schema["format"] = "date-time";
+ else if (type == typeof(Guid))
+ schema["format"] = "uuid";
+
+ return schema;
+ }
+
+ // 处理复杂类型(类、结构体)
+ if (type.IsClass || type.IsValueType)
+ {
+ var props = new Dictionary();
+ foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
+ {
+ props[prop.Name] = GenerateSchemaForType(prop.PropertyType);
+ }
+
+ return new Dictionary
+ {
+ { "type", "object" },
+ { "properties", props }
+ };
+ }
+
+ // 默认情况
+ return new Dictionary { { "type", "any" } };
+ }
+
+ private static bool IsCollectionType(Type type, out Type elementType)
+ {
+ if (type == typeof(string))
+ {
+ elementType = null;
+ return false;
+ }
+
+ if (type.IsArray)
+ {
+ elementType = type.GetElementType();
+ return true;
+ }
+
+ if (type.IsGenericType)
+ {
+ var genericTypeDef = type.GetGenericTypeDefinition();
+ if (genericTypeDef == typeof(IEnumerable<>) ||
+ genericTypeDef == typeof(List<>) ||
+ genericTypeDef == typeof(Collection<>))
+ {
+ elementType = type.GetGenericArguments()[0];
+ return true;
+ }
+ }
+
+ elementType = null;
+ return false;
+ }
+
+ private static bool IsPrimitiveType(Type type)
+ {
+ return type.IsPrimitive || type == typeof(string) || type == typeof(decimal) || type == typeof(DateTime) || type == typeof(Guid);
+ }
+
+ private static string MapClrTypeToJsonType(Type type)
+ {
+ if (type == typeof(int) || type == typeof(short) || type == typeof(long) ||
+ type == typeof(uint) || type == typeof(ushort) || type == typeof(ulong))
+ return "integer";
+ if (type == typeof(float) || type == typeof(double) || type == typeof(decimal))
+ return "number";
+ if (type == typeof(bool))
+ return "boolean";
+ if (type == typeof(string))
+ return "string";
+ if (type == typeof(DateTime) || type == typeof(Guid))
+ return "string";
+ return "any";
+ }
+}
\ No newline at end of file
diff --git a/common/LocalResource.cs b/common/LocalResource.cs
new file mode 100644
index 0000000..4f4e53a
--- /dev/null
+++ b/common/LocalResource.cs
@@ -0,0 +1,47 @@
+using System;
+using System.IO;
+using System.Windows.Controls;
+using System.Windows.Media.Imaging;
+
+namespace LinkToolAddin.common;
+
+public class LocalResource
+{
+ public static string ReadFileByResource(string resourceName)
+ {
+ var assembly = System.Reflection.Assembly.GetExecutingAssembly();
+
+ using (Stream stream = assembly.GetManifestResourceStream(resourceName))
+ {
+ if (stream == null)
+ {
+ return($"找不到嵌入资源:{resourceName}");
+ }
+
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ return reader.ReadToEnd();
+ }
+ }
+ }
+
+ public static BitmapImage ReadImageByResource(string resourceName)
+ {
+ var assembly = System.Reflection.Assembly.GetExecutingAssembly();
+ using (Stream stream = assembly.GetManifestResourceStream(resourceName))
+ {
+ if (stream == null)
+ {
+ Console.WriteLine("资源未找到,请检查资源名称是否正确。");
+ return null;
+ }
+
+ // 如果是 WPF,可以转换为 BitmapImage
+ BitmapImage bitmap = new BitmapImage();
+ bitmap.BeginInit();
+ bitmap.StreamSource = stream;
+ bitmap.EndInit();
+ return bitmap;
+ }
+ }
+}
\ No newline at end of file
diff --git a/doc/TodoList.md b/doc/TodoList.md
new file mode 100644
index 0000000..f674f27
--- /dev/null
+++ b/doc/TodoList.md
@@ -0,0 +1,28 @@
+# 待办事项
+
+本文档用于记录和跟踪当前阶段待办事项及完成进度
+
+## 核心程序
+
+- [x] 提示词调用完整实现:以面向对象形式取代字典形式,加入参数和描述
+- [x] 接入本地文件系统等基础性MCP服务
+- [ ] Python代码执行的实现
+- [x] 适配qwen3、deepseek等推理模型并迁移
+- [x] 工具执行错误消息合并为一条
+
+## 提示词工程
+
+- [x] 将原来的单独XML规则修改为内嵌XML规则
+- [x] 系统提示词明确提示调用动态Prompt和知识库
+- [x] 错误和继续提示词预留工具消息占位符
+- [x] 错误和继续提示词明示
+- [ ] 系统提示词泛化增强
+- [ ] 针对qwen3适当调整
+
+## 前端交互
+
+- [ ] 三类消息卡片
+- [ ] 流式输出,根据id匹配修改或新增
+- [ ] 添加工作空间、发送、复制粘贴等交互
+- [ ] 独立UI线程
+- [ ] 工具卡片特殊提示,弹出窗口显示内容
\ No newline at end of file
diff --git a/host/CallMcp.cs b/host/CallMcp.cs
new file mode 100644
index 0000000..0e48789
--- /dev/null
+++ b/host/CallMcp.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using LinkToolAddin.server;
+using log4net;
+using Newtonsoft.Json;
+
+namespace LinkToolAddin.host
+{
+ public class CallMcp
+ {
+ private static readonly ILog log = LogManager.GetLogger(typeof(CallMcp));
+ public static async Task CallInnerMcpTool(string jsonRpcString)
+ {
+ log.Info("通过反射调用内部MCP工具");
+ var jsonRpcEntity = JsonConvert.DeserializeObject(jsonRpcString);
+
+ Type type = Type.GetType("LinkToolAddin.client.tool"+jsonRpcEntity.Method.Split(':')[0]);
+ MethodInfo method = type.GetMethod(jsonRpcEntity.Method.Split(':')[1],BindingFlags.Public | BindingFlags.Static);
+ var task = method.Invoke(null, new object[] { jsonRpcEntity.Params }) as Task;
+ JsonRpcResultEntity result = await task;
+ return JsonConvert.SerializeObject(result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/host/Gateway.cs b/host/Gateway.cs
new file mode 100644
index 0000000..ad85e57
--- /dev/null
+++ b/host/Gateway.cs
@@ -0,0 +1,915 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+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;
+using LinkToolAddin.host.prompt;
+using LinkToolAddin.message;
+using LinkToolAddin.server;
+using LinkToolAddin.ui.dockpane;
+using log4net;
+using Microsoft.Extensions.AI;
+using ModelContextProtocol.Client;
+using ModelContextProtocol.Protocol.Types;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Schema;
+using Newtonsoft.Json.Schema.Generation;
+using Tool = LinkToolAddin.host.mcp.Tool;
+using LinkToolAddin.common;
+using LinkToolAddin.host.llm.entity.stream;
+using MessageBox = ArcGIS.Desktop.Framework.Dialogs.MessageBox;
+
+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
+ {
+ api_key = "sk-db177155677e438f832860e7f4da6afc"
+ };
+ List messages = new List();
+ string toolInfos = await GetToolInfos(new McpServerList());
+ log.Info(SystemPrompt.SysPrompt(gdbPath, toolInfos));
+ messages.Add(new Message
+ {
+ Role = "system",
+ Content = SystemPrompt.SysPrompt(gdbPath, toolInfos)
+ });
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = message
+ });
+ bool goOn = true;
+ string pattern = "^[\\s\\S]*?<\\/tool_use>$";
+ string promptPattern = "^[\\s\\S]*?<\\/prompt>$";
+ McpServerList mcpServerList = new McpServerList();
+ while (goOn)
+ {
+ string reponse = await bailian.SendChatAsync(new LlmJsonContent()
+ {
+ Model = model,
+ Messages = messages,
+ Temperature = 0.3,
+ TopP = 0.6,
+ TopK = 25,
+ MaxTokens = 1024,
+ ThinkingBudget = 1024
+ });
+ long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ log.Info(reponse);
+ messages.Add(new Message
+ {
+ Role = "assistant",
+ Content = reponse
+ });
+ if (Regex.IsMatch(reponse, pattern))
+ {
+ //工具类型的消息
+ XElement toolUse = XElement.Parse(reponse);
+ 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);
+ if (mcpServer is SseMcpServer)
+ {
+ SseMcpServer sseMcpServer = mcpServer as SseMcpServer;
+ SseMcpClient client = new SseMcpClient(sseMcpServer.BaseUrl);
+ CallToolResponse toolResponse = await client.CallToolAsync(toolName,toolParams);
+ MessageListItem toolMessageItem = new ToolMessageItem
+ {
+ toolName = toolName,
+ toolParams = toolParams,
+ type = MessageType.TOOL_MESSAGE,
+ status = toolResponse.IsError ? "fail" : "success",
+ content = JsonConvert.SerializeObject(toolResponse),
+ id = timestamp.ToString()
+ };
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = toolResponse.IsError ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse))
+ });
+ callback?.Invoke(toolMessageItem);
+ }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);
+ MessageListItem toolMessageItem = new ToolMessageItem
+ {
+ toolName = toolName,
+ toolParams = toolParams,
+ type = MessageType.TOOL_MESSAGE,
+ status = toolResponse.IsError ? "fail" : "success",
+ content = JsonConvert.SerializeObject(toolResponse),
+ id = timestamp.ToString()
+ };
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = toolResponse.IsError ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(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;
+ JsonRpcResultEntity innerResult = await task;
+ if (innerResult is JsonRpcErrorEntity)
+ {
+ MessageListItem toolMessageItem = new ToolMessageItem
+ {
+ toolName = toolName,
+ toolParams = toolParams,
+ type = MessageType.TOOL_MESSAGE,
+ status = "fail",
+ content = JsonConvert.SerializeObject(innerResult)
+ };
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(innerResult))
+ });
+ callback?.Invoke(toolMessageItem);
+ }else if (innerResult is JsonRpcSuccessEntity)
+ {
+ MessageListItem toolMessageItem = new ToolMessageItem
+ {
+ toolName = toolName,
+ toolParams = toolParams,
+ type = MessageType.TOOL_MESSAGE,
+ status = "success",
+ content = JsonConvert.SerializeObject(innerResult)
+ };
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(innerResult))
+ });
+ callback?.Invoke(toolMessageItem);
+ }
+ }
+ }
+ else if (Regex.IsMatch(reponse, promptPattern))
+ {
+ XElement prompt = XElement.Parse(reponse);
+ string fullPromptName = prompt.Element("name")?.Value;
+ string promptArgs = prompt.Element("arguments")?.Value;
+ Dictionary promptParams = JsonConvert.DeserializeObject>(promptArgs);
+ string serverName = fullPromptName.Contains(":") ? fullPromptName.Split(':')[0] : fullPromptName;
+ string promptName = fullPromptName.Contains(":") ? fullPromptName.Split(':')[1] : fullPromptName;
+ string promptRes = DynamicPrompt.GetPrompt(promptName, null);
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = promptRes
+ });
+ }
+ else
+ {
+ MessageListItem chatMessageListItem = new ChatMessageItem()
+ {
+ content = reponse,
+ role = "assistant",
+ type = MessageType.CHAT_MESSAGE,
+ id = timestamp.ToString()
+ };
+ callback?.Invoke(chatMessageListItem);
+ }
+ if (reponse.EndsWith("[DONE]"))
+ {
+ goOn = false;
+ }
+ }
+ }
+
+ private static (string Matched, string Remaining) ExtractMatchedPart(string input, string toolPattern)
+ {
+ if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(toolPattern))
+ return (string.Empty, input);
+
+ Regex regex = new Regex(toolPattern);
+ Match match = regex.Match(input);
+
+ if (!match.Success)
+ return (string.Empty, input);
+
+ string matched = match.Value;
+ int startIndex = match.Index;
+ int length = match.Length;
+
+ // 构造剩余字符串
+ string remaining = input.Substring(0, startIndex) + input.Substring(startIndex + length);
+
+ return (matched, remaining);
+ }
+
+ public static async void SendMessageStream(string message, string model, string gdbPath, Action callback)
+ {
+ Llm bailian = new Bailian
+ {
+ api_key = "sk-db177155677e438f832860e7f4da6afc"
+ };
+ List messages = new List();
+ string toolInfos = "";
+ try
+ {
+ toolInfos = await GetToolInfos(new McpServerList());
+ messages.Add(new Message
+ {
+ Role = "system",
+ Content = SystemPrompt.SysPrompt(gdbPath, toolInfos)
+ });
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = message
+ });
+ }catch (Exception ex)
+ {
+ log.Error(ex);
+ MessageBox.Show(ex.Message,"获取MCP列表失败");
+ }
+ goOn = true;
+ string toolPattern = "([\\s\\S]*?)([\\s\\S]*?)<\\/name>([\\s\\S]*?)([\\s\\S]*?)<\\/arguments>([\\s\\S]*?)<\\/tool_use>";
+ string promptPattern = "([\\s\\S]*?)([\\s\\S]*?)<\\/name>([\\s\\S]*?)([\\s\\S]*?)<\\/arguments>([\\s\\S]*?)<\\/prompt>";
+ McpServerList mcpServerList = new McpServerList();
+ PromptServerList promptServerList = new PromptServerList();
+ int loop = 0;
+ string messageContent = ""; //一次请求下完整的response
+ bool queriedKnowledge = false;
+ bool executedTool = false;
+ while (goOn)
+ {
+ loop++;
+ if (loop > 500)
+ {
+ MessageBox.Show("达到最大循环次数", "退出循环");
+ break;
+ }
+ LlmJsonContent jsonContent = new LlmJsonContent()
+ {
+ Model = model,
+ Messages = messages,
+ Temperature = 0.3,
+ TopP = 0.4,
+ TopK = 7,
+ MaxTokens = 1000,
+ };
+ long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ List mcpToolRequests = new List();
+ List promptRequests = new List();
+ var (toolMatched, toolRemaining) = ExtractMatchedPart(messageContent, toolPattern);
+ var (promptMatched, promptRemaining) = ExtractMatchedPart(messageContent, promptPattern);
+ if (toolMatched == "" && promptMatched == "" && messageContent != "")
+ {
+ //如果本次回复不包含任何工具的调用或提示词的调用,则不再请求
+ goOn = false;
+ MessageListItem endMessageListItem1 = new ChatMessageItem
+ {
+ type = MessageType.END_TAG,
+ content = "",
+ id = (timestamp + 3).ToString(),
+ role = "assistant"
+ };
+ callback?.Invoke(endMessageListItem1);
+ break;
+ }
+
+ try
+ {
+ await foreach(LlmStreamChat llmStreamChat in bailian.SendChatStreamAsync(jsonContent))
+ {
+ if (!goOn)
+ {
+ MessageListItem endMessageListItem2 = new ChatMessageItem
+ {
+ type = MessageType.END_TAG,
+ content = "",
+ id = (timestamp+3).ToString(),
+ role = "assistant"
+ };
+ callback?.Invoke(endMessageListItem2);
+ break;
+ }
+ try
+ {
+ string chunk = llmStreamChat.Choices[0].Delta.Content;
+ MessageListItem reasonMessageListItem = new ChatMessageItem()
+ {
+ content = llmStreamChat.Choices[0].Delta.ResoningContent,
+ role = "assistant",
+ type = MessageType.REASON_MESSAGE,
+ id = (timestamp+2).ToString()
+ };
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ callback?.Invoke(reasonMessageListItem);
+ });
+ messageContent = chunk;
+ var (matched, remaining) = ExtractMatchedPart(chunk, toolPattern);
+ if (matched == "")
+ {
+ var (matchedPrompt, remainingPrompt) = ExtractMatchedPart(chunk, promptPattern);
+ if (matchedPrompt == "")
+ {
+ //普通消息文本
+ MessageListItem chatMessageListItem = new ChatMessageItem()
+ {
+ content = remainingPrompt,
+ role = "assistant",
+ type = MessageType.CHAT_MESSAGE,
+ id = timestamp.ToString()
+ };
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ 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
+ {
+ //包含工具调用请求的消息
+ 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);
+ //将工具调用请求添加至列表中待一次回答完整后再统一进行调用
+ MessageListItem chatMessageListItem = new ChatMessageItem()
+ {
+ content = remaining,
+ role = "assistant",
+ type = MessageType.CHAT_MESSAGE,
+ id = timestamp.ToString()
+ };
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ callback?.Invoke(chatMessageListItem);
+ });
+ if (!executedTool)
+ {
+ 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()
+ {
+ McpServer = mcpServer,
+ ToolName = toolName,
+ ToolArgs = toolParams,
+ };
+ mcpToolRequests.Add(mcpToolRequest);
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ log.Error(e.Message);
+ }
+ }
+ }catch (Exception e)
+ {
+ log.Error(e.Message);
+ MessageBox.Show(e.Message, "请求大模型出错");
+ }
+ if (messageContent != "")
+ {
+ messages.Add(new Message
+ {
+ Role = "assistant",
+ Content = messageContent
+ });
+ }
+ /*统一处理本次请求中的MCP工具调用需求*/
+ foreach (McpToolRequest mcpToolRequest in mcpToolRequests)
+ {
+ executedTool = true;
+ 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);
+ MessageListItem toolMessageItem1 = new ToolMessageItem
+ {
+ toolName = toolName,
+ toolParams = toolParams,
+ type = MessageType.TOOL_MESSAGE,
+ status = toolResponse.IsError ? "fail" : "success",
+ content = JsonConvert.SerializeObject(toolResponse),
+ result = JsonConvert.SerializeObject(toolResponse),
+ id = (timestamp + 1).ToString(),
+ role = "user"
+ };
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = toolResponse.IsError
+ ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse))
+ : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse))
+ });
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ callback?.Invoke(toolMessageItem1);
+ });
+ }
+ 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);
+ MessageListItem toolMessageItem1 = new ToolMessageItem
+ {
+ toolName = toolName,
+ toolParams = toolParams,
+ type = MessageType.TOOL_MESSAGE,
+ status = toolResponse.IsError ? "fail" : "success",
+ content = JsonConvert.SerializeObject(toolResponse),
+ result = JsonConvert.SerializeObject(toolResponse),
+ id = (timestamp + 1).ToString(),
+ role = "user"
+ };
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = toolResponse.IsError
+ ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse))
+ : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse))
+ });
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ 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();
+ 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;
+ string displayToolName = toolName;
+ if (displayToolName == "ArcGisProTool")
+ {
+ displayToolName = "【GP】"+toolParams["toolName"].ToString();
+ }
+ queriedKnowledge = false;
+ if (innerResult is JsonRpcErrorEntity)
+ {
+ MessageListItem toolMessageItem1 = new ToolMessageItem
+ {
+ toolName = displayToolName,
+ toolParams = toolParams,
+ type = MessageType.TOOL_MESSAGE,
+ status = "fail",
+ content = JsonConvert.SerializeObject(innerResult),
+ result = JsonConvert.SerializeObject(innerResult),
+ id = (timestamp + 1).ToString(),
+ role = "user"
+ };
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(innerResult))
+ });
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ callback?.Invoke(toolMessageItem1);
+ });
+ }
+ else if (innerResult is JsonRpcSuccessEntity)
+ {
+ MessageListItem toolMessageItem1 = new ToolMessageItem
+ {
+ toolName = displayToolName,
+ toolParams = toolParams,
+ type = MessageType.TOOL_MESSAGE,
+ status = "success",
+ content = JsonConvert.SerializeObject(innerResult),
+ result = JsonConvert.SerializeObject(innerResult),
+ id = (timestamp + 1).ToString(),
+ role = "user"
+ };
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(innerResult))
+ });
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ callback?.Invoke(toolMessageItem1);
+ });
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ log.Error(ex.Message);
+ }
+
+ }
+ /*统一处理本次请求中的Prompt调用需求*/
+ foreach (PromptRequest promptRequest in promptRequests)
+ {
+ try
+ {
+ string promptContent = DynamicPrompt.GetPrompt(promptRequest.PromptName, promptRequest.PromptArgs);
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = promptContent
+ });
+ MessageListItem toolMessageItem = new ToolMessageItem
+ {
+ toolName = "调用提示词",
+ toolParams = new Dictionary(),
+ type = MessageType.TOOL_MESSAGE,
+ status = "success",
+ content = "成功调用提示词:"+promptRequest.PromptName,
+ id = (timestamp+1).ToString(),
+ result = "成功调用提示词:"+promptRequest.PromptName,
+ role = "user"
+ };
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ callback?.Invoke(toolMessageItem);
+ });
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ log.Error(e.Message);
+ }
+ }
+ MessageListItem endMessageListItem = new ChatMessageItem
+ {
+ type = MessageType.END_TAG,
+ content = "",
+ id = (timestamp+3).ToString(),
+ role = "assistant"
+ };
+ callback?.Invoke(endMessageListItem);
+ }
+ }
+
+ private static async Task GetToolInfos(McpServerList mcpServerList)
+ {
+ StringBuilder toolInfos = new StringBuilder();
+ int i = 0;
+ List failedMcp = new List();
+ List failedMcpString = new List();
+ foreach (McpServer mcpServer in mcpServerList.GetAllServers())
+ {
+ i++;
+ try
+ {
+ if (mcpServer is InnerMcpServer)
+ {
+ InnerMcpServer innerMcpServer = (InnerMcpServer)mcpServer;
+ Type type = Type.GetType("LinkToolAddin.client.tool." + innerMcpServer.Name);
+ MethodInfo[] methods = type.GetMethods();
+ foreach (MethodInfo method in methods)
+ {
+ if (method.IsPublic && method.IsStatic)
+ {
+ string methodName = method.Name;
+ string methodDescription = method.GetCustomAttribute()?.Description;
+ string methodParamSchema = LinkToolAddin.common.JsonSchemaGenerator.GenerateJsonSchema(method);
+ McpToolDefinition toolDefinition = new McpToolDefinition
+ {
+ Tool = new Tool
+ {
+ Name = innerMcpServer.Name + ":" + methodName,
+ Description = methodDescription,
+ Arguments = methodParamSchema
+ }
+ };
+ XNode node = JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition));
+ toolInfos.AppendLine(node.ToString());
+ toolInfos.AppendLine();
+ }
+ }
+ }
+ else if(mcpServer is SseMcpServer)
+ {
+ SseMcpClient client = new SseMcpClient((mcpServer as SseMcpServer).BaseUrl);
+ IList tools = await client.GetToolListAsync();
+ foreach (McpClientTool tool in tools)
+ {
+ string toolName = (mcpServer as SseMcpServer).Name + ":" + tool.Name;
+ string toolDescription = tool.Description;
+ string toolParamSchema = tool.JsonSchema.ToString();
+ McpToolDefinition toolDefinition = new McpToolDefinition
+ {
+ Tool = new Tool
+ {
+ Name = toolName,
+ Description = toolDescription,
+ Arguments = toolParamSchema
+ }
+ };
+ toolInfos.AppendLine(JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition)).ToString());
+ toolInfos.AppendLine();
+ }
+ }else if (mcpServer is StdioMcpServer)
+ {
+ StdioMcpClient client = new StdioMcpClient((mcpServer as StdioMcpServer).Command, (mcpServer as StdioMcpServer).Args);
+ IList tools = await client.GetToolListAsync();
+ foreach (McpClientTool tool in tools)
+ {
+ string toolName = (mcpServer as StdioMcpServer).Name + ":" + tool.Name;;
+ string toolDescription = tool.Description;
+ string toolParamSchema = tool.JsonSchema.ToString();
+ McpToolDefinition toolDefinition = new McpToolDefinition
+ {
+ Tool = new Tool
+ {
+ Name = toolName,
+ Description = toolDescription,
+ Arguments = CompressJson(toolParamSchema)
+ }
+ };
+ toolInfos.AppendLine(JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition)).ToString());
+ toolInfos.AppendLine();
+ }
+ }
+ }catch (Exception e)
+ {
+ log.Error(e.Message);
+ failedMcp.Add(i);
+ failedMcpString.Add(mcpServerList.GetAllServerNames()[i]);
+ }
+ }
+
+ List prompts = DynamicPrompt.GetAllPrompts();
+ foreach (UserPrompt userPrompt in prompts)
+ {
+ try
+ {
+ XNode node = JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(new PromptDefinition(){UserPrompt = userPrompt}));
+ toolInfos.AppendLine(node.ToString());
+ toolInfos.AppendLine();
+ }
+ catch (Exception e)
+ {
+ log.Error(e.Message);
+ }
+ }
+
+ if (!failedMcp.IsNullOrEmpty())
+ {
+ MessageBox.Show($"读取失败的MCP服务有:{JsonConvert.SerializeObject(failedMcpString)}","MCP读取错误");
+ }
+ return toolInfos.ToString();
+ }
+
+ public static string CompressJson(string json)
+ {
+ // 解析JSON并自动去除无关空白
+ var token = JToken.Parse(json);
+ // 序列化为无格式紧凑字符串
+ return token.ToString(Newtonsoft.Json.Formatting.None);
+ }
+
+ private static string GenerateMethodParamSchema(MethodInfo method)
+ {
+ var generator = new JSchemaGenerator
+ {
+ // 启用属性注解处理
+ DefaultRequired = Required.DisallowNull,
+ SchemaReferenceHandling = SchemaReferenceHandling.None
+ };
+
+ var paramSchema = new JSchema { Type = JSchemaType.Object };
+
+ foreach (ParameterInfo param in method.GetParameters())
+ {
+ // 生成参数类型的基础Schema
+ JSchema typeSchema = generator.Generate(param.ParameterType);
+
+ // 添加Description描述
+ var descriptionAttr = param.GetCustomAttribute();
+ if (descriptionAttr != null)
+ {
+ typeSchema.Description = descriptionAttr.Description; // 网页6的Description特性处理
+ }
+
+ paramSchema.Properties.Add(param.Name, typeSchema);
+ }
+ var settings = new JsonSerializerSettings {
+ Formatting = Newtonsoft.Json.Formatting.None, // 关键设置:禁用缩进和换行
+ NullValueHandling = NullValueHandling.Ignore // 可选:忽略空值
+ };
+ return JsonConvert.SerializeObject(paramSchema, settings);;
+ }
+
+ public static async void TestChatMessage(string message, string model, string gdbPath,
+ Action callback)
+ {
+ MessageListItem chatListItem = new ChatMessageItem
+ {
+ content = message,
+ role = "assistant",
+ type = MessageType.CHAT_MESSAGE,
+ id = "testmsg12345"
+ };
+ callback?.Invoke(chatListItem);
+ }
+
+ public static async void TestToolMessage(string message, string model, string gdbPath, Action callback)
+ {
+ MessageListItem toolListItem = new ToolMessageItem
+ {
+ content = message,
+ type = MessageType.TOOL_MESSAGE,
+ toolName = "arcgis_pro.executeTool",
+ toolParams = new Dictionary
+ {
+ {"gp_name","analysis.Buffer"},
+ {"gp_params","[\"C:\\test.gdb\\river\",\"30 Meters\"]"}
+ },
+ id = "testtool123456",
+ status = "success",
+ role = "user",
+ result = "成功创建缓冲区"
+ };
+ callback?.Invoke(toolListItem);
+ }
+
+ public static async void TestWorkflow(string message, string model, string gdbPath, Action callback)
+ {
+ Thread.Sleep(2000);
+ MessageListItem chatListItem = new ChatMessageItem
+ {
+ content = message,
+ role = "assistant",
+ type = MessageType.CHAT_MESSAGE,
+ id = "testid12345"
+ };
+ callback?.Invoke(chatListItem);
+ Thread.Sleep(1500);
+ MessageListItem toolListItem = new ToolMessageItem
+ {
+ content = message,
+ type = MessageType.TOOL_MESSAGE,
+ toolName = "arcgis_pro.executeTool",
+ toolParams = new Dictionary
+ {
+ {"gp_name","analysis.Buffer"},
+ {"gp_params","[\"C:\\test.gdb\\river\",\"30 Meters\"]"}
+ },
+ id = "testtool123456",
+ status = "success",
+ role = "user",
+ result = "成功创建缓冲区"
+ };
+ callback?.Invoke(toolListItem);
+ }
+}
\ No newline at end of file
diff --git a/host/McpServerList.cs b/host/McpServerList.cs
new file mode 100644
index 0000000..3393414
--- /dev/null
+++ b/host/McpServerList.cs
@@ -0,0 +1,119 @@
+using System.Collections.Generic;
+using LinkToolAddin.host.mcp;
+
+namespace LinkToolAddin.host;
+
+public class McpServerList
+{
+ private Dictionary servers = new Dictionary();
+
+ public McpServerList()
+ {
+ servers.Add("gaode",new SseMcpServer
+ {
+ Name = "gaode",
+ Type = "sse",
+ Description = "高德地图API",
+ IsActive = true,
+ BaseUrl = "https://mcp.amap.com/sse?key=ed418512c94ade8f83d42c37b77d2bb2",
+ Headers = new Dictionary()
+ {
+ {"Content-Type","application/json"}
+ }
+ });
+ servers.Add("ArcGisPro", new InnerMcpServer
+ {
+ Name = "ArcGisPro",
+ Type = "inner",
+ Description = "可以调用arcgis的地理处理工具或执行python代码等",
+ IsActive = true
+ });
+ servers.Add("KnowledgeBase", new InnerMcpServer
+ {
+ Name = "KnowledgeBase",
+ Type = "inner",
+ Description = "可以调用进行查询知识库,获取相关参考信息。" ,
+ // "有地理信息的相关案例步骤参考以及Arcgis Pro的工具详细信息",
+ IsActive = true
+ });
+ //servers.Add("filesystem", new StdioMcpServer()
+ //{
+ // Name = "filesystem",
+ // Type = "stdio",
+ // Command = "npx",
+ // Args = new List()
+ // {
+ // "-y",
+ // "@modelcontextprotocol/server-filesystem",
+ // "F:\\secondsemester\\linktool\\test\\LinkTool0607\\LinkTool0607.gdb"
+ // }
+ //});
+ //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",
+ // "F:\\secondsemester\\linktool\\test\\LinkTool0607\\LinkTool0607.gdb",
+ // "--python-path",
+ // "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe"
+ // }
+ //});
+ }
+
+ public McpServer GetServer(string name)
+ {
+ if (servers.ContainsKey(name))
+ {
+ return servers[name];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public List GetAllServers()
+ {
+ List serverList = new List();
+ foreach (var server in servers)
+ {
+ serverList.Add(server.Value);
+ }
+ 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/PromptServerList.cs b/host/PromptServerList.cs
new file mode 100644
index 0000000..5a69be1
--- /dev/null
+++ b/host/PromptServerList.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using LinkToolAddin.host.prompt;
+
+namespace LinkToolAddin.host;
+
+public class PromptServerList
+{
+ Dictionary promptServers = new Dictionary();
+
+ public PromptServerList()
+ {
+ promptServers.Add("plan", new PromptServer("plan",
+ "根据用户描述的问题推断出需要使用的ArcGIS Pro工具调用名称列表",
+ //"请根据用户所提问题进行工具规划,输出格式为'1.工具2.工具’,如果是ArcGIS Pro的工具,根据用户的具体需求和提供的数据类型," +
+ // "判断并列出所有必要的分析步骤和工具,同时严格遵守知识库中“ArcGIS Pro工具调用大全”里“工具调用名称”一列的工具命名规则(例如:analysis.Erase)," +
+ // "确保工具名的准确无误,工具与所属工具箱的对应关系正确无误,严格遵循文档规定的格式和大小写。工具的组合顺序优先参考知识库中“ArcGIS Pro的帮助文档”。" +
+ // "有一些与分析无关的数据能够进行排除,在选择工具时不受其干扰。"
+ "你对ArcGIS Pro的工具箱及其功能了如指掌,能够根据用户的具体需求和提供的数据类型,迅速判断并列出所有必要的分析步骤和工具,并随之调用知识库工具确认任务规划中的调用名是否正确,以及确认其对应参数" +
+ "同时严格遵守知识库中“ArcGIS Pro工具调用大全”里“工具调用名称”一列的工具命名规则(例如:analysis.Erase),确保工具名的准确无误。" +
+ "工具的组合顺序优先参考知识库中“ArcGIS Pro的帮助文档”。有一些与分析无关的数据能够进行排除,在选择工具时不受其干扰。" +
+ "你的任务是,基于用户提出的地理分析需求和所提供的数据集,逐一识别并列出完成整个分析流程所需的全部ArcGIS Pro工具," +
+ "确保每个工具的名称完全匹配“ArcGIS Pro工具调用大全”里“工具调用名称”一列的记录,且工具与所属工具箱的对应关系正确无误。" +
+ "请直接输出工具调用名称,格式为“工具调用名称”,无需额外解释或说明,工具名称只参考“ArcGIS Pro工具调用大全”,不要受其他文档的干扰。" +
+
+ "你的回复应简洁明了,仅包含所需工具的列表,输出格式为序号分隔的工具调用名以及调用知识库工具工具的XML格式,主要目的是纠正规划中错误的调用名和了解工具参数便于下一步的工具调用,因为调用名和参数对于工具执行非常重要,一旦出错则可能导致运行失败。严格遵循文档规定的格式和大小写,确保信息的准确性和专业性。"));
+ promptServers.Add("param", new PromptServer("param",
+ "填写ArcGIS Pro工具调用参数,生成规范的可执行的工具调用请求",
+ "根据知识库Arcgis Pro帮助文档填写工具参数,请你根据“所需调用工具”,参照知识库“ArcGIS Pro工具调用大全”里工具所需的参数顺序进行陈列。" +
+ "列出所需调用工具的名称及其按照“ArcGIS Pro工具调用大全”里“的该工具所需的参数顺序陈列对应的参数。如果跳过了可选参数需要用空字符表示。" +
+ "不能更改所需调用工具的名称。例如:arcpy.analysis.Buffer,不能只写成Buffer。确保格式、参数的完整性和准确性,避免任何遗漏或错误。" +
+ "特别注意,所有参数均应视为字符串类型,即使它们可能代表数字或文件路径。例如问题为:使用地理处理中的\"擦除分析\"工具(Erase),将圆形要素(circle.shp)与方形要素(square.shp)进行空间叠加运算。" +
+ "输出: \"in_features\":\"circle.shp\",\r\n \"erase_features\":\"sqaure.shp\",\r\n \"out_feature_class\":\"res.shp\",\r\n \"cluster_tolerance\":\"1\""));
+ promptServers.Add("code", new PromptServer("code",
+ "生成可运行的arcpy代码",
+ "根据你在多种编程语言、框架、设计模式和最佳实践方面拥有的广泛知识。现在需要根据用户需求生成高质量的代码,并确保语法正确。" +
+ "编写Arcpy代码时必须符合ArcGIS官方文档要求。参考官方文档的方法参数,确保编写正确。"));
+ }
+
+ public Dictionary GetPromptsDict()
+ {
+ return promptServers;
+ }
+
+ public PromptServer GetPromptServer(string key)
+ {
+ return promptServers[key];
+ }
+}
\ No newline at end of file
diff --git a/host/ToolRequest.cs b/host/ToolRequest.cs
new file mode 100644
index 0000000..944026a
--- /dev/null
+++ b/host/ToolRequest.cs
@@ -0,0 +1,6 @@
+namespace LinkToolAddin.host;
+
+public class ToolRequest
+{
+
+}
\ No newline at end of file
diff --git a/host/llm/Bailian.cs b/host/llm/Bailian.cs
new file mode 100644
index 0000000..6d7d273
--- /dev/null
+++ b/host/llm/Bailian.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+using LinkToolAddin.common;
+using LinkToolAddin.host.llm.entity;
+using LinkToolAddin.host.llm.entity.stream;
+using Newtonsoft.Json;
+
+namespace LinkToolAddin.host.llm;
+
+public class Bailian : Llm
+{
+ public string model { get; set; } = "qwen-max";
+ public string temperature { get; set; }
+ public string top_p { get; set; }
+ public string max_tokens { get; set; }
+ public string app_id { get; set; }
+ public string api_key { get; set; }
+ public async IAsyncEnumerable SendChatStreamAsync(LlmJsonContent jsonContent)
+ {
+ jsonContent.Stream = true;
+ StringBuilder contentBuilder = new StringBuilder();
+ StringBuilder reasonBuilder = new StringBuilder();
+ await foreach (LlmStreamChat chunk in HttpRequest.PostWithStreamingResponseAsync(
+ "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
+ JsonConvert.SerializeObject(jsonContent),
+ api_key))
+ {
+ contentBuilder.Append(chunk.Choices[0].Delta.Content);
+ reasonBuilder.Append(chunk.Choices[0].Delta.ResoningContent);
+ LlmStreamChat fullChunk = chunk;
+ fullChunk.Choices[0].Delta.Content = contentBuilder.ToString();
+ fullChunk.Choices[0].Delta.ResoningContent = reasonBuilder.ToString();
+ yield return fullChunk;
+ }
+ }
+
+ public IAsyncEnumerable SendApplicationStreamAsync(string message)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public async Task SendChatAsync(LlmJsonContent jsonContent)
+ {
+ string url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
+ string responseBody = await HttpRequest.SendPostRequestAsync(url, JsonConvert.SerializeObject(jsonContent), api_key);
+ LlmChat result = JsonConvert.DeserializeObject(responseBody);
+ return result.Choices[0].Message.Content;
+ }
+
+ public async Task SendApplicationAsync(CommonInput commonInput)
+ {
+ string url = $"https://dashscope.aliyuncs.com/api/v1/apps/{app_id}/completion";
+ string responseBody = await HttpRequest.SendPostRequestAsync(url, JsonConvert.SerializeObject(commonInput), api_key);
+ ApplicationOutput result = JsonConvert.DeserializeObject(responseBody);
+ return responseBody;
+ }
+}
\ No newline at end of file
diff --git a/host/llm/Llm.cs b/host/llm/Llm.cs
index 95e11c4..bb3ba53 100644
--- a/host/llm/Llm.cs
+++ b/host/llm/Llm.cs
@@ -1,4 +1,7 @@
using System.Collections.Generic;
+using System.Threading.Tasks;
+using LinkToolAddin.host.llm.entity;
+using LinkToolAddin.host.llm.entity.stream;
namespace LinkToolAddin.host.llm;
@@ -9,6 +12,8 @@ public interface Llm
public string top_p { get; set; }
public string max_tokens { get; set; }
- public IAsyncEnumerable SendChatStreamAsync(string message);
+ public IAsyncEnumerable SendChatStreamAsync(LlmJsonContent jsonContent);
public IAsyncEnumerable SendApplicationStreamAsync(string message);
+ public Task SendChatAsync(LlmJsonContent jsonContent);
+ public Task SendApplicationAsync(CommonInput commonInput);
}
\ No newline at end of file
diff --git a/host/llm/entity/ApplicationOutput.cs b/host/llm/entity/ApplicationOutput.cs
new file mode 100644
index 0000000..94405fa
--- /dev/null
+++ b/host/llm/entity/ApplicationOutput.cs
@@ -0,0 +1,51 @@
+namespace LinkToolAddin.host.llm.entity
+{
+ using System;
+ using System.Collections.Generic;
+
+ using System.Globalization;
+ using Newtonsoft.Json;
+ using Newtonsoft.Json.Converters;
+
+ public partial class ApplicationOutput
+ {
+ [JsonProperty("output")]
+ public Output Output { get; set; }
+
+ [JsonProperty("usage")]
+ public Usage Usage { get; set; }
+
+ [JsonProperty("request_id")]
+ public Guid RequestId { get; set; }
+ }
+
+ public partial class Output
+ {
+ [JsonProperty("finish_reason")]
+ public string FinishReason { get; set; }
+
+ [JsonProperty("session_id")]
+ public string SessionId { get; set; }
+
+ [JsonProperty("text")]
+ public string Text { get; set; }
+ }
+
+ public partial class Usage
+ {
+ [JsonProperty("models")]
+ public List Models { get; set; }
+ }
+
+ public partial class Model
+ {
+ [JsonProperty("output_tokens")]
+ public long OutputTokens { get; set; }
+
+ [JsonProperty("model_id")]
+ public string ModelId { get; set; }
+
+ [JsonProperty("input_tokens")]
+ public long InputTokens { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/host/llm/entity/CommonInput.cs b/host/llm/entity/CommonInput.cs
new file mode 100644
index 0000000..6d1da54
--- /dev/null
+++ b/host/llm/entity/CommonInput.cs
@@ -0,0 +1,31 @@
+namespace LinkToolAddin.host.llm.entity
+{
+ using System;
+ using System.Collections.Generic;
+
+ using System.Globalization;
+ using Newtonsoft.Json;
+ using Newtonsoft.Json.Converters;
+
+ public partial class CommonInput
+ {
+ [JsonProperty("input")]
+ public Input Input { get; set; }
+
+ [JsonProperty("parameters")]
+ public Debug Parameters { get; set; }
+
+ [JsonProperty("debug")]
+ public Debug Debug { get; set; }
+ }
+
+ public partial class Debug
+ {
+ }
+
+ public partial class Input
+ {
+ [JsonProperty("prompt")]
+ public string Prompt { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/host/llm/entity/KnowldgeResult.cs b/host/llm/entity/KnowldgeResult.cs
new file mode 100644
index 0000000..0937e30
--- /dev/null
+++ b/host/llm/entity/KnowldgeResult.cs
@@ -0,0 +1,42 @@
+namespace LinkToolAddin.host.llm.entity
+{
+ using System;
+ using System.Collections.Generic;
+
+ using System.Globalization;
+ using Newtonsoft.Json;
+ using Newtonsoft.Json.Converters;
+
+ public partial class KnowledgeResult
+ {
+ [JsonProperty("rewriteQuery")]
+ public string RewriteQuery { get; set; }
+
+ [JsonProperty("chunkList")]
+ public List ChunkList { get; set; }
+ }
+
+ public partial class ChunkList
+ {
+ [JsonProperty("score")]
+ public double Score { get; set; }
+
+ [JsonProperty("imagesUrl")]
+ public List