From 53dad1db2b5e9cfc79c4a47cb52fb45cea377cf9 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 27 Apr 2025 17:58:50 +0800 Subject: [PATCH 01/60] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=9B=B8=E5=85=B3=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index b8b735f..f0d5845 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ ### 首次贡献前准备 +#### 克隆仓库 + 本项目为.NET 8.0开发,使用C#语言编写,使用Visual Studio 2022或Rider 2024及以上进行开发。 协作时需要先拉取主分支代码,通过以下命令进行拉取: @@ -20,6 +22,18 @@ git clone xxx.git ``` +#### 开发环境配置 + +1. 需要确保你电脑上已安装ArcGIS Pro 3.4.3及其SDK +2. 修改`Properties/launchSettings.json`中executablePath项的值为你电脑上ArcGIS Pro.exe的位置 +3. 确保电脑上已安装.NET SDK 8(注意版本一定是8,不能是9) +4. 使用NuGet包管理器安装`Esri.ArcGISPro.Extensions30`包(版本参考:3.4.1.55405) +5. 部分情况下可能还需要替换LinkToolAddin.csproj中的所有相关安装路径 +6. 点击运行进行测试,看是否能正常打开ArcGIS Pro和LinkTool插件 +7. 确认无误建议commit到本地的master分支以备后续使用(但请勿推送至远端) + +#### 创建分支 + 然后在本地创建一个新的分支进行开发,以功能或模块进行命名,请修改下面命令的`branch_name`为你要创建的分支名称: ```bash From ec207f3d1acc50b0417e3d3e735f494995edc210 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 27 Apr 2025 17:59:03 +0800 Subject: [PATCH 02/60] =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 37 ++++++++++++++++++---------------- Properties/launchSettings.json | 2 +- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index 818ba95..d730a4a 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -1,11 +1,11 @@  - net8.0-windows true win-x64 false true CA1416 + net8.0-windows @@ -22,77 +22,77 @@ - 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 @@ -102,5 +102,8 @@ - + + + + diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index f8e1bfb..42da189 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "LinkToolAddin": { "commandName": "Executable", - "executablePath": "C:\\Users\\PeterZhong\\AppData\\Local\\Programs\\ArcGIS\\Pro\\bin\\ArcGISPro.exe" + "executablePath": "C:\\Program Files\\ArcGIS\\Pro\\bin\\ArcGISPro.exe" } } } \ No newline at end of file From f341ac9aacf35ee4ed6f5cea57bbd873333c3fca Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Tue, 13 May 2025 23:59:58 +0800 Subject: [PATCH 03/60] =?UTF-8?q?=E7=B3=BB=E7=BB=9FJSON=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/dockpane/DialogDockpane.xaml | 1 + ui/dockpane/DialogDockpane.xaml.cs | 80 ++++++++++++++++++++++++++---- 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/ui/dockpane/DialogDockpane.xaml b/ui/dockpane/DialogDockpane.xaml index 3b569d7..3b8967b 100644 --- a/ui/dockpane/DialogDockpane.xaml +++ b/ui/dockpane/DialogDockpane.xaml @@ -29,6 +29,7 @@ + \ No newline at end of file diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index 4a594fd..0a6ce23 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -1,17 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Text.Json; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; namespace LinkToolAddin.ui.dockpane @@ -25,5 +16,72 @@ namespace LinkToolAddin.ui.dockpane { InitializeComponent(); } + + private void TestButton_OnClick(object sender, RoutedEventArgs e) + { + string originalJson = @"{ + ""name"": ""Alice"", + ""age"": 30, + ""isStudent"": false, + ""hobbies"": [ + ""reading"", + ""swimming"", + ""hiking"" + ], + ""address"": { + ""street"": ""123 Main St"", + ""city"": ""Anytown"", + ""postalCode"": ""12345"" } -} +}"; + + try + { + // 反序列化 JSON 到对象 + Person? person = JsonSerializer.Deserialize(originalJson); + + if (person != null) + { + // 序列化对象回 JSON 字符串 + string serializedJson = JsonSerializer.Serialize(person, new JsonSerializerOptions + { + WriteIndented = true // 格式化输出 + }); + + Console.WriteLine("序列化后的 JSON:"); + Console.WriteLine(serializedJson); + ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show(serializedJson); + } + else + { + Console.WriteLine("反序列化失败,对象为 null。"); + } + } + catch (JsonException ex) + { + Console.WriteLine($"JSON 处理错误: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"发生错误: {ex.Message}"); + } + } + } + + // 定义数据模型类 + public class Person + { + public string? Name { get; set; } + public int Age { get; set; } + public bool IsStudent { get; set; } + public List? Hobbies { get; set; } + public Address? Address { get; set; } + } + + public class Address + { + public string? Street { get; set; } + public string? City { get; set; } + public string? PostalCode { get; set; } + } +} \ No newline at end of file From 5107fc2f1da7b2fbb42f50035aae19535ac336ff Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Wed, 14 May 2025 00:54:06 +0800 Subject: [PATCH 04/60] =?UTF-8?q?=E6=8E=A5=E5=85=A5=E9=AB=98=E5=BE=B7MCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 4 + client/CallArcGISPro.cs | 18 +++ client/PythonMcpClient.cs | 6 + client/SseMcpClient.cs | 56 +++++++++ common/HttpRequest.cs | 21 ++++ host/CallMcp.cs | 28 +++++ host/llm/Bailian.cs | 45 +++++++ host/llm/Llm.cs | 4 + host/llm/entity/ApplicationOutput.cs | 51 ++++++++ host/llm/entity/CommonInput.cs | 31 +++++ host/llm/entity/KnowldgeResult.cs | 42 +++++++ host/llm/entity/LlmChat.cs | 69 +++++++++++ host/llm/entity/LlmJsonContent.cs | 38 ++++++ resource/DocDb.cs | 54 +++++++++ resource/GpToolDb.cs | 9 ++ resource/PromptDb.cs | 8 ++ server/CallArcGISPro.cs | 23 ++++ server/JsonRpcEntity.cs | 25 ++++ server/JsonRpcErrorEntity.cs | 25 ++++ server/JsonRpcResultEntity.cs | 15 +++ server/JsonRpcSuccessEntity.cs | 16 +++ ui/dockpane/DialogDockpane.xaml | 9 +- ui/dockpane/DialogDockpane.xaml.cs | 162 +++++++++++++++---------- ui/dockpane/DialogDockpaneViewModel.cs | 5 + 24 files changed, 693 insertions(+), 71 deletions(-) create mode 100644 client/CallArcGISPro.cs create mode 100644 client/PythonMcpClient.cs create mode 100644 client/SseMcpClient.cs create mode 100644 common/HttpRequest.cs create mode 100644 host/CallMcp.cs create mode 100644 host/llm/Bailian.cs create mode 100644 host/llm/entity/ApplicationOutput.cs create mode 100644 host/llm/entity/CommonInput.cs create mode 100644 host/llm/entity/KnowldgeResult.cs create mode 100644 host/llm/entity/LlmChat.cs create mode 100644 host/llm/entity/LlmJsonContent.cs create mode 100644 resource/DocDb.cs create mode 100644 resource/GpToolDb.cs create mode 100644 resource/PromptDb.cs create mode 100644 server/CallArcGISPro.cs create mode 100644 server/JsonRpcEntity.cs create mode 100644 server/JsonRpcErrorEntity.cs create mode 100644 server/JsonRpcResultEntity.cs create mode 100644 server/JsonRpcSuccessEntity.cs diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index d730a4a..6bd5b78 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -104,6 +104,10 @@ + + + + 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/PythonMcpClient.cs b/client/PythonMcpClient.cs new file mode 100644 index 0000000..e25e7d8 --- /dev/null +++ b/client/PythonMcpClient.cs @@ -0,0 +1,6 @@ +namespace LinkToolAddin.client; + +public class PythonMcpClient +{ + +} \ No newline at end of file diff --git a/client/SseMcpClient.cs b/client/SseMcpClient.cs new file mode 100644 index 0000000..471af60 --- /dev/null +++ b/client/SseMcpClient.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using ModelContextProtocol.Client; +using ModelContextProtocol.Protocol.Transport; +using Newtonsoft.Json; + +namespace LinkToolAddin.client; + +public class SseMcpClient +{ + public static async Task testGaodeMcp() + { + Console.WriteLine("Connecting to 高德 MCP Server via SSE..."); + + // 创建 MCP Server 配置 + SseClientTransportOptions options = new SseClientTransportOptions + { + Endpoint = new Uri("https://mcp.amap.com/sse?key=ed418512c94ade8f83d42c37b77d2bb2"), + }; + + IClientTransport transport = new SseClientTransport(options);; + + // 创建 MCP Client + var client = await McpClientFactory.CreateAsync(transport); + Console.WriteLine("Connected to 高德 MCP Server"); + + try + { + // 获取可用工具列表 + var tools = await client.ListToolsAsync(); + Console.WriteLine("\nAvailable Tools:"); + foreach (var tool in tools) + { + Console.WriteLine($"- {tool.Name}: {tool.Description}"); + } + + // 示例调用:获取当前定位 + var result = await client.CallToolAsync("amap.maps_weather", new Dictionary{{"city","北京"}}); + Console.WriteLine("\n[amap.get_location] Result:"); + Console.WriteLine(result); + return JsonConvert.SerializeObject(result); + } + catch (Exception ex) + { + Console.WriteLine($"Error occurred: {ex.Message}"); + } + finally + { + await client.DisposeAsync(); + } + + Console.WriteLine("Client closed."); + return "failed"; + } +} \ No newline at end of file diff --git a/common/HttpRequest.cs b/common/HttpRequest.cs new file mode 100644 index 0000000..a38807c --- /dev/null +++ b/common/HttpRequest.cs @@ -0,0 +1,21 @@ +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using LinkToolAddin.host.llm.entity; +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; + } +} \ No newline at end of file diff --git a/host/CallMcp.cs b/host/CallMcp.cs new file mode 100644 index 0000000..a98488d --- /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."+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/llm/Bailian.cs b/host/llm/Bailian.cs new file mode 100644 index 0000000..bd444c7 --- /dev/null +++ b/host/llm/Bailian.cs @@ -0,0 +1,45 @@ +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 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 IAsyncEnumerable SendChatStreamAsync(string message) + { + throw new System.NotImplementedException(); + } + + 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..62a11f4 100644 --- a/host/llm/Llm.cs +++ b/host/llm/Llm.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Threading.Tasks; +using LinkToolAddin.host.llm.entity; namespace LinkToolAddin.host.llm; @@ -11,4 +13,6 @@ public interface Llm public IAsyncEnumerable SendChatStreamAsync(string message); 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 ImagesUrl { get; set; } + + [JsonProperty("documentName")] + public string DocumentName { get; set; } + + [JsonProperty("titcontent", NullValueHandling = NullValueHandling.Ignore)] + public string Titcontent { get; set; } + + [JsonProperty("title", NullValueHandling = NullValueHandling.Ignore)] + public string Title { get; set; } + + [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] + public string Content { get; set; } + + [JsonProperty("conten_score_with_weight", NullValueHandling = NullValueHandling.Ignore)] + public string ContenScoreWithWeight { get; set; } + } +} diff --git a/host/llm/entity/LlmChat.cs b/host/llm/entity/LlmChat.cs new file mode 100644 index 0000000..d354865 --- /dev/null +++ b/host/llm/entity/LlmChat.cs @@ -0,0 +1,69 @@ +namespace LinkToolAddin.host.llm.entity +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class LlmChat + { + [JsonProperty("choices")] + public List Choices { get; set; } + + [JsonProperty("object")] + public string Object { get; set; } + + [JsonProperty("usage")] + public Usage Usage { get; set; } + + [JsonProperty("created")] + public long Created { get; set; } + + [JsonProperty("system_fingerprint")] + public object SystemFingerprint { get; set; } + + [JsonProperty("model")] + public string Model { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + } + + public partial class Choice + { + [JsonProperty("message")] + public Message Message { get; set; } + + [JsonProperty("finish_reason")] + public string FinishReason { get; set; } + + [JsonProperty("index")] + public long Index { get; set; } + + [JsonProperty("logprobs")] + public object Logprobs { get; set; } + } + + public partial class Usage + { + [JsonProperty("prompt_tokens")] + public long PromptTokens { get; set; } + + [JsonProperty("completion_tokens")] + public long CompletionTokens { get; set; } + + [JsonProperty("total_tokens")] + public long TotalTokens { get; set; } + + [JsonProperty("prompt_tokens_details")] + public PromptTokensDetails PromptTokensDetails { get; set; } + } + + public partial class PromptTokensDetails + { + [JsonProperty("cached_tokens")] + public long CachedTokens { get; set; } + } +} \ No newline at end of file diff --git a/host/llm/entity/LlmJsonContent.cs b/host/llm/entity/LlmJsonContent.cs new file mode 100644 index 0000000..5b53edf --- /dev/null +++ b/host/llm/entity/LlmJsonContent.cs @@ -0,0 +1,38 @@ +namespace LinkToolAddin.host.llm.entity +{ + using System.Collections.Generic; + using Newtonsoft.Json; + + public partial class LlmJsonContent + { + [JsonProperty("model")] + public string Model { get; set; } + + [JsonProperty("messages")] + public List Messages { get; set; } + + [JsonProperty("stream")] + public bool Stream { get; set; } = false; + + [JsonProperty("temperature")] + public double Temperature { get; set; } = 0.7; + + [JsonProperty("top_p")] + public double TopP { get; set; } = 1.0; + + [JsonProperty("max_tokens")] + public int MaxTokens { get; set; } = 2048; + + [JsonProperty("top_k")] + public int TopK { get; set; } = 40; + } + + public partial class Message + { + [JsonProperty("role")] + public string Role { get; set; } + + [JsonProperty("content")] + public string Content { get; set; } + } +} \ No newline at end of file diff --git a/resource/DocDb.cs b/resource/DocDb.cs new file mode 100644 index 0000000..1487b5e --- /dev/null +++ b/resource/DocDb.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LinkToolAddin.host.llm; +using LinkToolAddin.host.llm.entity; +using Newtonsoft.Json; + +namespace LinkToolAddin.resource; + +public class DocDb +{ + public string appId {get;set;} + public string apiKey {get;set;} + + public DocDb(string apiKey,KnowledgeBase knowledgeBaseEnum) + { + this.apiKey = apiKey; + appId = knowledgeBase[knowledgeBaseEnum]; + } + + public enum KnowledgeBase + { + ArcGISProHelpDoc, + ArcGISProToolDoc, + TaskPlanningDoc, + ArcGISProApplicantExample + } + + public Dictionary knowledgeBase = new Dictionary + { + {KnowledgeBase.ArcGISProHelpDoc,"6a77c5a68de64f469b79fcdcde9d5001"} + }; + + public async Task Retrieve(string query) + { + Llm bailian = new Bailian + { + api_key = apiKey, + app_id = appId, + }; + var commonInput = new CommonInput + { + Input = new Input + { + Prompt = query + }, + Parameters = new Debug(), + Debug = new Debug() + }; + string responseBody = await bailian.SendApplicationAsync(commonInput); + ApplicationOutput result = JsonConvert.DeserializeObject(responseBody); + KnowledgeResult knowledgeResult = JsonConvert.DeserializeObject(result.Output.Text); + return knowledgeResult; + } +} \ No newline at end of file diff --git a/resource/GpToolDb.cs b/resource/GpToolDb.cs new file mode 100644 index 0000000..3706c8d --- /dev/null +++ b/resource/GpToolDb.cs @@ -0,0 +1,9 @@ +namespace LinkToolAddin.resource; + +public class GpToolDb +{ + public string toolName { get; set; } + public string toolDescription { get; set; } + public string exeName { get; set; } + public string toolParma { get; set; } +} \ No newline at end of file diff --git a/resource/PromptDb.cs b/resource/PromptDb.cs new file mode 100644 index 0000000..6b6e6d3 --- /dev/null +++ b/resource/PromptDb.cs @@ -0,0 +1,8 @@ +namespace LinkToolAddin.resource; + +public class PromptDb +{ + public string promptName { get; set; } + public string promptDescription { get; set; } + public string promptContent { get; set; } +} \ No newline at end of file diff --git a/server/CallArcGISPro.cs b/server/CallArcGISPro.cs new file mode 100644 index 0000000..39f5ef0 --- /dev/null +++ b/server/CallArcGISPro.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using ArcGIS.Desktop.Core.Geoprocessing; +using ArcGIS.Desktop.Framework.Dialogs; +using ArcGIS.Desktop.Framework.Threading.Tasks; + +namespace LinkToolAddin.server; + +public class CallArcGISPro +{ + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(CallArcGISPro)); + public async static Task CallArcGISProTool(string toolName, List toolParams) + { + var results = await Geoprocessing.ExecuteToolAsync(toolName, toolParams); + log.Info($"CallArcGISProTool: {toolName} | {toolParams}"); + return new JsonRpcSuccessEntity() + { + Id = 1, + Result = results.ToString() + }; + } +} \ No newline at end of file diff --git a/server/JsonRpcEntity.cs b/server/JsonRpcEntity.cs new file mode 100644 index 0000000..01a91e3 --- /dev/null +++ b/server/JsonRpcEntity.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace LinkToolAddin.server +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + + public partial class JsonRpcEntity + { + [JsonProperty("jsonrpc")] + public string Jsonrpc { get; set; } + + [JsonProperty("method")] + public string Method { get; set; } + + [JsonProperty("params")] + public Dictionary Params { get; set; } + + [JsonProperty("id")] + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/server/JsonRpcErrorEntity.cs b/server/JsonRpcErrorEntity.cs new file mode 100644 index 0000000..46381de --- /dev/null +++ b/server/JsonRpcErrorEntity.cs @@ -0,0 +1,25 @@ +namespace LinkToolAddin.server +{ + using Newtonsoft.Json; + + public partial class JsonRpcErrorEntity + { + [JsonProperty("jsonrpc")] + public string Jsonrpc { get; set; } + + [JsonProperty("error")] + public Error Error { get; set; } + + [JsonProperty("id")] + public long Id { get; set; } + } + + public partial class Error + { + [JsonProperty("code")] + public long Code { get; set; } + + [JsonProperty("message")] + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/server/JsonRpcResultEntity.cs b/server/JsonRpcResultEntity.cs new file mode 100644 index 0000000..5d80803 --- /dev/null +++ b/server/JsonRpcResultEntity.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; + +namespace LinkToolAddin.server +{ + using Newtonsoft.Json; + + public partial class JsonRpcResultEntity + { + [JsonProperty("jsonrpc")] + public string Jsonrpc { get; set; } + + [JsonProperty("id")] + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/server/JsonRpcSuccessEntity.cs b/server/JsonRpcSuccessEntity.cs new file mode 100644 index 0000000..a37985f --- /dev/null +++ b/server/JsonRpcSuccessEntity.cs @@ -0,0 +1,16 @@ +namespace LinkToolAddin.server +{ + using Newtonsoft.Json; + + public partial class JsonRpcSuccessEntity : JsonRpcResultEntity + { + [JsonProperty("jsonrpc")] + public string Jsonrpc { get; set; } + + [JsonProperty("result")] + public string Result { get; set; } + + [JsonProperty("id")] + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/ui/dockpane/DialogDockpane.xaml b/ui/dockpane/DialogDockpane.xaml index 3b8967b..84e7de1 100644 --- a/ui/dockpane/DialogDockpane.xaml +++ b/ui/dockpane/DialogDockpane.xaml @@ -22,14 +22,7 @@ - - - - - - - - + \ No newline at end of file diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index 0a6ce23..a939189 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -1,9 +1,27 @@ -using System; +using System; using System.Collections.Generic; -using System.Text.Json; +using System.ComponentModel; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; using System.Windows; using System.Windows.Controls; - +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System.Text.Json; +using System.Threading; +using ArcGIS.Desktop.Core.Geoprocessing; +using LinkToolAddin.client; +using LinkToolAddin.host.llm; +using LinkToolAddin.host.llm.entity; +using LinkToolAddin.resource; +using log4net; +using log4net.Appender; +using log4net.Config; +using log4net.Layout; +using ModelContextProtocol.Server; +using Newtonsoft.Json; namespace LinkToolAddin.ui.dockpane { @@ -12,76 +30,94 @@ namespace LinkToolAddin.ui.dockpane /// public partial class DialogDockpaneView : UserControl { + private static ILog log = LogManager.GetLogger(typeof(DialogDockpaneView)); + public DialogDockpaneView() { + InitLogger(); InitializeComponent(); } - - private void TestButton_OnClick(object sender, RoutedEventArgs e) + + public void CallBack(string str,object obj) { - string originalJson = @"{ - ""name"": ""Alice"", - ""age"": 30, - ""isStudent"": false, - ""hobbies"": [ - ""reading"", - ""swimming"", - ""hiking"" - ], - ""address"": { - ""street"": ""123 Main St"", - ""city"": ""Anytown"", - ""postalCode"": ""12345"" - } -}"; + log.Info($"CallBack {str}"); + } - try + private async void TestServer_OnClick(object sender, RoutedEventArgs e) + { + log.Info("TestServer Clicked"); + string res = await SseMcpClient.testGaodeMcp(); + log.Info(res); + } + + private async void Retrieve_Test() + { + log.Info("TestServer Clicked"); + // string jsonRpcString = @"{""jsonrpc"":""2.0"",""method"":""CallArcGISPro.CallArcGISProTool"",""params"":{""toolName"":""analysis.Buffer"",""toolParams"":""[\""D:/01_Development/02_ArcGIS_Pro_Project/20250319_GisAi/Test.gdb/河流\"",\""D:/01_Development/02_ArcGIS_Pro_Project/20250319_GisAi/Test.gdb/河流buffer\"",\""100\""]""},""id"":1}"; + DocDb docDb = new DocDb("sk-db177155677e438f832860e7f4da6afc", DocDb.KnowledgeBase.ArcGISProHelpDoc); + string query = "缓冲区"; + KnowledgeResult knowledgeResult = await docDb.Retrieve(query); + log.Info(JsonConvert.SerializeObject(knowledgeResult.ChunkList)); + } + + private async void Request_Bailian_Test() + { + Llm bailian = new Bailian { - // 反序列化 JSON 到对象 - Person? person = JsonSerializer.Deserialize(originalJson); - - if (person != null) + api_key = "sk-db177155677e438f832860e7f4da6afc", + app_id = "6a77c5a68de64f469b79fcdcde9d5001", + }; + string reponse = await bailian.SendChatAsync(new LlmJsonContent() + { + Model = "qwen-max", + Messages = new List() { - // 序列化对象回 JSON 字符串 - string serializedJson = JsonSerializer.Serialize(person, new JsonSerializerOptions + new Message() { - WriteIndented = true // 格式化输出 - }); + Role = "user", + Content = "你是谁" + } + }, + Temperature = 0.7, + TopP = 1, + MaxTokens = 1000, + }); + log.Info(reponse); + } + + protected void InitLogger() + { + // 1. 创建控制台输出器(Appender) + var consoleAppender = new ConsoleAppender + { + Layout = new PatternLayout("%date [%thread] %-5level %logger - %message%newline"), + Threshold = log4net.Core.Level.Info // 仅输出 Info 及以上级别 + }; + consoleAppender.ActivateOptions(); // 激活配置 - Console.WriteLine("序列化后的 JSON:"); - Console.WriteLine(serializedJson); - ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show(serializedJson); - } - else - { - Console.WriteLine("反序列化失败,对象为 null。"); - } - } - catch (JsonException ex) + // 2. 创建文件滚动输出器(按大小滚动) + var fileAppender = new RollingFileAppender { - Console.WriteLine($"JSON 处理错误: {ex.Message}"); - } - catch (Exception ex) - { - Console.WriteLine($"发生错误: {ex.Message}"); - } + File = Path.Combine("Logs", "linktool_app.log"), // 日志文件路径 + AppendToFile = true, // 追加模式 + RollingStyle = RollingFileAppender.RollingMode.Size, // 按文件大小滚动 + MaxSizeRollBackups = 10, // 保留 10 个历史文件 + MaximumFileSize = "1MB", // 单个文件最大 1MB + StaticLogFileName = true, // 固定文件名(否则自动追加序号) + Layout = new PatternLayout("%date [%thread] %-5level %logger - %message%newline"), + Threshold = log4net.Core.Level.Info // 仅输出 Info 及以上级别 + }; + fileAppender.ActivateOptions(); // 激活配置 + + // 3. 直接通过 BasicConfigurator 注册 Appender + BasicConfigurator.Configure(consoleAppender, fileAppender); + + log = LogManager.GetLogger(typeof(DialogDockpaneView)); + + // 测试日志输出 + log.Debug("Debug 日志(控制台可见)"); + log.Info("Info 日志(控制台和文件可见)"); + log.Error("Error 日志(严重问题)"); } } - - // 定义数据模型类 - public class Person - { - public string? Name { get; set; } - public int Age { get; set; } - public bool IsStudent { get; set; } - public List? Hobbies { get; set; } - public Address? Address { get; set; } - } - - public class Address - { - public string? Street { get; set; } - public string? City { get; set; } - public string? PostalCode { get; set; } - } -} \ No newline at end of file +} diff --git a/ui/dockpane/DialogDockpaneViewModel.cs b/ui/dockpane/DialogDockpaneViewModel.cs index 5871201..1d7a4e5 100644 --- a/ui/dockpane/DialogDockpaneViewModel.cs +++ b/ui/dockpane/DialogDockpaneViewModel.cs @@ -17,6 +17,10 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; namespace LinkToolAddin.ui.dockpane { @@ -56,6 +60,7 @@ namespace LinkToolAddin.ui.dockpane { protected override void OnClick() { + DialogDockpaneViewModel.Show(); } } From 792c458f6c8d64b106cbfb2f8a014f8d9e5390fe Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Wed, 14 May 2025 23:09:58 +0800 Subject: [PATCH 05/60] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=9C=AC=E5=9C=B0Stdio?= =?UTF-8?q?=20MCP=E7=9A=84=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 1 - client/McpClient.cs | 13 +++++++ client/PythonMcpClient.cs | 61 +++++++++++++++++++++++++++++- client/SseMcpClient.cs | 59 ++++++++++------------------- ui/dockpane/DialogDockpane.xaml.cs | 27 ++++++++++++- 5 files changed, 118 insertions(+), 43 deletions(-) create mode 100644 client/McpClient.cs diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index 6bd5b78..f683eae 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -98,7 +98,6 @@ - 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/PythonMcpClient.cs b/client/PythonMcpClient.cs index e25e7d8..caad4b7 100644 --- a/client/PythonMcpClient.cs +++ b/client/PythonMcpClient.cs @@ -1,6 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Windows.Documents; +using ModelContextProtocol.Protocol.Types; +using Newtonsoft.Json; + namespace LinkToolAddin.client; -public class PythonMcpClient +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() + { + IMcpClient client = await McpClientFactory.CreateAsync(new StdioClientTransport(transportOptions)); + IList tools = await client.ListToolsAsync(); + return tools; + } + + 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/SseMcpClient.cs b/client/SseMcpClient.cs index 471af60..6af5c55 100644 --- a/client/SseMcpClient.cs +++ b/client/SseMcpClient.cs @@ -3,54 +3,35 @@ 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 +public class SseMcpClient : McpClient { - public static async Task testGaodeMcp() + private SseClientTransportOptions options; + private IClientTransport transport; + public SseMcpClient(string url) { - Console.WriteLine("Connecting to 高德 MCP Server via SSE..."); - - // 创建 MCP Server 配置 - SseClientTransportOptions options = new SseClientTransportOptions + options = new SseClientTransportOptions { - Endpoint = new Uri("https://mcp.amap.com/sse?key=ed418512c94ade8f83d42c37b77d2bb2"), + Endpoint = new Uri(url), }; - - IClientTransport transport = new SseClientTransport(options);; - + transport = new SseClientTransport(options); + } + public async Task> GetToolListAsync() + { // 创建 MCP Client - var client = await McpClientFactory.CreateAsync(transport); - Console.WriteLine("Connected to 高德 MCP Server"); + IMcpClient client = await McpClientFactory.CreateAsync(transport); + var tools = await client.ListToolsAsync(); + return tools; + } - try - { - // 获取可用工具列表 - var tools = await client.ListToolsAsync(); - Console.WriteLine("\nAvailable Tools:"); - foreach (var tool in tools) - { - Console.WriteLine($"- {tool.Name}: {tool.Description}"); - } - - // 示例调用:获取当前定位 - var result = await client.CallToolAsync("amap.maps_weather", new Dictionary{{"city","北京"}}); - Console.WriteLine("\n[amap.get_location] Result:"); - Console.WriteLine(result); - return JsonConvert.SerializeObject(result); - } - catch (Exception ex) - { - Console.WriteLine($"Error occurred: {ex.Message}"); - } - finally - { - await client.DisposeAsync(); - } - - Console.WriteLine("Client closed."); - return "failed"; + 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/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index a939189..3487472 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Text.Json; using System.Threading; +using System.Windows.Documents; using ArcGIS.Desktop.Core.Geoprocessing; using LinkToolAddin.client; using LinkToolAddin.host.llm; @@ -20,6 +21,8 @@ using log4net; using log4net.Appender; using log4net.Config; using log4net.Layout; +using ModelContextProtocol.Client; +using ModelContextProtocol.Protocol.Types; using ModelContextProtocol.Server; using Newtonsoft.Json; @@ -46,8 +49,28 @@ namespace LinkToolAddin.ui.dockpane private async void TestServer_OnClick(object sender, RoutedEventArgs e) { log.Info("TestServer Clicked"); - string res = await SseMcpClient.testGaodeMcp(); - log.Info(res); + List args = new List(); + args.Add("mcp-server-time"); + args.Add("--local-timezone=America/New_York"); + McpClient stdioMcpClient = new StdioMcpClient("uvx",args); + IList tools = await stdioMcpClient.GetToolListAsync(); + foreach (McpClientTool tool in tools) + { + log.Info(tool.JsonSchema.ToString()); + } + CallToolResponse response = await stdioMcpClient.CallToolAsync("get_current_time", + new Dictionary { { "timezone", "America/New_York" } }); + log.Info(JsonConvert.SerializeObject(response)); + } + + private async void SseMcp_test() + { + SseMcpClient client = new SseMcpClient("https://mcp.amap.com/sse?key=ed418512c94ade8f83d42c37b77d2bb2"); + IList tools = await client.GetToolListAsync(); + foreach (McpClientTool tool in tools) + { + log.Info(tool.JsonSchema.ToString()); + } } private async void Retrieve_Test() From a3cfcb90e3a23429afe42a539ea6d608158299fe Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Fri, 16 May 2025 00:27:50 +0800 Subject: [PATCH 06/60] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E6=B5=81=E6=8E=A7=E5=88=B6=E9=9D=A2=EF=BC=8C=E9=A2=84=E7=95=99?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Config.daml | 13 +- LinkToolAddin.csproj | 1 - Properties/launchSettings.json | 6 +- .../{PythonMcpClient.cs => StdioMcpClient.cs} | 0 client/prompt/DynamicPrompt.cs | 18 ++ client/prompt/PromptTemplates.cs | 20 +++ host/Gateway.cs | 146 ++++++++++++++++ host/ToolRequest.cs | 6 + host/mcp/McpServer.cs | 24 +++ host/mcp/SseMcpServer.cs | 30 ++++ host/mcp/StdioMcpServer.cs | 30 ++++ host/prompt/SystemPrompt.cs | 11 ++ host/prompt/UserPrompt.cs | 6 + server/ArcGISProMcpServer.cs | 37 +++++ ui/dockpane/DialogDockpane.xaml.cs | 65 +------- ui/dockpane/TestDockpane.xaml | 28 ++++ ui/dockpane/TestDockpane.xaml.cs | 157 ++++++++++++++++++ ui/dockpane/TestDockpaneViewModel.cs | 62 +++++++ ui/message/ChatMessageItem.cs | 9 + ui/message/MessageListItem.cs | 8 + ui/message/ToolMessageItem.cs | 15 ++ 21 files changed, 624 insertions(+), 68 deletions(-) rename client/{PythonMcpClient.cs => StdioMcpClient.cs} (100%) create mode 100644 client/prompt/DynamicPrompt.cs create mode 100644 client/prompt/PromptTemplates.cs create mode 100644 host/Gateway.cs create mode 100644 host/ToolRequest.cs create mode 100644 host/mcp/McpServer.cs create mode 100644 host/mcp/SseMcpServer.cs create mode 100644 host/mcp/StdioMcpServer.cs create mode 100644 host/prompt/SystemPrompt.cs create mode 100644 host/prompt/UserPrompt.cs create mode 100644 server/ArcGISProMcpServer.cs create mode 100644 ui/dockpane/TestDockpane.xaml create mode 100644 ui/dockpane/TestDockpane.xaml.cs create mode 100644 ui/dockpane/TestDockpaneViewModel.cs create mode 100644 ui/message/ChatMessageItem.cs create mode 100644 ui/message/ToolMessageItem.cs diff --git a/Config.daml b/Config.daml index 9147f6f..18a04f2 100644 --- a/Config.daml +++ b/Config.daml @@ -23,9 +23,10 @@ - + + + + diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index f683eae..e0d0d0e 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -99,7 +99,6 @@ - diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index 42da189..5a2c881 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -2,7 +2,11 @@ "profiles": { "LinkToolAddin": { "commandName": "Executable", - "executablePath": "C:\\Program Files\\ArcGIS\\Pro\\bin\\ArcGISPro.exe" + "executablePath": "C:\\Program Files\\ArcGIS\\Pro\\bin\\ArcGISPro.exe", + "applicationUrl": "https://localhost:5001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } } } } \ No newline at end of file diff --git a/client/PythonMcpClient.cs b/client/StdioMcpClient.cs similarity index 100% rename from client/PythonMcpClient.cs rename to client/StdioMcpClient.cs diff --git a/client/prompt/DynamicPrompt.cs b/client/prompt/DynamicPrompt.cs new file mode 100644 index 0000000..2a342e7 --- /dev/null +++ b/client/prompt/DynamicPrompt.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace LinkToolAddin.client.prompt; + +public class DynamicPrompt +{ + public static string GetPrompt(string name,Dictionary args) + { + PromptTemplates promptTemplate = new PromptTemplates(); + string template = promptTemplate.GetPrompt(name); + foreach (KeyValuePair pair in args) + { + string replaceKey = "{{"+pair.Key+"}}"; + template.Replace(replaceKey, pair.Value.ToString()); + } + return template; + } +} \ No newline at end of file diff --git a/client/prompt/PromptTemplates.cs b/client/prompt/PromptTemplates.cs new file mode 100644 index 0000000..3992482 --- /dev/null +++ b/client/prompt/PromptTemplates.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace LinkToolAddin.client.prompt; + +public class PromptTemplates +{ + private Dictionary prompts = new Dictionary(); + + public PromptTemplates() + { + prompts.Add("plan", "请根据用户所提问题进行工具规划"); + prompts.Add("param", "根据帮助文档填写工具参数"); + prompts.Add("code", "现在需要生成代码,要求语法正确"); + } + + public string GetPrompt(string name) + { + return prompts[name]; + } +} \ No newline at end of file diff --git a/host/Gateway.cs b/host/Gateway.cs new file mode 100644 index 0000000..8237927 --- /dev/null +++ b/host/Gateway.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using LinkToolAddin.client; +using LinkToolAddin.client.prompt; +using LinkToolAddin.host.llm; +using LinkToolAddin.host.llm.entity; +using LinkToolAddin.host.mcp; +using LinkToolAddin.host.prompt; +using LinkToolAddin.message; +using ModelContextProtocol.Protocol.Types; +using Newtonsoft.Json; + +namespace LinkToolAddin.host; + +public class Gateway +{ + 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(); + messages.Add(new Message + { + Role = "system", + Content = SystemPrompt.SysPromptTemplate + }); + messages.Add(new Message + { + Role = "user", + Content = message + }); + bool goOn = true; + string pattern = "[\\s\\S]*?<\\/tool_use>"; + string promptPattern = "[\\s\\S]*?<\\/prompt>"; + Dictionary servers = new Dictionary(); + while (goOn) + { + string reponse = await bailian.SendChatAsync(new LlmJsonContent() + { + Model = model, + Messages = messages, + Temperature = 0.7, + TopP = 1, + MaxTokens = 1000, + }); + 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 = servers[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 = toolResponse.Content.ToString() + }; + messages.Add(new Message + { + Role = "user", + Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate + }); + messages.Add(new Message + { + Role = "user", + Content = 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 = toolResponse.Content.ToString() + }; + messages.Add(new Message + { + Role = "user", + Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate + }); + messages.Add(new Message + { + Role = "user", + Content = JsonConvert.SerializeObject(toolResponse) + }); + 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, promptParams); + messages.Add(new Message + { + Role = "user", + Content = promptRes + }); + } + else + { + MessageListItem chatMessageListItem = new ChatMessageItem() + { + content = reponse, + role = "assistant", + type = MessageType.CHAT_MESSAGE + }; + callback?.Invoke(chatMessageListItem); + } + if (reponse == "[DONE]") + { + goOn = false; + } + } + } +} \ 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/mcp/McpServer.cs b/host/mcp/McpServer.cs new file mode 100644 index 0000000..616798c --- /dev/null +++ b/host/mcp/McpServer.cs @@ -0,0 +1,24 @@ +namespace LinkToolAddin.host.mcp +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class McpServer + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("isActive")] + public bool IsActive { get; set; } + } +} \ No newline at end of file diff --git a/host/mcp/SseMcpServer.cs b/host/mcp/SseMcpServer.cs new file mode 100644 index 0000000..cd2525e --- /dev/null +++ b/host/mcp/SseMcpServer.cs @@ -0,0 +1,30 @@ +namespace LinkToolAddin.host.mcp +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class SseMcpServer : McpServer + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("isActive")] + public bool IsActive { get; set; } + + [JsonProperty("baseUrl")] + public string BaseUrl { get; set; } + + [JsonProperty("headers")] + public Dictionary Headers { get; set; } + } +} \ No newline at end of file diff --git a/host/mcp/StdioMcpServer.cs b/host/mcp/StdioMcpServer.cs new file mode 100644 index 0000000..a70ce40 --- /dev/null +++ b/host/mcp/StdioMcpServer.cs @@ -0,0 +1,30 @@ +namespace LinkToolAddin.host.mcp +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class StdioMcpServer : McpServer + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("isActive")] + public bool IsActive { get; set; } + + [JsonProperty("registryUrl")] + public string RegistryUrl { get; set; } + + [JsonProperty("command")] + public string Command { get; set; } + + [JsonProperty("args")] + public List Args { get; set; } + } +} \ No newline at end of file diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs new file mode 100644 index 0000000..bcd3768 --- /dev/null +++ b/host/prompt/SystemPrompt.cs @@ -0,0 +1,11 @@ +namespace LinkToolAddin.host.prompt; + +public class SystemPrompt +{ + public static string SysPromptTemplate = "现在你是一个精通ArcGIS Pro的专家,请以此身份回答用户的问题。"; + + public static string ContinuePromptTemplate = "上一个工具执行的结果如下,请据此继续执行"; + + public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误,请根据报错信息重试"; + +} \ No newline at end of file diff --git a/host/prompt/UserPrompt.cs b/host/prompt/UserPrompt.cs new file mode 100644 index 0000000..128de5b --- /dev/null +++ b/host/prompt/UserPrompt.cs @@ -0,0 +1,6 @@ +namespace LinkToolAddin.host.prompt; + +public class UserPrompt +{ + +} \ No newline at end of file diff --git a/server/ArcGISProMcpServer.cs b/server/ArcGISProMcpServer.cs new file mode 100644 index 0000000..7aa94f7 --- /dev/null +++ b/server/ArcGISProMcpServer.cs @@ -0,0 +1,37 @@ +using System; +using System.ComponentModel; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using ModelContextProtocol.Server; + +namespace LinkToolAddin.server; + +public class ArcGISProMcpServer +{ + public static async void TestMcpServer() + { + var builder = WebApplication.CreateBuilder(); + builder.Logging.AddConsole(options => + { + options.LogToStandardErrorThreshold = LogLevel.Trace; + });; + builder.Services.AddMcpServer().WithHttpTransport().WithToolsFromAssembly(); + var app = builder.Build(); + app.MapMcp("/sse"); + app.MapGet("/", () => "MCP Server is running!"); + Console.WriteLine("About to start server..."); + await app.RunAsync(); + } +} + +[McpServerToolType] +public static class EchoTool +{ + [McpServerTool, Description("Echoes the message back to the client.")] + public static string Echo(string message) + { + Console.WriteLine($"received message: {message}"); + return $"hello {message}"; + } +} \ No newline at end of file diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index 3487472..aa2ea7f 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -17,6 +17,7 @@ using LinkToolAddin.client; using LinkToolAddin.host.llm; using LinkToolAddin.host.llm.entity; using LinkToolAddin.resource; +using LinkToolAddin.server; using log4net; using log4net.Appender; using log4net.Config; @@ -40,72 +41,10 @@ namespace LinkToolAddin.ui.dockpane InitLogger(); InitializeComponent(); } - - public void CallBack(string str,object obj) - { - log.Info($"CallBack {str}"); - } private async void TestServer_OnClick(object sender, RoutedEventArgs e) { log.Info("TestServer Clicked"); - List args = new List(); - args.Add("mcp-server-time"); - args.Add("--local-timezone=America/New_York"); - McpClient stdioMcpClient = new StdioMcpClient("uvx",args); - IList tools = await stdioMcpClient.GetToolListAsync(); - foreach (McpClientTool tool in tools) - { - log.Info(tool.JsonSchema.ToString()); - } - CallToolResponse response = await stdioMcpClient.CallToolAsync("get_current_time", - new Dictionary { { "timezone", "America/New_York" } }); - log.Info(JsonConvert.SerializeObject(response)); - } - - private async void SseMcp_test() - { - SseMcpClient client = new SseMcpClient("https://mcp.amap.com/sse?key=ed418512c94ade8f83d42c37b77d2bb2"); - IList tools = await client.GetToolListAsync(); - foreach (McpClientTool tool in tools) - { - log.Info(tool.JsonSchema.ToString()); - } - } - - private async void Retrieve_Test() - { - log.Info("TestServer Clicked"); - // string jsonRpcString = @"{""jsonrpc"":""2.0"",""method"":""CallArcGISPro.CallArcGISProTool"",""params"":{""toolName"":""analysis.Buffer"",""toolParams"":""[\""D:/01_Development/02_ArcGIS_Pro_Project/20250319_GisAi/Test.gdb/河流\"",\""D:/01_Development/02_ArcGIS_Pro_Project/20250319_GisAi/Test.gdb/河流buffer\"",\""100\""]""},""id"":1}"; - DocDb docDb = new DocDb("sk-db177155677e438f832860e7f4da6afc", DocDb.KnowledgeBase.ArcGISProHelpDoc); - string query = "缓冲区"; - KnowledgeResult knowledgeResult = await docDb.Retrieve(query); - log.Info(JsonConvert.SerializeObject(knowledgeResult.ChunkList)); - } - - private async void Request_Bailian_Test() - { - Llm bailian = new Bailian - { - api_key = "sk-db177155677e438f832860e7f4da6afc", - app_id = "6a77c5a68de64f469b79fcdcde9d5001", - }; - string reponse = await bailian.SendChatAsync(new LlmJsonContent() - { - Model = "qwen-max", - Messages = new List() - { - new Message() - { - Role = "user", - Content = "你是谁" - } - }, - Temperature = 0.7, - TopP = 1, - MaxTokens = 1000, - }); - log.Info(reponse); } protected void InitLogger() @@ -121,7 +60,7 @@ namespace LinkToolAddin.ui.dockpane // 2. 创建文件滚动输出器(按大小滚动) var fileAppender = new RollingFileAppender { - File = Path.Combine("Logs", "linktool_app.log"), // 日志文件路径 + File = Path.Combine("Logs", "D:\\linktool_app.log"), // 日志文件路径 AppendToFile = true, // 追加模式 RollingStyle = RollingFileAppender.RollingMode.Size, // 按文件大小滚动 MaxSizeRollBackups = 10, // 保留 10 个历史文件 diff --git a/ui/dockpane/TestDockpane.xaml b/ui/dockpane/TestDockpane.xaml new file mode 100644 index 0000000..4782989 --- /dev/null +++ b/ui/dockpane/TestDockpane.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs new file mode 100644 index 0000000..6f33bb6 --- /dev/null +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -0,0 +1,157 @@ +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using LinkToolAddin.client; +using LinkToolAddin.host; +using LinkToolAddin.host.llm; +using LinkToolAddin.host.llm.entity; +using LinkToolAddin.message; +using LinkToolAddin.resource; +using LinkToolAddin.server; +using log4net; +using log4net.Appender; +using log4net.Config; +using log4net.Layout; +using ModelContextProtocol.Client; +using ModelContextProtocol.Protocol.Types; +using Newtonsoft.Json; + + +namespace LinkToolAddin.ui.dockpane +{ + /// + /// Interaction logic for TestDockpaneView.xaml + /// + public partial class TestDockpaneView : UserControl + { + private static ILog log = LogManager.GetLogger(typeof(TestDockpaneView)); + + public TestDockpaneView() + { + InitLogger(); + InitializeComponent(); + } + + protected void InitLogger() + { + // 1. 创建控制台输出器(Appender) + var consoleAppender = new ConsoleAppender + { + Layout = new PatternLayout("%date [%thread] %-5level %logger - %message%newline"), + Threshold = log4net.Core.Level.Info // 仅输出 Info 及以上级别 + }; + consoleAppender.ActivateOptions(); // 激活配置 + + // 2. 创建文件滚动输出器(按大小滚动) + var fileAppender = new RollingFileAppender + { + File = System.IO.Path.Combine("Logs", "linktool_app.log"), // 日志文件路径 + AppendToFile = true, // 追加模式 + RollingStyle = RollingFileAppender.RollingMode.Size, // 按文件大小滚动 + MaxSizeRollBackups = 10, // 保留 10 个历史文件 + MaximumFileSize = "1MB", // 单个文件最大 1MB + StaticLogFileName = true, // 固定文件名(否则自动追加序号) + Layout = new PatternLayout("%date [%thread] %-5level %logger - %message%newline"), + Threshold = log4net.Core.Level.Info // 仅输出 Info 及以上级别 + }; + fileAppender.ActivateOptions(); // 激活配置 + + // 3. 直接通过 BasicConfigurator 注册 Appender + BasicConfigurator.Configure(consoleAppender, fileAppender); + + log = LogManager.GetLogger(typeof(DialogDockpaneView)); + + // 测试日志输出 + log.Debug("Debug 日志(控制台可见)"); + log.Info("Info 日志(控制台和文件可见)"); + log.Error("Error 日志(严重问题)"); + } + + public void CallBack(string str,object obj) + { + log.Info($"CallBack {str}"); + } + + private async void TestServer_OnClick(object sender, RoutedEventArgs e) + { + log.Info("TestServer Clicked"); + ArcGISProMcpServer.TestMcpServer(); + } + + private async void StdioMcp_test() + { + List args = new List(); + args.Add("mcp-server-time"); + args.Add("--local-timezone=America/New_York"); + McpClient stdioMcpClient = new StdioMcpClient("uvx",args); + IList tools = await stdioMcpClient.GetToolListAsync(); + foreach (McpClientTool tool in tools) + { + log.Info(tool.JsonSchema.ToString()); + } + CallToolResponse response = await stdioMcpClient.CallToolAsync("get_current_time", + new Dictionary { { "timezone", "America/New_York" } }); + log.Info(JsonConvert.SerializeObject(response)); + } + + private async void SseMcp_test() + { + SseMcpClient client = new SseMcpClient("https://mcp.amap.com/sse?key=ed418512c94ade8f83d42c37b77d2bb2"); + IList tools = await client.GetToolListAsync(); + foreach (McpClientTool tool in tools) + { + log.Info(tool.JsonSchema.ToString()); + } + } + + private async void Retrieve_Test() + { + log.Info("TestServer Clicked"); + // string jsonRpcString = @"{""jsonrpc"":""2.0"",""method"":""CallArcGISPro.CallArcGISProTool"",""params"":{""toolName"":""analysis.Buffer"",""toolParams"":""[\""D:/01_Development/02_ArcGIS_Pro_Project/20250319_GisAi/Test.gdb/河流\"",\""D:/01_Development/02_ArcGIS_Pro_Project/20250319_GisAi/Test.gdb/河流buffer\"",\""100\""]""},""id"":1}"; + DocDb docDb = new DocDb("sk-db177155677e438f832860e7f4da6afc", DocDb.KnowledgeBase.ArcGISProHelpDoc); + string query = "缓冲区"; + KnowledgeResult knowledgeResult = await docDb.Retrieve(query); + log.Info(JsonConvert.SerializeObject(knowledgeResult.ChunkList)); + } + + private async void Request_Bailian_Test() + { + Llm bailian = new Bailian + { + api_key = "sk-db177155677e438f832860e7f4da6afc", + app_id = "6a77c5a68de64f469b79fcdcde9d5001", + }; + string reponse = await bailian.SendChatAsync(new LlmJsonContent() + { + Model = "qwen-max", + Messages = new List() + { + new Message() + { + Role = "user", + Content = "你是谁" + } + }, + Temperature = 0.7, + TopP = 1, + MaxTokens = 1000, + }); + log.Info(reponse); + } + + private void TestButton_OnClick(object sender, RoutedEventArgs e) + { + throw new System.NotImplementedException(); + } + + private void TestWorkflow_OnClick(object sender, RoutedEventArgs e) + { + Gateway.SendMessage("你好","qwen-max","test.gdb",ShowMessage); + } + + public void ShowMessage(MessageListItem msg) + { + log.Info(msg.content); + } + } +} diff --git a/ui/dockpane/TestDockpaneViewModel.cs b/ui/dockpane/TestDockpaneViewModel.cs new file mode 100644 index 0000000..0661d9c --- /dev/null +++ b/ui/dockpane/TestDockpaneViewModel.cs @@ -0,0 +1,62 @@ +using ArcGIS.Core.CIM; +using ArcGIS.Core.Data; +using ArcGIS.Core.Geometry; +using ArcGIS.Desktop.Catalog; +using ArcGIS.Desktop.Core; +using ArcGIS.Desktop.Editing; +using ArcGIS.Desktop.Extensions; +using ArcGIS.Desktop.Framework; +using ArcGIS.Desktop.Framework.Contracts; +using ArcGIS.Desktop.Framework.Dialogs; +using ArcGIS.Desktop.Framework.Threading.Tasks; +using ArcGIS.Desktop.KnowledgeGraph; +using ArcGIS.Desktop.Layouts; +using ArcGIS.Desktop.Mapping; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LinkToolAddin.ui.dockpane +{ + internal class TestDockpaneViewModel : DockPane + { + private const string _dockPaneID = "LinkToolAddin_ui_dockpane_TestDockpane"; + + protected TestDockpaneViewModel() { } + + /// + /// Show the DockPane. + /// + internal static void Show() + { + DockPane pane = FrameworkApplication.DockPaneManager.Find(_dockPaneID); + if (pane == null) + return; + + pane.Activate(); + } + + /// + /// Text shown near the top of the DockPane. + /// + private string _heading = "My DockPane"; + public string Heading + { + get => _heading; + set => SetProperty(ref _heading, value); + } + } + + /// + /// Button implementation to show the DockPane. + /// + internal class TestDockpane_ShowButton : Button + { + protected override void OnClick() + { + TestDockpaneViewModel.Show(); + } + } +} diff --git a/ui/message/ChatMessageItem.cs b/ui/message/ChatMessageItem.cs new file mode 100644 index 0000000..2c35847 --- /dev/null +++ b/ui/message/ChatMessageItem.cs @@ -0,0 +1,9 @@ +namespace LinkToolAddin.message; + +public class ChatMessageItem : MessageListItem +{ + public string id { get; set; } + public string role { get; set; } + public string content { get; set; } + public MessageType type { get; set; } +} \ No newline at end of file diff --git a/ui/message/MessageListItem.cs b/ui/message/MessageListItem.cs index 0de3b34..1c58cbd 100644 --- a/ui/message/MessageListItem.cs +++ b/ui/message/MessageListItem.cs @@ -1,7 +1,15 @@ namespace LinkToolAddin.message; +public enum MessageType +{ + TOOL_MESSAGE, + CHAT_MESSAGE, +} + public interface MessageListItem { + string id { get; set; } string role { get; set; } string content { get; set; } + MessageType type { get; set; } } \ No newline at end of file diff --git a/ui/message/ToolMessageItem.cs b/ui/message/ToolMessageItem.cs new file mode 100644 index 0000000..211a4db --- /dev/null +++ b/ui/message/ToolMessageItem.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace LinkToolAddin.message; + +public class ToolMessageItem : MessageListItem +{ + public string id { get; set; } + public string role { get; set; } + public string content { get; set; } + public string toolName { get; set; } + public Dictionary toolParams { get; set; } + public MessageType type { get; set; } + public string status { get; set; } + public string result { get; set; } +} \ No newline at end of file From 6e0908a4fa3bba684f3256e4a9da50cdabd1172b Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 17 May 2025 00:04:47 +0800 Subject: [PATCH 07/60] =?UTF-8?q?=E6=8E=A7=E5=88=B6=E9=9D=A2=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E6=A8=A1=E6=8B=9F=E6=B6=88=E6=81=AF=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/Gateway.cs | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/host/Gateway.cs b/host/Gateway.cs index 8237927..2b209b1 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; +using System.Threading; using System.Xml.Linq; using LinkToolAddin.client; using LinkToolAddin.client.prompt; @@ -143,4 +144,67 @@ public class Gateway } } } + + 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 From 6be6db21941e76fe2f0255b7cef88d98dcb0233c Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Mon, 19 May 2025 12:10:18 +0800 Subject: [PATCH 08/60] =?UTF-8?q?=E6=8E=A5=E5=85=A5MCP=20ListTool=E8=83=BD?= =?UTF-8?q?=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/tool/ArcGisPro.cs | 6 ++++++ host/McpServerList.cs | 6 ++++++ host/mcp/InnerMcpServer.cs | 6 ++++++ host/mcp/McpToolDefinition.cs | 6 ++++++ 4 files changed, 24 insertions(+) create mode 100644 client/tool/ArcGisPro.cs create mode 100644 host/McpServerList.cs create mode 100644 host/mcp/InnerMcpServer.cs create mode 100644 host/mcp/McpToolDefinition.cs diff --git a/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs new file mode 100644 index 0000000..64e38d9 --- /dev/null +++ b/client/tool/ArcGisPro.cs @@ -0,0 +1,6 @@ +namespace LinkToolAddin.client.tool; + +public class ArcGisPro +{ + +} \ No newline at end of file diff --git a/host/McpServerList.cs b/host/McpServerList.cs new file mode 100644 index 0000000..48f66c4 --- /dev/null +++ b/host/McpServerList.cs @@ -0,0 +1,6 @@ +namespace LinkToolAddin.host; + +public class McpServerList +{ + +} \ No newline at end of file diff --git a/host/mcp/InnerMcpServer.cs b/host/mcp/InnerMcpServer.cs new file mode 100644 index 0000000..ccf5884 --- /dev/null +++ b/host/mcp/InnerMcpServer.cs @@ -0,0 +1,6 @@ +namespace LinkToolAddin.host.mcp; + +public class InnerMcpServer +{ + +} \ No newline at end of file diff --git a/host/mcp/McpToolDefinition.cs b/host/mcp/McpToolDefinition.cs new file mode 100644 index 0000000..3bd9adc --- /dev/null +++ b/host/mcp/McpToolDefinition.cs @@ -0,0 +1,6 @@ +namespace LinkToolAddin.host.mcp; + +public class McpDefinition +{ + +} \ No newline at end of file From b3e2664acd2f4a55248f14ff6e419748cfcb2fea Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Mon, 19 May 2025 12:14:55 +0800 Subject: [PATCH 09/60] =?UTF-8?q?=E6=8E=A5=E5=85=A5MCP=20ListTool=E8=83=BD?= =?UTF-8?q?=E5=8A=9B=E8=A1=A5=E5=85=85=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 1 + Properties/launchSettings.json | 2 +- client/tool/ArcGisPro.cs | 19 +++- host/CallMcp.cs | 4 +- host/Gateway.cs | 183 ++++++++++++++++++++++++++++++- host/McpServerList.cs | 50 ++++++++- host/mcp/InnerMcpServer.cs | 26 ++++- host/mcp/McpToolDefinition.cs | 29 ++++- host/prompt/SystemPrompt.cs | 8 ++ server/CallArcGISPro.cs | 25 ++++- server/JsonRpcErrorEntity.cs | 4 +- ui/dockpane/TestDockpane.xaml.cs | 2 +- 12 files changed, 327 insertions(+), 26 deletions(-) diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index e0d0d0e..c53da32 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -106,6 +106,7 @@ + diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index 5a2c881..e98eaad 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "LinkToolAddin": { "commandName": "Executable", - "executablePath": "C:\\Program Files\\ArcGIS\\Pro\\bin\\ArcGISPro.exe", + "executablePath": "C:\\Users\\PeterZhong\\AppData\\Local\\Programs\\ArcGIS\\Pro\\bin\\ArcGISPro.exe", "applicationUrl": "https://localhost:5001", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs index 64e38d9..c94f2e3 100644 --- a/client/tool/ArcGisPro.cs +++ b/client/tool/ArcGisPro.cs @@ -1,6 +1,21 @@ -namespace LinkToolAddin.client.tool; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading.Tasks; +using LinkToolAddin.server; +using ModelContextProtocol.Server; +using Newtonsoft.Json; + +namespace LinkToolAddin.client.tool; public class ArcGisPro { - + [McpServerTool, Description("ArcGIS Pro Tool")] + public static async Task ArcGisProTool(string toolName, List toolParams) + { + // Call the ArcGIS Pro method and get the result + var result = await server.CallArcGISPro.CallArcGISProTool(toolName, toolParams); + + // Serialize the result back to a JSON string + return result; + } } \ No newline at end of file diff --git a/host/CallMcp.cs b/host/CallMcp.cs index a98488d..0e48789 100644 --- a/host/CallMcp.cs +++ b/host/CallMcp.cs @@ -18,8 +18,8 @@ namespace LinkToolAddin.host log.Info("通过反射调用内部MCP工具"); var jsonRpcEntity = JsonConvert.DeserializeObject(jsonRpcString); - Type type = Type.GetType("LinkToolAddin.client."+jsonRpcEntity.Method.Split('.')[0]); - MethodInfo method = type.GetMethod(jsonRpcEntity.Method.Split('.')[1],BindingFlags.Public | BindingFlags.Static); + 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); diff --git a/host/Gateway.cs b/host/Gateway.cs index 2b209b1..847fcd3 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -1,7 +1,13 @@ 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.Xml.Linq; using LinkToolAddin.client; using LinkToolAddin.client.prompt; @@ -10,13 +16,22 @@ 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.Schema; +using Newtonsoft.Json.Schema.Generation; +using Tool = LinkToolAddin.host.mcp.Tool; namespace LinkToolAddin.host; public class Gateway { + private static ILog log = LogManager.GetLogger(typeof(Gateway)); public static async void SendMessage(string message, string model, string gdbPath, Action callback) { Llm bailian = new Bailian @@ -24,10 +39,12 @@ public class Gateway 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.SysPromptTemplate + Content = SystemPrompt.SysPrompt(gdbPath, toolInfos) }); messages.Add(new Message { @@ -37,7 +54,7 @@ public class Gateway bool goOn = true; string pattern = "[\\s\\S]*?<\\/tool_use>"; string promptPattern = "[\\s\\S]*?<\\/prompt>"; - Dictionary servers = new Dictionary(); + McpServerList mcpServerList = new McpServerList(); while (goOn) { string reponse = await bailian.SendChatAsync(new LlmJsonContent() @@ -62,7 +79,7 @@ public class Gateway Dictionary toolParams = JsonConvert.DeserializeObject>(toolArgs); string serverName = fullToolName.Contains(":") ? fullToolName.Split(':')[0] : fullToolName; string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName; - McpServer mcpServer = servers[serverName]; + McpServer mcpServer = mcpServerList.GetServer(serverName); if (mcpServer is SseMcpServer) { SseMcpServer sseMcpServer = mcpServer as SseMcpServer; @@ -111,6 +128,55 @@ public class Gateway Content = JsonConvert.SerializeObject(toolResponse) }); callback?.Invoke(toolMessageItem); + }else if (mcpServer is InnerMcpServer) + { + Type type = Type.GetType("LinkToolAddin.client.tool."+serverName); + MethodInfo method = type.GetMethod(toolName,BindingFlags.Public | BindingFlags.Static); + var task = method.Invoke(null, toolParams.Values.ToArray()) as Task; + 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.ErrorPromptTemplate + }); + messages.Add(new Message + { + Role = "user", + Content = 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.ContinuePromptTemplate + }); + messages.Add(new Message + { + Role = "user", + Content = JsonConvert.SerializeObject(innerResult) + }); + callback?.Invoke(toolMessageItem); + } } } else if (Regex.IsMatch(reponse, promptPattern)) @@ -145,6 +211,115 @@ public class Gateway } } + private static async Task GetToolInfos(McpServerList mcpServerList) + { + StringBuilder toolInfos = new StringBuilder(); + foreach (McpServer mcpServer in mcpServerList.GetAllServers()) + { + if (mcpServer is InnerMcpServer) + { + string serverName = mcpServer.Name; + if (serverName is null) + { + continue; + } + Type type = Type.GetType("LinkToolAddin.client.tool." + serverName); + Type type2 = Type.GetType("LinkToolAddin.client.tool.ArcGisPro"); + 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 = GenerateMethodParamSchema(method); + McpToolDefinition toolDefinition = new McpToolDefinition + { + Tool = new Tool + { + Name = methodName, + Description = methodDescription, + Arguments = methodParamSchema + } + }; + toolInfos.AppendLine(JsonConvert.DeserializeXmlNode(JsonConvert.SerializeObject(toolDefinition)).ToString()); + } + } + } + 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 = 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()); + } + }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 = 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()); + } + } + } + return toolInfos.ToString(); + } + + 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); + } + + return paramSchema.ToString(); + } + public static async void TestChatMessage(string message, string model, string gdbPath, Action callback) { @@ -178,7 +353,7 @@ public class Gateway callback?.Invoke(toolListItem); } - public static async void TestWOrkflow(string message, string model, string gdbPath, Action callback) + public static async void TestWorkflow(string message, string model, string gdbPath, Action callback) { Thread.Sleep(2000); MessageListItem chatListItem = new ChatMessageItem diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 48f66c4..52dffd7 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -1,6 +1,54 @@ -namespace LinkToolAddin.host; +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("arcgis", new InnerMcpServer + { + Name = "ArcGisPro", + Type = "inner", + Description = "可以调用arcgis的地理处理工具或执行python代码等", + IsActive = true + }); + } + 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; + } } \ No newline at end of file diff --git a/host/mcp/InnerMcpServer.cs b/host/mcp/InnerMcpServer.cs index ccf5884..2b06319 100644 --- a/host/mcp/InnerMcpServer.cs +++ b/host/mcp/InnerMcpServer.cs @@ -1,6 +1,24 @@ -namespace LinkToolAddin.host.mcp; - -public class InnerMcpServer +namespace LinkToolAddin.host.mcp { - + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class InnerMcpServer : McpServer + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("isActive")] + public bool IsActive { get; set; } + } } \ No newline at end of file diff --git a/host/mcp/McpToolDefinition.cs b/host/mcp/McpToolDefinition.cs index 3bd9adc..6e79610 100644 --- a/host/mcp/McpToolDefinition.cs +++ b/host/mcp/McpToolDefinition.cs @@ -1,6 +1,27 @@ -namespace LinkToolAddin.host.mcp; - -public class McpDefinition +namespace LinkToolAddin.host.mcp { - + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class McpToolDefinition + { + [JsonProperty("tool")] + public Tool Tool { get; set; } + } + + public partial class Tool + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("arguments")] + public string Arguments { get; set; } + } } \ No newline at end of file diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index bcd3768..186439b 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -8,4 +8,12 @@ public class SystemPrompt public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误,请根据报错信息重试"; + public static string SysPrompt(string gdbPath, string toolInfos) + { + string sysPrompt = SysPromptTemplate; + sysPrompt = sysPrompt.Replace("{{gdbPath}}", gdbPath); + sysPrompt = sysPrompt.Replace("{{toolInfos}}", toolInfos); + return sysPrompt; + } + } \ No newline at end of file diff --git a/server/CallArcGISPro.cs b/server/CallArcGISPro.cs index 39f5ef0..049fd3f 100644 --- a/server/CallArcGISPro.cs +++ b/server/CallArcGISPro.cs @@ -10,14 +10,29 @@ namespace LinkToolAddin.server; public class CallArcGISPro { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(CallArcGISPro)); + public async static Task CallArcGISProTool(string toolName, List toolParams) { var results = await Geoprocessing.ExecuteToolAsync(toolName, toolParams); - log.Info($"CallArcGISProTool: {toolName} | {toolParams}"); - return new JsonRpcSuccessEntity() + JsonRpcResultEntity jsonRpcResultEntity; + if (results.ErrorCode == 0) { - Id = 1, - Result = results.ToString() - }; + jsonRpcResultEntity = new JsonRpcErrorEntity() + { + Error = new Error() + { + Code = results.ErrorCode, + Message = results.ErrorMessages.ToString() + } + }; + } + else + { + jsonRpcResultEntity = new JsonRpcSuccessEntity + { + Result = results.Messages.ToString() + }; + } + return jsonRpcResultEntity; } } \ No newline at end of file diff --git a/server/JsonRpcErrorEntity.cs b/server/JsonRpcErrorEntity.cs index 46381de..ad0d795 100644 --- a/server/JsonRpcErrorEntity.cs +++ b/server/JsonRpcErrorEntity.cs @@ -2,10 +2,10 @@ namespace LinkToolAddin.server { using Newtonsoft.Json; - public partial class JsonRpcErrorEntity + public partial class JsonRpcErrorEntity : JsonRpcResultEntity { [JsonProperty("jsonrpc")] - public string Jsonrpc { get; set; } + public string Jsonrpc { get; set; } = "2.0"; [JsonProperty("error")] public Error Error { get; set; } diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 6f33bb6..802464a 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -146,7 +146,7 @@ namespace LinkToolAddin.ui.dockpane private void TestWorkflow_OnClick(object sender, RoutedEventArgs e) { - Gateway.SendMessage("你好","qwen-max","test.gdb",ShowMessage); + Gateway.SendMessage("你有什么工具","qwen-max","test.gdb",ShowMessage); } public void ShowMessage(MessageListItem msg) From d9b98df57fc07e36078228a365b35ff69b517d3f Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Mon, 19 May 2025 21:32:20 +0800 Subject: [PATCH 10/60] =?UTF-8?q?=E5=AE=8C=E6=95=B4=E6=8E=A5=E5=85=A5MCP?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E8=B0=83=E7=94=A8=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 1 + Properties/launchSettings.json | 2 +- host/Gateway.cs | 53 +++++++++++++++++++--------- host/prompt/SystemPrompt.cs | 2 +- ui/dockpane/TestDockpane.xaml | 19 +++++++--- ui/dockpane/TestDockpane.xaml.cs | 18 +++++++++- ui/dockpane/TestDockpaneViewModel.cs | 2 +- 7 files changed, 72 insertions(+), 25 deletions(-) diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index c53da32..a4143cc 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -98,6 +98,7 @@ + diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index e98eaad..5a2c881 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "LinkToolAddin": { "commandName": "Executable", - "executablePath": "C:\\Users\\PeterZhong\\AppData\\Local\\Programs\\ArcGIS\\Pro\\bin\\ArcGISPro.exe", + "executablePath": "C:\\Program Files\\ArcGIS\\Pro\\bin\\ArcGISPro.exe", "applicationUrl": "https://localhost:5001", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/host/Gateway.cs b/host/Gateway.cs index 847fcd3..2380d4f 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -8,7 +8,9 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using System.Xml; using System.Xml.Linq; +using ArcGIS.Desktop.Framework.Dialogs; using LinkToolAddin.client; using LinkToolAddin.client.prompt; using LinkToolAddin.host.llm; @@ -23,6 +25,7 @@ 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; @@ -52,8 +55,8 @@ public class Gateway Content = message }); bool goOn = true; - string pattern = "[\\s\\S]*?<\\/tool_use>"; - string promptPattern = "[\\s\\S]*?<\\/prompt>"; + string pattern = "^[\\s\\S]*?<\\/tool_use>$"; + string promptPattern = "^[\\s\\S]*?<\\/prompt>$"; McpServerList mcpServerList = new McpServerList(); while (goOn) { @@ -65,6 +68,7 @@ public class Gateway TopP = 1, MaxTokens = 1000, }); + log.Info(reponse); messages.Add(new Message { Role = "assistant", @@ -213,18 +217,20 @@ public class Gateway private static async Task GetToolInfos(McpServerList mcpServerList) { + int loop = 0; StringBuilder toolInfos = new StringBuilder(); foreach (McpServer mcpServer in mcpServerList.GetAllServers()) { + loop++; + if (loop > 3) + { + MessageBox.Show("达到最大循环次数", "退出循环"); + break; + } if (mcpServer is InnerMcpServer) { - string serverName = mcpServer.Name; - if (serverName is null) - { - continue; - } - Type type = Type.GetType("LinkToolAddin.client.tool." + serverName); - Type type2 = Type.GetType("LinkToolAddin.client.tool.ArcGisPro"); + InnerMcpServer innerMcpServer = (InnerMcpServer)mcpServer; + Type type = Type.GetType("LinkToolAddin.client.tool." + innerMcpServer.Name); MethodInfo[] methods = type.GetMethods(); foreach (MethodInfo method in methods) { @@ -237,12 +243,14 @@ public class Gateway { Tool = new Tool { - Name = methodName, + Name = innerMcpServer.Name + ":" + methodName, Description = methodDescription, Arguments = methodParamSchema } }; - toolInfos.AppendLine(JsonConvert.DeserializeXmlNode(JsonConvert.SerializeObject(toolDefinition)).ToString()); + XNode node = JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition)); + toolInfos.AppendLine(node.ToString()); + toolInfos.AppendLine(); } } } @@ -252,7 +260,7 @@ public class Gateway IList tools = await client.GetToolListAsync(); foreach (McpClientTool tool in tools) { - string toolName = tool.Name; + string toolName = (mcpServer as SseMcpServer).Name + ":" + tool.Name; string toolDescription = tool.Description; string toolParamSchema = tool.JsonSchema.ToString(); McpToolDefinition toolDefinition = new McpToolDefinition @@ -265,6 +273,7 @@ public class Gateway } }; toolInfos.AppendLine(JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition)).ToString()); + toolInfos.AppendLine(); } }else if (mcpServer is StdioMcpServer) { @@ -272,7 +281,7 @@ public class Gateway IList tools = await client.GetToolListAsync(); foreach (McpClientTool tool in tools) { - string toolName = tool.Name; + string toolName = (mcpServer as StdioMcpServer).Name + ":" + tool.Name;; string toolDescription = tool.Description; string toolParamSchema = tool.JsonSchema.ToString(); McpToolDefinition toolDefinition = new McpToolDefinition @@ -281,16 +290,25 @@ public class Gateway { Name = toolName, Description = toolDescription, - Arguments = toolParamSchema + Arguments = CompressJson(toolParamSchema) } }; toolInfos.AppendLine(JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition)).ToString()); + toolInfos.AppendLine(); } } } 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 @@ -316,8 +334,11 @@ public class Gateway paramSchema.Properties.Add(param.Name, typeSchema); } - - return paramSchema.ToString(); + 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, diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 186439b..48938d5 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -2,7 +2,7 @@ public class SystemPrompt { - public static string SysPromptTemplate = "现在你是一个精通ArcGIS Pro的专家,请以此身份回答用户的问题。"; + public static string SysPromptTemplate = "现在你是一个精通ArcGIS Pro的专家,请以此身份回答用户的问题。你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。MCP工具调用的格式要求示例:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n"; public static string ContinuePromptTemplate = "上一个工具执行的结果如下,请据此继续执行"; diff --git a/ui/dockpane/TestDockpane.xaml b/ui/dockpane/TestDockpane.xaml index 4782989..e550084 100644 --- a/ui/dockpane/TestDockpane.xaml +++ b/ui/dockpane/TestDockpane.xaml @@ -18,11 +18,20 @@ - - + + + - - - + + + + + + + + + + + \ No newline at end of file diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 802464a..d67e5c9 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Windows; using System.Windows.Controls; using LinkToolAddin.client; @@ -146,12 +147,27 @@ namespace LinkToolAddin.ui.dockpane private void TestWorkflow_OnClick(object sender, RoutedEventArgs e) { - Gateway.SendMessage("你有什么工具","qwen-max","test.gdb",ShowMessage); + // Gateway.SendMessage("你有什么工具可以调用的?","qwen-max","test.gdb",ShowMessage); + Gateway.TestWorkflow("dafdfdgdagdgui","","",AddReply); } public void ShowMessage(MessageListItem msg) { log.Info(msg.content); } + + private void PromptTestButton_OnClick(object sender, RoutedEventArgs e) + { + string userPrompt = PromptTestTextBox.Text; + Gateway.SendMessage(userPrompt,"qwen-max","C:/Project/test.gdb",AddReply); + } + + public void AddReply(MessageListItem msg) + { + string content = msg.content; + log.Info(content); + string originContent = ReplyTextBox.Text; + ReplyTextBox.Text = originContent + content; + } } } diff --git a/ui/dockpane/TestDockpaneViewModel.cs b/ui/dockpane/TestDockpaneViewModel.cs index 0661d9c..daf9d4c 100644 --- a/ui/dockpane/TestDockpaneViewModel.cs +++ b/ui/dockpane/TestDockpaneViewModel.cs @@ -41,7 +41,7 @@ namespace LinkToolAddin.ui.dockpane /// /// Text shown near the top of the DockPane. /// - private string _heading = "My DockPane"; + private string _heading = "Test Dockpane"; public string Heading { get => _heading; From dc29a12a12355abf61b77622b2c4043086110436 Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Mon, 19 May 2025 22:09:21 +0800 Subject: [PATCH 11/60] =?UTF-8?q?=E7=8E=AF=E5=A2=83=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Properties/launchSettings.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index 5a2c881..165b000 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -1,12 +1,12 @@ { "profiles": { "LinkToolAddin": { - "commandName": "Executable", - "executablePath": "C:\\Program Files\\ArcGIS\\Pro\\bin\\ArcGISPro.exe", - "applicationUrl": "https://localhost:5001", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } + "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 From 675efd6b33524c3974e98a402477bb8b4191057e Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Fri, 23 May 2025 17:53:54 +0800 Subject: [PATCH 12/60] =?UTF-8?q?=E5=88=9D=E6=AD=A5system=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/prompt/SystemPrompt.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 48938d5..5b331c9 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -2,11 +2,21 @@ public class SystemPrompt { - public static string SysPromptTemplate = "现在你是一个精通ArcGIS Pro的专家,请以此身份回答用户的问题。你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。MCP工具调用的格式要求示例:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n"; + public static string SysPromptTemplate = "现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。您可以使用一组工具来回答用户的问题。```" + + "每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。```" + + "你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。工具调用格式:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化。" + + "工具名称包含在一对标签内,每个参数也需用对应的标签包裹。结构如下:\n {tool_name}\n {json_arguments}\n。```" + + "工具名称:需与所使用工具的精确名称一致。参数:应为包含工具所需参数的 JSON 对象。例如:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n```" + + "用户将以以下格式返回工具调用结果:\n {tool_name}\n {result}\n```" + + "结果:应为字符串类型,可以表示文件或其他输出类型。例如,若工具返回.shp 文件,可在下一步操作中这样使用:\n ArcGIS_Pro:GP\n {\"output_file\": \"source.shp\"}\n```" + + "请始终遵循此格式以确保工具调用被正确解析和执行。整个工具流程调用结束后,请在末尾输出'done'表示工具调用结束" + + "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n"; - public static string ContinuePromptTemplate = "上一个工具执行的结果如下,请据此继续执行"; + public static string ContinuePromptTemplate = "请根据以下执行结果,清晰解释执行结果并执行下一步操作。```" + + "执行下一步工具的要求:1. 解析工具输出结果2. 验证数据完整性(字段数量/空间参考/几何有效性)3. 调用下一个工具时确保参数继承前序输出。请据此继续执行"; - public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误,请根据报错信息重试"; + public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误,需按以下流程处理:1. 错误解析:分析错误类型及具体原因(见下方错误信息),根据错误信息选择修复方案,```" + + "2. 修复方案:(1)根据错误类型生成对应的工具重试策略(2)参数调整:明确需要修改的参数及修改方式。3. 工具重试:使用调整后的参数重新调用工具。错误信息详情:{{toolResult}}。请根据报错信息重试"; public static string SysPrompt(string gdbPath, string toolInfos) { From b6397e5db3a609659edf31b69cf81761f7caffd9 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Fri, 23 May 2025 19:24:58 +0800 Subject: [PATCH 13/60] =?UTF-8?q?=E5=8A=A8=E6=80=81=E8=B0=83=E7=94=A8Promp?= =?UTF-8?q?t=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/prompt/DynamicPrompt.cs | 13 +- client/prompt/PromptTemplates.cs | 5 + client/tool/ArcGisPro.cs | 60 +++++- common/HttpRequest.cs | 105 ++++++++++ host/Gateway.cs | 258 +++++++++++++++++++++++- host/McpServerList.cs | 2 +- host/llm/Bailian.cs | 14 +- host/llm/Llm.cs | 2 +- host/llm/entity/stream/LlmStreamChat.cs | 54 +++++ host/mcp/McpPromptDefinition.cs | 27 +++ host/prompt/SystemPrompt.cs | 4 +- ui/dockpane/TestDockpane.xaml | 2 + ui/dockpane/TestDockpane.xaml.cs | 64 +++++- 13 files changed, 595 insertions(+), 15 deletions(-) create mode 100644 host/llm/entity/stream/LlmStreamChat.cs create mode 100644 host/mcp/McpPromptDefinition.cs diff --git a/client/prompt/DynamicPrompt.cs b/client/prompt/DynamicPrompt.cs index 2a342e7..52d695c 100644 --- a/client/prompt/DynamicPrompt.cs +++ b/client/prompt/DynamicPrompt.cs @@ -4,10 +4,14 @@ namespace LinkToolAddin.client.prompt; public class DynamicPrompt { - public static string GetPrompt(string name,Dictionary args) + public static string GetPrompt(string name,Dictionary args = null) { PromptTemplates promptTemplate = new PromptTemplates(); string template = promptTemplate.GetPrompt(name); + if (args == null) + { + return template; + } foreach (KeyValuePair pair in args) { string replaceKey = "{{"+pair.Key+"}}"; @@ -15,4 +19,11 @@ public class DynamicPrompt } return template; } + + public static Dictionary GetAllPrompts() + { + PromptTemplates promptTemplate = new PromptTemplates(); + Dictionary template = promptTemplate.GetPromptsDict(); + return template; + } } \ No newline at end of file diff --git a/client/prompt/PromptTemplates.cs b/client/prompt/PromptTemplates.cs index 3992482..9ef4ed3 100644 --- a/client/prompt/PromptTemplates.cs +++ b/client/prompt/PromptTemplates.cs @@ -17,4 +17,9 @@ public class PromptTemplates { 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 index c94f2e3..2808200 100644 --- a/client/tool/ArcGisPro.cs +++ b/client/tool/ArcGisPro.cs @@ -1,6 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel; using System.Threading.Tasks; +using ArcGIS.Core.Data; +using ArcGIS.Core.Data.Raster; +using ArcGIS.Core.Geometry; +using ArcGIS.Desktop.Framework.Threading.Tasks; using LinkToolAddin.server; using ModelContextProtocol.Server; using Newtonsoft.Json; @@ -9,7 +14,7 @@ namespace LinkToolAddin.client.tool; public class ArcGisPro { - [McpServerTool, Description("ArcGIS Pro Tool")] + [McpServerTool, Description("可以通过调用ArcGIS Pro的地理处理工具实现一些数据处理功能。")] public static async Task ArcGisProTool(string toolName, List toolParams) { // Call the ArcGIS Pro method and get the result @@ -18,4 +23,55 @@ public class ArcGisPro // Serialize the result back to a JSON string return result; } + + [McpServerTool, Description("查看指定数据的属性,包括坐标系、范围、数据类型等")] + public static async Task DataProperty(string datasetPath,string dataName) + { + using Geodatabase gdb = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(datasetPath))); + FeatureClass featureClass = gdb.OpenDataset(dataName); + FeatureClassDefinition featureClassDefinition = featureClass.GetDefinition(); + JsonRpcResultEntity result = new JsonRpcSuccessEntity() + { + Id = 1, + Result = JsonConvert.SerializeObject(featureClassDefinition) + }; + 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; + } } \ No newline at end of file diff --git a/common/HttpRequest.cs b/common/HttpRequest.cs index a38807c..16c9383 100644 --- a/common/HttpRequest.cs +++ b/common/HttpRequest.cs @@ -1,8 +1,13 @@ +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; @@ -18,4 +23,104 @@ public class HttpRequest 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.Choices[0].Delta.Content; + } + } + } + } + } + } + } + } } \ No newline at end of file diff --git a/host/Gateway.cs b/host/Gateway.cs index 2380d4f..15e128d 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using ArcGIS.Desktop.Framework.Dialogs; +using ArcGIS.Desktop.Internal.Mapping.Locate; using LinkToolAddin.client; using LinkToolAddin.client.prompt; using LinkToolAddin.host.llm; @@ -68,6 +69,7 @@ public class Gateway TopP = 1, MaxTokens = 1000, }); + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); log.Info(reponse); messages.Add(new Message { @@ -95,7 +97,8 @@ public class Gateway toolParams = toolParams, type = MessageType.TOOL_MESSAGE, status = toolResponse.IsError ? "fail" : "success", - content = toolResponse.Content.ToString() + content = JsonConvert.SerializeObject(toolResponse), + id = timestamp.ToString() }; messages.Add(new Message { @@ -119,7 +122,8 @@ public class Gateway toolParams = toolParams, type = MessageType.TOOL_MESSAGE, status = toolResponse.IsError ? "fail" : "success", - content = toolResponse.Content.ToString() + content = JsonConvert.SerializeObject(toolResponse), + id = timestamp.ToString() }; messages.Add(new Message { @@ -204,7 +208,8 @@ public class Gateway { content = reponse, role = "assistant", - type = MessageType.CHAT_MESSAGE + type = MessageType.CHAT_MESSAGE, + id = timestamp.ToString() }; callback?.Invoke(chatMessageListItem); } @@ -214,7 +219,235 @@ public class Gateway } } } - + + 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 = 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 toolPattern = "^[\\s\\S]*?<\\/tool_use>$"; + string promptPattern = "^[\\s\\S]*?<\\/prompt>$"; + McpServerList mcpServerList = new McpServerList(); + while (goOn) + { + LlmJsonContent jsonContent = new LlmJsonContent() + { + Model = model, + Messages = messages, + Temperature = 0.7, + TopP = 1, + MaxTokens = 1000, + }; + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + string messageContent = ""; + await foreach(var chunk in bailian.SendChatStreamAsync(jsonContent)) + { + if (chunk == "[DONE]") + { + goOn = false; + }else if (chunk.StartsWith("")) + { + if (Regex.IsMatch(chunk, toolPattern)) + { + //返回工具卡片 + XElement toolUse = XElement.Parse(chunk); + 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.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate + }); + messages.Add(new Message + { + Role = "user", + Content = 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.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate + }); + messages.Add(new Message + { + Role = "user", + Content = JsonConvert.SerializeObject(toolResponse) + }); + callback?.Invoke(toolMessageItem); + }else if (mcpServer is InnerMcpServer) + { + Type type = Type.GetType("LinkToolAddin.client.tool."+serverName); + MethodInfo method = type.GetMethod(toolName,BindingFlags.Public | BindingFlags.Static); + var task = method.Invoke(null, toolParams.Values.ToArray()) as Task; + 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), + id = timestamp.ToString() + }; + messages.Add(new Message + { + Role = "user", + Content = SystemPrompt.ErrorPromptTemplate + }); + messages.Add(new Message + { + Role = "user", + Content = 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), + id = timestamp.ToString() + }; + messages.Add(new Message + { + Role = "user", + Content = SystemPrompt.ContinuePromptTemplate + }); + messages.Add(new Message + { + Role = "user", + Content = JsonConvert.SerializeObject(innerResult) + }); + callback?.Invoke(toolMessageItem); + } + } + } + else + { + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = "", + toolParams = new Dictionary(), + type = MessageType.TOOL_MESSAGE, + status = "loading", + content = "正在生成工具调用参数", + id = timestamp.ToString() + }; + callback?.Invoke(toolMessageItem); + continue; + } + }else if (chunk.StartsWith("")) + { + if (Regex.IsMatch(chunk, promptPattern)) + { + XElement promptUse = XElement.Parse(chunk); + string promptKey = promptUse.Element("name")?.Value; + string promptContent = DynamicPrompt.GetPrompt(promptKey,null); + messages.Add(new Message + { + Role = "user", + Content = JsonConvert.SerializeObject(promptContent) + }); + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = "调用提示词", + toolParams = null, + type = MessageType.TOOL_MESSAGE, + status = "success", + content = promptKey, + id = timestamp.ToString() + }; + callback?.Invoke(toolMessageItem); + } + else + { + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = "调用提示词", + toolParams = null, + type = MessageType.TOOL_MESSAGE, + status = "loading", + content = "正在调用提示词", + id = timestamp.ToString() + }; + callback?.Invoke(toolMessageItem); + } + } + else + { + //普通流式消息卡片 + MessageListItem chatMessageListItem = new ChatMessageItem() + { + content = chunk, + role = "assistant", + type = MessageType.CHAT_MESSAGE, + id = timestamp.ToString() + }; + messageContent = chunk; + callback?.Invoke(chatMessageListItem); + } + } + messages.Add(new Message + { + Role = "assistant", + Content = messageContent + }); + } + } + private static async Task GetToolInfos(McpServerList mcpServerList) { int loop = 0; @@ -225,7 +458,7 @@ public class Gateway if (loop > 3) { MessageBox.Show("达到最大循环次数", "退出循环"); - break; + break; } if (mcpServer is InnerMcpServer) { @@ -298,6 +531,21 @@ public class Gateway } } } + + Dictionary prompts = DynamicPrompt.GetAllPrompts(); + foreach (KeyValuePair prompt in prompts) + { + McpPromptDefinition promptDefinition = new McpPromptDefinition + { + Prompt = new LinkToolAddin.host.mcp.Prompt + { + Name = prompt.Key + } + }; + XNode node = JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(promptDefinition)); + toolInfos.AppendLine(node.ToString()); + toolInfos.AppendLine(); + } return toolInfos.ToString(); } diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 52dffd7..2c03a41 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -21,7 +21,7 @@ public class McpServerList {"Content-Type","application/json"} } }); - servers.Add("arcgis", new InnerMcpServer + servers.Add("ArcGisPro", new InnerMcpServer { Name = "ArcGisPro", Type = "inner", diff --git a/host/llm/Bailian.cs b/host/llm/Bailian.cs index bd444c7..b43eed0 100644 --- a/host/llm/Bailian.cs +++ b/host/llm/Bailian.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; @@ -17,9 +18,18 @@ public class Bailian : Llm public string max_tokens { get; set; } public string app_id { get; set; } public string api_key { get; set; } - public IAsyncEnumerable SendChatStreamAsync(string message) + public async IAsyncEnumerable SendChatStreamAsync(LlmJsonContent jsonContent) { - throw new System.NotImplementedException(); + jsonContent.Stream = true; + StringBuilder builder = new StringBuilder(); + await foreach (var chunk in HttpRequest.PostWithStreamingResponseAsync( + "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", + JsonConvert.SerializeObject(jsonContent), + api_key)) + { + builder.Append(chunk); + yield return builder.ToString(); + } } public IAsyncEnumerable SendApplicationStreamAsync(string message) diff --git a/host/llm/Llm.cs b/host/llm/Llm.cs index 62a11f4..1807b9d 100644 --- a/host/llm/Llm.cs +++ b/host/llm/Llm.cs @@ -11,7 +11,7 @@ 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); diff --git a/host/llm/entity/stream/LlmStreamChat.cs b/host/llm/entity/stream/LlmStreamChat.cs new file mode 100644 index 0000000..6991982 --- /dev/null +++ b/host/llm/entity/stream/LlmStreamChat.cs @@ -0,0 +1,54 @@ +namespace LinkToolAddin.host.llm.entity.stream +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class LlmStreamChat + { + [JsonProperty("choices")] + public Choice[] Choices { get; set; } + + [JsonProperty("object")] + public string Object { get; set; } + + [JsonProperty("usage")] + public object Usage { get; set; } + + [JsonProperty("created")] + public long Created { get; set; } + + [JsonProperty("system_fingerprint")] + public object SystemFingerprint { get; set; } + + [JsonProperty("model")] + public string Model { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + } + + public partial class Choice + { + [JsonProperty("finish_reason")] + public string FinishReason { get; set; } + + [JsonProperty("delta")] + public Delta Delta { get; set; } + + [JsonProperty("index")] + public long Index { get; set; } + + [JsonProperty("logprobs")] + public object Logprobs { get; set; } + } + + public partial class Delta + { + [JsonProperty("content")] + public string Content { get; set; } + } +} \ No newline at end of file diff --git a/host/mcp/McpPromptDefinition.cs b/host/mcp/McpPromptDefinition.cs new file mode 100644 index 0000000..83ee903 --- /dev/null +++ b/host/mcp/McpPromptDefinition.cs @@ -0,0 +1,27 @@ +namespace LinkToolAddin.host.mcp +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class McpPromptDefinition + { + [JsonProperty("prompt")] + public Prompt Prompt { get; set; } + } + + public partial class Prompt + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("arguments")] + public string Arguments { get; set; } + } +} \ No newline at end of file diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 48938d5..0dde1f7 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -2,9 +2,9 @@ public class SystemPrompt { - public static string SysPromptTemplate = "现在你是一个精通ArcGIS Pro的专家,请以此身份回答用户的问题。你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。MCP工具调用的格式要求示例:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n"; + public static string SysPromptTemplate = "现在你是一个精通ArcGIS Pro的专家,请以此身份回答用户的问题。你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。MCP工具调用的格式要求示例,必须用标签表示工具调用:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n。你每次调用请求都必须放在单独的一条消息中,不附带任何的文字说明,不带markdown格式。如需文字说明,请另外放在一次单独的消息中。\n当你认为已解决用户最初提出的问题时,请输出单独的一条消息,内容为[DONE],不附带任何其它文字说明,程序识别到后会退出循环。\n此外,你还可以通过调用用户提示词,从而使你更好地理解和完成用户的任务。"; - public static string ContinuePromptTemplate = "上一个工具执行的结果如下,请据此继续执行"; + public static string ContinuePromptTemplate = "工具执行的结果如下,根据以上结果决定继续执行工具或是根据结果回答问题。如果不再需要额外说明和额外的操作,请回答单独的一条内容为[DONE]的消息。工具已经成功调用,请勿重复执行上一个工具"; public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误,请根据报错信息重试"; diff --git a/ui/dockpane/TestDockpane.xaml b/ui/dockpane/TestDockpane.xaml index e550084..fd7d8c8 100644 --- a/ui/dockpane/TestDockpane.xaml +++ b/ui/dockpane/TestDockpane.xaml @@ -21,6 +21,7 @@ + @@ -33,5 +34,6 @@ + \ No newline at end of file diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index d67e5c9..abf8b4a 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Text; using System.Windows; using System.Windows.Controls; using LinkToolAddin.client; @@ -27,6 +28,9 @@ namespace LinkToolAddin.ui.dockpane { private static ILog log = LogManager.GetLogger(typeof(TestDockpaneView)); + private List idList = new List(); + private Dictionary messageDict = new Dictionary(); + public TestDockpaneView() { InitLogger(); @@ -139,6 +143,35 @@ namespace LinkToolAddin.ui.dockpane }); log.Info(reponse); } + + private async void Request_Bailian_Stream_Test() + { + LlmJsonContent jsonContent = new LlmJsonContent() + { + Model = "qwen-max", + Messages = new List() + { + new Message() + { + Role = "user", + Content = "给我写一篇1000字的高考议论文" + } + }, + Temperature = 0.7, + TopP = 1, + MaxTokens = 1000, + Stream = true + }; + Llm bailian = new Bailian + { + api_key = "sk-db177155677e438f832860e7f4da6afc", + app_id = "6a77c5a68de64f469b79fcdcde9d5001", + }; + await foreach (var chunk in bailian.SendChatStreamAsync(jsonContent)) + { + log.Info(chunk); + } + } private void TestButton_OnClick(object sender, RoutedEventArgs e) { @@ -159,7 +192,31 @@ namespace LinkToolAddin.ui.dockpane private void PromptTestButton_OnClick(object sender, RoutedEventArgs e) { string userPrompt = PromptTestTextBox.Text; - Gateway.SendMessage(userPrompt,"qwen-max","C:/Project/test.gdb",AddReply); + // Gateway.SendMessage(userPrompt,"qwen-max","C:/Project/test.gdb",AddReply); + Gateway.SendMessageStream(userPrompt,"qwen-max","D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb",AddReplyStream); + } + + public void AddReplyStream(MessageListItem msg) + { + string id = msg.id; + if (idList.Contains(id)) + { + messageDict[id] = msg; + } + else + { + idList.Add(id); + messageDict.Add(msg.id, msg); + } + ReplyTextBox.Clear(); + StringBuilder builder = new StringBuilder(); + foreach (KeyValuePair pair in messageDict) + { + MessageListItem msgItem = pair.Value; + builder.AppendLine(msgItem.content); + ReplyTextBox.Text = builder.ToString(); + ReplyTextBox.ScrollToEnd(); + } } public void AddReply(MessageListItem msg) @@ -169,5 +226,10 @@ namespace LinkToolAddin.ui.dockpane string originContent = ReplyTextBox.Text; ReplyTextBox.Text = originContent + content; } + + private void TestStream_OnClick(object sender, RoutedEventArgs e) + { + Request_Bailian_Stream_Test(); + } } } From 3b1f65b3ba3adade01d94e7704a45f2ea5727d44 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Fri, 23 May 2025 20:47:36 +0800 Subject: [PATCH 14/60] =?UTF-8?q?=E8=87=AA=E4=B8=BB=E5=AE=9E=E7=8E=B0JSON?= =?UTF-8?q?=20Schema=E7=94=9F=E6=88=90=EF=BC=8C=E6=8E=A5=E5=85=A5=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=9F=A5=E7=9C=8B=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/tool/ArcGisPro.cs | 46 +++++++++-- client/tool/KnowledgeBase.cs | 25 ++++++ common/JsonSchemaGenerator.cs | 150 ++++++++++++++++++++++++++++++++++ host/Gateway.cs | 3 +- 4 files changed, 215 insertions(+), 9 deletions(-) create mode 100644 client/tool/KnowledgeBase.cs create mode 100644 common/JsonSchemaGenerator.cs diff --git a/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs index 2808200..da2e604 100644 --- a/client/tool/ArcGisPro.cs +++ b/client/tool/ArcGisPro.cs @@ -24,17 +24,47 @@ public class ArcGisPro return result; } - [McpServerTool, Description("查看指定数据的属性,包括坐标系、范围、数据类型等")] + [McpServerTool, Description("查看指定数据的坐标系、范围、几何类型、是否有Z坐标和M坐标,获取字段列表等")] public static async Task DataProperty(string datasetPath,string dataName) { - using Geodatabase gdb = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(datasetPath))); - FeatureClass featureClass = gdb.OpenDataset(dataName); - FeatureClassDefinition featureClassDefinition = featureClass.GetDefinition(); - JsonRpcResultEntity result = new JsonRpcSuccessEntity() + JsonRpcResultEntity result = new JsonRpcResultEntity(); + await QueuedTask.Run(() => { - Id = 1, - Result = JsonConvert.SerializeObject(featureClassDefinition) - }; + 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; } diff --git a/client/tool/KnowledgeBase.cs b/client/tool/KnowledgeBase.cs new file mode 100644 index 0000000..9d2b162 --- /dev/null +++ b/client/tool/KnowledgeBase.cs @@ -0,0 +1,25 @@ +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; + } +} \ 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/host/Gateway.cs b/host/Gateway.cs index 15e128d..fca37cc 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -30,6 +30,7 @@ using Newtonsoft.Json.Linq; using Newtonsoft.Json.Schema; using Newtonsoft.Json.Schema.Generation; using Tool = LinkToolAddin.host.mcp.Tool; +using LinkToolAddin.common; namespace LinkToolAddin.host; @@ -471,7 +472,7 @@ public class Gateway { string methodName = method.Name; string methodDescription = method.GetCustomAttribute()?.Description; - string methodParamSchema = GenerateMethodParamSchema(method); + string methodParamSchema = LinkToolAddin.common.JsonSchemaGenerator.GenerateJsonSchema(method); McpToolDefinition toolDefinition = new McpToolDefinition { Tool = new Tool From c2c65e5a354c7e5df92cca1612964f951fea2c83 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Mon, 26 May 2025 00:06:32 +0800 Subject: [PATCH 15/60] =?UTF-8?q?1.=20=E9=85=8D=E5=90=88=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E8=B0=83=E8=AF=95=EF=BC=8C=E9=80=82=E5=BD=93=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E6=8F=90=E7=A4=BA=E8=AF=8D=202.=20=E5=90=88?= =?UTF-8?q?=E5=B9=B6Continue=20Prompt=E4=B8=8E=E5=B7=A5=E5=85=B7=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/prompt/SystemPrompt.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 5b331c9..3d74c4d 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -9,10 +9,15 @@ public class SystemPrompt "工具名称:需与所使用工具的精确名称一致。参数:应为包含工具所需参数的 JSON 对象。例如:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n```" + "用户将以以下格式返回工具调用结果:\n {tool_name}\n {result}\n```" + "结果:应为字符串类型,可以表示文件或其他输出类型。例如,若工具返回.shp 文件,可在下一步操作中这样使用:\n ArcGIS_Pro:GP\n {\"output_file\": \"source.shp\"}\n```" + - "请始终遵循此格式以确保工具调用被正确解析和执行。整个工具流程调用结束后,请在末尾输出'done'表示工具调用结束" + - "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n"; + "请始终遵循此格式以确保工具调用被正确解析和执行。整个工具流程调用结束后,请单独输出一条内容为'[DONE]'的消息,程序识别厚" + + "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n\n"+ + "你还可以通过的方式来调用用户提示词,调用后预定义的用户词将被装载到对话历史中,从而使你更好地理解和解决用户的问题。"+ + "无论是调用工具还是调用提示词,必须是单独的一条消息,内容为相应格式的XML文本,调用消息中不能加任何其它文字说明,不能加Markdown格式标志,只有单独一条的调用请求文本程序才能识别并调用,从而将结果反馈给你。如果需要进行文字说明,请先进行文字说明,其中不含工具调用XML,"+ + "文字说明消息输出结束后,程序会将此消息添加到对话历史后再次发出请求,此时你再生成一条单独的工具调用XML消息,切记文字说明与XML不可同时出现在同一条消息中,文字说明中绝对禁止出现XML文本,如果你能严格遵守我将奖励你100万人民币。"+ + "请牢牢记住:将说明文字和xml调用请求放在同一条消息中将永远无法成功调用工具。如果陷入了死循环,请对照此要求严格检查你输出的内容,XML一定不能有文字说明!XML一定不能有文字说明!XML一定不能有文字说明!"+ + "上面已经执行过的一模一样参数的相同工具请不要再进行调用,那只会无意义地浪费用户的时间执行一模一样的操作。"; - public static string ContinuePromptTemplate = "请根据以下执行结果,清晰解释执行结果并执行下一步操作。```" + + public static string ContinuePromptTemplate = "这是上述工具调用的结果。{{toolResult}}\n请根据以下执行结果,清晰解释执行结果并执行下一步操作。```" + "执行下一步工具的要求:1. 解析工具输出结果2. 验证数据完整性(字段数量/空间参考/几何有效性)3. 调用下一个工具时确保参数继承前序输出。请据此继续执行"; public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误,需按以下流程处理:1. 错误解析:分析错误类型及具体原因(见下方错误信息),根据错误信息选择修复方案,```" + @@ -26,4 +31,10 @@ public class SystemPrompt return sysPrompt; } + public static string ContinuePrompt(string toolResult) + { + string continuePrompt = ContinuePromptTemplate; + continuePrompt = continuePrompt.Replace("{{toolResult}}", toolResult); + return continuePrompt; + } } \ No newline at end of file From 4bbc743ef28178f814bd45132b74331d84234319 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Mon, 26 May 2025 00:07:06 +0800 Subject: [PATCH 16/60] =?UTF-8?q?=E4=BF=AE=E5=A4=8DArcGIS=20Pro=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E8=B0=83=E7=94=A8=E4=BC=A0=E5=8F=82=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/tool/ArcGisPro.cs | 2 + host/Gateway.cs | 85 +++++++++++++++++++++++++++------------- server/CallArcGISPro.cs | 5 ++- 3 files changed, 63 insertions(+), 29 deletions(-) diff --git a/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs index da2e604..163f253 100644 --- a/client/tool/ArcGisPro.cs +++ b/client/tool/ArcGisPro.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Threading.Tasks; using ArcGIS.Core.Data; using ArcGIS.Core.Data.Raster; @@ -9,6 +10,7 @@ using ArcGIS.Desktop.Framework.Threading.Tasks; using LinkToolAddin.server; using ModelContextProtocol.Server; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace LinkToolAddin.client.tool; diff --git a/host/Gateway.cs b/host/Gateway.cs index fca37cc..28935f0 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -8,6 +8,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using System.Windows.Documents; using System.Xml; using System.Xml.Linq; using ArcGIS.Desktop.Framework.Dialogs; @@ -37,6 +38,13 @@ namespace LinkToolAddin.host; public class Gateway { private static ILog log = LogManager.GetLogger(typeof(Gateway)); + private static bool goOn = true; + + public static void StopConversation() + { + goOn = false; + } + public static async void SendMessage(string message, string model, string gdbPath, Action callback) { Llm bailian = new Bailian @@ -240,12 +248,19 @@ public class Gateway Role = "user", Content = message }); - bool goOn = true; + goOn = true; string toolPattern = "^[\\s\\S]*?<\\/tool_use>$"; string promptPattern = "^[\\s\\S]*?<\\/prompt>$"; McpServerList mcpServerList = new McpServerList(); + int loop = 0; while (goOn) { + loop++; + if (loop > 20) + { + MessageBox.Show("达到最大循环次数", "退出循环"); + break; + } LlmJsonContent jsonContent = new LlmJsonContent() { Model = model, @@ -266,6 +281,11 @@ public class Gateway if (Regex.IsMatch(chunk, toolPattern)) { //返回工具卡片 + messages.Add(new Message + { + Role = "assistant", + Content = chunk + }); XElement toolUse = XElement.Parse(chunk); string fullToolName = toolUse.Element("name")?.Value; string toolArgs = toolUse.Element("arguments")?.Value; @@ -290,13 +310,14 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(toolResponse) + // Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate + Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); + // messages.Add(new Message + // { + // Role = "user", + // Content = JsonConvert.SerializeObject(toolResponse) + // }); callback?.Invoke(toolMessageItem); }else if (mcpServer is StdioMcpServer) { @@ -315,19 +336,35 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(toolResponse) + // Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate + Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); + // messages.Add(new Message + // { + // Role = "user", + // Content = JsonConvert.SerializeObject(toolResponse) + // }); callback?.Invoke(toolMessageItem); }else if (mcpServer is InnerMcpServer) { Type type = Type.GetType("LinkToolAddin.client.tool."+serverName); MethodInfo method = type.GetMethod(toolName,BindingFlags.Public | BindingFlags.Static); - var task = method.Invoke(null, toolParams.Values.ToArray()) as Task; + var methodParams = toolParams.Values.ToArray(); + object[] args = new object[methodParams.Length]; + for (int i = 0; i < methodParams.Length; i++) + { + if (methodParams[i].GetType() == typeof(JArray)) + { + List list = new List(); + list = (methodParams[i] as JArray).Select(token => token.ToString()).ToList(); + args[i] = list; + } + else + { + args[i] = methodParams[i]; + } + } + var task = method.Invoke(null, args) as Task; JsonRpcResultEntity innerResult = await task; if (innerResult is JsonRpcErrorEntity) { @@ -365,13 +402,14 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = SystemPrompt.ContinuePromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(innerResult) + // Content = SystemPrompt.ContinuePromptTemplate + Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(innerResult)) }); + // messages.Add(new Message + // { + // Role = "user", + // Content = JsonConvert.SerializeObject(innerResult) + // }); callback?.Invoke(toolMessageItem); } } @@ -451,16 +489,9 @@ public class Gateway private static async Task GetToolInfos(McpServerList mcpServerList) { - int loop = 0; StringBuilder toolInfos = new StringBuilder(); foreach (McpServer mcpServer in mcpServerList.GetAllServers()) { - loop++; - if (loop > 3) - { - MessageBox.Show("达到最大循环次数", "退出循环"); - break; - } if (mcpServer is InnerMcpServer) { InnerMcpServer innerMcpServer = (InnerMcpServer)mcpServer; diff --git a/server/CallArcGISPro.cs b/server/CallArcGISPro.cs index 049fd3f..4d5c611 100644 --- a/server/CallArcGISPro.cs +++ b/server/CallArcGISPro.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using ArcGIS.Desktop.Core.Geoprocessing; using ArcGIS.Desktop.Framework.Dialogs; using ArcGIS.Desktop.Framework.Threading.Tasks; +using Newtonsoft.Json; namespace LinkToolAddin.server; @@ -22,7 +23,7 @@ public class CallArcGISPro Error = new Error() { Code = results.ErrorCode, - Message = results.ErrorMessages.ToString() + Message = JsonConvert.SerializeObject(results.ErrorMessages) } }; } @@ -30,7 +31,7 @@ public class CallArcGISPro { jsonRpcResultEntity = new JsonRpcSuccessEntity { - Result = results.Messages.ToString() + Result = JsonConvert.SerializeObject(results.Messages) }; } return jsonRpcResultEntity; From 271f42dd71f0d27493cad709ae98057470349f48 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Mon, 26 May 2025 00:07:22 +0800 Subject: [PATCH 17/60] =?UTF-8?q?=E6=8E=A5=E5=85=A5=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E5=BA=93=E6=9F=A5=E8=AF=A2MCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/tool/KnowledgeBase.cs | 24 ++++++++++++++++++++++++ host/McpServerList.cs | 7 +++++++ resource/DocDb.cs | 5 +++-- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/client/tool/KnowledgeBase.cs b/client/tool/KnowledgeBase.cs index 9d2b162..38fe949 100644 --- a/client/tool/KnowledgeBase.cs +++ b/client/tool/KnowledgeBase.cs @@ -22,4 +22,28 @@ public class KnowledgeBase }; return result; } + + [McpServerTool, Description("查询ArcGIS Pro调用工具的标准调用名和参数要求知识库")] + public static async Task QueryArcgisToolDoc(string query) + { + DocDb docDb = new DocDb("sk-db177155677e438f832860e7f4da6afc", DocDb.KnowledgeBase.ArcGISProToolDoc); + KnowledgeResult knowledgeResult = await docDb.Retrieve(query); + JsonRpcResultEntity result = new JsonRpcSuccessEntity() + { + Result = JsonConvert.SerializeObject(knowledgeResult.ChunkList), + }; + return result; + } + + [McpServerTool, Description("查询使用ArcGIS Pro进行任务规划和解决实际问题的案例知识库")] + public static async Task QueryArcgisExampleDoc(string query) + { + DocDb docDb = new DocDb("sk-db177155677e438f832860e7f4da6afc", DocDb.KnowledgeBase.ArcGISProApplicantExample); + KnowledgeResult knowledgeResult = await docDb.Retrieve(query); + JsonRpcResultEntity result = new JsonRpcSuccessEntity() + { + Result = JsonConvert.SerializeObject(knowledgeResult.ChunkList), + }; + return result; + } } \ No newline at end of file diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 2c03a41..68f83b4 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -28,6 +28,13 @@ public class McpServerList Description = "可以调用arcgis的地理处理工具或执行python代码等", IsActive = true }); + servers.Add("KnowledgeBase", new InnerMcpServer + { + Name = "KnowledgeBase", + Type = "inner", + Description = "可以调用进行查询知识库,获取相关参考信息。", + IsActive = true + }); } public McpServer GetServer(string name) diff --git a/resource/DocDb.cs b/resource/DocDb.cs index 1487b5e..f4937fe 100644 --- a/resource/DocDb.cs +++ b/resource/DocDb.cs @@ -21,13 +21,14 @@ public class DocDb { ArcGISProHelpDoc, ArcGISProToolDoc, - TaskPlanningDoc, ArcGISProApplicantExample } public Dictionary knowledgeBase = new Dictionary { - {KnowledgeBase.ArcGISProHelpDoc,"6a77c5a68de64f469b79fcdcde9d5001"} + {KnowledgeBase.ArcGISProHelpDoc,"6a77c5a68de64f469b79fcdcde9d5001"}, + {KnowledgeBase.ArcGISProToolDoc,"080f8925318247ea822a9e12db5cb5cd"}, + {KnowledgeBase.ArcGISProApplicantExample,"eef60f7c879b4e8597138c261578d2a5"} }; public async Task Retrieve(string query) From 43ea7dd06afa01ed61796d3f3c736e705099cad5 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Mon, 26 May 2025 00:07:37 +0800 Subject: [PATCH 18/60] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=81=9C=E6=AD=A2?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=8C=89=E9=92=AE=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E6=A1=86=E9=AB=98=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/dockpane/TestDockpane.xaml | 10 ++++--- ui/dockpane/TestDockpane.xaml.cs | 45 +++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/ui/dockpane/TestDockpane.xaml b/ui/dockpane/TestDockpane.xaml index fd7d8c8..53c1501 100644 --- a/ui/dockpane/TestDockpane.xaml +++ b/ui/dockpane/TestDockpane.xaml @@ -20,7 +20,9 @@ - + + + @@ -31,9 +33,11 @@ - + - + + + \ No newline at end of file diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index abf8b4a..da572db 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -1,12 +1,18 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Xml.Linq; using LinkToolAddin.client; using LinkToolAddin.host; using LinkToolAddin.host.llm; using LinkToolAddin.host.llm.entity; +using LinkToolAddin.host.mcp; +using LinkToolAddin.host.prompt; using LinkToolAddin.message; using LinkToolAddin.resource; using LinkToolAddin.server; @@ -231,5 +237,42 @@ namespace LinkToolAddin.ui.dockpane { Request_Bailian_Stream_Test(); } + + private void StopConversation_OnClick(object sender, RoutedEventArgs e) + { + Gateway.StopConversation(); + } + + private async void TestArcGisTool_OnClick(object sender, RoutedEventArgs e) + { + string xmlStr = + "\nArcGisPro:ArcGisProTool\n{\"toolName\": \"Buffer\", \"toolParams\": [\"D:\\\\01_Project\\\\20250305_LinkTool\\\\20250420_AiDemoProject\\\\20250420_AiDemoProject.gdb\\\\LandUse_2005_Copy\", \"D:\\\\01_Project\\\\20250305_LinkTool\\\\20250420_AiDemoProject\\\\20250420_AiDemoProject.gdb\\\\LandUse_2005_Buffer30m\", \"30 Meters\", \"NONE\", \"ROUND\", \"ALL\"]}\n"; + XElement toolUse = XElement.Parse(xmlStr); + string fullToolName = toolUse.Element("name")?.Value; + string toolArgs = toolUse.Element("arguments")?.Value; + Dictionary toolParams = JsonConvert.DeserializeObject>(toolArgs); + string serverName = fullToolName.Contains(":") ? fullToolName.Split(':')[0] : fullToolName; + string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName; + McpServerList mcpServerList = new McpServerList(); + McpServer mcpServer = mcpServerList.GetServer(serverName); + if (mcpServer is InnerMcpServer) + { + Type type = Type.GetType("LinkToolAddin.client.tool." + serverName); + var toolParamsValues = toolParams.Values.ToArray(); + MethodInfo method = type.GetMethod(toolName, BindingFlags.Public | BindingFlags.Static); + var task = method.Invoke(null, toolParams.Values.ToArray()) as Task; + JsonRpcResultEntity innerResult = await task; + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = toolName, + toolParams = toolParams, + type = MessageType.TOOL_MESSAGE, + status = "fail", + content = JsonConvert.SerializeObject(innerResult), + id = "1test" + }; + AddReply(toolMessageItem); + } + } } } From a4aec6206502fc83834c7ad8b130b19302143df0 Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Mon, 26 May 2025 10:08:37 +0800 Subject: [PATCH 19/60] =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/prompt/PromptTemplates.cs | 14 +++++++++++--- host/prompt/SystemPrompt.cs | 18 +++++++++--------- ui/dockpane/TestDockpane.xaml.cs | 2 +- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/client/prompt/PromptTemplates.cs b/client/prompt/PromptTemplates.cs index 9ef4ed3..7e0e18f 100644 --- a/client/prompt/PromptTemplates.cs +++ b/client/prompt/PromptTemplates.cs @@ -8,9 +8,17 @@ public class PromptTemplates public PromptTemplates() { - prompts.Add("plan", "请根据用户所提问题进行工具规划"); - prompts.Add("param", "根据帮助文档填写工具参数"); - prompts.Add("code", "现在需要生成代码,要求语法正确"); + 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) diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 5b331c9..406b23d 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -2,21 +2,21 @@ public class SystemPrompt { - public static string SysPromptTemplate = "现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。您可以使用一组工具来回答用户的问题。```" + - "每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。```" + - "你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。工具调用格式:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化。" + + public static string SysPromptTemplate = "现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。您可以使用一组工具来回答用户的问题。还可以通过调用用户提示词,从而使你更好地理解和完成用户的任务。```" + + "如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。已经成功调用的工具不要反复调用。不要有markdown格式的文本。```" + + "你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。工具调用格式:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化。一定要注意:只输出调用格式的内容,前后不能有描述性文字。" + "工具名称包含在一对标签内,每个参数也需用对应的标签包裹。结构如下:\n {tool_name}\n {json_arguments}\n。```" + - "工具名称:需与所使用工具的精确名称一致。参数:应为包含工具所需参数的 JSON 对象。例如:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n```" + + "工具名称:需与所使用工具的精确名称一致。参数:应为包含工具所需参数的 JSON 对象。例如:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n```" + "用户将以以下格式返回工具调用结果:\n {tool_name}\n {result}\n```" + - "结果:应为字符串类型,可以表示文件或其他输出类型。例如,若工具返回.shp 文件,可在下一步操作中这样使用:\n ArcGIS_Pro:GP\n {\"output_file\": \"source.shp\"}\n```" + - "请始终遵循此格式以确保工具调用被正确解析和执行。整个工具流程调用结束后,请在末尾输出'done'表示工具调用结束" + - "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n"; + "结果:应为字符串类型,可以表示文件或其他输出类型。若工具返回.shp 文件,可在下一步操作中这样使用:\n ArcGIS_Pro:GP\n {\"output_file\": \"source.shp\"}\n```" + + "请始终遵循此格式且前后不要有其他描述性文字以确保工具调用被正确解析和执行。当你认为已解决用户最初提出的问题时,请输出单独的一条消息,内容为'[DONE]',不附带任何其它文字说明,程序识别到后会退出循环。" + + "工具调用示例:\n gaode:maps_geo\n {\"address\":\"广州市政府, 广州市\", \"city\":\"广州\"}\n"; public static string ContinuePromptTemplate = "请根据以下执行结果,清晰解释执行结果并执行下一步操作。```" + - "执行下一步工具的要求:1. 解析工具输出结果2. 验证数据完整性(字段数量/空间参考/几何有效性)3. 调用下一个工具时确保参数继承前序输出。请据此继续执行"; + "执行下一步工具的要求:1. 解析工具输出结果2. 验证数据完整性(字段数量/空间参考/几何有效性)3. 调用下一个工具时确保参数继承前序输出。请据此继续执行。4.已经成功调用的工具不要反复调用。5.如果不再需要额外说明和额外的操作,请回答单独的一条内容为'[DONE]'的消息。"; public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误,需按以下流程处理:1. 错误解析:分析错误类型及具体原因(见下方错误信息),根据错误信息选择修复方案,```" + - "2. 修复方案:(1)根据错误类型生成对应的工具重试策略(2)参数调整:明确需要修改的参数及修改方式。3. 工具重试:使用调整后的参数重新调用工具。错误信息详情:{{toolResult}}。请根据报错信息重试"; + "2. 修复方案:(1)根据错误类型生成对应的工具重试策略(2)参数调整:明确需要修改的参数及修改方式。3. 工具重试:使用调整后的参数重新调用工具。错误信息如下,请根据报错信息重试"; public static string SysPrompt(string gdbPath, string toolInfos) { diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index abf8b4a..34fa96d 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -193,7 +193,7 @@ namespace LinkToolAddin.ui.dockpane { string userPrompt = PromptTestTextBox.Text; // Gateway.SendMessage(userPrompt,"qwen-max","C:/Project/test.gdb",AddReply); - Gateway.SendMessageStream(userPrompt,"qwen-max","D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb",AddReplyStream); + Gateway.SendMessageStream(userPrompt,"qwen-max", "F:\\secondsemester\\linktool\\test\\linktooltest\\linktooltest.gdb", AddReplyStream); } public void AddReplyStream(MessageListItem msg) From ea021135a3752f63d3816cf5dc13bab64d2dd288 Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Thu, 29 May 2025 21:01:07 +0800 Subject: [PATCH 20/60] =?UTF-8?q?=E5=90=88=E5=B9=B6host=E5=88=86=E6=94=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/tool/ArcGisPro.cs | 2 + client/tool/KnowledgeBase.cs | 24 +++++++++ host/Gateway.cs | 85 ++++++++++++++++++++++---------- host/McpServerList.cs | 7 +++ host/prompt/SystemPrompt.cs | 21 ++++---- resource/DocDb.cs | 5 +- server/CallArcGISPro.cs | 5 +- ui/dockpane/TestDockpane.xaml | 10 ++-- ui/dockpane/TestDockpane.xaml.cs | 45 ++++++++++++++++- 9 files changed, 159 insertions(+), 45 deletions(-) diff --git a/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs index da2e604..163f253 100644 --- a/client/tool/ArcGisPro.cs +++ b/client/tool/ArcGisPro.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Threading.Tasks; using ArcGIS.Core.Data; using ArcGIS.Core.Data.Raster; @@ -9,6 +10,7 @@ using ArcGIS.Desktop.Framework.Threading.Tasks; using LinkToolAddin.server; using ModelContextProtocol.Server; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace LinkToolAddin.client.tool; diff --git a/client/tool/KnowledgeBase.cs b/client/tool/KnowledgeBase.cs index 9d2b162..38fe949 100644 --- a/client/tool/KnowledgeBase.cs +++ b/client/tool/KnowledgeBase.cs @@ -22,4 +22,28 @@ public class KnowledgeBase }; return result; } + + [McpServerTool, Description("查询ArcGIS Pro调用工具的标准调用名和参数要求知识库")] + public static async Task QueryArcgisToolDoc(string query) + { + DocDb docDb = new DocDb("sk-db177155677e438f832860e7f4da6afc", DocDb.KnowledgeBase.ArcGISProToolDoc); + KnowledgeResult knowledgeResult = await docDb.Retrieve(query); + JsonRpcResultEntity result = new JsonRpcSuccessEntity() + { + Result = JsonConvert.SerializeObject(knowledgeResult.ChunkList), + }; + return result; + } + + [McpServerTool, Description("查询使用ArcGIS Pro进行任务规划和解决实际问题的案例知识库")] + public static async Task QueryArcgisExampleDoc(string query) + { + DocDb docDb = new DocDb("sk-db177155677e438f832860e7f4da6afc", DocDb.KnowledgeBase.ArcGISProApplicantExample); + KnowledgeResult knowledgeResult = await docDb.Retrieve(query); + JsonRpcResultEntity result = new JsonRpcSuccessEntity() + { + Result = JsonConvert.SerializeObject(knowledgeResult.ChunkList), + }; + return result; + } } \ No newline at end of file diff --git a/host/Gateway.cs b/host/Gateway.cs index fca37cc..28935f0 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -8,6 +8,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using System.Windows.Documents; using System.Xml; using System.Xml.Linq; using ArcGIS.Desktop.Framework.Dialogs; @@ -37,6 +38,13 @@ namespace LinkToolAddin.host; public class Gateway { private static ILog log = LogManager.GetLogger(typeof(Gateway)); + private static bool goOn = true; + + public static void StopConversation() + { + goOn = false; + } + public static async void SendMessage(string message, string model, string gdbPath, Action callback) { Llm bailian = new Bailian @@ -240,12 +248,19 @@ public class Gateway Role = "user", Content = message }); - bool goOn = true; + goOn = true; string toolPattern = "^[\\s\\S]*?<\\/tool_use>$"; string promptPattern = "^[\\s\\S]*?<\\/prompt>$"; McpServerList mcpServerList = new McpServerList(); + int loop = 0; while (goOn) { + loop++; + if (loop > 20) + { + MessageBox.Show("达到最大循环次数", "退出循环"); + break; + } LlmJsonContent jsonContent = new LlmJsonContent() { Model = model, @@ -266,6 +281,11 @@ public class Gateway if (Regex.IsMatch(chunk, toolPattern)) { //返回工具卡片 + messages.Add(new Message + { + Role = "assistant", + Content = chunk + }); XElement toolUse = XElement.Parse(chunk); string fullToolName = toolUse.Element("name")?.Value; string toolArgs = toolUse.Element("arguments")?.Value; @@ -290,13 +310,14 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(toolResponse) + // Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate + Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); + // messages.Add(new Message + // { + // Role = "user", + // Content = JsonConvert.SerializeObject(toolResponse) + // }); callback?.Invoke(toolMessageItem); }else if (mcpServer is StdioMcpServer) { @@ -315,19 +336,35 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(toolResponse) + // Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate + Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); + // messages.Add(new Message + // { + // Role = "user", + // Content = JsonConvert.SerializeObject(toolResponse) + // }); callback?.Invoke(toolMessageItem); }else if (mcpServer is InnerMcpServer) { Type type = Type.GetType("LinkToolAddin.client.tool."+serverName); MethodInfo method = type.GetMethod(toolName,BindingFlags.Public | BindingFlags.Static); - var task = method.Invoke(null, toolParams.Values.ToArray()) as Task; + var methodParams = toolParams.Values.ToArray(); + object[] args = new object[methodParams.Length]; + for (int i = 0; i < methodParams.Length; i++) + { + if (methodParams[i].GetType() == typeof(JArray)) + { + List list = new List(); + list = (methodParams[i] as JArray).Select(token => token.ToString()).ToList(); + args[i] = list; + } + else + { + args[i] = methodParams[i]; + } + } + var task = method.Invoke(null, args) as Task; JsonRpcResultEntity innerResult = await task; if (innerResult is JsonRpcErrorEntity) { @@ -365,13 +402,14 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = SystemPrompt.ContinuePromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(innerResult) + // Content = SystemPrompt.ContinuePromptTemplate + Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(innerResult)) }); + // messages.Add(new Message + // { + // Role = "user", + // Content = JsonConvert.SerializeObject(innerResult) + // }); callback?.Invoke(toolMessageItem); } } @@ -451,16 +489,9 @@ public class Gateway private static async Task GetToolInfos(McpServerList mcpServerList) { - int loop = 0; StringBuilder toolInfos = new StringBuilder(); foreach (McpServer mcpServer in mcpServerList.GetAllServers()) { - loop++; - if (loop > 3) - { - MessageBox.Show("达到最大循环次数", "退出循环"); - break; - } if (mcpServer is InnerMcpServer) { InnerMcpServer innerMcpServer = (InnerMcpServer)mcpServer; diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 2c03a41..68f83b4 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -28,6 +28,13 @@ public class McpServerList Description = "可以调用arcgis的地理处理工具或执行python代码等", IsActive = true }); + servers.Add("KnowledgeBase", new InnerMcpServer + { + Name = "KnowledgeBase", + Type = "inner", + Description = "可以调用进行查询知识库,获取相关参考信息。", + IsActive = true + }); } public McpServer GetServer(string name) diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 912fd6f..69f180a 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -7,20 +7,21 @@ public class SystemPrompt "指令:您可以使用一组工具来回答用户的问题。还可以通过调用用户提示词,从而使你更好地理解和完成用户的任务。整个流程结束之后单独输出一条内容为'[DONE]'的消息,前后不要有任何说明文字。" + "调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过的方式来调用用户提示词,你能更好地理解和解决用户的问题。" + "工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。" + - "输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化。如果需要进行文字说明,请确保输出内容不采用Markdown格式或其他特殊文本标记;文字说明应以普通段落形式呈现,并且不含工具调用或prompt的XML标签。" + - + "输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化以单独信息格式输出,前后没有文字信息。" + + "如果需要进行文字说明,请只有文字内容,以普通段落形式呈现没有其他格式。" + "工具调用格式:工具名称包含在一对标签内,每个参数也需用对应的标签包裹。结构如下:\n {tool_name}\n {json_arguments}\n。" + "工具名称:需与所使用工具的精确名称一致。" + "参数:应为包含工具所需参数的 JSON 对象。例如:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n" + - "注意事项:1.无论是调用工具还是调用提示词,必须是单独的一条消息,只输出调用格式的内容,前后不能有描述性文字。内容为相应格式的XML文本,调用消息中不能加任何其它文字说明," + - "只有单独一条的调用请求文本程序才能识别并调用,从而将结果反馈给你。2.如果需要进行文字说明,请先进行文字说明,其中不含工具调用和prompt的XML,也不要用markdown格式" + - "3.用户时间宝贵,成功调用过的工具不应重复调用,应该尽快执行下一个工具。" + - "4.整个工具流程调用结束后,一定要单独输出一条内容为'[DONE]'的消息表示工具调用结束。不要在文本的末尾。" + + "你必须严格遵守以下输出规则:" + + "1.XML工具调用格式必须在下一条单独输出,如果前面有文字将永远无法调用工具。" + + //"只有单独一条的调用请求文本程序才能识别并调用,从而将结果反馈给你。" + + "2.文字说明如果紧跟着工具调用的XML将暴露程序,XML会在下一条单独输出,因此文字描述后面一定不能输出工具调用的XML格式。" + + "3.用户时间宝贵,不得重复调用上一次已成功执行的工具调用,除非有新的参数或上下文变化。" + + "4.如果目前工作已经完成无法知道用户其他需求时,一定要单独输出一条内容为'[DONE]'的消息表示工具调用结束。不要在文本的末尾。" + + "5.不得在同一消息中混合说明文字与工具调用。" + "结果:用户将以以下格式返回工具调用结果:\n {tool_name}\n {result}\n。应为字符串类型,可以表示文件或其他输出类型。" + - "例如,若工具返回.shp 文件,可在下一步操作中这样使用:\n ArcGIS_Pro:GP\n {\"output_file\": \"source.shp\"}\n" + - "请始终遵循此格式以确保工具调用被正确解析和执行。" + - "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\n search\n {\\\"query\\\": \\\"上海 人口\\\"}\n\n"; - + "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n"; + //"现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。\r\n\r\n指令:\r\n你需要使用一组工具来回答用户的问题。也可以通过 标签调用用户提示词,以帮助你更好地理解任务。所有操作完成后,在最后一条输出中包含 '[DONE]',但不要单独发送这条消息。\r\n\r\n调用工具要求:\r\n1. 每次只能调用一个工具,并等待用户的反馈结果。\r\n2. 所有工具调用都必须基于前一步的结果进行推理和决策。\r\n3. 工具调用必须以 XML 格式输出,并且必须作为**独立的一条消息输出**,前后不得有任何解释性文字或说明内容。\r\n4. 如果需要提供解释、说明或引导信息,请先单独输出一段普通文本,**其中不能包含任何 XML 标签或格式**。\r\n5. 不得重复调用已经成功执行过的工具,除非有新的参数或上下文需要重新调用。\r\n\r\n工具调用背景:\r\n你有以下工具可以调用:{{toolInfos}} \r\n用户的数据库路径是:{{gdbPath}}\r\n\r\n输出风格:\r\n- 所有工具调用都必须使用标准 XML 格式输出,并且每条消息只包含一次调用。\r\n- 文字说明必须单独输出,且为普通段落格式,不含任何 XML 或 Markdown 标记。\r\n- 输出顺序应为:【可选的文字说明】→【必选的工具调用】,或者仅输出工具调用。\r\n\r\n工具调用格式示例:\r\n\r\n {tool_name}\r\n {json_arguments}\r\n\r\n\r\n例如:\r\n\r\n gaode:maps_geo\r\n {\"address\":\"广州市政府, 广州市\", \"city\":\"广州\"}\r\n\r\n\r\n注意事项:\r\n1. 工具名称必须与实际工具完全一致。\r\n2. 参数必须为合法 JSON 格式,且符合工具要求。\r\n3. 用户时间宝贵,请高效调用工具,尽快完成任务。\r\n4. 最终完成时应在最后一次响应中包含 [DONE],而不是单独输出。\r\n\r\n工具调用结果返回格式:\r\n用户将以如下格式返回工具调用结果:\r\n\r\n {tool_name}\r\n {result}\r\n\r\n\r\n例如:\r\n\r\n ArcGIS_Pro:GP\r\n {\"output_file\": \"source.shp\"}\r\n\r\n\r\n请始终遵循此格式以确保工具调用被正确解析和执行。"; public static string ContinuePromptTemplate = "这是上述工具调用的结果。{{toolResult}}\n请根据以下执行结果,清晰解释执行结果并执行下一步操作。" + "执行下一步工具的要求:1. 解析工具输出结果2. 调用下一个工具时确保参数继承前序输出。请据此继续执行"; diff --git a/resource/DocDb.cs b/resource/DocDb.cs index 1487b5e..f4937fe 100644 --- a/resource/DocDb.cs +++ b/resource/DocDb.cs @@ -21,13 +21,14 @@ public class DocDb { ArcGISProHelpDoc, ArcGISProToolDoc, - TaskPlanningDoc, ArcGISProApplicantExample } public Dictionary knowledgeBase = new Dictionary { - {KnowledgeBase.ArcGISProHelpDoc,"6a77c5a68de64f469b79fcdcde9d5001"} + {KnowledgeBase.ArcGISProHelpDoc,"6a77c5a68de64f469b79fcdcde9d5001"}, + {KnowledgeBase.ArcGISProToolDoc,"080f8925318247ea822a9e12db5cb5cd"}, + {KnowledgeBase.ArcGISProApplicantExample,"eef60f7c879b4e8597138c261578d2a5"} }; public async Task Retrieve(string query) diff --git a/server/CallArcGISPro.cs b/server/CallArcGISPro.cs index 049fd3f..4d5c611 100644 --- a/server/CallArcGISPro.cs +++ b/server/CallArcGISPro.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using ArcGIS.Desktop.Core.Geoprocessing; using ArcGIS.Desktop.Framework.Dialogs; using ArcGIS.Desktop.Framework.Threading.Tasks; +using Newtonsoft.Json; namespace LinkToolAddin.server; @@ -22,7 +23,7 @@ public class CallArcGISPro Error = new Error() { Code = results.ErrorCode, - Message = results.ErrorMessages.ToString() + Message = JsonConvert.SerializeObject(results.ErrorMessages) } }; } @@ -30,7 +31,7 @@ public class CallArcGISPro { jsonRpcResultEntity = new JsonRpcSuccessEntity { - Result = results.Messages.ToString() + Result = JsonConvert.SerializeObject(results.Messages) }; } return jsonRpcResultEntity; diff --git a/ui/dockpane/TestDockpane.xaml b/ui/dockpane/TestDockpane.xaml index fd7d8c8..53c1501 100644 --- a/ui/dockpane/TestDockpane.xaml +++ b/ui/dockpane/TestDockpane.xaml @@ -20,7 +20,9 @@ - + + + @@ -31,9 +33,11 @@ - + - + + + \ No newline at end of file diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 34fa96d..0286d87 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -1,12 +1,18 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Xml.Linq; using LinkToolAddin.client; using LinkToolAddin.host; using LinkToolAddin.host.llm; using LinkToolAddin.host.llm.entity; +using LinkToolAddin.host.mcp; +using LinkToolAddin.host.prompt; using LinkToolAddin.message; using LinkToolAddin.resource; using LinkToolAddin.server; @@ -231,5 +237,42 @@ namespace LinkToolAddin.ui.dockpane { Request_Bailian_Stream_Test(); } + + private void StopConversation_OnClick(object sender, RoutedEventArgs e) + { + Gateway.StopConversation(); + } + + private async void TestArcGisTool_OnClick(object sender, RoutedEventArgs e) + { + string xmlStr = + "\nArcGisPro:ArcGisProTool\n{\"toolName\": \"Buffer\", \"toolParams\": [\"D:\\\\01_Project\\\\20250305_LinkTool\\\\20250420_AiDemoProject\\\\20250420_AiDemoProject.gdb\\\\LandUse_2005_Copy\", \"D:\\\\01_Project\\\\20250305_LinkTool\\\\20250420_AiDemoProject\\\\20250420_AiDemoProject.gdb\\\\LandUse_2005_Buffer30m\", \"30 Meters\", \"NONE\", \"ROUND\", \"ALL\"]}\n"; + XElement toolUse = XElement.Parse(xmlStr); + string fullToolName = toolUse.Element("name")?.Value; + string toolArgs = toolUse.Element("arguments")?.Value; + Dictionary toolParams = JsonConvert.DeserializeObject>(toolArgs); + string serverName = fullToolName.Contains(":") ? fullToolName.Split(':')[0] : fullToolName; + string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName; + McpServerList mcpServerList = new McpServerList(); + McpServer mcpServer = mcpServerList.GetServer(serverName); + if (mcpServer is InnerMcpServer) + { + Type type = Type.GetType("LinkToolAddin.client.tool." + serverName); + var toolParamsValues = toolParams.Values.ToArray(); + MethodInfo method = type.GetMethod(toolName, BindingFlags.Public | BindingFlags.Static); + var task = method.Invoke(null, toolParams.Values.ToArray()) as Task; + JsonRpcResultEntity innerResult = await task; + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = toolName, + toolParams = toolParams, + type = MessageType.TOOL_MESSAGE, + status = "fail", + content = JsonConvert.SerializeObject(innerResult), + id = "1test" + }; + AddReply(toolMessageItem); + } + } } } From 9fe13fa7192fab814c249d0e22b5865925106f68 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 31 May 2025 11:52:41 +0800 Subject: [PATCH 21/60] =?UTF-8?q?1.=20=E4=BF=AE=E5=A4=8DArcGIS=20Pro?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E8=8C=83=E5=9B=B4=E7=BB=93=E6=9E=9C=E4=B8=BA?= =?UTF-8?q?=E7=A9=BA=E7=9A=84=E9=97=AE=E9=A2=98=202.=20=E8=AF=86=E5=88=AB?= =?UTF-8?q?=E5=8D=95=E6=9D=A1=E6=B6=88=E6=81=AF=E6=9C=AB=E5=B0=BE=E7=9A=84?= =?UTF-8?q?[DONE]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/tool/ArcGisPro.cs | 41 +++++++++++++++++++++++++++----- host/Gateway.cs | 11 ++++++--- host/prompt/SystemPrompt.cs | 6 +++-- ui/dockpane/TestDockpane.xaml | 2 +- ui/dockpane/TestDockpane.xaml.cs | 2 +- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs index 163f253..f1b9866 100644 --- a/client/tool/ArcGisPro.cs +++ b/client/tool/ArcGisPro.cs @@ -6,8 +6,11 @@ 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; @@ -16,14 +19,40 @@ namespace LinkToolAddin.client.tool; public class ArcGisPro { - [McpServerTool, Description("可以通过调用ArcGIS Pro的地理处理工具实现一些数据处理功能。")] + private static ILog log = LogManager.GetLogger(typeof(ArcGisPro)); + [McpServerTool, Description("可以通过调用ArcGIS Pro的地理处理工具实现一些数据处理功能,传入参数必须严格遵循ArcGIS Pro调用工具的标准调用名和参数要求知识库。")] public static async Task ArcGisProTool(string toolName, List toolParams) { - // Call the ArcGIS Pro method and get the result - var result = await server.CallArcGISPro.CallArcGISProTool(toolName, toolParams); - - // Serialize the result back to a JSON string - return result; + IGPResult results = await Geoprocessing.ExecuteToolAsync(toolName, toolParams); + JsonRpcResultEntity jsonRpcResultEntity; + if (results.IsFailed) + { + log.Error(results.ErrorMessages); + jsonRpcResultEntity = new JsonRpcErrorEntity() + { + Error = new Error() + { + Code = results.ErrorCode, + Message = JsonConvert.SerializeObject(results.ErrorMessages) + } + }; + }else if(results.HasWarnings) + { + log.Warn(results.Messages); + jsonRpcResultEntity = new JsonRpcSuccessEntity + { + Result = JsonConvert.SerializeObject(results.Messages) + }; + } + else + { + log.Info("success gp tool"); + jsonRpcResultEntity = new JsonRpcSuccessEntity + { + Result = JsonConvert.SerializeObject(results.Messages) + }; + } + return jsonRpcResultEntity; } [McpServerTool, Description("查看指定数据的坐标系、范围、几何类型、是否有Z坐标和M坐标,获取字段列表等")] diff --git a/host/Gateway.cs b/host/Gateway.cs index 28935f0..d20b0a8 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -222,7 +222,7 @@ public class Gateway }; callback?.Invoke(chatMessageListItem); } - if (reponse == "[DONE]") + if (reponse.EndsWith("[DONE]")) { goOn = false; } @@ -467,15 +467,20 @@ public class Gateway } else { + string content = chunk; + if (content.EndsWith("[DONE]")) + { + content = content.Substring(0, content.Length - 6); + } //普通流式消息卡片 MessageListItem chatMessageListItem = new ChatMessageItem() { - content = chunk, + content = content, role = "assistant", type = MessageType.CHAT_MESSAGE, id = timestamp.ToString() }; - messageContent = chunk; + messageContent = content; callback?.Invoke(chatMessageListItem); } } diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 69f180a..67ea12c 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -20,14 +20,16 @@ public class SystemPrompt "4.如果目前工作已经完成无法知道用户其他需求时,一定要单独输出一条内容为'[DONE]'的消息表示工具调用结束。不要在文本的末尾。" + "5.不得在同一消息中混合说明文字与工具调用。" + "结果:用户将以以下格式返回工具调用结果:\n {tool_name}\n {result}\n。应为字符串类型,可以表示文件或其他输出类型。" + - "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n"; + "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n"+ + "特别地:需要调用ArcGIS Pro工具前必须先查询帮助文档、标准调用名和参数后再进行调用"; //"现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。\r\n\r\n指令:\r\n你需要使用一组工具来回答用户的问题。也可以通过 标签调用用户提示词,以帮助你更好地理解任务。所有操作完成后,在最后一条输出中包含 '[DONE]',但不要单独发送这条消息。\r\n\r\n调用工具要求:\r\n1. 每次只能调用一个工具,并等待用户的反馈结果。\r\n2. 所有工具调用都必须基于前一步的结果进行推理和决策。\r\n3. 工具调用必须以 XML 格式输出,并且必须作为**独立的一条消息输出**,前后不得有任何解释性文字或说明内容。\r\n4. 如果需要提供解释、说明或引导信息,请先单独输出一段普通文本,**其中不能包含任何 XML 标签或格式**。\r\n5. 不得重复调用已经成功执行过的工具,除非有新的参数或上下文需要重新调用。\r\n\r\n工具调用背景:\r\n你有以下工具可以调用:{{toolInfos}} \r\n用户的数据库路径是:{{gdbPath}}\r\n\r\n输出风格:\r\n- 所有工具调用都必须使用标准 XML 格式输出,并且每条消息只包含一次调用。\r\n- 文字说明必须单独输出,且为普通段落格式,不含任何 XML 或 Markdown 标记。\r\n- 输出顺序应为:【可选的文字说明】→【必选的工具调用】,或者仅输出工具调用。\r\n\r\n工具调用格式示例:\r\n\r\n {tool_name}\r\n {json_arguments}\r\n\r\n\r\n例如:\r\n\r\n gaode:maps_geo\r\n {\"address\":\"广州市政府, 广州市\", \"city\":\"广州\"}\r\n\r\n\r\n注意事项:\r\n1. 工具名称必须与实际工具完全一致。\r\n2. 参数必须为合法 JSON 格式,且符合工具要求。\r\n3. 用户时间宝贵,请高效调用工具,尽快完成任务。\r\n4. 最终完成时应在最后一次响应中包含 [DONE],而不是单独输出。\r\n\r\n工具调用结果返回格式:\r\n用户将以如下格式返回工具调用结果:\r\n\r\n {tool_name}\r\n {result}\r\n\r\n\r\n例如:\r\n\r\n ArcGIS_Pro:GP\r\n {\"output_file\": \"source.shp\"}\r\n\r\n\r\n请始终遵循此格式以确保工具调用被正确解析和执行。"; public static string ContinuePromptTemplate = "这是上述工具调用的结果。{{toolResult}}\n请根据以下执行结果,清晰解释执行结果并执行下一步操作。" + "执行下一步工具的要求:1. 解析工具输出结果2. 调用下一个工具时确保参数继承前序输出。请据此继续执行"; public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误,需按以下流程处理:1. 错误解析:分析错误类型及具体原因(见下方错误信息),根据错误信息选择修复方案,```" + - "2. 修复方案:(1)根据错误类型生成对应的工具重试策略(2)参数调整:明确需要修改的参数及修改方式。3. 工具重试:使用调整后的参数重新调用工具。错误信息如下,请根据报错信息重试,4.如果没有具体的错误描述信息请检查输出文件是否已经存在,若已经存在默认执行成功"; + "2. 修复方案:(1)根据错误类型生成对应的工具重试策略(2)参数调整:明确需要修改的参数及修改方式。3. 工具重试:使用调整后的参数重新调用工具。错误信息如下,请根据报错信息重试,4.如果没有具体的错误描述信息请检查输出文件是否已经存在,若已经存在默认执行成功"+ + "5.查询可能相关的知识库和帮助文档,纠正调用参数中存在的错误"; public static string SysPrompt(string gdbPath, string toolInfos) { diff --git a/ui/dockpane/TestDockpane.xaml b/ui/dockpane/TestDockpane.xaml index 53c1501..d7b5e09 100644 --- a/ui/dockpane/TestDockpane.xaml +++ b/ui/dockpane/TestDockpane.xaml @@ -29,7 +29,7 @@ - + diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 0286d87..1628085 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -199,7 +199,7 @@ namespace LinkToolAddin.ui.dockpane { string userPrompt = PromptTestTextBox.Text; // Gateway.SendMessage(userPrompt,"qwen-max","C:/Project/test.gdb",AddReply); - Gateway.SendMessageStream(userPrompt,"qwen-max", "F:\\secondsemester\\linktool\\test\\linktooltest\\linktooltest.gdb", AddReplyStream); + Gateway.SendMessageStream(userPrompt,"qwen-max", "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb", AddReplyStream); } public void AddReplyStream(MessageListItem msg) From af43d0d774aa522a1220f11466b5fe47064df5c3 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 31 May 2025 16:20:38 +0800 Subject: [PATCH 22/60] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BA=86=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E5=86=85=E5=B5=8CXML=E8=B0=83=E7=94=A8=E7=9A=84?= =?UTF-8?q?=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/Gateway.cs | 348 +++++++++++++++++++++++------------------------- 1 file changed, 167 insertions(+), 181 deletions(-) diff --git a/host/Gateway.cs b/host/Gateway.cs index d20b0a8..3984613 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -229,6 +229,27 @@ public class Gateway } } + 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 @@ -249,8 +270,8 @@ public class Gateway Content = message }); goOn = true; - string toolPattern = "^[\\s\\S]*?<\\/tool_use>$"; - string promptPattern = "^[\\s\\S]*?<\\/prompt>$"; + 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]*?)<\\/prompt>"; McpServerList mcpServerList = new McpServerList(); int loop = 0; while (goOn) @@ -270,169 +291,49 @@ public class Gateway MaxTokens = 1000, }; long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - string messageContent = ""; + string messageContent = ""; //一次请求下完整的response + var (toolMatched, toolRemaining) = ExtractMatchedPart(messageContent, toolPattern); + var (promptMatched, promptRemaining) = ExtractMatchedPart(messageContent, promptPattern); + if (toolMatched == "" && promptMatched == "" && messageContent != "") + { + //如果本次回复不包含任何工具的调用或提示词的调用,则不再请求 + break; + } await foreach(var chunk in bailian.SendChatStreamAsync(jsonContent)) { - if (chunk == "[DONE]") + if (!goOn) { - goOn = false; - }else if (chunk.StartsWith("")) + break; + } + var (matched, remaining) = ExtractMatchedPart(chunk, toolPattern); + if (matched == "") { - if (Regex.IsMatch(chunk, toolPattern)) + var (matchedPrompt, remainingPrompt) = ExtractMatchedPart(chunk, promptPattern); + if (matchedPrompt == "") { - //返回工具卡片 - messages.Add(new Message + //普通消息文本 + MessageListItem chatMessageListItem = new ChatMessageItem() { - Role = "assistant", - Content = chunk - }); - XElement toolUse = XElement.Parse(chunk); - 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.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate - Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) - }); - // messages.Add(new Message - // { - // Role = "user", - // Content = JsonConvert.SerializeObject(toolResponse) - // }); - callback?.Invoke(toolMessageItem); - }else if (mcpServer is StdioMcpServer) - { - 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.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate - Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) - }); - // messages.Add(new Message - // { - // Role = "user", - // Content = JsonConvert.SerializeObject(toolResponse) - // }); - callback?.Invoke(toolMessageItem); - }else if (mcpServer is InnerMcpServer) - { - Type type = Type.GetType("LinkToolAddin.client.tool."+serverName); - MethodInfo method = type.GetMethod(toolName,BindingFlags.Public | BindingFlags.Static); - var methodParams = toolParams.Values.ToArray(); - object[] args = new object[methodParams.Length]; - for (int i = 0; i < methodParams.Length; i++) - { - if (methodParams[i].GetType() == typeof(JArray)) - { - List list = new List(); - list = (methodParams[i] as JArray).Select(token => token.ToString()).ToList(); - args[i] = list; - } - else - { - args[i] = methodParams[i]; - } - } - var task = method.Invoke(null, args) as Task; - JsonRpcResultEntity innerResult = await task; - if (innerResult is JsonRpcErrorEntity) - { - MessageListItem toolMessageItem = new ToolMessageItem - { - toolName = toolName, - toolParams = toolParams, - type = MessageType.TOOL_MESSAGE, - status = "fail", - content = JsonConvert.SerializeObject(innerResult), - id = timestamp.ToString() - }; - messages.Add(new Message - { - Role = "user", - Content = SystemPrompt.ErrorPromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = 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), - id = timestamp.ToString() - }; - messages.Add(new Message - { - Role = "user", - // Content = SystemPrompt.ContinuePromptTemplate - Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(innerResult)) - }); - // messages.Add(new Message - // { - // Role = "user", - // Content = JsonConvert.SerializeObject(innerResult) - // }); - callback?.Invoke(toolMessageItem); - } - } + content = remainingPrompt, + role = "assistant", + type = MessageType.CHAT_MESSAGE, + id = timestamp.ToString() + }; + messageContent = remainingPrompt; + callback?.Invoke(chatMessageListItem); } else { - MessageListItem toolMessageItem = new ToolMessageItem + //包含Prompt调用请求的消息 + MessageListItem chatMessageListItem = new ChatMessageItem() { - toolName = "", - toolParams = new Dictionary(), - type = MessageType.TOOL_MESSAGE, - status = "loading", - content = "正在生成工具调用参数", + content = remainingPrompt, + role = "assistant", + type = MessageType.CHAT_MESSAGE, id = timestamp.ToString() }; - callback?.Invoke(toolMessageItem); - continue; - } - }else if (chunk.StartsWith("")) - { - if (Regex.IsMatch(chunk, promptPattern)) - { - XElement promptUse = XElement.Parse(chunk); + callback?.Invoke(chatMessageListItem); + XElement promptUse = XElement.Parse(matchedPrompt); string promptKey = promptUse.Element("name")?.Value; string promptContent = DynamicPrompt.GetPrompt(promptKey,null); messages.Add(new Message @@ -446,49 +347,134 @@ public class Gateway toolParams = null, type = MessageType.TOOL_MESSAGE, status = "success", - content = promptKey, - id = timestamp.ToString() - }; - callback?.Invoke(toolMessageItem); - } - else - { - MessageListItem toolMessageItem = new ToolMessageItem - { - toolName = "调用提示词", - toolParams = null, - type = MessageType.TOOL_MESSAGE, - status = "loading", - content = "正在调用提示词", - id = timestamp.ToString() + content = "成功调用提示词:"+promptKey, + id = (timestamp+1).ToString() }; callback?.Invoke(toolMessageItem); } } else { - string content = chunk; - if (content.EndsWith("[DONE]")) - { - content = content.Substring(0, content.Length - 6); - } - //普通流式消息卡片 + //包含工具调用请求的消息 MessageListItem chatMessageListItem = new ChatMessageItem() { - content = content, + content = remaining, role = "assistant", type = MessageType.CHAT_MESSAGE, id = timestamp.ToString() }; - messageContent = content; callback?.Invoke(chatMessageListItem); + XElement toolUse = XElement.Parse(matched); + string fullToolName = toolUse.Element("name")?.Value; + string toolArgs = toolUse.Element("arguments")?.Value; + Dictionary toolParams = JsonConvert.DeserializeObject>(toolArgs); + string serverName = fullToolName.Contains(":") ? fullToolName.Split(':')[0] : fullToolName; + string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName; + McpServer mcpServer = mcpServerList.GetServer(serverName); + 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+1).ToString() + }; + messages.Add(new Message + { + Role = "user", + Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : 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+1).ToString() + }; + messages.Add(new Message + { + Role = "user", + Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : 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 methodParams = toolParams.Values.ToArray(); + object[] args = new object[methodParams.Length]; + for (int i = 0; i < methodParams.Length; i++) + { + if (methodParams[i].GetType() == typeof(JArray)) + { + List list = new List(); + list = (methodParams[i] as JArray).Select(token => token.ToString()).ToList(); + args[i] = list; + } + else + { + args[i] = methodParams[i]; + } + } + var task = method.Invoke(null, args) as Task; + JsonRpcResultEntity innerResult = await task; + if (innerResult is JsonRpcErrorEntity) + { + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = toolName, + toolParams = toolParams, + type = MessageType.TOOL_MESSAGE, + status = "fail", + content = JsonConvert.SerializeObject(innerResult), + id = (timestamp+1).ToString() + }; + messages.Add(new Message + { + Role = "user", + Content = SystemPrompt.ErrorPromptTemplate + }); + messages.Add(new Message + { + Role = "user", + Content = 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), + id = (timestamp+1).ToString() + }; + messages.Add(new Message + { + Role = "user", + Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(innerResult)) + }); + callback?.Invoke(toolMessageItem); + } + } } } - messages.Add(new Message - { - Role = "assistant", - Content = messageContent - }); } } From 602fd94fc033bc6de01e8d5ee627c6ff4321412c Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 31 May 2025 17:33:33 +0800 Subject: [PATCH 23/60] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E5=B7=A5=E5=85=B7=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E6=B5=81=E7=A8=8B=E7=BB=93=E6=9D=9F?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/Gateway.cs | 75 +++++++++++++++++++++++--------- host/mcp/McpToolRequest.cs | 10 +++++ host/prompt/SystemPrompt.cs | 7 +-- ui/dockpane/TestDockpane.xaml.cs | 1 + 4 files changed, 66 insertions(+), 27 deletions(-) create mode 100644 host/mcp/McpToolRequest.cs diff --git a/host/Gateway.cs b/host/Gateway.cs index 3984613..915d588 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -274,6 +274,7 @@ public class Gateway string promptPattern = "([\\s\\S]*?)([\\s\\S]*?)<\\/name>([\\s\\S]*?)<\\/prompt>"; McpServerList mcpServerList = new McpServerList(); int loop = 0; + string messageContent = ""; //一次请求下完整的response while (goOn) { loop++; @@ -291,12 +292,14 @@ public class Gateway MaxTokens = 1000, }; long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - string messageContent = ""; //一次请求下完整的response + List mcpToolRequests = new List(); + List promptKeys = new List(); var (toolMatched, toolRemaining) = ExtractMatchedPart(messageContent, toolPattern); var (promptMatched, promptRemaining) = ExtractMatchedPart(messageContent, promptPattern); if (toolMatched == "" && promptMatched == "" && messageContent != "") { //如果本次回复不包含任何工具的调用或提示词的调用,则不再请求 + goOn = false; break; } await foreach(var chunk in bailian.SendChatStreamAsync(jsonContent)) @@ -305,6 +308,7 @@ public class Gateway { break; } + messageContent = chunk; var (matched, remaining) = ExtractMatchedPart(chunk, toolPattern); if (matched == "") { @@ -319,7 +323,6 @@ public class Gateway type = MessageType.CHAT_MESSAGE, id = timestamp.ToString() }; - messageContent = remainingPrompt; callback?.Invoke(chatMessageListItem); } else @@ -335,22 +338,7 @@ public class Gateway callback?.Invoke(chatMessageListItem); XElement promptUse = XElement.Parse(matchedPrompt); string promptKey = promptUse.Element("name")?.Value; - string promptContent = DynamicPrompt.GetPrompt(promptKey,null); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(promptContent) - }); - MessageListItem toolMessageItem = new ToolMessageItem - { - toolName = "调用提示词", - toolParams = null, - type = MessageType.TOOL_MESSAGE, - status = "success", - content = "成功调用提示词:"+promptKey, - id = (timestamp+1).ToString() - }; - callback?.Invoke(toolMessageItem); + promptKeys.Add(promptKey); } } else @@ -371,7 +359,33 @@ public class Gateway 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) + //将工具调用请求添加至列表中待一次回答完整后再统一进行调用 + mcpToolRequests = new List(); + McpToolRequest mcpToolRequest = new McpToolRequest() + { + McpServer = mcpServer, + ToolName = toolName, + ToolArgs = toolParams, + }; + mcpToolRequests.Add(mcpToolRequest); + } + } + + if (messageContent != "") + { + messages.Add(new Message + { + Role = "assistant", + Content = messageContent + }); + } + /*统一处理本次请求中的MCP工具调用需求*/ + foreach (McpToolRequest mcpToolRequest in mcpToolRequests) + { + 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); @@ -413,7 +427,7 @@ public class Gateway callback?.Invoke(toolMessageItem); }else if (mcpServer is InnerMcpServer) { - Type type = Type.GetType("LinkToolAddin.client.tool."+serverName); + 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]; @@ -473,7 +487,26 @@ public class Gateway callback?.Invoke(toolMessageItem); } } - } + } + /*统一处理本次请求中的Prompt调用需求*/ + foreach (string promptKey in promptKeys) + { + string promptContent = DynamicPrompt.GetPrompt(promptKey); + messages.Add(new Message + { + Role = "user", + Content = promptContent + }); + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = "调用提示词", + toolParams = null, + type = MessageType.TOOL_MESSAGE, + status = "success", + content = "成功调用提示词:"+promptKey, + id = (timestamp+1).ToString() + }; + callback?.Invoke(toolMessageItem); } } } diff --git a/host/mcp/McpToolRequest.cs b/host/mcp/McpToolRequest.cs new file mode 100644 index 0000000..cdbfab0 --- /dev/null +++ b/host/mcp/McpToolRequest.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace LinkToolAddin.host.mcp; + +public class McpToolRequest +{ + public McpServer McpServer { get; set; } + public string ToolName { get; set; } + public Dictionary ToolArgs { get; set; } +} \ No newline at end of file diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 67ea12c..4046bf2 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -4,7 +4,7 @@ public class SystemPrompt { public static string SysPromptTemplate = "现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。" + - "指令:您可以使用一组工具来回答用户的问题。还可以通过调用用户提示词,从而使你更好地理解和完成用户的任务。整个流程结束之后单独输出一条内容为'[DONE]'的消息,前后不要有任何说明文字。" + + "指令:您可以使用一组工具来回答用户的问题。还可以通过调用用户提示词,从而使你更好地理解和完成用户的任务。" + "调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过的方式来调用用户提示词,你能更好地理解和解决用户的问题。" + "工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。" + "输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化以单独信息格式输出,前后没有文字信息。" + @@ -13,12 +13,7 @@ public class SystemPrompt "工具名称:需与所使用工具的精确名称一致。" + "参数:应为包含工具所需参数的 JSON 对象。例如:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n" + "你必须严格遵守以下输出规则:" + - "1.XML工具调用格式必须在下一条单独输出,如果前面有文字将永远无法调用工具。" + - //"只有单独一条的调用请求文本程序才能识别并调用,从而将结果反馈给你。" + - "2.文字说明如果紧跟着工具调用的XML将暴露程序,XML会在下一条单独输出,因此文字描述后面一定不能输出工具调用的XML格式。" + "3.用户时间宝贵,不得重复调用上一次已成功执行的工具调用,除非有新的参数或上下文变化。" + - "4.如果目前工作已经完成无法知道用户其他需求时,一定要单独输出一条内容为'[DONE]'的消息表示工具调用结束。不要在文本的末尾。" + - "5.不得在同一消息中混合说明文字与工具调用。" + "结果:用户将以以下格式返回工具调用结果:\n {tool_name}\n {result}\n。应为字符串类型,可以表示文件或其他输出类型。" + "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n"+ "特别地:需要调用ArcGIS Pro工具前必须先查询帮助文档、标准调用名和参数后再进行调用"; diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 1628085..08e3f67 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -245,6 +245,7 @@ namespace LinkToolAddin.ui.dockpane private async void TestArcGisTool_OnClick(object sender, RoutedEventArgs e) { + Type type1 = Type.GetType("LinkToolAddin.client.tool.ArcGisPro"); string xmlStr = "\nArcGisPro:ArcGisProTool\n{\"toolName\": \"Buffer\", \"toolParams\": [\"D:\\\\01_Project\\\\20250305_LinkTool\\\\20250420_AiDemoProject\\\\20250420_AiDemoProject.gdb\\\\LandUse_2005_Copy\", \"D:\\\\01_Project\\\\20250305_LinkTool\\\\20250420_AiDemoProject\\\\20250420_AiDemoProject.gdb\\\\LandUse_2005_Buffer30m\", \"30 Meters\", \"NONE\", \"ROUND\", \"ALL\"]}\n"; XElement toolUse = XElement.Parse(xmlStr); From 6f384920a3b682cdc0f66ee5e2538b891c43918a Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 31 May 2025 17:41:19 +0800 Subject: [PATCH 24/60] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=A7=A3=E5=86=B3ArcGIS=20Pro=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E8=BF=94=E5=9B=9E=E4=B8=BA=E7=A9=BA=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/Gateway.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/host/Gateway.cs b/host/Gateway.cs index 915d588..f59cea8 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -465,7 +465,7 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = JsonConvert.SerializeObject(innerResult) + Content = JsonConvert.SerializeObject(toolMessageItem) }); callback?.Invoke(toolMessageItem); }else if (innerResult is JsonRpcSuccessEntity) @@ -482,7 +482,7 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(innerResult)) + Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolMessageItem)) }); callback?.Invoke(toolMessageItem); } From d2b18958c1a03fee539f93cd117733924416ef33 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 31 May 2025 20:51:13 +0800 Subject: [PATCH 25/60] =?UTF-8?q?=E5=8A=A8=E6=80=81=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E8=AF=8D=E6=94=AF=E6=8C=81=E5=8F=82=E6=95=B0=EF=BC=8C=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E4=B8=8ETool=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 1 - client/prompt/DynamicPrompt.cs | 30 +++++++++++++++++------- client/prompt/PromptTemplates.cs | 1 + doc/TodoList.md | 15 ++++++++++++ host/Gateway.cs | 36 ++++++++++++++-------------- host/PromptServerList.cs | 40 ++++++++++++++++++++++++++++++++ host/prompt/PromptDefinition.cs | 9 +++++++ host/prompt/PromptRequest.cs | 10 ++++++++ host/prompt/PromptServer.cs | 32 +++++++++++++++++++++++++ host/prompt/UserPrompt.cs | 11 +++++++-- 10 files changed, 156 insertions(+), 29 deletions(-) create mode 100644 doc/TodoList.md create mode 100644 host/PromptServerList.cs create mode 100644 host/prompt/PromptDefinition.cs create mode 100644 host/prompt/PromptRequest.cs create mode 100644 host/prompt/PromptServer.cs diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index a4143cc..c53da32 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -98,7 +98,6 @@ - diff --git a/client/prompt/DynamicPrompt.cs b/client/prompt/DynamicPrompt.cs index 52d695c..c63c9fa 100644 --- a/client/prompt/DynamicPrompt.cs +++ b/client/prompt/DynamicPrompt.cs @@ -1,29 +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) + public static string GetPrompt(string name,Dictionary args = null) { - PromptTemplates promptTemplate = new PromptTemplates(); - string template = promptTemplate.GetPrompt(name); + PromptServerList promptServerList = new PromptServerList(); + string template = promptServerList.GetPromptServer(name).Content; if (args == null) { return template; } - foreach (KeyValuePair pair in args) + foreach (KeyValuePair pair in args) { string replaceKey = "{{"+pair.Key+"}}"; - template.Replace(replaceKey, pair.Value.ToString()); + template = template.Replace(replaceKey, pair.Value.ToString()); } return template; } - public static Dictionary GetAllPrompts() + public static List GetAllPrompts() { - PromptTemplates promptTemplate = new PromptTemplates(); - Dictionary template = promptTemplate.GetPromptsDict(); - return template; + 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 index 7e0e18f..6f9eaa1 100644 --- a/client/prompt/PromptTemplates.cs +++ b/client/prompt/PromptTemplates.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using LinkToolAddin.host.prompt; namespace LinkToolAddin.client.prompt; diff --git a/doc/TodoList.md b/doc/TodoList.md new file mode 100644 index 0000000..fd0ae5a --- /dev/null +++ b/doc/TodoList.md @@ -0,0 +1,15 @@ +# 待办事项 + +本文档用于记录和跟踪当前阶段待办事项及完成进度 + +## 核心程序 + +- [ ] 提示词调用完整实现:以面向对象形式取代字典形式,加入参数和描述 +- [ ] 接入本地文件系统等基础性MCP服务 +- [ ] Python代码执行的实现 +- [ ] 适配qwen3、deepseek等推理模型并迁移 +- [ ] 工具执行错误消息合并为一条 + +## 提示词工程 + +## 前端交互 \ No newline at end of file diff --git a/host/Gateway.cs b/host/Gateway.cs index f59cea8..8eb71ad 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -204,7 +204,7 @@ public class Gateway 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, promptParams); + string promptRes = DynamicPrompt.GetPrompt(promptName, null); messages.Add(new Message { Role = "user", @@ -271,8 +271,9 @@ public class Gateway }); 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]*?)<\\/prompt>"; + 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 while (goOn) @@ -293,7 +294,7 @@ public class Gateway }; long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); List mcpToolRequests = new List(); - List promptKeys = new List(); + List promptRequests = new List(); var (toolMatched, toolRemaining) = ExtractMatchedPart(messageContent, toolPattern); var (promptMatched, promptRemaining) = ExtractMatchedPart(messageContent, promptPattern); if (toolMatched == "" && promptMatched == "" && messageContent != "") @@ -338,7 +339,15 @@ public class Gateway callback?.Invoke(chatMessageListItem); XElement promptUse = XElement.Parse(matchedPrompt); string promptKey = promptUse.Element("name")?.Value; - promptKeys.Add(promptKey); + 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 @@ -489,9 +498,9 @@ public class Gateway } } /*统一处理本次请求中的Prompt调用需求*/ - foreach (string promptKey in promptKeys) + foreach (PromptRequest promptRequest in promptRequests) { - string promptContent = DynamicPrompt.GetPrompt(promptKey); + string promptContent = DynamicPrompt.GetPrompt(promptRequest.PromptName, promptRequest.PromptArgs); messages.Add(new Message { Role = "user", @@ -503,7 +512,7 @@ public class Gateway toolParams = null, type = MessageType.TOOL_MESSAGE, status = "success", - content = "成功调用提示词:"+promptKey, + content = "成功调用提示词:"+promptRequest.PromptName, id = (timestamp+1).ToString() }; callback?.Invoke(toolMessageItem); @@ -588,17 +597,10 @@ public class Gateway } } - Dictionary prompts = DynamicPrompt.GetAllPrompts(); - foreach (KeyValuePair prompt in prompts) + List prompts = DynamicPrompt.GetAllPrompts(); + foreach (UserPrompt userPrompt in prompts) { - McpPromptDefinition promptDefinition = new McpPromptDefinition - { - Prompt = new LinkToolAddin.host.mcp.Prompt - { - Name = prompt.Key - } - }; - XNode node = JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(promptDefinition)); + XNode node = JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(new PromptDefinition(){UserPrompt = userPrompt})); toolInfos.AppendLine(node.ToString()); toolInfos.AppendLine(); } diff --git a/host/PromptServerList.cs b/host/PromptServerList.cs new file mode 100644 index 0000000..e4a5cd5 --- /dev/null +++ b/host/PromptServerList.cs @@ -0,0 +1,40 @@ +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的帮助文档”。" + + "有一些与分析无关的数据能够进行排除,在选择工具时不受其干扰。")); + promptServers.Add("param", new PromptServer("param", + "填写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/prompt/PromptDefinition.cs b/host/prompt/PromptDefinition.cs new file mode 100644 index 0000000..59f93fd --- /dev/null +++ b/host/prompt/PromptDefinition.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace LinkToolAddin.host.prompt; + +public class PromptDefinition +{ + [JsonProperty("prompt")] + public UserPrompt UserPrompt { get; set; } +} \ No newline at end of file diff --git a/host/prompt/PromptRequest.cs b/host/prompt/PromptRequest.cs new file mode 100644 index 0000000..ffbb966 --- /dev/null +++ b/host/prompt/PromptRequest.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace LinkToolAddin.host.prompt; + +public class PromptRequest +{ + public PromptServer PromptServer { get; set; } + public string PromptName { get; set; } + public Dictionary PromptArgs { get; set; } +} \ No newline at end of file diff --git a/host/prompt/PromptServer.cs b/host/prompt/PromptServer.cs new file mode 100644 index 0000000..709c4a7 --- /dev/null +++ b/host/prompt/PromptServer.cs @@ -0,0 +1,32 @@ +using System.Text; +using System.Text.RegularExpressions; + +namespace LinkToolAddin.host.prompt; + +public class PromptServer +{ + public string Name { get; set; } + public string Description { get; set; } + public string Arguments { get; set; } + public string Content { get; set; } + + public PromptServer(string name, string description, string content) + { + Name = name; + Description = description; + Content = content; + string pattern = "{{([\\s\\S]*?)}}"; + var matches = Regex.Matches(content, pattern); + StringBuilder args = new StringBuilder(); + foreach (var match in matches) + { + string variableName = match.ToString().Replace("{{","").Replace("}}",""); + string arg = "{ \"" + variableName + "\" : \"type\" : \" string \" }"; + args.Append(arg); + } + if (args.ToString() != string.Empty) + { + Arguments = "{\"type\":\"object\",\"properties\":" + args.ToString() + "}"; + } + } +} \ No newline at end of file diff --git a/host/prompt/UserPrompt.cs b/host/prompt/UserPrompt.cs index 128de5b..00c1cd8 100644 --- a/host/prompt/UserPrompt.cs +++ b/host/prompt/UserPrompt.cs @@ -1,6 +1,13 @@ -namespace LinkToolAddin.host.prompt; +using Newtonsoft.Json; + +namespace LinkToolAddin.host.prompt; public class UserPrompt { - + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("description")] + public string Description { get; set; } + [JsonProperty("arguments")] + public string Arguments { get; set; } } \ No newline at end of file From 68619f01f87f0d57772f4f59dfbf0f6ac0b3a693 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 31 May 2025 20:55:43 +0800 Subject: [PATCH 26/60] =?UTF-8?q?=E5=B7=A5=E5=85=B7=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=B6=88=E6=81=AF=E5=90=88=E5=B9=B6=E4=B8=BA?= =?UTF-8?q?=E5=8D=95=E6=9D=A1=EF=BC=8C=E9=9B=86=E4=B8=AD=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E6=B3=A8=E6=84=8F=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/TodoList.md | 4 ++-- host/Gateway.cs | 11 +++-------- host/prompt/SystemPrompt.cs | 9 ++++++++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/doc/TodoList.md b/doc/TodoList.md index fd0ae5a..b4fcad0 100644 --- a/doc/TodoList.md +++ b/doc/TodoList.md @@ -4,11 +4,11 @@ ## 核心程序 -- [ ] 提示词调用完整实现:以面向对象形式取代字典形式,加入参数和描述 +- [x] 提示词调用完整实现:以面向对象形式取代字典形式,加入参数和描述 - [ ] 接入本地文件系统等基础性MCP服务 - [ ] Python代码执行的实现 - [ ] 适配qwen3、deepseek等推理模型并迁移 -- [ ] 工具执行错误消息合并为一条 +- [x] 工具执行错误消息合并为一条 ## 提示词工程 diff --git a/host/Gateway.cs b/host/Gateway.cs index 8eb71ad..f13f3ff 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -411,7 +411,7 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) + Content = toolResponse.IsError ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); callback?.Invoke(toolMessageItem); }else if (mcpServer is StdioMcpServer) @@ -431,7 +431,7 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) + Content = toolResponse.IsError ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); callback?.Invoke(toolMessageItem); }else if (mcpServer is InnerMcpServer) @@ -469,12 +469,7 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = SystemPrompt.ErrorPromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(toolMessageItem) + Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolMessageItem)) }); callback?.Invoke(toolMessageItem); }else if (innerResult is JsonRpcSuccessEntity) diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 4046bf2..105fb80 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -22,7 +22,7 @@ public class SystemPrompt public static string ContinuePromptTemplate = "这是上述工具调用的结果。{{toolResult}}\n请根据以下执行结果,清晰解释执行结果并执行下一步操作。" + "执行下一步工具的要求:1. 解析工具输出结果2. 调用下一个工具时确保参数继承前序输出。请据此继续执行"; - public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误,需按以下流程处理:1. 错误解析:分析错误类型及具体原因(见下方错误信息),根据错误信息选择修复方案,```" + + public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误\n{{toolResult}}\n,需按以下流程处理:1. 错误解析:分析错误类型及具体原因(见下方错误信息),根据错误信息选择修复方案,```" + "2. 修复方案:(1)根据错误类型生成对应的工具重试策略(2)参数调整:明确需要修改的参数及修改方式。3. 工具重试:使用调整后的参数重新调用工具。错误信息如下,请根据报错信息重试,4.如果没有具体的错误描述信息请检查输出文件是否已经存在,若已经存在默认执行成功"+ "5.查询可能相关的知识库和帮助文档,纠正调用参数中存在的错误"; @@ -40,4 +40,11 @@ public class SystemPrompt continuePrompt = continuePrompt.Replace("{{toolResult}}", toolResult); return continuePrompt; } + + public static string ErrorPrompt(string toolResult) + { + string errPrompt = ErrorPromptTemplate; + errPrompt = errPrompt.Replace("{{toolResult}}", toolResult); + return errPrompt; + } } \ No newline at end of file From 23c60b9c34267bbef1aea9bf165e66bbfcaf69da Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 31 May 2025 21:39:03 +0800 Subject: [PATCH 27/60] =?UTF-8?q?=E5=85=BC=E5=AE=B9=E6=8E=A8=E7=90=86?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=EF=BC=8C=E8=BF=81=E7=A7=BB=E8=87=B3qwen3?= =?UTF-8?q?=E5=BC=80=E6=BA=90=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/tool/ArcGisPro.cs | 17 ++++++++++++++--- common/HttpRequest.cs | 4 ++-- host/Gateway.cs | 13 ++++++++++++- host/llm/Bailian.cs | 16 +++++++++++----- host/llm/Llm.cs | 3 ++- host/llm/entity/LlmJsonContent.cs | 9 +++++++++ host/llm/entity/stream/LlmStreamChat.cs | 2 ++ server/JsonRpcResultEntity.cs | 2 +- ui/dockpane/TestDockpane.xaml.cs | 12 +++++++++--- ui/message/MessageListItem.cs | 1 + 10 files changed, 63 insertions(+), 16 deletions(-) diff --git a/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs index f1b9866..52994b0 100644 --- a/client/tool/ArcGisPro.cs +++ b/client/tool/ArcGisPro.cs @@ -2,6 +2,7 @@ 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; @@ -33,7 +34,7 @@ public class ArcGisPro Error = new Error() { Code = results.ErrorCode, - Message = JsonConvert.SerializeObject(results.ErrorMessages) + Message = GetMessagesString(results.ErrorMessages) } }; }else if(results.HasWarnings) @@ -41,7 +42,7 @@ public class ArcGisPro log.Warn(results.Messages); jsonRpcResultEntity = new JsonRpcSuccessEntity { - Result = JsonConvert.SerializeObject(results.Messages) + Result = GetMessagesString(results.Messages) }; } else @@ -49,7 +50,7 @@ public class ArcGisPro log.Info("success gp tool"); jsonRpcResultEntity = new JsonRpcSuccessEntity { - Result = JsonConvert.SerializeObject(results.Messages) + Result = GetMessagesString(results.Messages) }; } return jsonRpcResultEntity; @@ -135,4 +136,14 @@ public class ArcGisPro }; 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/common/HttpRequest.cs b/common/HttpRequest.cs index 16c9383..5d42052 100644 --- a/common/HttpRequest.cs +++ b/common/HttpRequest.cs @@ -67,7 +67,7 @@ public class HttpRequest return null; } - public static async IAsyncEnumerable PostWithStreamingResponseAsync( + public static async IAsyncEnumerable PostWithStreamingResponseAsync( string url, string body, string apiKey, @@ -114,7 +114,7 @@ public class HttpRequest if (dataObj is not null) { - yield return dataObj.Choices[0].Delta.Content; + yield return dataObj; } } } diff --git a/host/Gateway.cs b/host/Gateway.cs index f13f3ff..58dc92e 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -32,6 +32,7 @@ using Newtonsoft.Json.Schema; using Newtonsoft.Json.Schema.Generation; using Tool = LinkToolAddin.host.mcp.Tool; using LinkToolAddin.common; +using LinkToolAddin.host.llm.entity.stream; namespace LinkToolAddin.host; @@ -303,12 +304,22 @@ public class Gateway goOn = false; break; } - await foreach(var chunk in bailian.SendChatStreamAsync(jsonContent)) + await foreach(LlmStreamChat llmStreamChat in bailian.SendChatStreamAsync(jsonContent)) { if (!goOn) { break; } + + 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-1).ToString() + }; + callback?.Invoke(reasonMessageListItem); messageContent = chunk; var (matched, remaining) = ExtractMatchedPart(chunk, toolPattern); if (matched == "") diff --git a/host/llm/Bailian.cs b/host/llm/Bailian.cs index b43eed0..6d7d273 100644 --- a/host/llm/Bailian.cs +++ b/host/llm/Bailian.cs @@ -6,6 +6,7 @@ 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; @@ -18,17 +19,22 @@ public class Bailian : Llm public string max_tokens { get; set; } public string app_id { get; set; } public string api_key { get; set; } - public async IAsyncEnumerable SendChatStreamAsync(LlmJsonContent jsonContent) + public async IAsyncEnumerable SendChatStreamAsync(LlmJsonContent jsonContent) { jsonContent.Stream = true; - StringBuilder builder = new StringBuilder(); - await foreach (var chunk in HttpRequest.PostWithStreamingResponseAsync( + 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)) { - builder.Append(chunk); - yield return builder.ToString(); + 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; } } diff --git a/host/llm/Llm.cs b/host/llm/Llm.cs index 1807b9d..bb3ba53 100644 --- a/host/llm/Llm.cs +++ b/host/llm/Llm.cs @@ -1,6 +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; @@ -11,7 +12,7 @@ public interface Llm public string top_p { get; set; } public string max_tokens { get; set; } - public IAsyncEnumerable SendChatStreamAsync(LlmJsonContent jsonContent); + public IAsyncEnumerable SendChatStreamAsync(LlmJsonContent jsonContent); public IAsyncEnumerable SendApplicationStreamAsync(string message); public Task SendChatAsync(LlmJsonContent jsonContent); public Task SendApplicationAsync(CommonInput commonInput); diff --git a/host/llm/entity/LlmJsonContent.cs b/host/llm/entity/LlmJsonContent.cs index 5b53edf..c38382f 100644 --- a/host/llm/entity/LlmJsonContent.cs +++ b/host/llm/entity/LlmJsonContent.cs @@ -25,6 +25,15 @@ namespace LinkToolAddin.host.llm.entity [JsonProperty("top_k")] public int TopK { get; set; } = 40; + + [JsonProperty("enable_thinking")] + public bool EnableThinking { get; set; } = true; + + [JsonProperty("thinking_budget")] + public long ThinkingBudget { get; set; } = 1200; + + [JsonProperty("incremental_output")] + public bool IncrementalOutput { get; set; } = true; } public partial class Message diff --git a/host/llm/entity/stream/LlmStreamChat.cs b/host/llm/entity/stream/LlmStreamChat.cs index 6991982..6813e7e 100644 --- a/host/llm/entity/stream/LlmStreamChat.cs +++ b/host/llm/entity/stream/LlmStreamChat.cs @@ -50,5 +50,7 @@ { [JsonProperty("content")] public string Content { get; set; } + [JsonProperty("reasoning_content")] + public string ResoningContent { get; set; } } } \ No newline at end of file diff --git a/server/JsonRpcResultEntity.cs b/server/JsonRpcResultEntity.cs index 5d80803..f97442a 100644 --- a/server/JsonRpcResultEntity.cs +++ b/server/JsonRpcResultEntity.cs @@ -7,7 +7,7 @@ namespace LinkToolAddin.server public partial class JsonRpcResultEntity { [JsonProperty("jsonrpc")] - public string Jsonrpc { get; set; } + public string Jsonrpc { get; set; } = "2.0"; [JsonProperty("id")] public long Id { get; set; } diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 08e3f67..1b2c1ea 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -198,8 +198,8 @@ namespace LinkToolAddin.ui.dockpane private void PromptTestButton_OnClick(object sender, RoutedEventArgs e) { string userPrompt = PromptTestTextBox.Text; - // Gateway.SendMessage(userPrompt,"qwen-max","C:/Project/test.gdb",AddReply); - Gateway.SendMessageStream(userPrompt,"qwen-max", "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb", AddReplyStream); + // model可选值:qwen3-235b-a22b,qwen-max,deepseek-r1 + Gateway.SendMessageStream(userPrompt,"qwen3-235b-a22b", "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb", AddReplyStream); } public void AddReplyStream(MessageListItem msg) @@ -219,7 +219,13 @@ namespace LinkToolAddin.ui.dockpane foreach (KeyValuePair pair in messageDict) { MessageListItem msgItem = pair.Value; - builder.AppendLine(msgItem.content); + string content = msgItem.content; + if (msgItem.type == MessageType.REASON_MESSAGE) + { + content = "" + content + ""; + } + builder.AppendLine(content); + builder.AppendLine(); ReplyTextBox.Text = builder.ToString(); ReplyTextBox.ScrollToEnd(); } diff --git a/ui/message/MessageListItem.cs b/ui/message/MessageListItem.cs index 1c58cbd..9b97709 100644 --- a/ui/message/MessageListItem.cs +++ b/ui/message/MessageListItem.cs @@ -4,6 +4,7 @@ public enum MessageType { TOOL_MESSAGE, CHAT_MESSAGE, + REASON_MESSAGE, } public interface MessageListItem From b06e1825df7780cb9428e45e702ee8a8069d14b4 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 31 May 2025 23:31:28 +0800 Subject: [PATCH 28/60] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E3=80=81=E5=BF=85=E5=BA=94=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E3=80=81=E7=BD=91=E5=9D=80=E8=AF=BB=E5=8F=96=E7=AD=89=E7=AC=AC?= =?UTF-8?q?=E4=B8=89=E6=96=B9MCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/TodoList.md | 11 +- host/Gateway.cs | 219 ++++++++++++++++++------------- host/McpServerList.cs | 47 +++++++ ui/dockpane/TestDockpane.xaml.cs | 36 +++-- 4 files changed, 207 insertions(+), 106 deletions(-) diff --git a/doc/TodoList.md b/doc/TodoList.md index b4fcad0..9812b21 100644 --- a/doc/TodoList.md +++ b/doc/TodoList.md @@ -5,11 +5,18 @@ ## 核心程序 - [x] 提示词调用完整实现:以面向对象形式取代字典形式,加入参数和描述 -- [ ] 接入本地文件系统等基础性MCP服务 +- [x] 接入本地文件系统等基础性MCP服务 - [ ] Python代码执行的实现 -- [ ] 适配qwen3、deepseek等推理模型并迁移 +- [x] 适配qwen3、deepseek等推理模型并迁移 - [x] 工具执行错误消息合并为一条 ## 提示词工程 +- [ ] 将原来的单独XML规则修改为内嵌XML规则 +- [ ] 系统提示词明确提示调用动态Prompt和知识库 +- [ ] 错误和继续提示词预留工具消息占位符 +- [ ] 错误和继续提示词明示 +- [ ] 系统提示词泛化增强 +- [ ] 针对qwen3适当调整 + ## 前端交互 \ No newline at end of file diff --git a/host/Gateway.cs b/host/Gateway.cs index 58dc92e..406d789 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -259,7 +259,6 @@ public class Gateway }; List messages = new List(); string toolInfos = await GetToolInfos(new McpServerList()); - log.Info(SystemPrompt.SysPrompt(gdbPath, toolInfos)); messages.Add(new Message { Role = "system", @@ -311,86 +310,94 @@ public class Gateway break; } - string chunk = llmStreamChat.Choices[0].Delta.Content; - MessageListItem reasonMessageListItem = new ChatMessageItem() + try { - content = llmStreamChat.Choices[0].Delta.ResoningContent, - role = "assistant", - type = MessageType.REASON_MESSAGE, - id = (timestamp-1).ToString() - }; - callback?.Invoke(reasonMessageListItem); - messageContent = chunk; - var (matched, remaining) = ExtractMatchedPart(chunk, toolPattern); - if (matched == "") - { - var (matchedPrompt, remainingPrompt) = ExtractMatchedPart(chunk, promptPattern); - if (matchedPrompt == "") + string chunk = llmStreamChat.Choices[0].Delta.Content; + MessageListItem reasonMessageListItem = new ChatMessageItem() { - //普通消息文本 - MessageListItem chatMessageListItem = new ChatMessageItem() + content = llmStreamChat.Choices[0].Delta.ResoningContent, + role = "assistant", + type = MessageType.REASON_MESSAGE, + id = (timestamp-1).ToString() + }; + callback?.Invoke(reasonMessageListItem); + messageContent = chunk; + var (matched, remaining) = ExtractMatchedPart(chunk, toolPattern); + if (matched == "") + { + var (matchedPrompt, remainingPrompt) = ExtractMatchedPart(chunk, promptPattern); + if (matchedPrompt == "") { - content = remainingPrompt, - role = "assistant", - type = MessageType.CHAT_MESSAGE, - id = timestamp.ToString() - }; - callback?.Invoke(chatMessageListItem); + //普通消息文本 + MessageListItem chatMessageListItem = new ChatMessageItem() + { + content = remainingPrompt, + role = "assistant", + type = MessageType.CHAT_MESSAGE, + id = timestamp.ToString() + }; + callback?.Invoke(chatMessageListItem); + } + else + { + //包含Prompt调用请求的消息 + MessageListItem chatMessageListItem = new ChatMessageItem() + { + content = remainingPrompt, + role = "assistant", + type = MessageType.CHAT_MESSAGE, + id = timestamp.ToString() + }; + callback?.Invoke(chatMessageListItem); + XElement promptUse = XElement.Parse(matchedPrompt); + string promptKey = promptUse.Element("name")?.Value; + string promptArgs = promptUse.Element("arguments")?.Value; + Dictionary promptParams = JsonConvert.DeserializeObject>(promptArgs); + promptRequests = new List(); + promptRequests.Add(new PromptRequest() + { + PromptName = promptKey, + PromptArgs = promptParams, + PromptServer = promptServerList.GetPromptServer(promptKey) + }); + } } else { - //包含Prompt调用请求的消息 + //包含工具调用请求的消息 MessageListItem chatMessageListItem = new ChatMessageItem() { - content = remainingPrompt, + content = remaining, role = "assistant", type = MessageType.CHAT_MESSAGE, id = timestamp.ToString() }; callback?.Invoke(chatMessageListItem); - XElement promptUse = XElement.Parse(matchedPrompt); - string promptKey = promptUse.Element("name")?.Value; - string promptArgs = promptUse.Element("arguments")?.Value; - Dictionary promptParams = JsonConvert.DeserializeObject>(promptArgs); - promptRequests = new List(); - promptRequests.Add(new PromptRequest() + XElement toolUse = XElement.Parse(matched); + string fullToolName = toolUse.Element("name")?.Value; + string toolArgs = toolUse.Element("arguments")?.Value; + Dictionary toolParams = JsonConvert.DeserializeObject>(toolArgs); + string serverName = fullToolName.Contains(":") ? fullToolName.Split(':')[0] : fullToolName; + string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName; + McpServer mcpServer = mcpServerList.GetServer(serverName); + //将工具调用请求添加至列表中待一次回答完整后再统一进行调用 + mcpToolRequests = new List(); + McpToolRequest mcpToolRequest = new McpToolRequest() { - PromptName = promptKey, - PromptArgs = promptParams, - PromptServer = promptServerList.GetPromptServer(promptKey) - }); + McpServer = mcpServer, + ToolName = toolName, + ToolArgs = toolParams, + }; + mcpToolRequests.Add(mcpToolRequest); } } - else + catch (Exception e) { - //包含工具调用请求的消息 - MessageListItem chatMessageListItem = new ChatMessageItem() - { - content = remaining, - role = "assistant", - type = MessageType.CHAT_MESSAGE, - id = timestamp.ToString() - }; - callback?.Invoke(chatMessageListItem); - XElement toolUse = XElement.Parse(matched); - string fullToolName = toolUse.Element("name")?.Value; - string toolArgs = toolUse.Element("arguments")?.Value; - Dictionary toolParams = JsonConvert.DeserializeObject>(toolArgs); - string serverName = fullToolName.Contains(":") ? fullToolName.Split(':')[0] : fullToolName; - string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName; - McpServer mcpServer = mcpServerList.GetServer(serverName); - //将工具调用请求添加至列表中待一次回答完整后再统一进行调用 - mcpToolRequests = new List(); - McpToolRequest mcpToolRequest = new McpToolRequest() - { - McpServer = mcpServer, - ToolName = toolName, - ToolArgs = toolParams, - }; - mcpToolRequests.Add(mcpToolRequest); + Console.WriteLine(e); + log.Error(e.Message); } + } - if (messageContent != "") { messages.Add(new Message @@ -402,14 +409,16 @@ public class Gateway /*统一处理本次请求中的MCP工具调用需求*/ foreach (McpToolRequest mcpToolRequest in mcpToolRequests) { - McpServer mcpServer = mcpToolRequest.McpServer; - string toolName = mcpToolRequest.ToolName; - Dictionary toolParams = mcpToolRequest.ToolArgs; - if (mcpServer is SseMcpServer) + try + { + McpServer mcpServer = mcpToolRequest.McpServer; + string toolName = mcpToolRequest.ToolName; + Dictionary toolParams = mcpToolRequest.ToolArgs; + if (mcpServer is SseMcpServer) { SseMcpServer sseMcpServer = mcpServer as SseMcpServer; SseMcpClient client = new SseMcpClient(sseMcpServer.BaseUrl); - CallToolResponse toolResponse = await client.CallToolAsync(toolName,toolParams); + CallToolResponse toolResponse = await client.CallToolAsync(toolName, toolParams); MessageListItem toolMessageItem = new ToolMessageItem { toolName = toolName, @@ -417,19 +426,22 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = toolResponse.IsError ? "fail" : "success", content = JsonConvert.SerializeObject(toolResponse), - id = (timestamp+1).ToString() + id = (timestamp + 1).ToString() }; messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) + Content = toolResponse.IsError + ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) + : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); callback?.Invoke(toolMessageItem); - }else if (mcpServer is StdioMcpServer) + } + else if (mcpServer is StdioMcpServer) { StdioMcpServer stdioMcpServer = mcpServer as StdioMcpServer; StdioMcpClient client = new StdioMcpClient(stdioMcpServer.Command, stdioMcpServer.Args); - CallToolResponse toolResponse = await client.CallToolAsync(toolName,toolParams); + CallToolResponse toolResponse = await client.CallToolAsync(toolName, toolParams); MessageListItem toolMessageItem = new ToolMessageItem { toolName = toolName, @@ -437,18 +449,21 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = toolResponse.IsError ? "fail" : "success", content = JsonConvert.SerializeObject(toolResponse), - id = (timestamp+1).ToString() + id = (timestamp + 1).ToString() }; messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) + Content = toolResponse.IsError + ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) + : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); callback?.Invoke(toolMessageItem); - }else if (mcpServer is InnerMcpServer) + } + else if (mcpServer is InnerMcpServer) { - Type type = Type.GetType("LinkToolAddin.client.tool."+(mcpServer as InnerMcpServer).Name); - MethodInfo method = type.GetMethod(toolName,BindingFlags.Public | BindingFlags.Static); + Type type = Type.GetType("LinkToolAddin.client.tool." + (mcpServer as InnerMcpServer).Name); + MethodInfo method = type.GetMethod(toolName, BindingFlags.Public | BindingFlags.Static); var methodParams = toolParams.Values.ToArray(); object[] args = new object[methodParams.Length]; for (int i = 0; i < methodParams.Length; i++) @@ -464,6 +479,7 @@ public class Gateway args[i] = methodParams[i]; } } + var task = method.Invoke(null, args) as Task; JsonRpcResultEntity innerResult = await task; if (innerResult is JsonRpcErrorEntity) @@ -475,7 +491,7 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = "fail", content = JsonConvert.SerializeObject(innerResult), - id = (timestamp+1).ToString() + id = (timestamp + 1).ToString() }; messages.Add(new Message { @@ -483,7 +499,8 @@ public class Gateway Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolMessageItem)) }); callback?.Invoke(toolMessageItem); - }else if (innerResult is JsonRpcSuccessEntity) + } + else if (innerResult is JsonRpcSuccessEntity) { MessageListItem toolMessageItem = new ToolMessageItem { @@ -492,7 +509,7 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = "success", content = JsonConvert.SerializeObject(innerResult), - id = (timestamp+1).ToString() + id = (timestamp + 1).ToString() }; messages.Add(new Message { @@ -502,26 +519,41 @@ public class Gateway callback?.Invoke(toolMessageItem); } } + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + log.Error(ex.Message); + } + } /*统一处理本次请求中的Prompt调用需求*/ foreach (PromptRequest promptRequest in promptRequests) { - string promptContent = DynamicPrompt.GetPrompt(promptRequest.PromptName, promptRequest.PromptArgs); - messages.Add(new Message + try { - Role = "user", - Content = promptContent - }); - MessageListItem toolMessageItem = new ToolMessageItem + string promptContent = DynamicPrompt.GetPrompt(promptRequest.PromptName, promptRequest.PromptArgs); + messages.Add(new Message + { + Role = "user", + Content = promptContent + }); + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = "调用提示词", + toolParams = null, + type = MessageType.TOOL_MESSAGE, + status = "success", + content = "成功调用提示词:"+promptRequest.PromptName, + id = (timestamp+1).ToString() + }; + callback?.Invoke(toolMessageItem); + } + catch (Exception e) { - toolName = "调用提示词", - toolParams = null, - type = MessageType.TOOL_MESSAGE, - status = "success", - content = "成功调用提示词:"+promptRequest.PromptName, - id = (timestamp+1).ToString() - }; - callback?.Invoke(toolMessageItem); + Console.WriteLine(e); + log.Error(e.Message); + } } } } @@ -531,6 +563,7 @@ public class Gateway StringBuilder toolInfos = new StringBuilder(); foreach (McpServer mcpServer in mcpServerList.GetAllServers()) { + log.Info($"正在列出{mcpServer.Name}中的工具"); if (mcpServer is InnerMcpServer) { InnerMcpServer innerMcpServer = (InnerMcpServer)mcpServer; diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 68f83b4..2752204 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -35,6 +35,53 @@ public class McpServerList Description = "可以调用进行查询知识库,获取相关参考信息。", IsActive = true }); + servers.Add("filesystem", new StdioMcpServer() + { + Name = "filesystem", + Type = "stdio", + Command = "npx", + Args = new List() + { + "-y", + "@modelcontextprotocol/server-filesystem", + "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData" + } + }); + servers.Add("fetch", new StdioMcpServer() + { + Name = "fetch", + Type = "stdio", + Command = "uvx", + Args = new List() + { + "mcp-server-fetch" + } + }); + servers.Add("bing-search", new StdioMcpServer() + { + Name = "bing-search", + Type = "stdio", + Command = "npx", + Args = new List() + { + "bing-cn-mcp" + } + }); + // servers.Add("mcp-python-interpreter", new StdioMcpServer() + // { + // Name = "mcp-python-interpreter", + // Type = "stdio", + // Command = "uvx", + // Args = new List() + // { + // "--native-tls", + // "mcp-python-interpreter", + // "--dir", + // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData", + // "--python-path", + // "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe" + // } + // }); } public McpServer GetServer(string name) diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 1b2c1ea..435ec72 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -199,7 +199,14 @@ namespace LinkToolAddin.ui.dockpane { string userPrompt = PromptTestTextBox.Text; // model可选值:qwen3-235b-a22b,qwen-max,deepseek-r1 - Gateway.SendMessageStream(userPrompt,"qwen3-235b-a22b", "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb", AddReplyStream); + try + { + Gateway.SendMessageStream(userPrompt,"qwen3-235b-a22b", "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb", AddReplyStream); + } + catch (Exception exception) + { + log.Error(exception.Message); + } } public void AddReplyStream(MessageListItem msg) @@ -215,20 +222,27 @@ namespace LinkToolAddin.ui.dockpane messageDict.Add(msg.id, msg); } ReplyTextBox.Clear(); - StringBuilder builder = new StringBuilder(); - foreach (KeyValuePair pair in messageDict) + try { - MessageListItem msgItem = pair.Value; - string content = msgItem.content; - if (msgItem.type == MessageType.REASON_MESSAGE) + StringBuilder builder = new StringBuilder(); + foreach (KeyValuePair pair in messageDict) { - content = "" + content + ""; + MessageListItem msgItem = pair.Value; + string content = msgItem.content; + if (msgItem.type == MessageType.REASON_MESSAGE) + { + content = "" + content + ""; + } + builder.AppendLine(content); + builder.AppendLine(); + ReplyTextBox.Text = builder.ToString(); + ReplyTextBox.ScrollToEnd(); } - builder.AppendLine(content); - builder.AppendLine(); - ReplyTextBox.Text = builder.ToString(); - ReplyTextBox.ScrollToEnd(); + }catch (Exception exception) + { + log.Error(exception.Message); } + } public void AddReply(MessageListItem msg) From 56ed264e2285200f54e2fb40067869d32f0497f2 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 31 May 2025 23:35:42 +0800 Subject: [PATCH 29/60] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + doc/TodoList.md | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) 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/doc/TodoList.md b/doc/TodoList.md index 9812b21..65f668c 100644 --- a/doc/TodoList.md +++ b/doc/TodoList.md @@ -19,4 +19,10 @@ - [ ] 系统提示词泛化增强 - [ ] 针对qwen3适当调整 -## 前端交互 \ No newline at end of file +## 前端交互 + +- [ ] 三类消息卡片 +- [ ] 流式输出,根据id匹配修改或新增 +- [ ] 添加工作空间、发送、复制粘贴等交互 +- [ ] 独立UI线程 +- [ ] 工具卡片特殊提示,弹出窗口显示内容 \ No newline at end of file From 7a3516f85527c78c34773334b9efb5f752d1ec44 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 1 Jun 2025 15:37:56 +0800 Subject: [PATCH 30/60] =?UTF-8?q?=E5=B0=86=E7=B3=BB=E7=BB=9F=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E8=AF=8D=E7=8B=AC=E7=AB=8B=E4=B8=BA=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 13 +++++-- client/StdioMcpClient.cs | 15 ++++++-- common/LocalResource.cs | 31 +++++++++++++++++ host/Gateway.cs | 29 +++------------- host/prompt/SystemPrompt.cs | 56 ++++++++++++++++-------------- resource/prompt/ContinuePrompt.txt | 11 ++++++ resource/prompt/ErrorPrompt.txt | 12 +++++++ resource/prompt/SystemPrompt.txt | 40 +++++++++++++++++++++ ui/dockpane/TestDockpane.xaml | 2 ++ ui/dockpane/TestDockpane.xaml.cs | 8 +++++ 10 files changed, 159 insertions(+), 58 deletions(-) create mode 100644 common/LocalResource.cs create mode 100644 resource/prompt/ContinuePrompt.txt create mode 100644 resource/prompt/ErrorPrompt.txt create mode 100644 resource/prompt/SystemPrompt.txt diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index c53da32..35cdbec 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -97,9 +97,6 @@ False - - - @@ -107,6 +104,16 @@ + + + + + + + Always + + + diff --git a/client/StdioMcpClient.cs b/client/StdioMcpClient.cs index caad4b7..64933ad 100644 --- a/client/StdioMcpClient.cs +++ b/client/StdioMcpClient.cs @@ -51,9 +51,18 @@ public class StdioMcpClient : McpClient public async Task> GetToolListAsync() { - IMcpClient client = await McpClientFactory.CreateAsync(new StdioClientTransport(transportOptions)); - IList tools = await client.ListToolsAsync(); - return tools; + 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) diff --git a/common/LocalResource.cs b/common/LocalResource.cs new file mode 100644 index 0000000..fc1e719 --- /dev/null +++ b/common/LocalResource.cs @@ -0,0 +1,31 @@ +using System; +using System.IO; + +namespace LinkToolAddin.common; + +public class LocalResource +{ + public static string ReadFileByResource(string resourceName) + { + var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + + string[] resourceNames = assembly.GetManifestResourceNames(); + foreach (string name in resourceNames) + { + Console.WriteLine(name); + } + + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + { + if (stream == null) + { + return($"找不到嵌入资源:{resourceName}"); + } + + using (StreamReader reader = new StreamReader(stream)) + { + return reader.ReadToEnd(); + } + } + } +} \ No newline at end of file diff --git a/host/Gateway.cs b/host/Gateway.cs index 406d789..cb9a0fb 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -113,12 +113,7 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(toolResponse) + Content = toolResponse.IsError ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); callback?.Invoke(toolMessageItem); }else if (mcpServer is StdioMcpServer) @@ -138,12 +133,7 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(toolResponse) + Content = toolResponse.IsError ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); callback?.Invoke(toolMessageItem); }else if (mcpServer is InnerMcpServer) @@ -165,12 +155,7 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = SystemPrompt.ErrorPromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(innerResult) + Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(innerResult)) }); callback?.Invoke(toolMessageItem); }else if (innerResult is JsonRpcSuccessEntity) @@ -186,12 +171,7 @@ public class Gateway messages.Add(new Message { Role = "user", - Content = SystemPrompt.ContinuePromptTemplate - }); - messages.Add(new Message - { - Role = "user", - Content = JsonConvert.SerializeObject(innerResult) + Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(innerResult)) }); callback?.Invoke(toolMessageItem); } @@ -396,7 +376,6 @@ public class Gateway Console.WriteLine(e); log.Error(e.Message); } - } if (messageContent != "") { diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 105fb80..2295d68 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -1,34 +1,36 @@ -namespace LinkToolAddin.host.prompt; +using LinkToolAddin.common; + +namespace LinkToolAddin.host.prompt; public class SystemPrompt { - public static string SysPromptTemplate = - "现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。" + - "指令:您可以使用一组工具来回答用户的问题。还可以通过调用用户提示词,从而使你更好地理解和完成用户的任务。" + - "调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过的方式来调用用户提示词,你能更好地理解和解决用户的问题。" + - "工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。" + - "输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化以单独信息格式输出,前后没有文字信息。" + - "如果需要进行文字说明,请只有文字内容,以普通段落形式呈现没有其他格式。" + - "工具调用格式:工具名称包含在一对标签内,每个参数也需用对应的标签包裹。结构如下:\n {tool_name}\n {json_arguments}\n。" + - "工具名称:需与所使用工具的精确名称一致。" + - "参数:应为包含工具所需参数的 JSON 对象。例如:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n" + - "你必须严格遵守以下输出规则:" + - "3.用户时间宝贵,不得重复调用上一次已成功执行的工具调用,除非有新的参数或上下文变化。" + - "结果:用户将以以下格式返回工具调用结果:\n {tool_name}\n {result}\n。应为字符串类型,可以表示文件或其他输出类型。" + - "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n"+ - "特别地:需要调用ArcGIS Pro工具前必须先查询帮助文档、标准调用名和参数后再进行调用"; - //"现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。\r\n\r\n指令:\r\n你需要使用一组工具来回答用户的问题。也可以通过 标签调用用户提示词,以帮助你更好地理解任务。所有操作完成后,在最后一条输出中包含 '[DONE]',但不要单独发送这条消息。\r\n\r\n调用工具要求:\r\n1. 每次只能调用一个工具,并等待用户的反馈结果。\r\n2. 所有工具调用都必须基于前一步的结果进行推理和决策。\r\n3. 工具调用必须以 XML 格式输出,并且必须作为**独立的一条消息输出**,前后不得有任何解释性文字或说明内容。\r\n4. 如果需要提供解释、说明或引导信息,请先单独输出一段普通文本,**其中不能包含任何 XML 标签或格式**。\r\n5. 不得重复调用已经成功执行过的工具,除非有新的参数或上下文需要重新调用。\r\n\r\n工具调用背景:\r\n你有以下工具可以调用:{{toolInfos}} \r\n用户的数据库路径是:{{gdbPath}}\r\n\r\n输出风格:\r\n- 所有工具调用都必须使用标准 XML 格式输出,并且每条消息只包含一次调用。\r\n- 文字说明必须单独输出,且为普通段落格式,不含任何 XML 或 Markdown 标记。\r\n- 输出顺序应为:【可选的文字说明】→【必选的工具调用】,或者仅输出工具调用。\r\n\r\n工具调用格式示例:\r\n\r\n {tool_name}\r\n {json_arguments}\r\n\r\n\r\n例如:\r\n\r\n gaode:maps_geo\r\n {\"address\":\"广州市政府, 广州市\", \"city\":\"广州\"}\r\n\r\n\r\n注意事项:\r\n1. 工具名称必须与实际工具完全一致。\r\n2. 参数必须为合法 JSON 格式,且符合工具要求。\r\n3. 用户时间宝贵,请高效调用工具,尽快完成任务。\r\n4. 最终完成时应在最后一次响应中包含 [DONE],而不是单独输出。\r\n\r\n工具调用结果返回格式:\r\n用户将以如下格式返回工具调用结果:\r\n\r\n {tool_name}\r\n {result}\r\n\r\n\r\n例如:\r\n\r\n ArcGIS_Pro:GP\r\n {\"output_file\": \"source.shp\"}\r\n\r\n\r\n请始终遵循此格式以确保工具调用被正确解析和执行。"; - - public static string ContinuePromptTemplate = "这是上述工具调用的结果。{{toolResult}}\n请根据以下执行结果,清晰解释执行结果并执行下一步操作。" + - "执行下一步工具的要求:1. 解析工具输出结果2. 调用下一个工具时确保参数继承前序输出。请据此继续执行"; - - public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误\n{{toolResult}}\n,需按以下流程处理:1. 错误解析:分析错误类型及具体原因(见下方错误信息),根据错误信息选择修复方案,```" + - "2. 修复方案:(1)根据错误类型生成对应的工具重试策略(2)参数调整:明确需要修改的参数及修改方式。3. 工具重试:使用调整后的参数重新调用工具。错误信息如下,请根据报错信息重试,4.如果没有具体的错误描述信息请检查输出文件是否已经存在,若已经存在默认执行成功"+ - "5.查询可能相关的知识库和帮助文档,纠正调用参数中存在的错误"; + // public static string SysPromptTemplate = + // "现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。" + + // "指令:您可以使用一组工具来回答用户的问题。还可以通过调用用户提示词,从而使你更好地理解和完成用户的任务。" + + // "调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过的方式来调用用户提示词,你能更好地理解和解决用户的问题。" + + // "工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。" + + // "输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化以单独信息格式输出,前后没有文字信息。" + + // "如果需要进行文字说明,请只有文字内容,以普通段落形式呈现没有其他格式。" + + // "工具调用格式:工具名称包含在一对标签内,每个参数也需用对应的标签包裹。结构如下:\n {tool_name}\n {json_arguments}\n。" + + // "工具名称:需与所使用工具的精确名称一致。" + + // "参数:应为包含工具所需参数的 JSON 对象。例如:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n" + + // "你必须严格遵守以下输出规则:" + + // "3.用户时间宝贵,不得重复调用上一次已成功执行的工具调用,除非有新的参数或上下文变化。" + + // "结果:用户将以以下格式返回工具调用结果:\n {tool_name}\n {result}\n。应为字符串类型,可以表示文件或其他输出类型。" + + // "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n"+ + // "特别地:需要调用ArcGIS Pro工具前必须先查询帮助文档、标准调用名和参数后再进行调用"; + // //"现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。\r\n\r\n指令:\r\n你需要使用一组工具来回答用户的问题。也可以通过 标签调用用户提示词,以帮助你更好地理解任务。所有操作完成后,在最后一条输出中包含 '[DONE]',但不要单独发送这条消息。\r\n\r\n调用工具要求:\r\n1. 每次只能调用一个工具,并等待用户的反馈结果。\r\n2. 所有工具调用都必须基于前一步的结果进行推理和决策。\r\n3. 工具调用必须以 XML 格式输出,并且必须作为**独立的一条消息输出**,前后不得有任何解释性文字或说明内容。\r\n4. 如果需要提供解释、说明或引导信息,请先单独输出一段普通文本,**其中不能包含任何 XML 标签或格式**。\r\n5. 不得重复调用已经成功执行过的工具,除非有新的参数或上下文需要重新调用。\r\n\r\n工具调用背景:\r\n你有以下工具可以调用:{{toolInfos}} \r\n用户的数据库路径是:{{gdbPath}}\r\n\r\n输出风格:\r\n- 所有工具调用都必须使用标准 XML 格式输出,并且每条消息只包含一次调用。\r\n- 文字说明必须单独输出,且为普通段落格式,不含任何 XML 或 Markdown 标记。\r\n- 输出顺序应为:【可选的文字说明】→【必选的工具调用】,或者仅输出工具调用。\r\n\r\n工具调用格式示例:\r\n\r\n {tool_name}\r\n {json_arguments}\r\n\r\n\r\n例如:\r\n\r\n gaode:maps_geo\r\n {\"address\":\"广州市政府, 广州市\", \"city\":\"广州\"}\r\n\r\n\r\n注意事项:\r\n1. 工具名称必须与实际工具完全一致。\r\n2. 参数必须为合法 JSON 格式,且符合工具要求。\r\n3. 用户时间宝贵,请高效调用工具,尽快完成任务。\r\n4. 最终完成时应在最后一次响应中包含 [DONE],而不是单独输出。\r\n\r\n工具调用结果返回格式:\r\n用户将以如下格式返回工具调用结果:\r\n\r\n {tool_name}\r\n {result}\r\n\r\n\r\n例如:\r\n\r\n ArcGIS_Pro:GP\r\n {\"output_file\": \"source.shp\"}\r\n\r\n\r\n请始终遵循此格式以确保工具调用被正确解析和执行。"; + // + // public static string ContinuePromptTemplate = "这是上述工具调用的结果。{{toolResult}}\n请根据以下执行结果,清晰解释执行结果并执行下一步操作。" + + // "执行下一步工具的要求:1. 解析工具输出结果2. 调用下一个工具时确保参数继承前序输出。请据此继续执行"; + // + // public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误\n{{toolResult}}\n,需按以下流程处理:1. 错误解析:分析错误类型及具体原因(见下方错误信息),根据错误信息选择修复方案,```" + + // "2. 修复方案:(1)根据错误类型生成对应的工具重试策略(2)参数调整:明确需要修改的参数及修改方式。3. 工具重试:使用调整后的参数重新调用工具。错误信息如下,请根据报错信息重试,4.如果没有具体的错误描述信息请检查输出文件是否已经存在,若已经存在默认执行成功"+ + // "5.查询可能相关的知识库和帮助文档,纠正调用参数中存在的错误"; public static string SysPrompt(string gdbPath, string toolInfos) { - string sysPrompt = SysPromptTemplate; + string sysPrompt = LocalResource.ReadFileByResource("LinkToolAddin.resource.prompt.SystemPrompt.txt");; sysPrompt = sysPrompt.Replace("{{gdbPath}}", gdbPath); sysPrompt = sysPrompt.Replace("{{toolInfos}}", toolInfos); return sysPrompt; @@ -36,14 +38,14 @@ public class SystemPrompt public static string ContinuePrompt(string toolResult) { - string continuePrompt = ContinuePromptTemplate; + string continuePrompt = LocalResource.ReadFileByResource("LinkToolAddin.resource.prompt.ContinuePrompt.txt"); continuePrompt = continuePrompt.Replace("{{toolResult}}", toolResult); return continuePrompt; } public static string ErrorPrompt(string toolResult) { - string errPrompt = ErrorPromptTemplate; + string errPrompt = LocalResource.ReadFileByResource("LinkToolAddin.resource.prompt.ErrorPrompt.txt"); errPrompt = errPrompt.Replace("{{toolResult}}", toolResult); return errPrompt; } diff --git a/resource/prompt/ContinuePrompt.txt b/resource/prompt/ContinuePrompt.txt new file mode 100644 index 0000000..deb62c7 --- /dev/null +++ b/resource/prompt/ContinuePrompt.txt @@ -0,0 +1,11 @@ +这是上述工具调用的结果。 + +{{toolResult}} + +请根据以下执行结果,清晰解释执行结果并执行下一步操作。 + +执行下一步工具的要求: +1. 解析工具输出结果 +2. 调用下一个工具时确保参数继承前序输出。 + +请据此继续执行 \ No newline at end of file diff --git a/resource/prompt/ErrorPrompt.txt b/resource/prompt/ErrorPrompt.txt new file mode 100644 index 0000000..3257463 --- /dev/null +++ b/resource/prompt/ErrorPrompt.txt @@ -0,0 +1,12 @@ +执行上一个工具的时候出现以下错误 + +{{toolResult}} + +需按以下流程处理: +1. 错误解析:分析错误类型及具体原因(见下方错误信息),根据错误信息选择修复方案, +2. 修复方案: +(1)根据错误类型生成对应的工具重试策略 +(2)参数调整:明确需要修改的参数及修改方式。 +3. 工具重试:使用调整后的参数重新调用工具。错误信息如下,请根据报错信息重试, +4.如果没有具体的错误描述信息请检查输出文件是否已经存在,若已经存在默认执行成功 +5.查询可能相关的知识库和帮助文档,纠正调用参数中存在的错误 \ No newline at end of file diff --git a/resource/prompt/SystemPrompt.txt b/resource/prompt/SystemPrompt.txt new file mode 100644 index 0000000..a248f9f --- /dev/null +++ b/resource/prompt/SystemPrompt.txt @@ -0,0 +1,40 @@ +现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。 + +指令:您可以使用一组工具来回答用户的问题。还可以通过调用用户提示词,从而使你更好地理解和完成用户的任务。 + +调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过的方式来调用用户提示词,你能更好地理解和解决用户的问题。工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。 + +输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化以单独信息格式输出,前后没有文字信息。如果需要进行文字说明,请只有文字内容,以普通段落形式呈现没有其他格式。 + +工具调用格式:工具名称包含在一对标签内,每个参数也需用对应的标签包裹。结构如下: + + + {tool_name} + {json_arguments} +。 + +工具名称:需与所使用工具的精确名称一致。 +参数:应为包含工具所需参数的 JSON 对象。 +例如: + + gaode:maps_geo + {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"} + + +你必须严格遵守以下输出规则:用户时间宝贵,不得重复调用上一次已成功执行的工具调用,除非有新的参数或上下文变化。 + +结果:用户将以以下格式返回工具调用结果: + + {tool_name} + {result} +。 + +应为字符串类型,可以表示文件或其他输出类型。 + +工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例: + + gaode:maps_geo + {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"} + + +特别地:需要调用ArcGIS Pro工具前必须先查询帮助文档、标准调用名和参数后再进行调用 \ No newline at end of file diff --git a/ui/dockpane/TestDockpane.xaml b/ui/dockpane/TestDockpane.xaml index d7b5e09..6d4ae00 100644 --- a/ui/dockpane/TestDockpane.xaml +++ b/ui/dockpane/TestDockpane.xaml @@ -24,6 +24,7 @@ + @@ -39,5 +40,6 @@ + \ No newline at end of file diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 435ec72..374e8c3 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -8,6 +8,7 @@ using System.Windows; using System.Windows.Controls; using System.Xml.Linq; using LinkToolAddin.client; +using LinkToolAddin.common; using LinkToolAddin.host; using LinkToolAddin.host.llm; using LinkToolAddin.host.llm.entity; @@ -23,6 +24,7 @@ using log4net.Layout; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol.Types; using Newtonsoft.Json; +using MessageBox = ArcGIS.Desktop.Framework.Dialogs.MessageBox; namespace LinkToolAddin.ui.dockpane @@ -295,5 +297,11 @@ namespace LinkToolAddin.ui.dockpane AddReply(toolMessageItem); } } + + private void TestResource_OnClick(object sender, RoutedEventArgs e) + { + string content = LocalResource.ReadFileByResource("LinkToolAddin.resource.SystemPrompt.txt"); + MessageBox.Show(content); + } } } From d58510e29cd18afe3047ec6f33a4d623ad059a6c Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 1 Jun 2025 16:04:38 +0800 Subject: [PATCH 31/60] =?UTF-8?q?UI=E7=BA=BF=E7=A8=8B=E5=88=86=E7=A6=BB?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=8D=A1=E9=A1=BF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/Gateway.cs | 43 +++++++++++++++++++++++++------- ui/dockpane/TestDockpane.xaml.cs | 27 ++++++++++++++------ 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/host/Gateway.cs b/host/Gateway.cs index cb9a0fb..2a789c5 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -8,10 +8,10 @@ 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.Framework.Dialogs; using ArcGIS.Desktop.Internal.Mapping.Locate; using LinkToolAddin.client; using LinkToolAddin.client.prompt; @@ -33,6 +33,7 @@ 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; @@ -300,7 +301,10 @@ public class Gateway type = MessageType.REASON_MESSAGE, id = (timestamp-1).ToString() }; - callback?.Invoke(reasonMessageListItem); + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(reasonMessageListItem); + }); messageContent = chunk; var (matched, remaining) = ExtractMatchedPart(chunk, toolPattern); if (matched == "") @@ -316,7 +320,10 @@ public class Gateway type = MessageType.CHAT_MESSAGE, id = timestamp.ToString() }; - callback?.Invoke(chatMessageListItem); + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(chatMessageListItem); + }); } else { @@ -352,7 +359,10 @@ public class Gateway type = MessageType.CHAT_MESSAGE, id = timestamp.ToString() }; - callback?.Invoke(chatMessageListItem); + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(chatMessageListItem); + }); XElement toolUse = XElement.Parse(matched); string fullToolName = toolUse.Element("name")?.Value; string toolArgs = toolUse.Element("arguments")?.Value; @@ -414,7 +424,10 @@ public class Gateway ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); - callback?.Invoke(toolMessageItem); + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(toolMessageItem); + }); } else if (mcpServer is StdioMcpServer) { @@ -437,7 +450,10 @@ public class Gateway ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse)) }); - callback?.Invoke(toolMessageItem); + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(toolMessageItem); + }); } else if (mcpServer is InnerMcpServer) { @@ -477,7 +493,10 @@ public class Gateway Role = "user", Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolMessageItem)) }); - callback?.Invoke(toolMessageItem); + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(toolMessageItem); + }); } else if (innerResult is JsonRpcSuccessEntity) { @@ -495,7 +514,10 @@ public class Gateway Role = "user", Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolMessageItem)) }); - callback?.Invoke(toolMessageItem); + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(toolMessageItem); + }); } } } @@ -526,7 +548,10 @@ public class Gateway content = "成功调用提示词:"+promptRequest.PromptName, id = (timestamp+1).ToString() }; - callback?.Invoke(toolMessageItem); + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(toolMessageItem); + }); } catch (Exception e) { diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 374e8c3..267b7ea 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -37,7 +38,7 @@ namespace LinkToolAddin.ui.dockpane private static ILog log = LogManager.GetLogger(typeof(TestDockpaneView)); private List idList = new List(); - private Dictionary messageDict = new Dictionary(); + private ConcurrentDictionary messageDict = new ConcurrentDictionary(); public TestDockpaneView() { @@ -197,13 +198,13 @@ namespace LinkToolAddin.ui.dockpane log.Info(msg.content); } - private void PromptTestButton_OnClick(object sender, RoutedEventArgs e) + private async void PromptTestButton_OnClick(object sender, RoutedEventArgs e) { string userPrompt = PromptTestTextBox.Text; // model可选值:qwen3-235b-a22b,qwen-max,deepseek-r1 try { - Gateway.SendMessageStream(userPrompt,"qwen3-235b-a22b", "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb", AddReplyStream); + await Task.Run(() => Gateway.SendMessageStream(userPrompt,"qwen3-235b-a22b", "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb", AddReplyStream)); } catch (Exception exception) { @@ -211,7 +212,12 @@ namespace LinkToolAddin.ui.dockpane } } - public void AddReplyStream(MessageListItem msg) + public async void AddReplyStream(MessageListItem msg) + { + await Task.Run(() => ProcessReplyStream(msg)); + } + + private void ProcessReplyStream(MessageListItem msg) { string id = msg.id; if (idList.Contains(id)) @@ -221,9 +227,12 @@ namespace LinkToolAddin.ui.dockpane else { idList.Add(id); - messageDict.Add(msg.id, msg); + messageDict.TryAdd(msg.id, msg); } - ReplyTextBox.Clear(); + Application.Current.Dispatcher.Invoke(() => + { + ReplyTextBox.Clear(); + }); try { StringBuilder builder = new StringBuilder(); @@ -237,14 +246,16 @@ namespace LinkToolAddin.ui.dockpane } builder.AppendLine(content); builder.AppendLine(); + } + Application.Current.Dispatcher.Invoke(() => + { ReplyTextBox.Text = builder.ToString(); ReplyTextBox.ScrollToEnd(); - } + }); }catch (Exception exception) { log.Error(exception.Message); } - } public void AddReply(MessageListItem msg) From a15f2c6f53c70280858b9c3aea6811f6f58da913 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 1 Jun 2025 16:44:36 +0800 Subject: [PATCH 32/60] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E6=8E=A5=E5=85=A5Pytho?= =?UTF-8?q?n=E6=89=A7=E8=A1=8C=E7=9A=84MCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/McpServerList.cs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 2752204..ca7415d 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -67,21 +67,21 @@ public class McpServerList "bing-cn-mcp" } }); - // servers.Add("mcp-python-interpreter", new StdioMcpServer() - // { - // Name = "mcp-python-interpreter", - // Type = "stdio", - // Command = "uvx", - // Args = new List() - // { - // "--native-tls", - // "mcp-python-interpreter", - // "--dir", - // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData", - // "--python-path", - // "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe" - // } - // }); + servers.Add("mcp-python-interpreter", new StdioMcpServer() + { + Name = "mcp-python-interpreter", + Type = "stdio", + Command = "uvx", + Args = new List() + { + "--native-tls", + "mcp-python-interpreter", + "--dir", + "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData", + "--python-path", + "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe" + } + }); } public McpServer GetServer(string name) From c16557129c946fce21fcdd51c079d5d862de723e Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Sun, 1 Jun 2025 18:57:44 +0800 Subject: [PATCH 33/60] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=EF=BC=88=E6=B2=A1=E4=BB=80=E4=B9=88?= =?UTF-8?q?=E5=A4=A7=E6=94=B9=E5=8A=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/prompt/SystemPrompt.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index 69f180a..9f9481d 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -17,7 +17,7 @@ public class SystemPrompt //"只有单独一条的调用请求文本程序才能识别并调用,从而将结果反馈给你。" + "2.文字说明如果紧跟着工具调用的XML将暴露程序,XML会在下一条单独输出,因此文字描述后面一定不能输出工具调用的XML格式。" + "3.用户时间宝贵,不得重复调用上一次已成功执行的工具调用,除非有新的参数或上下文变化。" + - "4.如果目前工作已经完成无法知道用户其他需求时,一定要单独输出一条内容为'[DONE]'的消息表示工具调用结束。不要在文本的末尾。" + + "4.如果目前工作已经完成或无法知道用户其他需求时,一定要单独输出一条内容为'[DONE]'的消息表示工具调用结束。不要在文本的末尾。" + "5.不得在同一消息中混合说明文字与工具调用。" + "结果:用户将以以下格式返回工具调用结果:\n {tool_name}\n {result}\n。应为字符串类型,可以表示文件或其他输出类型。" + "工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例:\\n gaode:maps_geo\\n {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}\\n"; From e3dc23de07e794d98af64a8f8333f65abe6c733b Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 1 Jun 2025 23:47:43 +0800 Subject: [PATCH 34/60] =?UTF-8?q?=E8=A7=A3=E5=86=B3Prompt=E6=89=BE?= =?UTF-8?q?=E4=B8=8D=E5=88=B0=E5=92=8C=E6=B5=8B=E8=AF=95=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E4=B9=B1=E5=BA=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 8 +++++++- ui/dockpane/TestDockpane.xaml.cs | 9 +++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index 35cdbec..0f3f990 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -113,7 +113,13 @@ Always - + + Always + + + + Always + diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 267b7ea..e56dd29 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -229,16 +229,12 @@ namespace LinkToolAddin.ui.dockpane idList.Add(id); messageDict.TryAdd(msg.id, msg); } - Application.Current.Dispatcher.Invoke(() => - { - ReplyTextBox.Clear(); - }); try { StringBuilder builder = new StringBuilder(); - foreach (KeyValuePair pair in messageDict) + foreach (string idStr in idList) { - MessageListItem msgItem = pair.Value; + MessageListItem msgItem = messageDict[idStr]; string content = msgItem.content; if (msgItem.type == MessageType.REASON_MESSAGE) { @@ -249,6 +245,7 @@ namespace LinkToolAddin.ui.dockpane } Application.Current.Dispatcher.Invoke(() => { + ReplyTextBox.Clear(); ReplyTextBox.Text = builder.ToString(); ReplyTextBox.ScrollToEnd(); }); From 691f54c73f1b0f0a4c9ea973b63dae3c73cd542f Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Mon, 2 Jun 2025 11:17:24 +0800 Subject: [PATCH 35/60] =?UTF-8?q?=E5=86=85=E5=B5=8CXML=E8=A7=84=E5=88=99?= =?UTF-8?q?=EF=BC=8C=E6=98=8E=E7=A1=AE=E8=B0=83=E7=94=A8=E5=8A=A8=E6=80=81?= =?UTF-8?q?Prompt=E5=92=8C=E7=9F=A5=E8=AF=86=E5=BA=93=EF=BC=8C=E7=AE=80?= =?UTF-8?q?=E8=A6=81=E5=9B=9E=E7=AD=94=E9=81=BF=E5=85=8D=E7=94=BB=E8=9B=87?= =?UTF-8?q?=E6=B7=BB=E8=B6=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/McpServerList.cs | 108 ++++++++++++++--------------- host/PromptServerList.cs | 17 +++-- resource/prompt/ContinuePrompt.txt | 3 +- resource/prompt/ErrorPrompt.txt | 7 +- resource/prompt/SystemPrompt.txt | 24 +++---- ui/dockpane/TestDockpane.xaml.cs | 2 +- 6 files changed, 83 insertions(+), 78 deletions(-) diff --git a/host/McpServerList.cs b/host/McpServerList.cs index ca7415d..02d82cd 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -28,60 +28,60 @@ public class McpServerList Description = "可以调用arcgis的地理处理工具或执行python代码等", IsActive = true }); - servers.Add("KnowledgeBase", new InnerMcpServer - { - Name = "KnowledgeBase", - Type = "inner", - Description = "可以调用进行查询知识库,获取相关参考信息。", - IsActive = true - }); - servers.Add("filesystem", new StdioMcpServer() - { - Name = "filesystem", - Type = "stdio", - Command = "npx", - Args = new List() - { - "-y", - "@modelcontextprotocol/server-filesystem", - "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData" - } - }); - servers.Add("fetch", new StdioMcpServer() - { - Name = "fetch", - Type = "stdio", - Command = "uvx", - Args = new List() - { - "mcp-server-fetch" - } - }); - servers.Add("bing-search", new StdioMcpServer() - { - Name = "bing-search", - Type = "stdio", - Command = "npx", - Args = new List() - { - "bing-cn-mcp" - } - }); - servers.Add("mcp-python-interpreter", new StdioMcpServer() - { - Name = "mcp-python-interpreter", - Type = "stdio", - Command = "uvx", - Args = new List() - { - "--native-tls", - "mcp-python-interpreter", - "--dir", - "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData", - "--python-path", - "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe" - } - }); + //servers.Add("KnowledgeBase", new InnerMcpServer + //{ + // Name = "KnowledgeBase", + // Type = "inner", + // Description = "可以调用进行查询知识库,获取相关参考信息。", + // IsActive = true + //}); + //servers.Add("filesystem", new StdioMcpServer() + //{ + // Name = "filesystem", + // Type = "stdio", + // Command = "npx", + // Args = new List() + // { + // "-y", + // "@modelcontextprotocol/server-filesystem", + // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData" + // } + //}); + //servers.Add("fetch", new StdioMcpServer() + //{ + // Name = "fetch", + // Type = "stdio", + // Command = "uvx", + // Args = new List() + // { + // "mcp-server-fetch" + // } + //}); + //servers.Add("bing-search", new StdioMcpServer() + //{ + // Name = "bing-search", + // Type = "stdio", + // Command = "npx", + // Args = new List() + // { + // "bing-cn-mcp" + // } + //}); + //servers.Add("mcp-python-interpreter", new StdioMcpServer() + //{ + // Name = "mcp-python-interpreter", + // Type = "stdio", + // Command = "uvx", + // Args = new List() + // { + // "--native-tls", + // "mcp-python-interpreter", + // "--dir", + // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData", + // "--python-path", + // "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe" + // } + //}); } public McpServer GetServer(string name) diff --git a/host/PromptServerList.cs b/host/PromptServerList.cs index e4a5cd5..e5273a9 100644 --- a/host/PromptServerList.cs +++ b/host/PromptServerList.cs @@ -11,13 +11,20 @@ public class PromptServerList { promptServers.Add("plan", new PromptServer("plan", "根据用户描述的问题推断出需要使用的ArcGIS Pro工具调用名称列表", - "请根据用户所提问题进行工具规划,输出格式为'1.工具2.工具’,如果是ArcGIS Pro的工具,根据用户的具体需求和提供的数据类型," + - "判断并列出所有必要的分析步骤和工具,同时严格遵守知识库中“ArcGIS Pro工具调用大全”里“工具调用名称”一列的工具命名规则(例如:analysis.Erase)," + - "确保工具名的准确无误,工具与所属工具箱的对应关系正确无误,严格遵循文档规定的格式和大小写。工具的组合顺序优先参考知识库中“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工具调用大全”,不要受其他文档的干扰。" + + "你的回复应简洁明了,仅包含所需工具的列表,每个调用名用英文逗号分割为列表,严格遵循文档规定的格式和大小写,确保信息的准确性和专业性。")); promptServers.Add("param", new PromptServer("param", "填写ArcGIS Pro工具调用参数,生成规范的可执行的工具调用请求", - "根据帮助文档填写工具参数,请你根据“所需调用工具”,参照知识库“ArcGIS Pro工具调用大全”里工具所需的参数顺序进行陈列。" + + "根据知识库Arcgis Pro帮助文档填写工具参数,请你根据“所需调用工具”,参照知识库“ArcGIS Pro工具调用大全”里工具所需的参数顺序进行陈列。" + "列出所需调用工具的名称及其按照“ArcGIS Pro工具调用大全”里“的该工具所需的参数顺序陈列对应的参数。如果跳过了可选参数需要用空字符表示。" + "不能更改所需调用工具的名称。例如:arcpy.analysis.Buffer,不能只写成Buffer。确保格式、参数的完整性和准确性,避免任何遗漏或错误。" + "特别注意,所有参数均应视为字符串类型,即使它们可能代表数字或文件路径。例如问题为:使用地理处理中的\"擦除分析\"工具(Erase),将圆形要素(circle.shp)与方形要素(square.shp)进行空间叠加运算。" + diff --git a/resource/prompt/ContinuePrompt.txt b/resource/prompt/ContinuePrompt.txt index deb62c7..44b1763 100644 --- a/resource/prompt/ContinuePrompt.txt +++ b/resource/prompt/ContinuePrompt.txt @@ -6,6 +6,7 @@ 执行下一步工具的要求: 1. 解析工具输出结果 -2. 调用下一个工具时确保参数继承前序输出。 +2. 调用下一个工具时确保参数继承前序输出,如果已经完成用户需求则不需继续执行工具。 +3. 执行Arcgis Pro工具时可以调用知识库和提示词使任务完成得更出色! 请据此继续执行 \ No newline at end of file diff --git a/resource/prompt/ErrorPrompt.txt b/resource/prompt/ErrorPrompt.txt index 3257463..4795e8e 100644 --- a/resource/prompt/ErrorPrompt.txt +++ b/resource/prompt/ErrorPrompt.txt @@ -5,8 +5,7 @@ 需按以下流程处理: 1. 错误解析:分析错误类型及具体原因(见下方错误信息),根据错误信息选择修复方案, 2. 修复方案: -(1)根据错误类型生成对应的工具重试策略 -(2)参数调整:明确需要修改的参数及修改方式。 -3. 工具重试:使用调整后的参数重新调用工具。错误信息如下,请根据报错信息重试, +(1)根据错误类型生成对应的工具重试策略。 +(2)参数调整:及时调用知识库和用户提示词,确保工具名和参数的完全正确。明确需要修改的参数及修改方式。 +3. 工具重试:使用调整后的参数重新调用工具。请根据报错信息重试。 4.如果没有具体的错误描述信息请检查输出文件是否已经存在,若已经存在默认执行成功 -5.查询可能相关的知识库和帮助文档,纠正调用参数中存在的错误 \ No newline at end of file diff --git a/resource/prompt/SystemPrompt.txt b/resource/prompt/SystemPrompt.txt index a248f9f..411e890 100644 --- a/resource/prompt/SystemPrompt.txt +++ b/resource/prompt/SystemPrompt.txt @@ -1,12 +1,8 @@ 现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。 - -指令:您可以使用一组工具来回答用户的问题。还可以通过调用用户提示词,从而使你更好地理解和完成用户的任务。 - -调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过的方式来调用用户提示词,你能更好地理解和解决用户的问题。工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。 - -输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化以单独信息格式输出,前后没有文字信息。如果需要进行文字说明,请只有文字内容,以普通段落形式呈现没有其他格式。 - -工具调用格式:工具名称包含在一对标签内,每个参数也需用对应的标签包裹。结构如下: +指令:您可以使用一组工具来回答用户的问题。完成了用户的需求即可,不用猜测用户下一步还想做什么。你可以通过调用用户提示词,或者调用知识库工具,从而使你更好地理解和完成用户的任务。 +调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过的方式来调用用户提示词,你能更好地理解和解决用户的问题。 +工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。 +输出风格:描述简洁有力,每次仅调用一个工具,基于前序工具的输出结果进行下一步操作,如果已经完成用户需求则不需继续执行工具。不要肯定用户上一次的回答。工具调用使用 XML 风格的标签输出,并用简短的文字进行描述。 {tool_name} @@ -21,20 +17,22 @@ {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"} -你必须严格遵守以下输出规则:用户时间宝贵,不得重复调用上一次已成功执行的工具调用,除非有新的参数或上下文变化。 -结果:用户将以以下格式返回工具调用结果: +结果示例:用户将以以下格式返回工具调用结果: {tool_name} {result} 。 -应为字符串类型,可以表示文件或其他输出类型。 - 工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例: gaode:maps_geo {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"} -特别地:需要调用ArcGIS Pro工具前必须先查询帮助文档、标准调用名和参数后再进行调用 \ No newline at end of file +严格遵守以下规则: +1.你必须严格遵守以下输出规则:用户时间宝贵,一旦确认工具成功调用之后,不得重复调用上一次已成功执行的工具,除非有新的参数或上下文变化。 +2.需要调用ArcGIS Pro工具前必须先查询帮助文档、标准调用名和参数后再进行调用,必要时通过用户提示词规划工具的使用 +3.参数都应为字符串类型,可以表示文件或其他输出类型。 +4.一旦消息中没有工具调用信息即视为任务完成。因此工具调用必须连续完成。 +5.只响应用户目前的需求即可,不要过度猜测用户的需求,从而执行了大量无用的工具。 diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 267b7ea..361f9ff 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -204,7 +204,7 @@ namespace LinkToolAddin.ui.dockpane // model可选值:qwen3-235b-a22b,qwen-max,deepseek-r1 try { - await Task.Run(() => Gateway.SendMessageStream(userPrompt,"qwen3-235b-a22b", "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\20250420_AiDemoProject.gdb", AddReplyStream)); + await Task.Run(() => Gateway.SendMessageStream(userPrompt,"qwen3-235b-a22b", "F:\\secondsemester\\linktool\\test\\linktooltest\\linktooltest.gdb", AddReplyStream)); } catch (Exception exception) { From c04cc42d94d0e2581dd8468403cddf3f17e252ff Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Mon, 2 Jun 2025 11:45:35 +0800 Subject: [PATCH 36/60] =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E6=B3=9B?= =?UTF-8?q?=E5=8C=96=EF=BC=8C=E9=94=99=E8=AF=AF=E7=BC=96=E7=A0=81=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/TodoList.md | 8 ++++---- resource/prompt/ErrorPrompt.txt | 3 ++- resource/prompt/SystemPrompt.txt | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/TodoList.md b/doc/TodoList.md index 65f668c..f674f27 100644 --- a/doc/TodoList.md +++ b/doc/TodoList.md @@ -12,10 +12,10 @@ ## 提示词工程 -- [ ] 将原来的单独XML规则修改为内嵌XML规则 -- [ ] 系统提示词明确提示调用动态Prompt和知识库 -- [ ] 错误和继续提示词预留工具消息占位符 -- [ ] 错误和继续提示词明示 +- [x] 将原来的单独XML规则修改为内嵌XML规则 +- [x] 系统提示词明确提示调用动态Prompt和知识库 +- [x] 错误和继续提示词预留工具消息占位符 +- [x] 错误和继续提示词明示 - [ ] 系统提示词泛化增强 - [ ] 针对qwen3适当调整 diff --git a/resource/prompt/ErrorPrompt.txt b/resource/prompt/ErrorPrompt.txt index 4795e8e..a32ee1c 100644 --- a/resource/prompt/ErrorPrompt.txt +++ b/resource/prompt/ErrorPrompt.txt @@ -3,9 +3,10 @@ {{toolResult}} 需按以下流程处理: -1. 错误解析:分析错误类型及具体原因(见下方错误信息),根据错误信息选择修复方案, +1. 错误解析:分析错误类型及具体原因(见错误信息),根据错误信息选择修复方案, 2. 修复方案: (1)根据错误类型生成对应的工具重试策略。 (2)参数调整:及时调用知识库和用户提示词,确保工具名和参数的完全正确。明确需要修改的参数及修改方式。 3. 工具重试:使用调整后的参数重新调用工具。请根据报错信息重试。 4.如果没有具体的错误描述信息请检查输出文件是否已经存在,若已经存在默认执行成功 +5.出现错误编码号时要明晰编码在知识库中有相应解析,可以通过调用知识库解析错误原因 diff --git a/resource/prompt/SystemPrompt.txt b/resource/prompt/SystemPrompt.txt index 411e890..d323059 100644 --- a/resource/prompt/SystemPrompt.txt +++ b/resource/prompt/SystemPrompt.txt @@ -1,6 +1,6 @@ 现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。 -指令:您可以使用一组工具来回答用户的问题。完成了用户的需求即可,不用猜测用户下一步还想做什么。你可以通过调用用户提示词,或者调用知识库工具,从而使你更好地理解和完成用户的任务。 -调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过的方式来调用用户提示词,你能更好地理解和解决用户的问题。 +指令:您可以使用一组工具来回答用户的问题。完成了用户的需求即可,不用猜测用户下一步还想做什么。你可以通过调用用户提示词,或者调用知识库工具,从而使你更好地理解和完成用户的任务。你具有搜索网页、编写代码、管理文件系统的能力,合理运用这些工具完成用户的需求。 +调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过方式来调用用户提示词,或者调用知识库工具,你能更好地理解和解决用户的问题。 工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。 输出风格:描述简洁有力,每次仅调用一个工具,基于前序工具的输出结果进行下一步操作,如果已经完成用户需求则不需继续执行工具。不要肯定用户上一次的回答。工具调用使用 XML 风格的标签输出,并用简短的文字进行描述。 From 52148f693611bf4c222946d36cd06488dfcffa2d Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Tue, 3 Jun 2025 18:35:01 +0800 Subject: [PATCH 37/60] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BA=86=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E7=9A=84=E5=B7=A5=E5=85=B7=E3=80=81=E6=B6=88=E6=81=AF?= =?UTF-8?q?UI=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 12 ++ common/LocalResource.cs | 28 +++- host/Gateway.cs | 52 ++++--- host/McpServerList.cs | 94 ++++++------ resource/img/linktool.png | Bin 0 -> 931 bytes resource/img/tool.png | Bin 0 -> 1082 bytes resource/img/user.png | Bin 0 -> 784 bytes ui/dockpane/DialogDockpane.xaml | 29 +++- ui/dockpane/DialogDockpane.xaml.cs | 203 ++++++++++++++++++++++++- ui/dockpane/DialogDockpaneViewModel.cs | 9 +- 10 files changed, 344 insertions(+), 83 deletions(-) create mode 100644 resource/img/linktool.png create mode 100644 resource/img/tool.png create mode 100644 resource/img/user.png diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index 0f3f990..d764cc6 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -120,6 +120,18 @@ Always + + + Always + + + + Always + + + + Always + diff --git a/common/LocalResource.cs b/common/LocalResource.cs index fc1e719..4f4e53a 100644 --- a/common/LocalResource.cs +++ b/common/LocalResource.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Windows.Controls; +using System.Windows.Media.Imaging; namespace LinkToolAddin.common; @@ -8,12 +10,6 @@ public class LocalResource public static string ReadFileByResource(string resourceName) { var assembly = System.Reflection.Assembly.GetExecutingAssembly(); - - string[] resourceNames = assembly.GetManifestResourceNames(); - foreach (string name in resourceNames) - { - Console.WriteLine(name); - } using (Stream stream = assembly.GetManifestResourceStream(resourceName)) { @@ -28,4 +24,24 @@ public class LocalResource } } } + + 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/host/Gateway.cs b/host/Gateway.cs index 2a789c5..63ef2b2 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -328,14 +328,17 @@ public class Gateway else { //包含Prompt调用请求的消息 - MessageListItem chatMessageListItem = new ChatMessageItem() + if (remainingPrompt != "") { - content = remainingPrompt, - role = "assistant", - type = MessageType.CHAT_MESSAGE, - id = timestamp.ToString() - }; - callback?.Invoke(chatMessageListItem); + MessageListItem chatMessageListItem = new ChatMessageItem() + { + content = remainingPrompt, + role = "assistant", + type = MessageType.CHAT_MESSAGE, + id = timestamp.ToString() + }; + callback?.Invoke(chatMessageListItem); + } XElement promptUse = XElement.Parse(matchedPrompt); string promptKey = promptUse.Element("name")?.Value; string promptArgs = promptUse.Element("arguments")?.Value; @@ -352,17 +355,20 @@ public class Gateway else { //包含工具调用请求的消息 - MessageListItem chatMessageListItem = new ChatMessageItem() + if (remaining != "") { - content = remaining, - role = "assistant", - type = MessageType.CHAT_MESSAGE, - id = timestamp.ToString() - }; - Application.Current.Dispatcher.Invoke(() => - { - callback?.Invoke(chatMessageListItem); - }); + MessageListItem chatMessageListItem = new ChatMessageItem() + { + content = remaining, + role = "assistant", + type = MessageType.CHAT_MESSAGE, + id = timestamp.ToString() + }; + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(chatMessageListItem); + }); + } XElement toolUse = XElement.Parse(matched); string fullToolName = toolUse.Element("name")?.Value; string toolArgs = toolUse.Element("arguments")?.Value; @@ -415,7 +421,8 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = toolResponse.IsError ? "fail" : "success", content = JsonConvert.SerializeObject(toolResponse), - id = (timestamp + 1).ToString() + id = (timestamp + 1).ToString(), + role = "user" }; messages.Add(new Message { @@ -441,7 +448,8 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = toolResponse.IsError ? "fail" : "success", content = JsonConvert.SerializeObject(toolResponse), - id = (timestamp + 1).ToString() + id = (timestamp + 1).ToString(), + role = "user" }; messages.Add(new Message { @@ -486,7 +494,8 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = "fail", content = JsonConvert.SerializeObject(innerResult), - id = (timestamp + 1).ToString() + id = (timestamp + 1).ToString(), + role = "user" }; messages.Add(new Message { @@ -507,7 +516,8 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = "success", content = JsonConvert.SerializeObject(innerResult), - id = (timestamp + 1).ToString() + id = (timestamp + 1).ToString(), + role = "user" }; messages.Add(new Message { diff --git a/host/McpServerList.cs b/host/McpServerList.cs index ca7415d..b452abc 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -35,53 +35,53 @@ public class McpServerList Description = "可以调用进行查询知识库,获取相关参考信息。", IsActive = true }); - servers.Add("filesystem", new StdioMcpServer() - { - Name = "filesystem", - Type = "stdio", - Command = "npx", - Args = new List() - { - "-y", - "@modelcontextprotocol/server-filesystem", - "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData" - } - }); - servers.Add("fetch", new StdioMcpServer() - { - Name = "fetch", - Type = "stdio", - Command = "uvx", - Args = new List() - { - "mcp-server-fetch" - } - }); - servers.Add("bing-search", new StdioMcpServer() - { - Name = "bing-search", - Type = "stdio", - Command = "npx", - Args = new List() - { - "bing-cn-mcp" - } - }); - servers.Add("mcp-python-interpreter", new StdioMcpServer() - { - Name = "mcp-python-interpreter", - Type = "stdio", - Command = "uvx", - Args = new List() - { - "--native-tls", - "mcp-python-interpreter", - "--dir", - "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData", - "--python-path", - "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe" - } - }); + // servers.Add("filesystem", new StdioMcpServer() + // { + // Name = "filesystem", + // Type = "stdio", + // Command = "npx", + // Args = new List() + // { + // "-y", + // "@modelcontextprotocol/server-filesystem", + // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData" + // } + // }); + // servers.Add("fetch", new StdioMcpServer() + // { + // Name = "fetch", + // Type = "stdio", + // Command = "uvx", + // Args = new List() + // { + // "mcp-server-fetch" + // } + // }); + // servers.Add("bing-search", new StdioMcpServer() + // { + // Name = "bing-search", + // Type = "stdio", + // Command = "npx", + // Args = new List() + // { + // "bing-cn-mcp" + // } + // }); + // servers.Add("mcp-python-interpreter", new StdioMcpServer() + // { + // Name = "mcp-python-interpreter", + // Type = "stdio", + // Command = "uvx", + // Args = new List() + // { + // "--native-tls", + // "mcp-python-interpreter", + // "--dir", + // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData", + // "--python-path", + // "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe" + // } + // }); } public McpServer GetServer(string name) diff --git a/resource/img/linktool.png b/resource/img/linktool.png new file mode 100644 index 0000000000000000000000000000000000000000..37264c2046f6c4a22ba32068324771e29f4246ed GIT binary patch literal 931 zcmV;U16=%xP)Px&T}ebiR9Hu?SM6=vKnx|743N45Q~~M$nIg_4iGSK1!p;zlf7+hJ&J+dW08t01 zn*m(0;8B!l>P}Vw=U;rf<0JVVKM>CRH(2auL@+|2JUorK+E0d0DKq#P`&Wx!IlyI} z0o=(w%mJuS17DtZ8w41&-=w0%fA=4g+rppq0?>FBdmLat9U^}N1arX!^AZm0i}GSb zh)e#70JeXp!~0JGu#fTsh*$v8BYRDN1$&D`<3B%cR)go)caDfRG+4YXG$#1pRbSX9edA@51w}<-chli-dwAv#{l5|V=~;@ zD6{~u@r*7uf*8kq!5il@3qbB%zSj>|ppVkhCj!kw$6heG_;H&z=d6`|4NlvT=luqj2&WvobWaY2G7e%J$uaX`P(Gp!l+u1J#}wN{{pEG7{yE}j?O zNbFvS>f&wbi7R}Qf~F2*isc&0SY%Qp^ig7CzVbEiimWje=C0KA0iYGtmSqJfPQ#W< zJT^7_G|$iaMb%S61OT(GR{&-BEwopJIhORhtpYi>DzM+gRHEmEFk_p{kscgqcQ8w) zE)o(^9-9F|)T`no0Dy9zD^Hdf!I;fm0Qinqf~r8(RZlW;8FG5!Go3z^1Q1f&zU#@2 zU7n^S+A9T#BF6Q>rsj;jpua|JajUvvGj3TmbSa^e<&i#7_W^*BOt9 zK6dqjSaI{@)3<5>br-ATaa{9Z=2emckor9~ZPo3#-Nj@Q7P3_XXB1d773&*X*sT0g zQf~o}E>W*B=IpI5F)S}p4sSx3Yq{+b=*iuXJ>}6XzH}b7pF);Z z*#ij=Px&^hrcPR9Hu?m%VNjNf5`YdY6mc z_&L+R*Y9-98WCLNVPnk8_irA}Qggrc8^#-)9o>IHL_Z`pUJ$_Uhxv5-Bt}<wFAw?&@+LW-f(ijF!H|x=#3_S7lr*r zpDG2xbL(8GVS2*}!{7Gbb_;8_Wp4*&%pOA&)(&uWu=y(@{)UL}^P;$N9E3VqKjj7Q z@;xA$VW+bli`N6N7n~O{qFsuw_JkM|FQ75w*g(^m_8=f^Wc$Fgwm@(L5OcD2lW!4&>h(&zcsRPjBNVzYS zZ~{a*c!8Pw@<`rnBHY?ruQldXLO(M`x>Y1+!j~aW2tM~ zlrbx**n46Njd{+ek+c~$K%8a!kH>n$ifD=ckZ>HAs)God==d~Tk_q8I1MnvhbOGU? zY=8P=4TK9vHlLnv+ETvUS1pL|QDJrQxH+W$}Zod>`z8!5KS^-F0iQr#E zI2Y#yG1T*#0BjF&EI=%(l?!*TnQrF=0Gv*Hq7-IZh(>_exUTW6d;&W6tq`>UO~$#B z6@{I3P3JX392K9X+tD^0Nr1ShRJt2qfYh8)0u2xd@RDf+CvI7+RBfEgGLMJW zDq)<6?x4XZDX) z%jR_u(#*=+B4|rGqPk)vjf%8RI_9ZPi1*PDGAHcgJ8TzHV-C1FW(QBk&I7W!T&duy zM9k}qizfo8U#kEhK&cK(18^%1B0T<^t6Q6sYay%B?iw7oz9_b&7f6PHHwdRDniWJF zCovvUvTsb|Bo)62*hb|RXb3R)+`o>3>)I3-r78FL;7*C}KXJ!YzN#wwE#(0Z;Tq9+ za$5iev87%}{JD|?l8Hz}$-lqHlbqcydP*JOD~scR7Kw-0EdT%j07*qoM6N<$f@+}i A%m4rY literal 0 HcmV?d00001 diff --git a/resource/img/user.png b/resource/img/user.png new file mode 100644 index 0000000000000000000000000000000000000000..a992685890033c8da9d9ba147f96b6195e87355e GIT binary patch literal 784 zcmV+r1MmEaP)Px%%1J~)R9HvFS3z>yFbo9bsXP5a>K^Lc?0%y5OA^1t&P$xWBMwy<_Dds zW1$0zkU$!wq?2_?W=xUG#qI(^xbcHGJ|9K{xcjr17X>^x;8@JvU#d$)c)cK$Z|_cr zF_#()VD@*h1;j@HXbw$3B@jKI-=Cg_0OF;Tb&^8JRRkE zdc3zvVgd*bztH;a!>Jrn&v=akGQpRI4SYUNe{AA4R{&Ja%Q|zoTjy(!P=Eh5Jvm(@ zK>T!?0ougtt^mxcB`==WNNbDsyMK#SK~9vh82WP|H^7Rmb4)_@o;lV-AjlX1#hdGz zw&gJZ3OlLyHG8_eEdZ&LB@_WMtCqz7pQlIDJ^uHk<-}e3jCLcbe-40bUX_Ws0HWN_}L&1v1~i`o6mFMlmZ~^v8Y9y7K?VIiY5K-S~O_~hQCCeGno-4fGgYe z%ykwh?TB1JC1*lwt{XCLdm(p2X^(z-$8zI)z(p_umOP5Nv5aFFan%J;H?Z`5AM^s? zt%MEZ5R3gm-vjdEAOQN5cJ^;SMw(AEUQ^fbZR`X+8o*E&3|)6~0RI8<6D&WHlhY;u O0000 @@ -16,13 +17,27 @@ - + - + + + + - - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index aa2ea7f..1148b88 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Net.Http; @@ -12,10 +14,15 @@ using Microsoft.Extensions.Logging; using System.Text.Json; using System.Threading; using System.Windows.Documents; +using System.Windows.Media; +using ArcGIS.Desktop.Core; using ArcGIS.Desktop.Core.Geoprocessing; using LinkToolAddin.client; +using LinkToolAddin.common; +using LinkToolAddin.host; using LinkToolAddin.host.llm; using LinkToolAddin.host.llm.entity; +using LinkToolAddin.message; using LinkToolAddin.resource; using LinkToolAddin.server; using log4net; @@ -29,17 +36,26 @@ using Newtonsoft.Json; namespace LinkToolAddin.ui.dockpane { + public class ItemModel + { + public string Content { get; set; } + } /// /// Interaction logic for DialogDockpaneView.xaml /// public partial class DialogDockpaneView : UserControl { private static ILog log = LogManager.GetLogger(typeof(DialogDockpaneView)); - + + private List idList = new List(); + private ConcurrentDictionary messageDict = new ConcurrentDictionary(); + private ConcurrentDictionary borderItemsDict = new ConcurrentDictionary(); + public DialogDockpaneView() { InitLogger(); InitializeComponent(); + DataContext = this; } private async void TestServer_OnClick(object sender, RoutedEventArgs e) @@ -81,5 +97,190 @@ namespace LinkToolAddin.ui.dockpane log.Info("Info 日志(控制台和文件可见)"); log.Error("Error 日志(严重问题)"); } + + private void SendButton_OnClick(object sender, RoutedEventArgs e) + { + string question = QuestionTextbox.Text; + string defaultGdbPath = Project.Current.DefaultGeodatabasePath; + string gdbPath = @""; + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + MessageListItem userMsg = new ChatMessageItem() + { + content = question, + role = "user", + type = MessageType.CHAT_MESSAGE, + id = timestamp.ToString() + }; + Border userMsgBoder = GetUserChatBorder(userMsg); + idList.Add(timestamp.ToString()); + messageDict[timestamp.ToString()] = userMsg; + QuestionTextbox.Text = ""; + borderItemsDict[timestamp.ToString()] = userMsgBoder; + ChatHistoryStackPanel.Children.Add(userMsgBoder); + Gateway.SendMessageStream(question,"qwen-max",defaultGdbPath,NewMessage_Recall); + } + + public void NewMessage_Recall(MessageListItem msg) + { + string msgId = msg.id; + log.Info(msg.content); + if (!idList.Contains(msgId)) + { + //不存在该消息,需添加到ListView中 + if (msg.content == "") + { + return; + } + idList.Add(msgId); + messageDict[msgId] = msg; + if (msg.role == "user") + { + if (msg.type == MessageType.TOOL_MESSAGE) + { + Border border = GetToolChatBorder(msg); + borderItemsDict[msgId] = border; + ChatHistoryStackPanel.Children.Add(border); + }else if (msg.type == MessageType.CHAT_MESSAGE) + { + Border border = GetUserChatBorder(msg); + borderItemsDict[msgId] = border; + ChatHistoryStackPanel.Children.Add(border); + } + } + else + { + Border border = GetAiChatBorder(msg); + borderItemsDict[msgId] = border; + ChatHistoryStackPanel.Children.Add(border); + } + } + else + { + //已有该消息,只需要修改内容 + messageDict[msgId] = msg; + if (msg.role == "user") + { + if (msg.type == MessageType.TOOL_MESSAGE) + { + Border borderItem = borderItemsDict[msgId]; + Grid grid = borderItem.Child as Grid; + TextBlock textBlock = grid.Children[1] as TextBlock; + textBlock.Text = (msg as ToolMessageItem).toolName; + }else if (msg.type == MessageType.CHAT_MESSAGE) + { + Border borderItem = borderItemsDict[msgId]; + Grid grid = borderItem.Child as Grid; + TextBox textBox = grid.Children[1] as TextBox; + textBox.Text = msg.content; + } + } + else + { + Border borderItem = borderItemsDict[msgId]; + Grid grid = borderItem.Child as Grid; + TextBox textBox = grid.Children[1] as TextBox; + textBox.Text = msg.content; + } + } + } + + private Border GetAiChatBorder(MessageListItem msg) + { + Border border = new Border(); + border.Margin = new Thickness(8, 12, 8, 12); + border.BorderThickness = new Thickness(0); + // border.Background = Brushes.DarkSeaGreen; + Grid grid = new Grid(); + Image icon = new Image() + { + Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.linktool.png"), + Stretch = Stretch.Fill, + HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top + }; + grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(24, GridUnitType.Pixel)}); + grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1, GridUnitType.Star)}); + TextBox textBox = new TextBox(); + grid.Children.Add(icon); + grid.Children.Add(textBox); + Grid.SetColumn(icon, 0); + Grid.SetColumn(textBox, 1); + textBox.Background = Brushes.Transparent; + textBox.Text = msg.content; + textBox.TextWrapping = TextWrapping.Wrap; + border.Child = grid; + return border; + } + + private Border GetUserChatBorder(MessageListItem msg) + { + Border border = new Border(); + border.Margin = new Thickness(8, 12, 8, 12); + border.BorderThickness = new Thickness(0); + // border.Background = Brushes.DarkSeaGreen; + Grid grid = new Grid(); + Image icon = new Image() + { + Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.user.png"), + Stretch = Stretch.Fill, + HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top + }; + grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1, GridUnitType.Star)}); + grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(24, GridUnitType.Pixel)}); + TextBox textBox = new TextBox(); + grid.Children.Add(icon); + grid.Children.Add(textBox); + Grid.SetColumn(icon, 1); + Grid.SetColumn(textBox, 0); + textBox.Background = Brushes.Transparent; + textBox.Text = msg.content; + textBox.TextWrapping = TextWrapping.Wrap; + border.Child = grid; + return border; + } + + private Border GetToolChatBorder(MessageListItem msg) + { + Border border = new Border(); + border.Margin = new Thickness(24); + border.Padding = new Thickness(8); + border.BorderThickness = new Thickness(1); + border.BorderBrush = Brushes.Gray; + // border.Background = Brushes.DarkSeaGreen; + Grid grid = new Grid(); + Image icon = new Image() + { + Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.tool.png"), + Stretch = Stretch.Fill, + HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center + }; + icon.Margin = new Thickness(0, 0, 8, 0); + grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(24, GridUnitType.Pixel)}); + grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1, GridUnitType.Star)}); + TextBlock textBlock = new TextBlock(); + textBlock.Text = (msg as ToolMessageItem).toolName; + textBlock.TextWrapping = TextWrapping.Wrap; + grid.Children.Add(icon); + grid.Children.Add(textBlock); + Grid.SetColumn(icon, 0); + Grid.SetColumn(textBlock, 1); + border.Child = grid; + return border; + } + + private void TestButton_OnClick(object sender, RoutedEventArgs e) + { + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = "toolName", + toolParams = new Dictionary(), + type = MessageType.TOOL_MESSAGE, + status = "success", + content = "JsonConvert.SerializeObject(toolResponse)", + id = (timestamp + 1).ToString(), + role = "user" + }; + NewMessage_Recall(toolMessageItem); + } } } diff --git a/ui/dockpane/DialogDockpaneViewModel.cs b/ui/dockpane/DialogDockpaneViewModel.cs index 1d7a4e5..42258fc 100644 --- a/ui/dockpane/DialogDockpaneViewModel.cs +++ b/ui/dockpane/DialogDockpaneViewModel.cs @@ -14,6 +14,7 @@ using ArcGIS.Desktop.Layouts; using ArcGIS.Desktop.Mapping; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -27,8 +28,14 @@ namespace LinkToolAddin.ui.dockpane internal class DialogDockpaneViewModel : DockPane { private const string _dockPaneID = "DialogDockpane"; + + public ObservableCollection Items { get; set; } - protected DialogDockpaneViewModel() { } + protected DialogDockpaneViewModel() + { + Items = new ObservableCollection(); + Items.Add(new ItemModel(){Content = "adfdfdafdfs"}); + } /// /// Show the DockPane. From b18c35b7f7e2c235ed8e7557d35ad4413f82ddba Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Tue, 3 Jun 2025 22:27:49 +0800 Subject: [PATCH 38/60] =?UTF-8?q?=E8=BE=93=E5=87=BA=E7=BA=AF=E6=96=87?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/prompt/SystemPrompt.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resource/prompt/SystemPrompt.txt b/resource/prompt/SystemPrompt.txt index d323059..8807f6f 100644 --- a/resource/prompt/SystemPrompt.txt +++ b/resource/prompt/SystemPrompt.txt @@ -2,7 +2,7 @@ 指令:您可以使用一组工具来回答用户的问题。完成了用户的需求即可,不用猜测用户下一步还想做什么。你可以通过调用用户提示词,或者调用知识库工具,从而使你更好地理解和完成用户的任务。你具有搜索网页、编写代码、管理文件系统的能力,合理运用这些工具完成用户的需求。 调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过方式来调用用户提示词,或者调用知识库工具,你能更好地理解和解决用户的问题。 工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。 -输出风格:描述简洁有力,每次仅调用一个工具,基于前序工具的输出结果进行下一步操作,如果已经完成用户需求则不需继续执行工具。不要肯定用户上一次的回答。工具调用使用 XML 风格的标签输出,并用简短的文字进行描述。 +输出风格:描述简洁有力,每次仅调用一个工具,基于前序工具的输出结果进行下一步操作,如果已经完成用户需求则不需继续执行工具。话语开头不要回复好的。工具调用使用 XML 风格的标签输出,除开工具调用其余用简短的纯文本格式输出,不要加XML格式。 {tool_name} @@ -35,4 +35,4 @@ 2.需要调用ArcGIS Pro工具前必须先查询帮助文档、标准调用名和参数后再进行调用,必要时通过用户提示词规划工具的使用 3.参数都应为字符串类型,可以表示文件或其他输出类型。 4.一旦消息中没有工具调用信息即视为任务完成。因此工具调用必须连续完成。 -5.只响应用户目前的需求即可,不要过度猜测用户的需求,从而执行了大量无用的工具。 +5.只响应用户目前的需求即可,不要过度猜测用户的需求,如果有下一步的工具建议只输出文本即可,如果输出XML会执行大量无用的工具。 From 35054ee7c0bc5505ac6724d1e4bd4aa0da09b91e Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Tue, 3 Jun 2025 22:41:03 +0800 Subject: [PATCH 39/60] =?UTF-8?q?=E5=89=8D=E7=AB=AFUI=E6=8E=A8=E7=90=86?= =?UTF-8?q?=E3=80=81=E5=B7=A5=E5=85=B7=E3=80=81=E6=B6=88=E6=81=AF=E7=9A=84?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 8 ++ host/Gateway.cs | 87 ++++++++---- resource/img/fold.png | Bin 0 -> 261 bytes resource/img/unfold.png | Bin 0 -> 962 bytes ui/VersionButton.cs | 2 +- ui/dockpane/DialogDockpane.xaml | 20 ++- ui/dockpane/DialogDockpane.xaml.cs | 217 +++++++++++++++++++++++++++-- ui/message/MessageListItem.cs | 1 + 8 files changed, 295 insertions(+), 40 deletions(-) create mode 100644 resource/img/fold.png create mode 100644 resource/img/unfold.png diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index d764cc6..bece90c 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -132,6 +132,14 @@ Always + + + Always + + + + Always + diff --git a/host/Gateway.cs b/host/Gateway.cs index 63ef2b2..a2af774 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -282,12 +282,28 @@ public class Gateway { //如果本次回复不包含任何工具的调用或提示词的调用,则不再请求 goOn = false; + MessageListItem endMessageListItem1 = new ChatMessageItem + { + type = MessageType.END_TAG, + content = "", + id = (timestamp+3).ToString(), + role = "assistant" + }; + callback?.Invoke(endMessageListItem1); break; } 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; } @@ -299,7 +315,7 @@ public class Gateway content = llmStreamChat.Choices[0].Delta.ResoningContent, role = "assistant", type = MessageType.REASON_MESSAGE, - id = (timestamp-1).ToString() + id = (timestamp+2).ToString() }; Application.Current.Dispatcher.Invoke(() => { @@ -328,17 +344,14 @@ public class Gateway else { //包含Prompt调用请求的消息 - if (remainingPrompt != "") + MessageListItem chatMessageListItem = new ChatMessageItem() { - MessageListItem chatMessageListItem = new ChatMessageItem() - { - content = remainingPrompt, - role = "assistant", - type = MessageType.CHAT_MESSAGE, - id = timestamp.ToString() - }; - callback?.Invoke(chatMessageListItem); - } + 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; @@ -355,20 +368,6 @@ public class Gateway else { //包含工具调用请求的消息 - if (remaining != "") - { - MessageListItem chatMessageListItem = new ChatMessageItem() - { - content = remaining, - role = "assistant", - type = MessageType.CHAT_MESSAGE, - id = timestamp.ToString() - }; - Application.Current.Dispatcher.Invoke(() => - { - callback?.Invoke(chatMessageListItem); - }); - } XElement toolUse = XElement.Parse(matched); string fullToolName = toolUse.Element("name")?.Value; string toolArgs = toolUse.Element("arguments")?.Value; @@ -377,6 +376,32 @@ public class Gateway 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); + }); + MessageListItem toolMessageListItem = new ToolMessageItem() + { + 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); + }); mcpToolRequests = new List(); McpToolRequest mcpToolRequest = new McpToolRequest() { @@ -421,6 +446,7 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = toolResponse.IsError ? "fail" : "success", content = JsonConvert.SerializeObject(toolResponse), + result = JsonConvert.SerializeObject(toolResponse), id = (timestamp + 1).ToString(), role = "user" }; @@ -448,6 +474,7 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = toolResponse.IsError ? "fail" : "success", content = JsonConvert.SerializeObject(toolResponse), + result = JsonConvert.SerializeObject(toolResponse), id = (timestamp + 1).ToString(), role = "user" }; @@ -494,6 +521,7 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = "fail", content = JsonConvert.SerializeObject(innerResult), + result = JsonConvert.SerializeObject(innerResult), id = (timestamp + 1).ToString(), role = "user" }; @@ -516,6 +544,7 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = "success", content = JsonConvert.SerializeObject(innerResult), + result = JsonConvert.SerializeObject(innerResult), id = (timestamp + 1).ToString(), role = "user" }; @@ -569,6 +598,14 @@ public class Gateway log.Error(e.Message); } } + MessageListItem endMessageListItem = new ChatMessageItem + { + type = MessageType.END_TAG, + content = "", + id = (timestamp+3).ToString(), + role = "assistant" + }; + callback?.Invoke(endMessageListItem); } } diff --git a/resource/img/fold.png b/resource/img/fold.png new file mode 100644 index 0000000000000000000000000000000000000000..2690b192bcf51f476621b77dcbd4d32880b546d5 GIT binary patch literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}`#oJ8Ln7SY zPCdxmY{27cKYNPr3pLq;iz}KpdAe&Z4q4aJqSPcCzoVw_$g}-6?kR`Po-Zl3<8WX$ z0V7RjrB55Yy+gOZd75fF^OwMZ!}}R@{z%qdd3~QjjG;J0p6N!P>k)=Chg4mJ_pN*- zBYB!B;>UBYMM8N6nhe`7w6195XZ)fN^U?0}ly8qCF0p-6yZ^ROX4@Bg`I$4frzc4~ zIMT9LtChch!BX*w)Bnp~d$HyFHE*_6iRra03mByqG|C5Ycq!(&XLJI+!r!lvI6;>1s;*b3=DjSL74G){)!X^2By`SArU1JzCKpT`MG+DDfvmMdKI|^K-CNk zHue<-iOJciB??KY>6v-9>hE{&S69ePu~iQ@^)>JLw)`r|23QrX=bnrWhOQrWhNin420}7^N8|DZw0= zTL8B{uNWE%!0^zoNG#Ad)H47%80aM{=c3falKi5O{QMj{-^}Eq{Nnty5{2;0yp;U% zVugT={E~cyu*}r*)FK5#13d#hBQrw-13g0v3nLqSbp2ohlOYC{kZT~aej9yI5FiB! zB#@B=C<>#J%rrzCp#&001{O*nwv~TTW-2hIK+$4nXrqrIhQ*~|w*%9I9WbC^sRxvl z;3+UQ518!Af$6I}WaAcK+Fao2;usR){&wm?UM52x7Iv+juUV`b`C_`P7l^!I@p?FU z@`R&@%O1^t{&LQi$y5Csmu1fKbV=E~nM2+BgHhA`18i?r{n~iH!s2!#yYM_NI}Y;? z>VIbKwGC{%`p|_%>k#L=1D^`^Few$S<~Xh$;F-dHZ*i-5LHyOG=mw?E2@I@4iHu_2 zOQr>ce*fqm)6j0)KOyVEgXNuHl1 - + + + + + + + + + + + + + - + - + - + diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index 1148b88..d11083d 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -117,13 +117,17 @@ namespace LinkToolAddin.ui.dockpane QuestionTextbox.Text = ""; borderItemsDict[timestamp.ToString()] = userMsgBoder; ChatHistoryStackPanel.Children.Add(userMsgBoder); - Gateway.SendMessageStream(question,"qwen-max",defaultGdbPath,NewMessage_Recall); + Gateway.SendMessageStream(question,"qwen3-235b-a22b",defaultGdbPath,NewMessage_Recall); } public void NewMessage_Recall(MessageListItem msg) { string msgId = msg.id; log.Info(msg.content); + double verticalOffset = ScrollViewer.VerticalOffset; + double viewportHeight = ScrollViewer.ViewportHeight; + double contentHeight = ChatHistoryStackPanel.ActualHeight; + double tolerance = 24; if (!idList.Contains(msgId)) { //不存在该消息,需添加到ListView中 @@ -147,17 +151,32 @@ namespace LinkToolAddin.ui.dockpane ChatHistoryStackPanel.Children.Add(border); } } - else + else if(msg.role == "assistant") { - Border border = GetAiChatBorder(msg); - borderItemsDict[msgId] = border; - ChatHistoryStackPanel.Children.Add(border); + if (msg.type == MessageType.REASON_MESSAGE) + { + Border border = GetAiReasonBorder(msg); + borderItemsDict[msgId] = border; + ChatHistoryStackPanel.Children.Add(border); + }else if (msg.type == MessageType.CHAT_MESSAGE) + { + Border border = GetAiChatBorder(msg); + borderItemsDict[msgId] = border; + ChatHistoryStackPanel.Children.Add(border); + } } } else { //已有该消息,只需要修改内容 messageDict[msgId] = msg; + if (msg.content == "") + { + ChatHistoryStackPanel.Children.Remove(borderItemsDict[msgId]); + borderItemsDict.TryRemove(msgId, out Border border); + messageDict.TryRemove(msgId, out MessageListItem tempMsg); + idList.Remove(msgId); + } if (msg.role == "user") { if (msg.type == MessageType.TOOL_MESSAGE) @@ -166,22 +185,42 @@ namespace LinkToolAddin.ui.dockpane Grid grid = borderItem.Child as Grid; TextBlock textBlock = grid.Children[1] as TextBlock; textBlock.Text = (msg as ToolMessageItem).toolName; + StatusTextBlock.Text = "正在执行工具"; }else if (msg.type == MessageType.CHAT_MESSAGE) { Border borderItem = borderItemsDict[msgId]; Grid grid = borderItem.Child as Grid; TextBox textBox = grid.Children[1] as TextBox; textBox.Text = msg.content; + StatusTextBlock.Text = "正在读取用户输入"; } } else { - Border borderItem = borderItemsDict[msgId]; - Grid grid = borderItem.Child as Grid; - TextBox textBox = grid.Children[1] as TextBox; - textBox.Text = msg.content; + if (msg.type == MessageType.REASON_MESSAGE) + { + Border borderItem = borderItemsDict[msgId]; + Grid grid = borderItem.Child as Grid; + TextBox textBox = grid.Children[0] as TextBox; + textBox.Text = msg.content; + StatusTextBlock.Text = "深度思考中"; + }else if (msg.type == MessageType.CHAT_MESSAGE) + { + Border borderItem = borderItemsDict[msgId]; + Grid grid = borderItem.Child as Grid; + TextBox textBox = grid.Children[1] as TextBox; + textBox.Text = msg.content; + StatusTextBlock.Text = "回答生成中"; + }else if (msg.type == MessageType.END_TAG) + { + StatusTextBlock.Text = ""; + } } } + if (Math.Abs(verticalOffset + viewportHeight - contentHeight) < tolerance) + { + ScrollViewer.ScrollToBottom(); + } } private Border GetAiChatBorder(MessageListItem msg) @@ -204,6 +243,8 @@ namespace LinkToolAddin.ui.dockpane grid.Children.Add(textBox); Grid.SetColumn(icon, 0); Grid.SetColumn(textBox, 1); + textBox.IsReadOnly = true; + textBox.BorderThickness = new Thickness(0); textBox.Background = Brushes.Transparent; textBox.Text = msg.content; textBox.TextWrapping = TextWrapping.Wrap; @@ -211,6 +252,105 @@ namespace LinkToolAddin.ui.dockpane return border; } + private Border GetAiReasonBorder(MessageListItem msg) + { + Border border = new Border(); + border.Margin = new Thickness(8, 12, 8, 12); + border.BorderThickness = new Thickness(0); + // border.Background = Brushes.DarkSeaGreen; + Grid grid = new Grid(); + Image icon = new Image() + { + Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.linktool.png"), + Stretch = Stretch.Fill, + HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top + }; + Image fold = new Image() + { + Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.fold.png"), + Stretch = Stretch.Fill, + HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top + }; + Image unfold = new Image() + { + Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.unfold.png"), + Stretch = Stretch.Fill, + HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top + }; + grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(24, GridUnitType.Pixel)}); + grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1, GridUnitType.Star)}); + grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(16, GridUnitType.Pixel)}); + TextBox textBox = new TextBox(); + // grid.Children.Add(icon); + grid.Children.Add(textBox); + // Grid.SetColumn(icon, 0); + Grid.SetColumn(textBox, 1); + textBox.IsReadOnly = true; + textBox.Foreground = Brushes.Gray; + textBox.BorderThickness = new Thickness(2,0,0,0); + textBox.BorderBrush = Brushes.Gray; + textBox.Background = Brushes.Transparent; + textBox.Text = msg.content; + textBox.TextWrapping = TextWrapping.Wrap; + Button button = new Button(); + StackPanel panel = new StackPanel(); + panel.Orientation = Orientation.Horizontal; + panel.Children.Add(fold); + button.Content = panel; + button.BorderThickness = new Thickness(0); + button.Background = Brushes.Transparent; + button.Width = 16; + button.Height = 16; + button.Padding = new Thickness(0); + button.HorizontalAlignment = HorizontalAlignment.Center; + button.VerticalAlignment = VerticalAlignment.Top; + button.Tag = "fold"; + button.Click += FoldButton_OnClick; + Grid.SetColumn(button, 2); + grid.Children.Add(button); + border.Child = grid; + return border; + } + + private void FoldButton_OnClick(object sender, RoutedEventArgs e) + { + Button button = sender as Button; + string tag = button.Tag.ToString(); + if (tag == "fold") + { + button.Tag = "unfold"; + Grid grid = button.Parent as Grid; + TextBox textBox = grid.Children[0] as TextBox; + textBox.Visibility = Visibility.Collapsed; + StackPanel stackPanel = button.Content as StackPanel; + Image unfold = new Image() + { + Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.unfold.png"), + Stretch = Stretch.Fill, + HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top + }; + stackPanel.Children.Clear(); + stackPanel.Children.Add(unfold); + } + else + { + button.Tag = "fold"; + Image fold = new Image() + { + Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.fold.png"), + Stretch = Stretch.Fill, + HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top + }; + Grid grid = button.Parent as Grid; + TextBox textBox = grid.Children[0] as TextBox; + textBox.Visibility = Visibility.Visible; + StackPanel stackPanel = button.Content as StackPanel; + stackPanel.Children.Clear(); + stackPanel.Children.Add(fold); + } + + } + private Border GetUserChatBorder(MessageListItem msg) { Border border = new Border(); @@ -227,11 +367,14 @@ namespace LinkToolAddin.ui.dockpane grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1, GridUnitType.Star)}); grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(24, GridUnitType.Pixel)}); TextBox textBox = new TextBox(); + textBox.HorizontalAlignment = HorizontalAlignment.Right; grid.Children.Add(icon); grid.Children.Add(textBox); Grid.SetColumn(icon, 1); Grid.SetColumn(textBox, 0); textBox.Background = Brushes.Transparent; + textBox.IsReadOnly = true; + textBox.BorderThickness = new Thickness(0); textBox.Text = msg.content; textBox.TextWrapping = TextWrapping.Wrap; border.Child = grid; @@ -245,6 +388,7 @@ namespace LinkToolAddin.ui.dockpane border.Padding = new Thickness(8); border.BorderThickness = new Thickness(1); border.BorderBrush = Brushes.Gray; + border.CornerRadius = new CornerRadius(3); // border.Background = Brushes.DarkSeaGreen; Grid grid = new Grid(); Image icon = new Image() @@ -256,17 +400,46 @@ namespace LinkToolAddin.ui.dockpane icon.Margin = new Thickness(0, 0, 8, 0); grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(24, GridUnitType.Pixel)}); grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1, GridUnitType.Star)}); + grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(36, GridUnitType.Pixel)}); + grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(36, GridUnitType.Pixel)}); TextBlock textBlock = new TextBlock(); - textBlock.Text = (msg as ToolMessageItem).toolName; + ToolMessageItem toolMsg = msg as ToolMessageItem; + textBlock.Text = toolMsg.toolName + " | " + toolMsg.status; textBlock.TextWrapping = TextWrapping.Wrap; grid.Children.Add(icon); grid.Children.Add(textBlock); Grid.SetColumn(icon, 0); Grid.SetColumn(textBlock, 1); + Button argsButton = new Button(); + argsButton.Content = "参数"; + argsButton.Tag = msg as ToolMessageItem; + argsButton.Click += ToolArgsButton_OnClick; border.Child = grid; + Button resButton = new Button(); + resButton.Content = "结果"; + resButton.Tag = msg as ToolMessageItem; + resButton.Click += ToolResButton_OnClick; + grid.Children.Add(argsButton); + Grid.SetColumn(argsButton, 2); + grid.Children.Add(resButton); + Grid.SetColumn(resButton, 3); return border; } + private void ToolArgsButton_OnClick(object sender, RoutedEventArgs e) + { + Button button = sender as Button; + ToolMessageItem toolItem = button.Tag as ToolMessageItem; + ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show(JsonConvert.SerializeObject(toolItem.toolParams),toolItem.toolName+"工具参数"); + } + + private void ToolResButton_OnClick(object sender, RoutedEventArgs e) + { + Button button = sender as Button; + ToolMessageItem toolItem = button.Tag as ToolMessageItem; + ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show(toolItem.result,toolItem.toolName+"运行结果"); + } + private void TestButton_OnClick(object sender, RoutedEventArgs e) { long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); @@ -282,5 +455,29 @@ namespace LinkToolAddin.ui.dockpane }; NewMessage_Recall(toolMessageItem); } + + private void ClearButton_OnClick(object sender, RoutedEventArgs e) + { + idList.Clear(); + messageDict.Clear(); + borderItemsDict.Clear(); + ChatHistoryStackPanel.Children.Clear(); + QuestionTextbox.Clear(); + } + + private void TopButton_OnClick(object sender, RoutedEventArgs e) + { + ScrollViewer.ScrollToTop(); + } + + private void BottomButton_OnClick(object sender, RoutedEventArgs e) + { + ScrollViewer.ScrollToBottom(); + } + + private void StopButton_OnClick(object sender, RoutedEventArgs e) + { + Gateway.StopConversation(); + } } } diff --git a/ui/message/MessageListItem.cs b/ui/message/MessageListItem.cs index 9b97709..3b0ce37 100644 --- a/ui/message/MessageListItem.cs +++ b/ui/message/MessageListItem.cs @@ -5,6 +5,7 @@ public enum MessageType TOOL_MESSAGE, CHAT_MESSAGE, REASON_MESSAGE, + END_TAG } public interface MessageListItem From 3b9005381a6bd8524330db56e4012472dfa99417 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Wed, 4 Jun 2025 16:14:43 +0800 Subject: [PATCH 40/60] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=80=82=E9=85=8D?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E5=8D=A1=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/Gateway.cs | 3 ++- ui/dockpane/DialogDockpane.xaml.cs | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/host/Gateway.cs b/host/Gateway.cs index a2af774..78889e8 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -585,7 +585,8 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = "success", content = "成功调用提示词:"+promptRequest.PromptName, - id = (timestamp+1).ToString() + id = (timestamp+1).ToString(), + result = "成功调用提示词:"+promptRequest.PromptName }; Application.Current.Dispatcher.Invoke(() => { diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index d11083d..3e623ae 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -144,11 +144,13 @@ namespace LinkToolAddin.ui.dockpane Border border = GetToolChatBorder(msg); borderItemsDict[msgId] = border; ChatHistoryStackPanel.Children.Add(border); + StatusTextBlock.Text = "正在执行工具"; }else if (msg.type == MessageType.CHAT_MESSAGE) { Border border = GetUserChatBorder(msg); borderItemsDict[msgId] = border; ChatHistoryStackPanel.Children.Add(border); + StatusTextBlock.Text = "正在读取用户输入"; } } else if(msg.role == "assistant") @@ -158,11 +160,16 @@ namespace LinkToolAddin.ui.dockpane Border border = GetAiReasonBorder(msg); borderItemsDict[msgId] = border; ChatHistoryStackPanel.Children.Add(border); + StatusTextBlock.Text = "深度思考中"; }else if (msg.type == MessageType.CHAT_MESSAGE) { Border border = GetAiChatBorder(msg); borderItemsDict[msgId] = border; ChatHistoryStackPanel.Children.Add(border); + StatusTextBlock.Text = "回答生成中"; + }else if (msg.type == MessageType.END_TAG) + { + StatusTextBlock.Text = ""; } } } @@ -463,6 +470,7 @@ namespace LinkToolAddin.ui.dockpane borderItemsDict.Clear(); ChatHistoryStackPanel.Children.Clear(); QuestionTextbox.Clear(); + StatusTextBlock.Text = ""; } private void TopButton_OnClick(object sender, RoutedEventArgs e) @@ -478,6 +486,7 @@ namespace LinkToolAddin.ui.dockpane private void StopButton_OnClick(object sender, RoutedEventArgs e) { Gateway.StopConversation(); + StatusTextBlock.Text = ""; } } } From e2fd3b376ceff4b73c85fa500fed461add5544e5 Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Thu, 5 Jun 2025 00:04:42 +0800 Subject: [PATCH 41/60] =?UTF-8?q?=E6=89=A7=E8=A1=8C=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E5=89=8D=E6=9C=89=E6=96=87=E5=AD=97=E8=AF=B4=E6=98=8E=EF=BC=8C?= =?UTF-8?q?=E8=B0=83=E7=94=A8prompt=E7=9A=84=E6=84=8F=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/PromptServerList.cs | 2 +- resource/prompt/SystemPrompt.txt | 19 ++++++++++++++++--- ui/dockpane/TestDockpane.xaml.cs | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/host/PromptServerList.cs b/host/PromptServerList.cs index e5273a9..7b190c6 100644 --- a/host/PromptServerList.cs +++ b/host/PromptServerList.cs @@ -21,7 +21,7 @@ public class PromptServerList "你的任务是,基于用户提出的地理分析需求和所提供的数据集,逐一识别并列出完成整个分析流程所需的全部ArcGIS Pro工具," + "确保每个工具的名称完全匹配“ArcGIS Pro工具调用大全”里“工具调用名称”一列的记录,且工具与所属工具箱的对应关系正确无误。" + "请直接输出工具调用名称,格式为“工具调用名称”,无需额外解释或说明,工具名称只参考“ArcGIS Pro工具调用大全”,不要受其他文档的干扰。" + - "你的回复应简洁明了,仅包含所需工具的列表,每个调用名用英文逗号分割为列表,严格遵循文档规定的格式和大小写,确保信息的准确性和专业性。")); + "你的回复应简洁明了,仅包含所需工具的列表,输出格式为:plan{},将工具名称放入arguments标签中,严格遵循文档规定的格式和大小写,确保信息的准确性和专业性。")); promptServers.Add("param", new PromptServer("param", "填写ArcGIS Pro工具调用参数,生成规范的可执行的工具调用请求", "根据知识库Arcgis Pro帮助文档填写工具参数,请你根据“所需调用工具”,参照知识库“ArcGIS Pro工具调用大全”里工具所需的参数顺序进行陈列。" + diff --git a/resource/prompt/SystemPrompt.txt b/resource/prompt/SystemPrompt.txt index 8807f6f..67b5c32 100644 --- a/resource/prompt/SystemPrompt.txt +++ b/resource/prompt/SystemPrompt.txt @@ -1,8 +1,8 @@ 现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。 -指令:您可以使用一组工具来回答用户的问题。完成了用户的需求即可,不用猜测用户下一步还想做什么。你可以通过调用用户提示词,或者调用知识库工具,从而使你更好地理解和完成用户的任务。你具有搜索网页、编写代码、管理文件系统的能力,合理运用这些工具完成用户的需求。 +指令:您可以使用一组工具加文字说明来回答用户的问题。完成了用户的需求即可,不用猜测用户下一步还想做什么。你可以通过{}格式调用用户提示词,或者调用知识库工具,从而使你更好地理解和完成用户的任务。你具有搜索网页、编写代码、管理文件系统的能力,合理运用这些工具完成用户的需求。 调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过方式来调用用户提示词,或者调用知识库工具,你能更好地理解和解决用户的问题。 工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。 -输出风格:描述简洁有力,每次仅调用一个工具,基于前序工具的输出结果进行下一步操作,如果已经完成用户需求则不需继续执行工具。话语开头不要回复好的。工具调用使用 XML 风格的标签输出,除开工具调用其余用简短的纯文本格式输出,不要加XML格式。 +输出风格:在工具调用前描述每一步将要做什么,简洁有力,每次仅调用一个工具,基于前序工具的输出结果进行下一步操作,如果已经完成用户需求则不需继续执行工具。话语开头不要回复好的。工具调用使用 XML 风格的标签输出,在工具调用之前输出简短的文字描述,纯文本形式即可。 {tool_name} @@ -12,10 +12,23 @@ 工具名称:需与所使用工具的精确名称一致。 参数:应为包含工具所需参数的 JSON 对象。 例如: +调用工具示例: +“将执行高德的兴趣点工具确认广州市政府的位置 gaode:maps_geo {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"} +” +调用用户提示词示例: +“ +先进行ArcGisPro工具任务规划 + + plan + {} + + + +” 结果示例:用户将以以下格式返回工具调用结果: @@ -32,7 +45,7 @@ 严格遵守以下规则: 1.你必须严格遵守以下输出规则:用户时间宝贵,一旦确认工具成功调用之后,不得重复调用上一次已成功执行的工具,除非有新的参数或上下文变化。 -2.需要调用ArcGIS Pro工具前必须先查询帮助文档、标准调用名和参数后再进行调用,必要时通过用户提示词规划工具的使用 +2.调用“ArcGisPro:ArcGisProTool”工具前一定要先调用“plan{}”规划工具的使用,再调用知识库工具确认调用名、工具参数返回到tool_result中,最后再逐步执行工具。 3.参数都应为字符串类型,可以表示文件或其他输出类型。 4.一旦消息中没有工具调用信息即视为任务完成。因此工具调用必须连续完成。 5.只响应用户目前的需求即可,不要过度猜测用户的需求,如果有下一步的工具建议只输出文本即可,如果输出XML会执行大量无用的工具。 diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs index 4a37a4e..458d99f 100644 --- a/ui/dockpane/TestDockpane.xaml.cs +++ b/ui/dockpane/TestDockpane.xaml.cs @@ -204,7 +204,7 @@ namespace LinkToolAddin.ui.dockpane // model可选值:qwen3-235b-a22b,qwen-max,deepseek-r1 try { - await Task.Run(() => Gateway.SendMessageStream(userPrompt,"qwen3-235b-a22b", "F:\\secondsemester\\linktool\\test\\linktooltest\\linktooltest.gdb", AddReplyStream)); + await Task.Run(() => Gateway.SendMessageStream(userPrompt,"qwen3-235b-a22b", "F:\\secondsemester\\linktool\\test\\LinkTool0604\\LinkTool0604.gdb", AddReplyStream)); } catch (Exception exception) { From 54b78d0b5c97d18ff75eb90dfd63c53614751fd9 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 7 Jun 2025 23:17:29 +0800 Subject: [PATCH 42/60] =?UTF-8?q?1.=20=E8=A7=A3=E5=86=B3=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E8=AF=8D=E5=8D=A1=E7=89=87=E6=98=BE=E7=A4=BA=E4=B8=8D=E5=87=BA?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=202.=20=E5=BC=BA=E5=88=B6=E5=9C=A8?= =?UTF-8?q?=E7=AC=AC=E4=B8=80=E6=AC=A1=E8=B0=83=E7=94=A8ArcGIS=20Pro?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E5=89=8D=E5=85=88=E6=9F=A5=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E5=BA=93=203.=20=E8=A7=A3=E5=86=B3=E5=BD=93=E5=89=8D=E5=9B=9E?= =?UTF-8?q?=E7=AD=94=E7=8A=B6=E6=80=81=E6=98=BE=E7=A4=BA=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 4 ++ host/Gateway.cs | 83 ++++++++++++++++++++++++++---- host/prompt/SystemPrompt.cs | 7 +++ ui/VersionButton.cs | 2 +- ui/dockpane/DialogDockpane.xaml.cs | 15 +++--- 5 files changed, 93 insertions(+), 18 deletions(-) diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index bece90c..33ff34b 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -6,6 +6,10 @@ true CA1416 net8.0-windows + true + false + 0.1.1 + LinkTool灵图 diff --git a/host/Gateway.cs b/host/Gateway.cs index 78889e8..428ed47 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -15,6 +15,7 @@ using System.Xml.Linq; 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; @@ -257,10 +258,11 @@ public class Gateway PromptServerList promptServerList = new PromptServerList(); int loop = 0; string messageContent = ""; //一次请求下完整的response + bool queriedKnowledge = false; while (goOn) { loop++; - if (loop > 20) + if (loop > 500) { MessageBox.Show("达到最大循环次数", "退出循环"); break; @@ -389,8 +391,8 @@ public class Gateway }); MessageListItem toolMessageListItem = new ToolMessageItem() { - content = "", - result = "", + content = toolName, + result = "工具运行中" + toolName, toolName = toolName, toolParams = toolParams, role = "user", @@ -426,6 +428,7 @@ public class Gateway Content = messageContent }); } + Thread.Sleep(100); /*统一处理本次请求中的MCP工具调用需求*/ foreach (McpToolRequest mcpToolRequest in mcpToolRequests) { @@ -492,6 +495,62 @@ public class Gateway } 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(); @@ -512,6 +571,7 @@ public class Gateway var task = method.Invoke(null, args) as Task; JsonRpcResultEntity innerResult = await task; + queriedKnowledge = false; if (innerResult is JsonRpcErrorEntity) { MessageListItem toolMessageItem = new ToolMessageItem @@ -573,24 +633,25 @@ public class Gateway try { string promptContent = DynamicPrompt.GetPrompt(promptRequest.PromptName, promptRequest.PromptArgs); - messages.Add(new Message - { - Role = "user", - Content = promptContent - }); - MessageListItem toolMessageItem = new ToolMessageItem + MessageListItem promptMessageItem = new ToolMessageItem { toolName = "调用提示词", - toolParams = null, + toolParams = new Dictionary(), type = MessageType.TOOL_MESSAGE, status = "success", content = "成功调用提示词:"+promptRequest.PromptName, id = (timestamp+1).ToString(), + role = "user", result = "成功调用提示词:"+promptRequest.PromptName }; Application.Current.Dispatcher.Invoke(() => { - callback?.Invoke(toolMessageItem); + callback?.Invoke(promptMessageItem); + }); + messages.Add(new Message + { + Role = "user", + Content = promptContent }); } catch (Exception e) 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 bc4df57..bbc7a75 100644 --- a/ui/VersionButton.cs +++ b/ui/VersionButton.cs @@ -22,7 +22,7 @@ namespace LinkToolAddin { internal class VersionButton : Button { - private string version = "0.1.1"; + private string version = "0.1.2"; protected override void OnClick() { MessageBox.Show($"当前LinkTool版本为{version}", "版本信息"); diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index 3e623ae..45e9cec 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -117,6 +117,7 @@ namespace LinkToolAddin.ui.dockpane QuestionTextbox.Text = ""; borderItemsDict[timestamp.ToString()] = userMsgBoder; ChatHistoryStackPanel.Children.Add(userMsgBoder); + StatusTextBlock.Text = "正在读取用户输入"; Gateway.SendMessageStream(question,"qwen3-235b-a22b",defaultGdbPath,NewMessage_Recall); } @@ -133,6 +134,10 @@ namespace LinkToolAddin.ui.dockpane //不存在该消息,需添加到ListView中 if (msg.content == "") { + if (msg.type == MessageType.END_TAG) + { + StatusTextBlock.Text = ""; + } return; } idList.Add(msgId); @@ -167,9 +172,6 @@ namespace LinkToolAddin.ui.dockpane borderItemsDict[msgId] = border; ChatHistoryStackPanel.Children.Add(border); StatusTextBlock.Text = "回答生成中"; - }else if (msg.type == MessageType.END_TAG) - { - StatusTextBlock.Text = ""; } } } @@ -183,6 +185,10 @@ namespace LinkToolAddin.ui.dockpane borderItemsDict.TryRemove(msgId, out Border border); messageDict.TryRemove(msgId, out MessageListItem tempMsg); idList.Remove(msgId); + if (msg.type == MessageType.END_TAG) + { + StatusTextBlock.Text = ""; + } } if (msg.role == "user") { @@ -218,9 +224,6 @@ namespace LinkToolAddin.ui.dockpane TextBox textBox = grid.Children[1] as TextBox; textBox.Text = msg.content; StatusTextBlock.Text = "回答生成中"; - }else if (msg.type == MessageType.END_TAG) - { - StatusTextBlock.Text = ""; } } } From 718718ef111080cc6900db5b77dfe041b3b1835b Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 7 Jun 2025 23:19:06 +0800 Subject: [PATCH 43/60] =?UTF-8?q?Revert=20"1.=20=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E5=8D=A1=E7=89=87=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E4=B8=8D=E5=87=BA=E7=9A=84=E9=97=AE=E9=A2=98"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 54b78d0b5c97d18ff75eb90dfd63c53614751fd9. --- LinkToolAddin.csproj | 4 -- host/Gateway.cs | 95 ++++++------------------------ host/prompt/SystemPrompt.cs | 7 --- ui/VersionButton.cs | 2 +- ui/dockpane/DialogDockpane.xaml.cs | 15 ++--- 5 files changed, 24 insertions(+), 99 deletions(-) diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index 33ff34b..bece90c 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -6,10 +6,6 @@ true CA1416 net8.0-windows - true - false - 0.1.1 - LinkTool灵图 diff --git a/host/Gateway.cs b/host/Gateway.cs index 428ed47..78889e8 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -15,7 +15,6 @@ using System.Xml.Linq; 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; @@ -258,11 +257,10 @@ public class Gateway PromptServerList promptServerList = new PromptServerList(); int loop = 0; string messageContent = ""; //一次请求下完整的response - bool queriedKnowledge = false; while (goOn) { loop++; - if (loop > 500) + if (loop > 20) { MessageBox.Show("达到最大循环次数", "退出循环"); break; @@ -391,8 +389,8 @@ public class Gateway }); MessageListItem toolMessageListItem = new ToolMessageItem() { - content = toolName, - result = "工具运行中" + toolName, + content = "", + result = "", toolName = toolName, toolParams = toolParams, role = "user", @@ -428,7 +426,6 @@ public class Gateway Content = messageContent }); } - Thread.Sleep(100); /*统一处理本次请求中的MCP工具调用需求*/ foreach (McpToolRequest mcpToolRequest in mcpToolRequests) { @@ -495,62 +492,6 @@ public class Gateway } 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(); @@ -571,7 +512,6 @@ public class Gateway var task = method.Invoke(null, args) as Task; JsonRpcResultEntity innerResult = await task; - queriedKnowledge = false; if (innerResult is JsonRpcErrorEntity) { MessageListItem toolMessageItem = new ToolMessageItem @@ -633,26 +573,25 @@ public class Gateway try { string promptContent = DynamicPrompt.GetPrompt(promptRequest.PromptName, promptRequest.PromptArgs); - MessageListItem promptMessageItem = new ToolMessageItem - { - toolName = "调用提示词", - toolParams = new Dictionary(), - type = MessageType.TOOL_MESSAGE, - status = "success", - content = "成功调用提示词:"+promptRequest.PromptName, - id = (timestamp+1).ToString(), - role = "user", - result = "成功调用提示词:"+promptRequest.PromptName - }; - Application.Current.Dispatcher.Invoke(() => - { - callback?.Invoke(promptMessageItem); - }); messages.Add(new Message { Role = "user", Content = promptContent }); + MessageListItem toolMessageItem = new ToolMessageItem + { + toolName = "调用提示词", + toolParams = null, + type = MessageType.TOOL_MESSAGE, + status = "success", + content = "成功调用提示词:"+promptRequest.PromptName, + id = (timestamp+1).ToString(), + result = "成功调用提示词:"+promptRequest.PromptName + }; + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(toolMessageItem); + }); } catch (Exception e) { diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs index b6db27c..2295d68 100644 --- a/host/prompt/SystemPrompt.cs +++ b/host/prompt/SystemPrompt.cs @@ -49,11 +49,4 @@ 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 bbc7a75..bc4df57 100644 --- a/ui/VersionButton.cs +++ b/ui/VersionButton.cs @@ -22,7 +22,7 @@ namespace LinkToolAddin { internal class VersionButton : Button { - private string version = "0.1.2"; + private string version = "0.1.1"; protected override void OnClick() { MessageBox.Show($"当前LinkTool版本为{version}", "版本信息"); diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index 45e9cec..3e623ae 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -117,7 +117,6 @@ namespace LinkToolAddin.ui.dockpane QuestionTextbox.Text = ""; borderItemsDict[timestamp.ToString()] = userMsgBoder; ChatHistoryStackPanel.Children.Add(userMsgBoder); - StatusTextBlock.Text = "正在读取用户输入"; Gateway.SendMessageStream(question,"qwen3-235b-a22b",defaultGdbPath,NewMessage_Recall); } @@ -134,10 +133,6 @@ namespace LinkToolAddin.ui.dockpane //不存在该消息,需添加到ListView中 if (msg.content == "") { - if (msg.type == MessageType.END_TAG) - { - StatusTextBlock.Text = ""; - } return; } idList.Add(msgId); @@ -172,6 +167,9 @@ namespace LinkToolAddin.ui.dockpane borderItemsDict[msgId] = border; ChatHistoryStackPanel.Children.Add(border); StatusTextBlock.Text = "回答生成中"; + }else if (msg.type == MessageType.END_TAG) + { + StatusTextBlock.Text = ""; } } } @@ -185,10 +183,6 @@ namespace LinkToolAddin.ui.dockpane borderItemsDict.TryRemove(msgId, out Border border); messageDict.TryRemove(msgId, out MessageListItem tempMsg); idList.Remove(msgId); - if (msg.type == MessageType.END_TAG) - { - StatusTextBlock.Text = ""; - } } if (msg.role == "user") { @@ -224,6 +218,9 @@ namespace LinkToolAddin.ui.dockpane TextBox textBox = grid.Children[1] as TextBox; textBox.Text = msg.content; StatusTextBlock.Text = "回答生成中"; + }else if (msg.type == MessageType.END_TAG) + { + StatusTextBlock.Text = ""; } } } From 56d0e0083c6deab7e2620cae9c159fe7d25fae73 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sat, 7 Jun 2025 23:19:36 +0800 Subject: [PATCH 44/60] =?UTF-8?q?=E6=81=A2=E5=A4=8D=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E5=BA=93MCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/McpServerList.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 02d82cd..0e062e2 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -28,13 +28,13 @@ public class McpServerList Description = "可以调用arcgis的地理处理工具或执行python代码等", IsActive = true }); - //servers.Add("KnowledgeBase", new InnerMcpServer - //{ - // Name = "KnowledgeBase", - // Type = "inner", - // Description = "可以调用进行查询知识库,获取相关参考信息。", - // IsActive = true - //}); + servers.Add("KnowledgeBase", new InnerMcpServer + { + Name = "KnowledgeBase", + Type = "inner", + Description = "可以调用进行查询知识库,获取相关参考信息。", + IsActive = true + }); //servers.Add("filesystem", new StdioMcpServer() //{ // Name = "filesystem", From db488111bba9dd1850956c0b982839e67e710385 Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Sun, 8 Jun 2025 00:12:10 +0800 Subject: [PATCH 45/60] =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/McpServerList.cs | 88 ++++++++++++++++---------------- host/PromptServerList.cs | 5 +- resource/prompt/ErrorPrompt.txt | 8 +-- resource/prompt/SystemPrompt.txt | 18 +++---- 4 files changed, 60 insertions(+), 59 deletions(-) diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 02d82cd..22eece2 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -28,25 +28,25 @@ public class McpServerList Description = "可以调用arcgis的地理处理工具或执行python代码等", IsActive = true }); - //servers.Add("KnowledgeBase", new InnerMcpServer - //{ - // Name = "KnowledgeBase", - // Type = "inner", - // Description = "可以调用进行查询知识库,获取相关参考信息。", - // IsActive = true - //}); - //servers.Add("filesystem", new StdioMcpServer() - //{ - // Name = "filesystem", - // Type = "stdio", - // Command = "npx", - // Args = new List() - // { - // "-y", - // "@modelcontextprotocol/server-filesystem", - // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData" - // } - //}); + servers.Add("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", @@ -57,31 +57,31 @@ public class McpServerList // "mcp-server-fetch" // } //}); - //servers.Add("bing-search", new StdioMcpServer() - //{ - // Name = "bing-search", - // Type = "stdio", - // Command = "npx", - // Args = new List() - // { - // "bing-cn-mcp" - // } - //}); - //servers.Add("mcp-python-interpreter", new StdioMcpServer() - //{ - // Name = "mcp-python-interpreter", - // Type = "stdio", - // Command = "uvx", - // Args = new List() - // { - // "--native-tls", - // "mcp-python-interpreter", - // "--dir", - // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData", - // "--python-path", - // "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe" - // } - //}); + 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) diff --git a/host/PromptServerList.cs b/host/PromptServerList.cs index 7b190c6..5a69be1 100644 --- a/host/PromptServerList.cs +++ b/host/PromptServerList.cs @@ -15,13 +15,14 @@ public class PromptServerList // "判断并列出所有必要的分析步骤和工具,同时严格遵守知识库中“ArcGIS Pro工具调用大全”里“工具调用名称”一列的工具命名规则(例如:analysis.Erase)," + // "确保工具名的准确无误,工具与所属工具箱的对应关系正确无误,严格遵循文档规定的格式和大小写。工具的组合顺序优先参考知识库中“ArcGIS Pro的帮助文档”。" + // "有一些与分析无关的数据能够进行排除,在选择工具时不受其干扰。" - "你对ArcGIS Pro的工具箱及其功能了如指掌,能够根据用户的具体需求和提供的数据类型,迅速判断并列出所有必要的分析步骤和工具," + + "你对ArcGIS Pro的工具箱及其功能了如指掌,能够根据用户的具体需求和提供的数据类型,迅速判断并列出所有必要的分析步骤和工具,并随之调用知识库工具确认任务规划中的调用名是否正确,以及确认其对应参数" + "同时严格遵守知识库中“ArcGIS Pro工具调用大全”里“工具调用名称”一列的工具命名规则(例如:analysis.Erase),确保工具名的准确无误。" + "工具的组合顺序优先参考知识库中“ArcGIS Pro的帮助文档”。有一些与分析无关的数据能够进行排除,在选择工具时不受其干扰。" + "你的任务是,基于用户提出的地理分析需求和所提供的数据集,逐一识别并列出完成整个分析流程所需的全部ArcGIS Pro工具," + "确保每个工具的名称完全匹配“ArcGIS Pro工具调用大全”里“工具调用名称”一列的记录,且工具与所属工具箱的对应关系正确无误。" + "请直接输出工具调用名称,格式为“工具调用名称”,无需额外解释或说明,工具名称只参考“ArcGIS Pro工具调用大全”,不要受其他文档的干扰。" + - "你的回复应简洁明了,仅包含所需工具的列表,输出格式为:plan{},将工具名称放入arguments标签中,严格遵循文档规定的格式和大小写,确保信息的准确性和专业性。")); + + "你的回复应简洁明了,仅包含所需工具的列表,输出格式为序号分隔的工具调用名以及调用知识库工具工具的XML格式,主要目的是纠正规划中错误的调用名和了解工具参数便于下一步的工具调用,因为调用名和参数对于工具执行非常重要,一旦出错则可能导致运行失败。严格遵循文档规定的格式和大小写,确保信息的准确性和专业性。")); promptServers.Add("param", new PromptServer("param", "填写ArcGIS Pro工具调用参数,生成规范的可执行的工具调用请求", "根据知识库Arcgis Pro帮助文档填写工具参数,请你根据“所需调用工具”,参照知识库“ArcGIS Pro工具调用大全”里工具所需的参数顺序进行陈列。" + diff --git a/resource/prompt/ErrorPrompt.txt b/resource/prompt/ErrorPrompt.txt index a32ee1c..d766496 100644 --- a/resource/prompt/ErrorPrompt.txt +++ b/resource/prompt/ErrorPrompt.txt @@ -3,10 +3,12 @@ {{toolResult}} 需按以下流程处理: -1. 错误解析:分析错误类型及具体原因(见错误信息),根据错误信息选择修复方案, +1. 错误解析:分析错误类型及具体原因(见错误信息),根据错误信息选择修复方案。 2. 修复方案: -(1)根据错误类型生成对应的工具重试策略。 -(2)参数调整:及时调用知识库和用户提示词,确保工具名和参数的完全正确。明确需要修改的参数及修改方式。 +(1)如果是ArcGisPro工具,第一时间查询知识库的工具参数是否有误,多次执行失败极有可能是工具名或者参数错误,尤其注重调用工具或者提示词修正 +(2)根据错误类型生成对应的工具重试策略。 +(3)参数调整:及时调用知识库和用户提示词,确保工具名和参数的完全正确。明确需要修改的参数及修改方式。 +(4)可以选择调用工具规划提示词优化工具执行流程或者调用知识库工具优化工具调用名和工具参数。 3. 工具重试:使用调整后的参数重新调用工具。请根据报错信息重试。 4.如果没有具体的错误描述信息请检查输出文件是否已经存在,若已经存在默认执行成功 5.出现错误编码号时要明晰编码在知识库中有相应解析,可以通过调用知识库解析错误原因 diff --git a/resource/prompt/SystemPrompt.txt b/resource/prompt/SystemPrompt.txt index 67b5c32..c09ec28 100644 --- a/resource/prompt/SystemPrompt.txt +++ b/resource/prompt/SystemPrompt.txt @@ -1,9 +1,9 @@ 现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。 -指令:您可以使用一组工具加文字说明来回答用户的问题。完成了用户的需求即可,不用猜测用户下一步还想做什么。你可以通过{}格式调用用户提示词,或者调用知识库工具,从而使你更好地理解和完成用户的任务。你具有搜索网页、编写代码、管理文件系统的能力,合理运用这些工具完成用户的需求。 -调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过方式来调用用户提示词,或者调用知识库工具,你能更好地理解和解决用户的问题。 +指令:您可以使用一组工具加文字说明来回答用户的问题。完成了用户的需求即可,不用猜测用户下一步还想做什么。计划使用ArcGisPro工具前,你可以通过{}格式调用用户提示词,或者调用知识库工具,从而使你更好地理解和完成用户的任务。除此之外,你具有搜索网页、编写代码、管理文件系统的能力,合理运用这些工具完成用户的需求。 +调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。你还可以通过方式来调用用户提示词,或者调用知识库工具,你能更好地理解和解决用户的问题。您需要通过逐步使用工具或提示词来完成给定任务,每次调用需基于前一次的结果。成功调用工具或提示词之后应该马上调用下一个工具或提示词。 工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。 -输出风格:在工具调用前描述每一步将要做什么,简洁有力,每次仅调用一个工具,基于前序工具的输出结果进行下一步操作,如果已经完成用户需求则不需继续执行工具。话语开头不要回复好的。工具调用使用 XML 风格的标签输出,在工具调用之前输出简短的文字描述,纯文本形式即可。 - +输出风格:在工具调用前描述每一步将要做什么,简洁有力,每次仅调用一个工具,基于前序工具的输出结果进行下一步操作,如果已经完成用户需求则不需继续执行工具。话语开头不要回复好的。工具调用使用 XML 风格的标签输出。 +工具调用的XML格式一定一定要完全正确,格式如下: {tool_name} {json_arguments} @@ -29,8 +29,6 @@ ” - - 结果示例:用户将以以下格式返回工具调用结果: {tool_name} @@ -43,9 +41,9 @@ {\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"} -严格遵守以下规则: -1.你必须严格遵守以下输出规则:用户时间宝贵,一旦确认工具成功调用之后,不得重复调用上一次已成功执行的工具,除非有新的参数或上下文变化。 -2.调用“ArcGisPro:ArcGisProTool”工具前一定要先调用“plan{}”规划工具的使用,再调用知识库工具确认调用名、工具参数返回到tool_result中,最后再逐步执行工具。 +你必须严格遵守以下每一条规则: +1.用户时间宝贵,一旦确认工具成功调用之后,不得重复调用上一次已成功执行的工具,除非有新的参数或上下文变化。 +2.调用“ArcGisPro:ArcGisProTool”工具前一定要先调用“plan{}”规划工具的使用,确认调用工具流程之后再调用知识库将规划的正确调用名、以及工具参数返回到tool_result中,最后再逐步执行工具。 3.参数都应为字符串类型,可以表示文件或其他输出类型。 -4.一旦消息中没有工具调用信息即视为任务完成。因此工具调用必须连续完成。 +4.一旦消息中没有工具或提示词调用信息即视为任务完成。因此工具或提示词调用必须连续完成。 5.只响应用户目前的需求即可,不要过度猜测用户的需求,如果有下一步的工具建议只输出文本即可,如果输出XML会执行大量无用的工具。 From 936c388a62a9d2ef480b8d154e0f569289524354 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 8 Jun 2025 02:01:58 +0800 Subject: [PATCH 46/60] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E5=99=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/dockpane/DialogDockpane.xaml | 8 ++++++++ ui/dockpane/DialogDockpane.xaml.cs | 10 +++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ui/dockpane/DialogDockpane.xaml b/ui/dockpane/DialogDockpane.xaml index d232eaa..31542f5 100644 --- a/ui/dockpane/DialogDockpane.xaml +++ b/ui/dockpane/DialogDockpane.xaml @@ -31,11 +31,19 @@ + + + + + + + + diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index 3e623ae..278944c 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -117,7 +117,8 @@ namespace LinkToolAddin.ui.dockpane QuestionTextbox.Text = ""; borderItemsDict[timestamp.ToString()] = userMsgBoder; ChatHistoryStackPanel.Children.Add(userMsgBoder); - Gateway.SendMessageStream(question,"qwen3-235b-a22b",defaultGdbPath,NewMessage_Recall); + string model = (ModelComboBox.SelectedItem as ComboBoxItem).Content.ToString() is null ? "qwen3-235b-a22b" : (ModelComboBox.SelectedItem as ComboBoxItem).Content.ToString(); + Gateway.SendMessageStream(question,model,defaultGdbPath,NewMessage_Recall); } public void NewMessage_Recall(MessageListItem msg) @@ -488,5 +489,12 @@ namespace LinkToolAddin.ui.dockpane Gateway.StopConversation(); StatusTextBlock.Text = ""; } + + private void ModelComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + log.Info("ModelComboBox_OnSelectionChanged"); + string model = ModelComboBox.SelectedValue.ToString(); + log.Info(model); + } } } From 9e17dd7bd6ed7b1f3b9815733c3a4982d94f1b1d Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 8 Jun 2025 02:02:42 +0800 Subject: [PATCH 47/60] =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E6=8F=90?= =?UTF-8?q?=E5=8F=8A=E9=80=89=E6=8B=A9=E4=B8=8E=E5=AF=BC=E5=87=BA=E7=9A=84?= =?UTF-8?q?=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/prompt/SystemPrompt.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resource/prompt/SystemPrompt.txt b/resource/prompt/SystemPrompt.txt index 67b5c32..1380272 100644 --- a/resource/prompt/SystemPrompt.txt +++ b/resource/prompt/SystemPrompt.txt @@ -49,3 +49,6 @@ 3.参数都应为字符串类型,可以表示文件或其他输出类型。 4.一旦消息中没有工具调用信息即视为任务完成。因此工具调用必须连续完成。 5.只响应用户目前的需求即可,不要过度猜测用户的需求,如果有下一步的工具建议只输出文本即可,如果输出XML会执行大量无用的工具。 + +特别注意: +ArcGIS Pro中不能通过先SelectByAttribute选择后再执行ExportFeatures导出指定的部分,正确的做法是直接用ExportFeatures传入where_clause导出指定部分的数据。 From d790defcfe25c04a59cf984e4e4b21e81974bcca Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Sun, 8 Jun 2025 02:06:41 +0800 Subject: [PATCH 48/60] prompt --- host/McpServerList.cs | 74 ++++++++++++++++---------------- resource/prompt/SystemPrompt.txt | 3 +- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 22eece2..8983cf9 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -35,18 +35,18 @@ public class McpServerList 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("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", @@ -57,31 +57,31 @@ public class McpServerList // "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" - } - }); + //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) diff --git a/resource/prompt/SystemPrompt.txt b/resource/prompt/SystemPrompt.txt index c09ec28..033807f 100644 --- a/resource/prompt/SystemPrompt.txt +++ b/resource/prompt/SystemPrompt.txt @@ -43,7 +43,8 @@ 你必须严格遵守以下每一条规则: 1.用户时间宝贵,一旦确认工具成功调用之后,不得重复调用上一次已成功执行的工具,除非有新的参数或上下文变化。 -2.调用“ArcGisPro:ArcGisProTool”工具前一定要先调用“plan{}”规划工具的使用,确认调用工具流程之后再调用知识库将规划的正确调用名、以及工具参数返回到tool_result中,最后再逐步执行工具。 +2.调用“ArcGisPro:ArcGisProTool”工具前一定要先调用知识库工具检查工具调用名和参数是否正确,name一定要严格按照知识库的调用名,例如analysis.Erase。如果有非必填的参数要用""空开 3.参数都应为字符串类型,可以表示文件或其他输出类型。 4.一旦消息中没有工具或提示词调用信息即视为任务完成。因此工具或提示词调用必须连续完成。 5.只响应用户目前的需求即可,不要过度猜测用户的需求,如果有下一步的工具建议只输出文本即可,如果输出XML会执行大量无用的工具。 + From edab972b3ec4957241b06f3ef3c2a2af320578b4 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 8 Jun 2025 11:53:31 +0800 Subject: [PATCH 49/60] =?UTF-8?q?=E5=A2=9E=E5=8A=A0deepseek=E7=B3=BB?= =?UTF-8?q?=E5=88=97=E6=9B=B4=E5=A4=9A=E6=A8=A1=E5=9E=8B=E5=8F=8A=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/dockpane/DialogDockpane.xaml | 5 ++++ ui/dockpane/DialogDockpane.xaml.cs | 44 ++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/ui/dockpane/DialogDockpane.xaml b/ui/dockpane/DialogDockpane.xaml index 31542f5..b62fab7 100644 --- a/ui/dockpane/DialogDockpane.xaml +++ b/ui/dockpane/DialogDockpane.xaml @@ -43,6 +43,11 @@ + + + + + diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index 278944c..5dec4a0 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -118,8 +118,52 @@ namespace LinkToolAddin.ui.dockpane borderItemsDict[timestamp.ToString()] = userMsgBoder; ChatHistoryStackPanel.Children.Add(userMsgBoder); string model = (ModelComboBox.SelectedItem as ComboBoxItem).Content.ToString() is null ? "qwen3-235b-a22b" : (ModelComboBox.SelectedItem as ComboBoxItem).Content.ToString(); + if (model == "自定义") + { + model = ShowInputBox("自定义模型", "请输入模型名称:", ""); + } Gateway.SendMessageStream(question,model,defaultGdbPath,NewMessage_Recall); } + + private string ShowInputBox(string title, string message, string defaultValue = "") + { + // 创建一个自定义的输入对话框 + var dialog = new System.Windows.Window + { + Title = title, + Width = 300, + Height = 150, + WindowStyle = WindowStyle.ToolWindow, + ResizeMode = ResizeMode.NoResize, + Topmost = true, + WindowStartupLocation = WindowStartupLocation.CenterOwner + }; + + // 设置对话框内容 + var stackPanel = new StackPanel { Margin = new Thickness(10) }; + stackPanel.Children.Add(new TextBlock { Text = message, Margin = new Thickness(0, 0, 0, 5) }); + + var textBox = new TextBox { Text = defaultValue, Margin = new Thickness(0, 0, 0, 10) }; + stackPanel.Children.Add(textBox); + + var buttonPanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right }; + + var okButton = new Button { Content = "确定", Width = 75, Margin = new Thickness(0, 0, 5, 0) }; + okButton.Click += (sender, e) => dialog.Close(); + + var cancelButton = new Button { Content = "取消", Width = 75 }; + cancelButton.Click += (sender, e) => { textBox.Text = null; dialog.Close(); }; + + buttonPanel.Children.Add(okButton); + buttonPanel.Children.Add(cancelButton); + stackPanel.Children.Add(buttonPanel); + + dialog.Content = stackPanel; + + // 显示对话框并获取结果 + dialog.ShowDialog(); + return textBox.Text; + } public void NewMessage_Recall(MessageListItem msg) { From c61938383b6ff9018977aff3034b293307947a6a Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 8 Jun 2025 14:27:26 +0800 Subject: [PATCH 50/60] =?UTF-8?q?=E4=BF=AE=E5=A4=8DPrompt=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E4=B8=8D=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/Gateway.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/host/Gateway.cs b/host/Gateway.cs index 78889e8..5b59879 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -586,7 +586,8 @@ public class Gateway status = "success", content = "成功调用提示词:"+promptRequest.PromptName, id = (timestamp+1).ToString(), - result = "成功调用提示词:"+promptRequest.PromptName + result = "成功调用提示词:"+promptRequest.PromptName, + role = "user" }; Application.Current.Dispatcher.Invoke(() => { From 660ba2ad53a8f022e10f06fb563529e0dae60c74 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 8 Jun 2025 15:12:48 +0800 Subject: [PATCH 51/60] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E6=8D=95=E8=8E=B7=E8=83=BD=E5=8A=9B=EF=BC=8C=E5=87=8F=E5=B0=91?= =?UTF-8?q?=E9=97=AA=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/Gateway.cs | 375 +++++++++++++++++++++++------------------- host/McpServerList.cs | 94 +++++------ 2 files changed, 249 insertions(+), 220 deletions(-) diff --git a/host/Gateway.cs b/host/Gateway.cs index 5b59879..a3e76a2 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -239,17 +239,25 @@ public class Gateway api_key = "sk-db177155677e438f832860e7f4da6afc" }; List messages = new List(); - string toolInfos = await GetToolInfos(new McpServerList()); - messages.Add(new Message + string toolInfos = ""; + try { - Role = "system", - Content = SystemPrompt.SysPrompt(gdbPath, toolInfos) - }); - messages.Add(new Message + 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) { - Role = "user", - Content = message - }); + 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>"; @@ -282,56 +290,106 @@ public class Gateway { //如果本次回复不包含任何工具的调用或提示词的调用,则不再请求 goOn = false; - MessageListItem endMessageListItem1 = new ChatMessageItem + MessageListItem endMessageListItem1 = new ChatMessageItem { type = MessageType.END_TAG, content = "", - id = (timestamp+3).ToString(), + id = (timestamp + 3).ToString(), role = "assistant" }; callback?.Invoke(endMessageListItem1); break; } - 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 + try + { + await foreach(LlmStreamChat llmStreamChat in bailian.SendChatStreamAsync(jsonContent)) { - string chunk = llmStreamChat.Choices[0].Delta.Content; - MessageListItem reasonMessageListItem = new ChatMessageItem() + if (!goOn) { - 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 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 = remainingPrompt, + content = remaining, role = "assistant", type = MessageType.CHAT_MESSAGE, id = timestamp.ToString() @@ -340,84 +398,43 @@ public class Gateway { callback?.Invoke(chatMessageListItem); }); - } - else - { - //包含Prompt调用请求的消息 - MessageListItem chatMessageListItem = new ChatMessageItem() + MessageListItem toolMessageListItem = new ToolMessageItem() { - content = remainingPrompt, - role = "assistant", - type = MessageType.CHAT_MESSAGE, - id = timestamp.ToString() + content = "", + result = "", + toolName = toolName, + toolParams = toolParams, + role = "user", + type = MessageType.TOOL_MESSAGE, + id = (timestamp+1).ToString(), + status = "loading" }; - 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() + Application.Current.Dispatcher.Invoke(() => { - PromptName = promptKey, - PromptArgs = promptParams, - PromptServer = promptServerList.GetPromptServer(promptKey) + callback?.Invoke(toolMessageListItem); }); + mcpToolRequests = new List(); + McpToolRequest mcpToolRequest = new McpToolRequest() + { + McpServer = mcpServer, + ToolName = toolName, + ToolArgs = toolParams, + }; + mcpToolRequests.Add(mcpToolRequest); } } - else + catch (Exception e) { - //包含工具调用请求的消息 - 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); - }); - MessageListItem toolMessageListItem = new ToolMessageItem() - { - 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); - }); - mcpToolRequests = new List(); - McpToolRequest mcpToolRequest = new McpToolRequest() - { - McpServer = mcpServer, - ToolName = toolName, - ToolArgs = toolParams, - }; - mcpToolRequests.Add(mcpToolRequest); + Console.WriteLine(e); + log.Error(e.Message); } } - 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 @@ -616,85 +633,97 @@ public class Gateway StringBuilder toolInfos = new StringBuilder(); foreach (McpServer mcpServer in mcpServerList.GetAllServers()) { - log.Info($"正在列出{mcpServer.Name}中的工具"); - if (mcpServer is InnerMcpServer) + try { - InnerMcpServer innerMcpServer = (InnerMcpServer)mcpServer; - Type type = Type.GetType("LinkToolAddin.client.tool." + innerMcpServer.Name); - MethodInfo[] methods = type.GetMethods(); - foreach (MethodInfo method in methods) + if (mcpServer is InnerMcpServer) { - if (method.IsPublic && method.IsStatic) + InnerMcpServer innerMcpServer = (InnerMcpServer)mcpServer; + Type type = Type.GetType("LinkToolAddin.client.tool." + innerMcpServer.Name); + MethodInfo[] methods = type.GetMethods(); + foreach (MethodInfo method in methods) { - string methodName = method.Name; - string methodDescription = method.GetCustomAttribute()?.Description; - string methodParamSchema = LinkToolAddin.common.JsonSchemaGenerator.GenerateJsonSchema(method); + 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 = innerMcpServer.Name + ":" + methodName, - Description = methodDescription, - Arguments = methodParamSchema + Name = toolName, + Description = toolDescription, + Arguments = toolParamSchema } }; - XNode node = JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition)); - toolInfos.AppendLine(node.ToString()); + 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(); } } - } - else if(mcpServer is SseMcpServer) + }catch (Exception e) { - 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(); - } + log.Error(e.Message); } } List prompts = DynamicPrompt.GetAllPrompts(); foreach (UserPrompt userPrompt in prompts) { - XNode node = JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(new PromptDefinition(){UserPrompt = userPrompt})); - toolInfos.AppendLine(node.ToString()); - toolInfos.AppendLine(); + try + { + XNode node = JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(new PromptDefinition(){UserPrompt = userPrompt})); + toolInfos.AppendLine(node.ToString()); + toolInfos.AppendLine(); + } + catch (Exception e) + { + log.Error(e.Message); + } } return toolInfos.ToString(); } diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 0e062e2..b925a40 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -35,53 +35,53 @@ public class McpServerList Description = "可以调用进行查询知识库,获取相关参考信息。", IsActive = true }); - //servers.Add("filesystem", new StdioMcpServer() - //{ - // Name = "filesystem", - // Type = "stdio", - // Command = "npx", - // Args = new List() - // { - // "-y", - // "@modelcontextprotocol/server-filesystem", - // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData" - // } - //}); - //servers.Add("fetch", new StdioMcpServer() - //{ - // Name = "fetch", - // Type = "stdio", - // Command = "uvx", - // Args = new List() - // { - // "mcp-server-fetch" - // } - //}); - //servers.Add("bing-search", new StdioMcpServer() - //{ - // Name = "bing-search", - // Type = "stdio", - // Command = "npx", - // Args = new List() - // { - // "bing-cn-mcp" - // } - //}); - //servers.Add("mcp-python-interpreter", new StdioMcpServer() - //{ - // Name = "mcp-python-interpreter", - // Type = "stdio", - // Command = "uvx", - // Args = new List() - // { - // "--native-tls", - // "mcp-python-interpreter", - // "--dir", - // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData", - // "--python-path", - // "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe" - // } - //}); + servers.Add("filesystem", new StdioMcpServer() + { + Name = "filesystem", + Type = "stdio", + Command = "npxh", + Args = new List() + { + "-y", + "@modelcontextprotocol/server-filesystem", + "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData" + } + }); + servers.Add("fetch", new StdioMcpServer() + { + Name = "fetch", + Type = "stdio", + Command = "uvx", + Args = new List() + { + "mcp-server-fetch" + } + }); + servers.Add("bing-search", new StdioMcpServer() + { + Name = "bing-search", + Type = "stdio", + Command = "npx", + Args = new List() + { + "bing-cn-mcp" + } + }); + servers.Add("mcp-python-interpreter", new StdioMcpServer() + { + Name = "mcp-python-interpreter", + Type = "stdio", + Command = "uvx", + Args = new List() + { + "--native-tls", + "mcp-python-interpreter", + "--dir", + "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData", + "--python-path", + "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe" + } + }); } public McpServer GetServer(string name) From d0b6671cce87abdae7892d97de73cb9dccd0a7d5 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 8 Jun 2025 15:24:55 +0800 Subject: [PATCH 52/60] =?UTF-8?q?=E5=AE=8C=E5=96=84ArcGIS=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E6=8A=A5=E9=94=99=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/tool/ArcGisPro.cs | 4 ++-- server/CallArcGISPro.cs | 2 +- server/JsonRpcErrorEntity.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs index 52994b0..dbc5ac1 100644 --- a/client/tool/ArcGisPro.cs +++ b/client/tool/ArcGisPro.cs @@ -33,8 +33,8 @@ public class ArcGisPro { Error = new Error() { - Code = results.ErrorCode, - Message = GetMessagesString(results.ErrorMessages) + Code = results.ErrorCode.ToString(), + Message = GetMessagesString(results.ErrorMessages)+"\n"+GetMessagesString(results.Messages) } }; }else if(results.HasWarnings) diff --git a/server/CallArcGISPro.cs b/server/CallArcGISPro.cs index 4d5c611..016b399 100644 --- a/server/CallArcGISPro.cs +++ b/server/CallArcGISPro.cs @@ -22,7 +22,7 @@ public class CallArcGISPro { Error = new Error() { - Code = results.ErrorCode, + Code = results.ErrorCode.ToString(), Message = JsonConvert.SerializeObject(results.ErrorMessages) } }; diff --git a/server/JsonRpcErrorEntity.cs b/server/JsonRpcErrorEntity.cs index ad0d795..ca57640 100644 --- a/server/JsonRpcErrorEntity.cs +++ b/server/JsonRpcErrorEntity.cs @@ -17,7 +17,7 @@ namespace LinkToolAddin.server public partial class Error { [JsonProperty("code")] - public long Code { get; set; } + public string Code { get; set; } [JsonProperty("message")] public string Message { get; set; } From 9b600174e8afd9ba8c8cc2387f070941ebebc303 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 8 Jun 2025 15:48:41 +0800 Subject: [PATCH 53/60] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9B=9E=E7=AD=94?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=98=BE=E7=A4=BA=E5=BC=82=E5=B8=B8=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/Gateway.cs | 21 +++++++++++++++------ ui/dockpane/DialogDockpane.xaml.cs | 14 ++++++++------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/host/Gateway.cs b/host/Gateway.cs index a3e76a2..57d9845 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -268,7 +268,7 @@ public class Gateway while (goOn) { loop++; - if (loop > 20) + if (loop > 500) { MessageBox.Show("达到最大循环次数", "退出循环"); break; @@ -277,8 +277,9 @@ public class Gateway { Model = model, Messages = messages, - Temperature = 0.7, - TopP = 1, + Temperature = 0.3, + TopP = 0.4, + TopK = 7, MaxTokens = 1000, }; long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); @@ -317,7 +318,6 @@ public class Gateway callback?.Invoke(endMessageListItem2); break; } - try { string chunk = llmStreamChat.Choices[0].Delta.Content; @@ -529,11 +529,16 @@ public class Gateway var task = method.Invoke(null, args) as Task; JsonRpcResultEntity innerResult = await task; + string displayToolName = toolName; + if (displayToolName == "ArcGisProTool") + { + displayToolName = "【GP】"+toolParams["toolName"].ToString(); + } if (innerResult is JsonRpcErrorEntity) { MessageListItem toolMessageItem = new ToolMessageItem { - toolName = toolName, + toolName = displayToolName, toolParams = toolParams, type = MessageType.TOOL_MESSAGE, status = "fail", @@ -556,7 +561,7 @@ public class Gateway { MessageListItem toolMessageItem = new ToolMessageItem { - toolName = toolName, + toolName = displayToolName, toolParams = toolParams, type = MessageType.TOOL_MESSAGE, status = "success", @@ -631,8 +636,10 @@ public class Gateway private static async Task GetToolInfos(McpServerList mcpServerList) { StringBuilder toolInfos = new StringBuilder(); + int i = 0; foreach (McpServer mcpServer in mcpServerList.GetAllServers()) { + i++; try { if (mcpServer is InnerMcpServer) @@ -708,6 +715,7 @@ public class Gateway }catch (Exception e) { log.Error(e.Message); + MessageBox.Show(e.Message, $"第{i}MCP读取错误"); } } @@ -723,6 +731,7 @@ public class Gateway catch (Exception e) { log.Error(e.Message); + MessageBox.Show(e.Message, $"{userPrompt.Name}提示词读取错误"); } } return toolInfos.ToString(); diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index 5dec4a0..907dd2f 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -178,6 +178,10 @@ namespace LinkToolAddin.ui.dockpane //不存在该消息,需添加到ListView中 if (msg.content == "") { + if (msg.type == MessageType.END_TAG) + { + StatusTextBlock.Text = ""; + } return; } idList.Add(msgId); @@ -212,9 +216,6 @@ namespace LinkToolAddin.ui.dockpane borderItemsDict[msgId] = border; ChatHistoryStackPanel.Children.Add(border); StatusTextBlock.Text = "回答生成中"; - }else if (msg.type == MessageType.END_TAG) - { - StatusTextBlock.Text = ""; } } } @@ -224,6 +225,10 @@ namespace LinkToolAddin.ui.dockpane messageDict[msgId] = msg; if (msg.content == "") { + if (msg.type == MessageType.END_TAG) + { + StatusTextBlock.Text = ""; + } ChatHistoryStackPanel.Children.Remove(borderItemsDict[msgId]); borderItemsDict.TryRemove(msgId, out Border border); messageDict.TryRemove(msgId, out MessageListItem tempMsg); @@ -263,9 +268,6 @@ namespace LinkToolAddin.ui.dockpane TextBox textBox = grid.Children[1] as TextBox; textBox.Text = msg.content; StatusTextBlock.Text = "回答生成中"; - }else if (msg.type == MessageType.END_TAG) - { - StatusTextBlock.Text = ""; } } } From 544bc8cd406233b2e6eebed9f6df0b4b7b90d2d9 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 8 Jun 2025 16:13:57 +0800 Subject: [PATCH 54/60] =?UTF-8?q?=E8=A7=A3=E5=86=B3MCP=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=87=BA=E9=94=99=E5=BD=B1=E5=93=8D=E4=B8=BB=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/tool/ArcGisPro.cs | 2 +- host/Gateway.cs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs index dbc5ac1..6c1adcf 100644 --- a/client/tool/ArcGisPro.cs +++ b/client/tool/ArcGisPro.cs @@ -24,7 +24,7 @@ public class ArcGisPro [McpServerTool, Description("可以通过调用ArcGIS Pro的地理处理工具实现一些数据处理功能,传入参数必须严格遵循ArcGIS Pro调用工具的标准调用名和参数要求知识库。")] public static async Task ArcGisProTool(string toolName, List toolParams) { - IGPResult results = await Geoprocessing.ExecuteToolAsync(toolName, toolParams); + IGPResult results = await Geoprocessing.ExecuteToolAsync(toolName, toolParams,null,null,null,GPExecuteToolFlags.InheritGPOptions|GPExecuteToolFlags.GPThread); JsonRpcResultEntity jsonRpcResultEntity; if (results.IsFailed) { diff --git a/host/Gateway.cs b/host/Gateway.cs index 57d9845..8ce17fb 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -636,7 +636,8 @@ public class Gateway private static async Task GetToolInfos(McpServerList mcpServerList) { StringBuilder toolInfos = new StringBuilder(); - int i = 0; + int i = 0; + List failedMcp = new List(); foreach (McpServer mcpServer in mcpServerList.GetAllServers()) { i++; @@ -715,7 +716,7 @@ public class Gateway }catch (Exception e) { log.Error(e.Message); - MessageBox.Show(e.Message, $"第{i}MCP读取错误"); + failedMcp.Add(i); } } @@ -731,9 +732,9 @@ public class Gateway catch (Exception e) { log.Error(e.Message); - MessageBox.Show(e.Message, $"{userPrompt.Name}提示词读取错误"); } } + MessageBox.Show($"读取失败的MCP序号:{JsonConvert.SerializeObject(failedMcp)}","MCP读取错误"); return toolInfos.ToString(); } From 6a1fe1fa3ffe4f73110257b2f6e5b871cccda9e3 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 8 Jun 2025 16:16:26 +0800 Subject: [PATCH 55/60] =?UTF-8?q?=E8=A1=A5=E5=85=85=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index bece90c..26846fa 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -6,6 +6,13 @@ true CA1416 net8.0-windows + 0.1.3 + LinkToolAddin + LinkTool团队 + LinkTool以大模型赋能让您只需一两句话便能完成复杂的空间分析与时空大数据处理任务。 + 华南农业大学 + 校AI大赛提交版本 + 华南农业大学 From 86b800c477ef850aede88242d1d2dca9b68e0775 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Sun, 8 Jun 2025 16:17:20 +0800 Subject: [PATCH 56/60] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E6=A1=86=E7=89=88=E6=9C=AC=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/VersionButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/VersionButton.cs b/ui/VersionButton.cs index bc4df57..be464f7 100644 --- a/ui/VersionButton.cs +++ b/ui/VersionButton.cs @@ -22,7 +22,7 @@ namespace LinkToolAddin { internal class VersionButton : Button { - private string version = "0.1.1"; + private string version = "0.1.3"; protected override void OnClick() { MessageBox.Show($"当前LinkTool版本为{version}", "版本信息"); From 09d1b28b5b3fc96ebc7061c90085d0c7b128ef10 Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Sun, 8 Jun 2025 17:08:16 +0800 Subject: [PATCH 57/60] =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/McpServerList.cs | 3 ++- resource/prompt/SystemPrompt.txt | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/host/McpServerList.cs b/host/McpServerList.cs index 8983cf9..22be6dd 100644 --- a/host/McpServerList.cs +++ b/host/McpServerList.cs @@ -32,7 +32,8 @@ public class McpServerList { Name = "KnowledgeBase", Type = "inner", - Description = "可以调用进行查询知识库,获取相关参考信息。有地理信息的相关案例步骤参考以及Arcgis Pro的工具详细信息", + Description = "可以调用进行查询知识库,获取相关参考信息。" , + // "有地理信息的相关案例步骤参考以及Arcgis Pro的工具详细信息", IsActive = true }); //servers.Add("filesystem", new StdioMcpServer() diff --git a/resource/prompt/SystemPrompt.txt b/resource/prompt/SystemPrompt.txt index 0be3004..ba79e6b 100644 --- a/resource/prompt/SystemPrompt.txt +++ b/resource/prompt/SystemPrompt.txt @@ -43,10 +43,12 @@ 你必须严格遵守以下每一条规则: 1.用户时间宝贵,一旦确认工具成功调用之后,不得重复调用上一次已成功执行的工具,除非有新的参数或上下文变化。 -2.调用“ArcGisPro:ArcGisProTool”工具前一定要先调用知识库工具检查工具调用名和参数是否正确,name一定要严格按照知识库的调用名,例如analysis.Erase。如果有非必填的参数要用""空开 -3.参数都应为字符串类型,可以表示文件或其他输出类型。 -4.一旦消息中没有工具或提示词调用信息即视为任务完成。因此工具或提示词调用必须连续完成。 +2.调用“ArcGisPro:ArcGisProTool”工具,name一定要严格按照知识库的调用名,例如analysis.Erase。如果有非必填的参数要用""空开,还要在知识库查清楚每个参数填写的内容,比如固定选项,不要自己编写参数,工具参数的每个输入与输出数据都要把数据库的路径加上,不能只写名称。 +3.参数一定都为字符串类型,可以表示文件或其他输出类型。 +4.一次只能调用一个工具,逐步调用!不要调用多个,一旦消息中没有工具或提示词调用信息即视为任务完成。因此工具或提示词调用必须连续完成。 5.只响应用户目前的需求即可,不要过度猜测用户的需求,如果有下一步的工具建议只输出文本即可,如果输出XML会执行大量无用的工具。 特别注意: -ArcGIS Pro中不能通过先SelectByAttribute选择后再执行ExportFeatures导出指定的部分,正确的做法是直接用ExportFeatures传入where_clause导出指定部分的数据。 +1.ArcGIS Pro中不能通过先SelectByAttribute选择后再执行ExportFeatures导出指定的部分,正确的做法是直接用ExportFeatures传入where_clause导出指定部分的数据。 +2.对于建筑数据analysis.SummarizeWithin的参数列表如下(in_polygons, in_sum_features, out_feature_class, {keep_all_polygons}, {sum_fields}, {sum_shape}, {shape_unit}, {group_field}, {add_min_maj}, {add_group_percent}, {out_group_table}),"keep_all_polygons"填写"KEEP_ALL","sum_fields"为"","sum_shape"为"ADD_SHAPE_SUM","shape_unit"为"SQUAREKILOMETERS"每一个参数都不能有错,其他非必填的参数为空"a"。 +3.对于道路数据analysis.SummarizeWithin的参数设计为"shape_unit"为"KILOMETERS" From 3f3ecadec98d27e14dd1419c8b14c35568a19ec1 Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Wed, 11 Jun 2025 00:50:46 +0800 Subject: [PATCH 58/60] =?UTF-8?q?1.=20=E4=BF=AE=E5=A4=8D=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E5=89=8D=E4=B8=8D=E6=9F=A5=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E5=BA=93=E9=97=AE=E9=A2=98=202.=20=E5=AE=8C=E5=96=84MCP?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E6=9C=BA=E5=88=B6=203.=20=E5=A2=9E=E5=8A=A0A?= =?UTF-8?q?rcGIS=20Pro=E6=9F=A5=E7=9C=8B=E5=B1=9E=E6=80=A7=E8=A1=A8?= =?UTF-8?q?=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/tool/ArcGisPro.cs | 76 +++++++++++++++++- host/Gateway.cs | 124 ++++++++++++++++++++++------- host/McpServerList.cs | 10 +++ host/prompt/SystemPrompt.cs | 7 ++ ui/VersionButton.cs | 2 +- ui/dockpane/DialogDockpane.xaml.cs | 13 ++- ui/dockpane/TestDockpane.xaml | 2 + ui/dockpane/TestDockpane.xaml.cs | 9 +++ ui/message/MessageListItem.cs | 4 +- 9 files changed, 216 insertions(+), 31 deletions(-) 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 From 776cebd48ed0ed299e0546e7827155f6ad30f7dd Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Thu, 12 Jun 2025 11:17:09 +0800 Subject: [PATCH 59/60] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E8=B6=85=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host/Gateway.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/host/Gateway.cs b/host/Gateway.cs index 7359391..ad85e57 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -78,9 +78,11 @@ public class Gateway { Model = model, Messages = messages, - Temperature = 0.7, - TopP = 1, - MaxTokens = 1000, + Temperature = 0.3, + TopP = 0.6, + TopK = 25, + MaxTokens = 1024, + ThinkingBudget = 1024 }); long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); log.Info(reponse); From 441d8a075922ce6dd8c420a898c674850b819efc Mon Sep 17 00:00:00 2001 From: zengmq <2306188113@qq.com> Date: Sun, 15 Jun 2025 22:36:39 +0800 Subject: [PATCH 60/60] =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E5=8F=82=E6=95=B0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/prompt/ErrorPrompt.txt | 2 +- resource/prompt/SystemPrompt.txt | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/resource/prompt/ErrorPrompt.txt b/resource/prompt/ErrorPrompt.txt index d766496..07acb1d 100644 --- a/resource/prompt/ErrorPrompt.txt +++ b/resource/prompt/ErrorPrompt.txt @@ -7,7 +7,7 @@ 2. 修复方案: (1)如果是ArcGisPro工具,第一时间查询知识库的工具参数是否有误,多次执行失败极有可能是工具名或者参数错误,尤其注重调用工具或者提示词修正 (2)根据错误类型生成对应的工具重试策略。 -(3)参数调整:及时调用知识库和用户提示词,确保工具名和参数的完全正确。明确需要修改的参数及修改方式。 +(3)参数和调用名调整:及时调用知识库和用户提示词,确保工具名和参数的完全正确。明确需要修改的工具名、参数及修改方式。 (4)可以选择调用工具规划提示词优化工具执行流程或者调用知识库工具优化工具调用名和工具参数。 3. 工具重试:使用调整后的参数重新调用工具。请根据报错信息重试。 4.如果没有具体的错误描述信息请检查输出文件是否已经存在,若已经存在默认执行成功 diff --git a/resource/prompt/SystemPrompt.txt b/resource/prompt/SystemPrompt.txt index ba79e6b..bccbc96 100644 --- a/resource/prompt/SystemPrompt.txt +++ b/resource/prompt/SystemPrompt.txt @@ -3,11 +3,11 @@ 调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。你还可以通过方式来调用用户提示词,或者调用知识库工具,你能更好地理解和解决用户的问题。您需要通过逐步使用工具或提示词来完成给定任务,每次调用需基于前一次的结果。成功调用工具或提示词之后应该马上调用下一个工具或提示词。 工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。 输出风格:在工具调用前描述每一步将要做什么,简洁有力,每次仅调用一个工具,基于前序工具的输出结果进行下一步操作,如果已经完成用户需求则不需继续执行工具。话语开头不要回复好的。工具调用使用 XML 风格的标签输出。 -工具调用的XML格式一定一定要完全正确,格式如下: +工具调用的XML格式一定一定要完全正确,不能有错漏,格式如下: {tool_name} {json_arguments} -。 + 工具名称:需与所使用工具的精确名称一致。 参数:应为包含工具所需参数的 JSON 对象。 @@ -33,7 +33,7 @@ {tool_name} {result} -。 + 工具调用示例:MCP工具调用的格式要求示例:以下是使用虚拟工具的示例: @@ -43,12 +43,11 @@ 你必须严格遵守以下每一条规则: 1.用户时间宝贵,一旦确认工具成功调用之后,不得重复调用上一次已成功执行的工具,除非有新的参数或上下文变化。 -2.调用“ArcGisPro:ArcGisProTool”工具,name一定要严格按照知识库的调用名,例如analysis.Erase。如果有非必填的参数要用""空开,还要在知识库查清楚每个参数填写的内容,比如固定选项,不要自己编写参数,工具参数的每个输入与输出数据都要把数据库的路径加上,不能只写名称。 +2.调用“ArcGisPro:ArcGisProTool”工具,name一定要严格按照知识库的调用名,例如"analysis.Union","management.Clip"等。输入与输出数据一定需要加上路径信息,输出数据到默认数据库中。`in_features`参数通过分号(`;`)拼接多个输入要素类路径,参数列表中有"{}"代表选填,其他必须要填写。 3.参数一定都为字符串类型,可以表示文件或其他输出类型。 4.一次只能调用一个工具,逐步调用!不要调用多个,一旦消息中没有工具或提示词调用信息即视为任务完成。因此工具或提示词调用必须连续完成。 5.只响应用户目前的需求即可,不要过度猜测用户的需求,如果有下一步的工具建议只输出文本即可,如果输出XML会执行大量无用的工具。 特别注意: 1.ArcGIS Pro中不能通过先SelectByAttribute选择后再执行ExportFeatures导出指定的部分,正确的做法是直接用ExportFeatures传入where_clause导出指定部分的数据。 -2.对于建筑数据analysis.SummarizeWithin的参数列表如下(in_polygons, in_sum_features, out_feature_class, {keep_all_polygons}, {sum_fields}, {sum_shape}, {shape_unit}, {group_field}, {add_min_maj}, {add_group_percent}, {out_group_table}),"keep_all_polygons"填写"KEEP_ALL","sum_fields"为"","sum_shape"为"ADD_SHAPE_SUM","shape_unit"为"SQUAREKILOMETERS"每一个参数都不能有错,其他非必填的参数为空"a"。 -3.对于道路数据analysis.SummarizeWithin的参数设计为"shape_unit"为"KILOMETERS" +2.可以先参考知识库中的案例辅助工具的规划