diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj
index a4143cc..35cdbec 100644
--- a/LinkToolAddin.csproj
+++ b/LinkToolAddin.csproj
@@ -97,10 +97,6 @@
False
-
-
-
-
@@ -108,6 +104,16 @@
+
+
+
+
+
+
+ Always
+
+
+
diff --git a/README.md b/README.md
index f0d5845..71d45b5 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,7 @@ git clone xxx.git
5. 部分情况下可能还需要替换LinkToolAddin.csproj中的所有相关安装路径
6. 点击运行进行测试,看是否能正常打开ArcGIS Pro和LinkTool插件
7. 确认无误建议commit到本地的master分支以备后续使用(但请勿推送至远端)
+8. 修改McpServerList里面filesystem的白名单目录
#### 创建分支
diff --git a/client/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/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/client/tool/ArcGisPro.cs b/client/tool/ArcGisPro.cs
index 163f253..52994b0 100644
--- a/client/tool/ArcGisPro.cs
+++ b/client/tool/ArcGisPro.cs
@@ -2,12 +2,16 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
+using System.Text;
using System.Threading.Tasks;
using ArcGIS.Core.Data;
using ArcGIS.Core.Data.Raster;
using ArcGIS.Core.Geometry;
+using ArcGIS.Desktop.Core.Geoprocessing;
using ArcGIS.Desktop.Framework.Threading.Tasks;
using LinkToolAddin.server;
+using LinkToolAddin.ui.dockpane;
+using log4net;
using ModelContextProtocol.Server;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -16,14 +20,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 = GetMessagesString(results.ErrorMessages)
+ }
+ };
+ }else if(results.HasWarnings)
+ {
+ log.Warn(results.Messages);
+ jsonRpcResultEntity = new JsonRpcSuccessEntity
+ {
+ Result = GetMessagesString(results.Messages)
+ };
+ }
+ else
+ {
+ log.Info("success gp tool");
+ jsonRpcResultEntity = new JsonRpcSuccessEntity
+ {
+ Result = GetMessagesString(results.Messages)
+ };
+ }
+ return jsonRpcResultEntity;
}
[McpServerTool, Description("查看指定数据的坐标系、范围、几何类型、是否有Z坐标和M坐标,获取字段列表等")]
@@ -106,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/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/doc/TodoList.md b/doc/TodoList.md
new file mode 100644
index 0000000..65f668c
--- /dev/null
+++ b/doc/TodoList.md
@@ -0,0 +1,28 @@
+# 待办事项
+
+本文档用于记录和跟踪当前阶段待办事项及完成进度
+
+## 核心程序
+
+- [x] 提示词调用完整实现:以面向对象形式取代字典形式,加入参数和描述
+- [x] 接入本地文件系统等基础性MCP服务
+- [ ] Python代码执行的实现
+- [x] 适配qwen3、deepseek等推理模型并迁移
+- [x] 工具执行错误消息合并为一条
+
+## 提示词工程
+
+- [ ] 将原来的单独XML规则修改为内嵌XML规则
+- [ ] 系统提示词明确提示调用动态Prompt和知识库
+- [ ] 错误和继续提示词预留工具消息占位符
+- [ ] 错误和继续提示词明示
+- [ ] 系统提示词泛化增强
+- [ ] 针对qwen3适当调整
+
+## 前端交互
+
+- [ ] 三类消息卡片
+- [ ] 流式输出,根据id匹配修改或新增
+- [ ] 添加工作空间、发送、复制粘贴等交互
+- [ ] 独立UI线程
+- [ ] 工具卡片特殊提示,弹出窗口显示内容
\ No newline at end of file
diff --git a/host/Gateway.cs b/host/Gateway.cs
index 28935f0..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;
@@ -32,6 +32,8 @@ using Newtonsoft.Json.Schema;
using Newtonsoft.Json.Schema.Generation;
using Tool = LinkToolAddin.host.mcp.Tool;
using LinkToolAddin.common;
+using LinkToolAddin.host.llm.entity.stream;
+using MessageBox = ArcGIS.Desktop.Framework.Dialogs.MessageBox;
namespace LinkToolAddin.host;
@@ -112,12 +114,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)
@@ -137,12 +134,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)
@@ -164,12 +156,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)
@@ -185,12 +172,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);
}
@@ -204,7 +186,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",
@@ -222,13 +204,34 @@ public class Gateway
};
callback?.Invoke(chatMessageListItem);
}
- if (reponse == "[DONE]")
+ if (reponse.EndsWith("[DONE]"))
{
goOn = false;
}
}
}
+ private static (string Matched, string Remaining) ExtractMatchedPart(string input, string toolPattern)
+ {
+ if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(toolPattern))
+ return (string.Empty, input);
+
+ Regex regex = new Regex(toolPattern);
+ Match match = regex.Match(input);
+
+ if (!match.Success)
+ return (string.Empty, input);
+
+ string matched = match.Value;
+ int startIndex = match.Index;
+ int length = match.Length;
+
+ // 构造剩余字符串
+ string remaining = input.Substring(0, startIndex) + input.Substring(startIndex + length);
+
+ return (matched, remaining);
+ }
+
public static async void SendMessageStream(string message, string model, string gdbPath, Action callback)
{
Llm bailian = new Bailian
@@ -237,7 +240,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",
@@ -249,10 +251,12 @@ 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]*?)([\\s\\S]*?)<\\/arguments>([\\s\\S]*?)<\\/prompt>";
McpServerList mcpServerList = new McpServerList();
+ PromptServerList promptServerList = new PromptServerList();
int loop = 0;
+ string messageContent = ""; //一次请求下完整的response
while (goOn)
{
loop++;
@@ -270,220 +274,291 @@ public class Gateway
MaxTokens = 1000,
};
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
- string messageContent = "";
- await foreach(var chunk in bailian.SendChatStreamAsync(jsonContent))
+ List mcpToolRequests = new List();
+ List promptRequests = new List();
+ var (toolMatched, toolRemaining) = ExtractMatchedPart(messageContent, toolPattern);
+ var (promptMatched, promptRemaining) = ExtractMatchedPart(messageContent, promptPattern);
+ if (toolMatched == "" && promptMatched == "" && messageContent != "")
{
- if (chunk == "[DONE]")
+ //如果本次回复不包含任何工具的调用或提示词的调用,则不再请求
+ goOn = false;
+ break;
+ }
+ await foreach(LlmStreamChat llmStreamChat in bailian.SendChatStreamAsync(jsonContent))
+ {
+ if (!goOn)
{
- goOn = false;
- }else if (chunk.StartsWith(""))
+ break;
+ }
+
+ try
{
- if (Regex.IsMatch(chunk, toolPattern))
+ string chunk = llmStreamChat.Choices[0].Delta.Content;
+ MessageListItem reasonMessageListItem = new ChatMessageItem()
{
- //返回工具卡片
- messages.Add(new Message
+ content = llmStreamChat.Choices[0].Delta.ResoningContent,
+ role = "assistant",
+ type = MessageType.REASON_MESSAGE,
+ id = (timestamp-1).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 == "")
{
- Role = "assistant",
- Content = chunk
+ //普通消息文本
+ 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
+ {
+ //包含工具调用请求的消息
+ 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(chunk);
+ 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)
+ //将工具调用请求添加至列表中待一次回答完整后再统一进行调用
+ mcpToolRequests = new List();
+ McpToolRequest mcpToolRequest = new McpToolRequest()
{
- 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);
- }
- }
+ McpServer = mcpServer,
+ ToolName = toolName,
+ ToolArgs = toolParams,
+ };
+ mcpToolRequests.Add(mcpToolRequest);
}
- else
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ log.Error(e.Message);
+ }
+ }
+ if (messageContent != "")
+ {
+ messages.Add(new Message
+ {
+ Role = "assistant",
+ Content = messageContent
+ });
+ }
+ /*统一处理本次请求中的MCP工具调用需求*/
+ foreach (McpToolRequest mcpToolRequest in mcpToolRequests)
+ {
+ try
+ {
+ McpServer mcpServer = mcpToolRequest.McpServer;
+ string toolName = mcpToolRequest.ToolName;
+ Dictionary toolParams = mcpToolRequest.ToolArgs;
+ if (mcpServer is SseMcpServer)
{
+ SseMcpServer sseMcpServer = mcpServer as SseMcpServer;
+ SseMcpClient client = new SseMcpClient(sseMcpServer.BaseUrl);
+ CallToolResponse toolResponse = await client.CallToolAsync(toolName, toolParams);
MessageListItem toolMessageItem = new ToolMessageItem
{
- toolName = "",
- toolParams = new Dictionary(),
+ toolName = toolName,
+ toolParams = toolParams,
type = MessageType.TOOL_MESSAGE,
- status = "loading",
- content = "正在生成工具调用参数",
- id = timestamp.ToString()
+ status = toolResponse.IsError ? "fail" : "success",
+ content = JsonConvert.SerializeObject(toolResponse),
+ id = (timestamp + 1).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)
+ Content = toolResponse.IsError
+ ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse))
+ : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse))
});
- MessageListItem toolMessageItem = new ToolMessageItem
+ Application.Current.Dispatcher.Invoke(() =>
{
- toolName = "调用提示词",
- toolParams = null,
- type = MessageType.TOOL_MESSAGE,
- status = "success",
- content = promptKey,
- id = timestamp.ToString()
- };
- callback?.Invoke(toolMessageItem);
+ callback?.Invoke(toolMessageItem);
+ });
}
- else
+ 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 = "调用提示词",
- toolParams = null,
+ toolName = toolName,
+ toolParams = toolParams,
type = MessageType.TOOL_MESSAGE,
- status = "loading",
- content = "正在调用提示词",
- id = timestamp.ToString()
+ status = toolResponse.IsError ? "fail" : "success",
+ content = JsonConvert.SerializeObject(toolResponse),
+ id = (timestamp + 1).ToString()
};
- callback?.Invoke(toolMessageItem);
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = toolResponse.IsError
+ ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse))
+ : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse))
+ });
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ callback?.Invoke(toolMessageItem);
+ });
+ }
+ 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);
+ 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.ErrorPrompt(JsonConvert.SerializeObject(toolMessageItem))
+ });
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ 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(toolMessageItem))
+ });
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ callback?.Invoke(toolMessageItem);
+ });
+ }
}
}
- else
+ catch (Exception ex)
{
- //普通流式消息卡片
- MessageListItem chatMessageListItem = new ChatMessageItem()
+ Console.WriteLine(ex.Message);
+ log.Error(ex.Message);
+ }
+
+ }
+ /*统一处理本次请求中的Prompt调用需求*/
+ foreach (PromptRequest promptRequest in promptRequests)
+ {
+ try
+ {
+ string promptContent = DynamicPrompt.GetPrompt(promptRequest.PromptName, promptRequest.PromptArgs);
+ messages.Add(new Message
{
- content = chunk,
- role = "assistant",
- type = MessageType.CHAT_MESSAGE,
- id = timestamp.ToString()
+ Role = "user",
+ Content = promptContent
+ });
+ MessageListItem toolMessageItem = new ToolMessageItem
+ {
+ toolName = "调用提示词",
+ toolParams = null,
+ type = MessageType.TOOL_MESSAGE,
+ status = "success",
+ content = "成功调用提示词:"+promptRequest.PromptName,
+ id = (timestamp+1).ToString()
};
- messageContent = chunk;
- callback?.Invoke(chatMessageListItem);
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ callback?.Invoke(toolMessageItem);
+ });
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ log.Error(e.Message);
}
}
- messages.Add(new Message
- {
- Role = "assistant",
- Content = messageContent
- });
}
}
@@ -492,6 +567,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;
@@ -564,17 +640,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/McpServerList.cs b/host/McpServerList.cs
index 68f83b4..ca7415d 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/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/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/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/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/SystemPrompt.cs b/host/prompt/SystemPrompt.cs
index 9f9481d..2295d68 100644
--- a/host/prompt/SystemPrompt.cs
+++ b/host/prompt/SystemPrompt.cs
@@ -1,37 +1,36 @@
-namespace LinkToolAddin.host.prompt;
+using LinkToolAddin.common;
+
+namespace LinkToolAddin.host.prompt;
public class SystemPrompt
{
- public static string SysPromptTemplate =
- "现在你是一个精通地理信息分析和ArcGIS Pro软件的专家,请以此身份回答用户的问题。" +
- "指令:您可以使用一组工具来回答用户的问题。还可以通过调用用户提示词,从而使你更好地理解和完成用户的任务。整个流程结束之后单独输出一条内容为'[DONE]'的消息,前后不要有任何说明文字。" +
- "调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过的方式来调用用户提示词,你能更好地理解和解决用户的问题。" +
- "工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。" +
- "输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化以单独信息格式输出,前后没有文字信息。" +
- "如果需要进行文字说明,请只有文字内容,以普通段落形式呈现没有其他格式。" +
- "工具调用格式:工具名称包含在一对标签内,每个参数也需用对应的标签包裹。结构如下:\n {tool_name}\n {json_arguments}\n。" +
- "工具名称:需与所使用工具的精确名称一致。" +
- "参数:应为包含工具所需参数的 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软件的专家,请以此身份回答用户的问题。\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.如果没有具体的错误描述信息请检查输出文件是否已经存在,若已经存在默认执行成功";
+ // 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;
@@ -39,8 +38,15 @@ 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 = LocalResource.ReadFileByResource("LinkToolAddin.resource.prompt.ErrorPrompt.txt");
+ errPrompt = errPrompt.Replace("{{toolResult}}", toolResult);
+ return errPrompt;
+ }
}
\ 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
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/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 b/ui/dockpane/TestDockpane.xaml
index 53c1501..6d4ae00 100644
--- a/ui/dockpane/TestDockpane.xaml
+++ b/ui/dockpane/TestDockpane.xaml
@@ -24,12 +24,13 @@
+
-
+
@@ -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 0286d87..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;
@@ -8,6 +9,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 +25,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
@@ -35,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()
{
@@ -195,14 +198,26 @@ 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;
- // Gateway.SendMessage(userPrompt,"qwen-max","C:/Project/test.gdb",AddReply);
- Gateway.SendMessageStream(userPrompt,"qwen-max", "F:\\secondsemester\\linktool\\test\\linktooltest\\linktooltest.gdb", AddReplyStream);
+ // 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));
+ }
+ catch (Exception exception)
+ {
+ log.Error(exception.Message);
+ }
}
- 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))
@@ -212,16 +227,34 @@ namespace LinkToolAddin.ui.dockpane
else
{
idList.Add(id);
- messageDict.Add(msg.id, msg);
+ messageDict.TryAdd(msg.id, msg);
}
- ReplyTextBox.Clear();
- StringBuilder builder = new StringBuilder();
- foreach (KeyValuePair pair in messageDict)
+ Application.Current.Dispatcher.Invoke(() =>
{
- MessageListItem msgItem = pair.Value;
- builder.AppendLine(msgItem.content);
- ReplyTextBox.Text = builder.ToString();
- ReplyTextBox.ScrollToEnd();
+ ReplyTextBox.Clear();
+ });
+ try
+ {
+ StringBuilder builder = new StringBuilder();
+ foreach (KeyValuePair pair in messageDict)
+ {
+ MessageListItem msgItem = pair.Value;
+ string content = msgItem.content;
+ if (msgItem.type == MessageType.REASON_MESSAGE)
+ {
+ content = "" + content + "";
+ }
+ builder.AppendLine(content);
+ builder.AppendLine();
+ }
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ ReplyTextBox.Text = builder.ToString();
+ ReplyTextBox.ScrollToEnd();
+ });
+ }catch (Exception exception)
+ {
+ log.Error(exception.Message);
}
}
@@ -245,6 +278,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);
@@ -274,5 +308,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);
+ }
}
}
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