1008 lines
46 KiB
C#
1008 lines
46 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.ComponentModel.DataAnnotations;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Text;
|
||
using System.Text.RegularExpressions;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Documents;
|
||
using System.Xml;
|
||
using System.Xml.Linq;
|
||
using ArcGIS.Desktop.Internal.Mapping;
|
||
using ArcGIS.Desktop.Internal.Mapping.Locate;
|
||
using LinkToolAddin.client;
|
||
using LinkToolAddin.client.prompt;
|
||
using LinkToolAddin.client.tool;
|
||
using LinkToolAddin.host.llm;
|
||
using LinkToolAddin.host.llm.entity;
|
||
using LinkToolAddin.host.mcp;
|
||
using LinkToolAddin.host.prompt;
|
||
using LinkToolAddin.message;
|
||
using LinkToolAddin.server;
|
||
using LinkToolAddin.ui.dockpane;
|
||
using log4net;
|
||
using Microsoft.Extensions.AI;
|
||
using ModelContextProtocol.Client;
|
||
using ModelContextProtocol.Protocol.Types;
|
||
using Newtonsoft.Json;
|
||
using Newtonsoft.Json.Linq;
|
||
using Newtonsoft.Json.Schema;
|
||
using Newtonsoft.Json.Schema.Generation;
|
||
using Tool = LinkToolAddin.host.mcp.Tool;
|
||
using LinkToolAddin.common;
|
||
using LinkToolAddin.host.llm.entity.stream;
|
||
using MessageBox = ArcGIS.Desktop.Framework.Dialogs.MessageBox;
|
||
|
||
namespace LinkToolAddin.host;
|
||
|
||
public class Gateway
|
||
{
|
||
private static ILog log = LogManager.GetLogger(typeof(Gateway));
|
||
private static bool goOn = true;
|
||
|
||
public static void StopConversation()
|
||
{
|
||
goOn = false;
|
||
}
|
||
|
||
public static async void SendMessage(string message, string model, string gdbPath, Action<MessageListItem> callback)
|
||
{
|
||
Llm bailian = new Bailian
|
||
{
|
||
api_key = "sk-db177155677e438f832860e7f4da6afc"
|
||
};
|
||
List<Message> messages = new List<Message>();
|
||
string toolInfos = await GetToolInfos(new McpServerList(),callback);
|
||
log.Info(SystemPrompt.SysPrompt(gdbPath, toolInfos));
|
||
messages.Add(new Message
|
||
{
|
||
Role = "system",
|
||
Content = SystemPrompt.SysPrompt(gdbPath, toolInfos)
|
||
});
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = message
|
||
});
|
||
bool goOn = true;
|
||
string pattern = "^<tool_use>[\\s\\S]*?<\\/tool_use>$";
|
||
string promptPattern = "^<prompt>[\\s\\S]*?<\\/prompt>$";
|
||
McpServerList mcpServerList = new McpServerList();
|
||
while (goOn)
|
||
{
|
||
string reponse = await bailian.SendChatAsync(new LlmJsonContent()
|
||
{
|
||
Model = model,
|
||
Messages = messages,
|
||
Temperature = 0.3,
|
||
TopP = 0.6,
|
||
TopK = 25,
|
||
MaxTokens = 1024,
|
||
ThinkingBudget = 1024
|
||
});
|
||
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||
log.Info(reponse);
|
||
messages.Add(new Message
|
||
{
|
||
Role = "assistant",
|
||
Content = reponse
|
||
});
|
||
if (Regex.IsMatch(reponse, pattern))
|
||
{
|
||
//工具类型的消息
|
||
XElement toolUse = XElement.Parse(reponse);
|
||
string fullToolName = toolUse.Element("name")?.Value;
|
||
string toolArgs = toolUse.Element("arguments")?.Value;
|
||
Dictionary<string, object> toolParams = JsonConvert.DeserializeObject<Dictionary<string, object>>(toolArgs);
|
||
string serverName = fullToolName.Contains(":") ? fullToolName.Split(':')[0] : fullToolName;
|
||
string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName;
|
||
McpServer mcpServer = mcpServerList.GetServer(serverName);
|
||
if (mcpServer is SseMcpServer)
|
||
{
|
||
SseMcpServer sseMcpServer = mcpServer as SseMcpServer;
|
||
SseMcpClient client = new SseMcpClient(sseMcpServer.BaseUrl);
|
||
CallToolResponse toolResponse = await client.CallToolAsync(toolName,toolParams);
|
||
MessageListItem toolMessageItem = new ToolMessageItem
|
||
{
|
||
toolName = toolName,
|
||
toolParams = toolParams,
|
||
type = MessageType.TOOL_MESSAGE,
|
||
status = toolResponse.IsError ? "fail" : "success",
|
||
content = JsonConvert.SerializeObject(toolResponse),
|
||
id = timestamp.ToString()
|
||
};
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = toolResponse.IsError ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse))
|
||
});
|
||
callback?.Invoke(toolMessageItem);
|
||
}else if (mcpServer is StdioMcpServer)
|
||
{
|
||
StdioMcpServer stdioMcpServer = mcpServer as StdioMcpServer;
|
||
StdioMcpClient client = new StdioMcpClient(stdioMcpServer.Command, stdioMcpServer.Args);
|
||
CallToolResponse toolResponse = await client.CallToolAsync(toolName,toolParams);
|
||
MessageListItem toolMessageItem = new ToolMessageItem
|
||
{
|
||
toolName = toolName,
|
||
toolParams = toolParams,
|
||
type = MessageType.TOOL_MESSAGE,
|
||
status = toolResponse.IsError ? "fail" : "success",
|
||
content = JsonConvert.SerializeObject(toolResponse),
|
||
id = timestamp.ToString()
|
||
};
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = toolResponse.IsError ? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse)) : SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse))
|
||
});
|
||
callback?.Invoke(toolMessageItem);
|
||
}else if (mcpServer is InnerMcpServer)
|
||
{
|
||
Type type = Type.GetType("LinkToolAddin.client.tool."+serverName);
|
||
MethodInfo method = type.GetMethod(toolName,BindingFlags.Public | BindingFlags.Static);
|
||
var task = method.Invoke(null, toolParams.Values.ToArray()) as Task<JsonRpcResultEntity>;
|
||
JsonRpcResultEntity innerResult = await task;
|
||
if (innerResult is JsonRpcErrorEntity)
|
||
{
|
||
MessageListItem toolMessageItem = new ToolMessageItem
|
||
{
|
||
toolName = toolName,
|
||
toolParams = toolParams,
|
||
type = MessageType.TOOL_MESSAGE,
|
||
status = "fail",
|
||
content = JsonConvert.SerializeObject(innerResult)
|
||
};
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(innerResult))
|
||
});
|
||
callback?.Invoke(toolMessageItem);
|
||
}else if (innerResult is JsonRpcSuccessEntity)
|
||
{
|
||
MessageListItem toolMessageItem = new ToolMessageItem
|
||
{
|
||
toolName = toolName,
|
||
toolParams = toolParams,
|
||
type = MessageType.TOOL_MESSAGE,
|
||
status = "success",
|
||
content = JsonConvert.SerializeObject(innerResult)
|
||
};
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(innerResult))
|
||
});
|
||
callback?.Invoke(toolMessageItem);
|
||
}
|
||
}
|
||
}
|
||
else if (Regex.IsMatch(reponse, promptPattern))
|
||
{
|
||
XElement prompt = XElement.Parse(reponse);
|
||
string fullPromptName = prompt.Element("name")?.Value;
|
||
string promptArgs = prompt.Element("arguments")?.Value;
|
||
Dictionary<string, object> promptParams = JsonConvert.DeserializeObject<Dictionary<string, object>>(promptArgs);
|
||
string serverName = fullPromptName.Contains(":") ? fullPromptName.Split(':')[0] : fullPromptName;
|
||
string promptName = fullPromptName.Contains(":") ? fullPromptName.Split(':')[1] : fullPromptName;
|
||
string promptRes = DynamicPrompt.GetPrompt(promptName, null);
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = promptRes
|
||
});
|
||
}
|
||
else
|
||
{
|
||
MessageListItem chatMessageListItem = new ChatMessageItem()
|
||
{
|
||
content = reponse,
|
||
role = "assistant",
|
||
type = MessageType.CHAT_MESSAGE,
|
||
id = timestamp.ToString()
|
||
};
|
||
callback?.Invoke(chatMessageListItem);
|
||
}
|
||
if (reponse.EndsWith("[DONE]"))
|
||
{
|
||
goOn = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
private static (string Matched, string Remaining) ExtractMatchedPart(string input, string toolPattern)
|
||
{
|
||
if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(toolPattern))
|
||
return (string.Empty, input);
|
||
|
||
Regex regex = new Regex(toolPattern);
|
||
Match match = regex.Match(input);
|
||
|
||
if (!match.Success)
|
||
return (string.Empty, input);
|
||
|
||
string matched = match.Value;
|
||
int startIndex = match.Index;
|
||
int length = match.Length;
|
||
|
||
// 构造剩余字符串
|
||
string remaining = input.Substring(0, startIndex) + input.Substring(startIndex + length);
|
||
|
||
return (matched, remaining);
|
||
}
|
||
|
||
public static async void SendMessageStream(string message, string model, string gdbPath, Action<MessageListItem> callback)
|
||
{
|
||
Llm modelObj = new Bailian();
|
||
List<string> bailianModels = ["qwen3-235b-a22b","qwen3-32b","qwq-32b","qwen-max-latest","deepseek-r1","deepseek-r1-0528","deepseek-r1-distill-qwen-32b","deepseek-r1-distill-llama-70b","deepseek-v3"];
|
||
List<string> dmxModels = ["gpt-4o","claude-sonnet-4-20250514-thinking","claude-sonnet-4-20250514","grok-3-reasoner","gemini-2.5-pro","o4-mini"];
|
||
if (bailianModels.Contains(model))
|
||
{
|
||
modelObj = new Bailian
|
||
{
|
||
api_key = "sk-db177155677e438f832860e7f4da6afc"
|
||
};
|
||
}else if (dmxModels.Contains(model))
|
||
{
|
||
modelObj = new DmxApi
|
||
{
|
||
api_key = "sk-VQeuLUmhO1LL8H97tFj5kuWOqGFD4CFRmAsdqhxkmkYxUUlP"
|
||
};
|
||
}
|
||
else
|
||
{
|
||
MessageListItem endMessageListItem2 = new ChatMessageItem
|
||
{
|
||
type = MessageType.ERROR,
|
||
content = $"目前暂未支持{model}模型,请选择其他模型。",
|
||
id = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(),
|
||
role = "system"
|
||
};
|
||
callback?.Invoke(endMessageListItem2);
|
||
MessageListItem endMessageListItem1 = new ChatMessageItem
|
||
{
|
||
type = MessageType.END_TAG,
|
||
content = "",
|
||
id = (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + 3).ToString(),
|
||
role = "assistant"
|
||
};
|
||
callback?.Invoke(endMessageListItem1);
|
||
return;
|
||
}
|
||
|
||
List<Message> messages = new List<Message>();
|
||
string toolInfos = "";
|
||
try
|
||
{
|
||
toolInfos = await GetToolInfos(new McpServerList(),callback);
|
||
messages.Add(new Message
|
||
{
|
||
Role = "system",
|
||
Content = SystemPrompt.SysPrompt(gdbPath, toolInfos)
|
||
});
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = message
|
||
});
|
||
}catch (Exception ex)
|
||
{
|
||
log.Error(ex);
|
||
MessageListItem endMessageListItem2 = new ChatMessageItem
|
||
{
|
||
type = MessageType.WARNING,
|
||
content = $"MCP列表读取失败{ex.Message}",
|
||
id = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(),
|
||
role = "system"
|
||
};
|
||
callback?.Invoke(endMessageListItem2);
|
||
MessageListItem endMessageListItem1 = new ChatMessageItem
|
||
{
|
||
type = MessageType.END_TAG,
|
||
content = "",
|
||
id = (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + 3).ToString(),
|
||
role = "assistant"
|
||
};
|
||
callback?.Invoke(endMessageListItem1);
|
||
// MessageBox.Show(ex.Message,"获取MCP列表失败");
|
||
}
|
||
goOn = true;
|
||
long accTokens = 0;
|
||
string toolPattern = "<tool_use>([\\s\\S]*?)<name>([\\s\\S]*?)<\\/name>([\\s\\S]*?)<arguments>([\\s\\S]*?)<\\/arguments>([\\s\\S]*?)<\\/tool_use>";
|
||
string promptPattern = "<prompt>([\\s\\S]*?)<name>([\\s\\S]*?)<\\/name>([\\s\\S]*?)<arguments>([\\s\\S]*?)<\\/arguments>([\\s\\S]*?)<\\/prompt>";
|
||
McpServerList mcpServerList = new McpServerList();
|
||
PromptServerList promptServerList = new PromptServerList();
|
||
int loop = 0;
|
||
string messageContent = ""; //一次请求下完整的response
|
||
bool queriedKnowledge = false;
|
||
bool executedTool = false;
|
||
while (goOn)
|
||
{
|
||
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||
loop++;
|
||
if (loop > 500)
|
||
{
|
||
MessageListItem endMessageListItem2 = new ChatMessageItem
|
||
{
|
||
type = MessageType.ERROR,
|
||
content = "达到最大循环次数,已退出循环。",
|
||
id = timestamp.ToString(),
|
||
role = "system"
|
||
};
|
||
callback?.Invoke(endMessageListItem2);
|
||
MessageListItem endMessageListItem1 = new ChatMessageItem
|
||
{
|
||
type = MessageType.END_TAG,
|
||
content = "",
|
||
id = (timestamp + 3).ToString(),
|
||
role = "assistant"
|
||
};
|
||
callback?.Invoke(endMessageListItem1);
|
||
// MessageBox.Show("达到最大循环次数", "退出循环");
|
||
break;
|
||
}
|
||
LlmJsonContent jsonContent = new LlmJsonContent()
|
||
{
|
||
Model = model,
|
||
Messages = messages,
|
||
Temperature = 0.3,
|
||
TopP = 0.4,
|
||
MaxTokens = 1000,
|
||
};
|
||
List<McpToolRequest> mcpToolRequests = new List<McpToolRequest>();
|
||
List<PromptRequest> promptRequests = new List<PromptRequest>();
|
||
var (toolMatched, toolRemaining) = ExtractMatchedPart(messageContent, toolPattern);
|
||
var (promptMatched, promptRemaining) = ExtractMatchedPart(messageContent, promptPattern);
|
||
if (toolMatched == "" && promptMatched == "" && messageContent != "")
|
||
{
|
||
//如果本次回复不包含任何工具的调用或提示词的调用,则不再请求
|
||
goOn = false;
|
||
MessageListItem endMessageListItem1 = new ChatMessageItem
|
||
{
|
||
type = MessageType.END_TAG,
|
||
content = "",
|
||
id = (timestamp + 3).ToString(),
|
||
role = "assistant"
|
||
};
|
||
callback?.Invoke(endMessageListItem1);
|
||
break;
|
||
}
|
||
|
||
try
|
||
{
|
||
await foreach(LlmStreamChat llmStreamChat in modelObj.SendChatStreamAsync(jsonContent))
|
||
{
|
||
if (!goOn)
|
||
{
|
||
MessageListItem endMessageListItem2 = new ChatMessageItem
|
||
{
|
||
type = MessageType.END_TAG,
|
||
content = "",
|
||
id = (timestamp+3).ToString(),
|
||
role = "assistant"
|
||
};
|
||
callback?.Invoke(endMessageListItem2);
|
||
break;
|
||
}
|
||
try
|
||
{
|
||
string chunk = llmStreamChat.Choices[0].Delta.Content;
|
||
MessageListItem reasonMessageListItem = new ChatMessageItem()
|
||
{
|
||
content = llmStreamChat.Choices[0].Delta.ResoningContent,
|
||
role = "assistant",
|
||
type = MessageType.REASON_MESSAGE,
|
||
id = (timestamp+2).ToString()
|
||
};
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
callback?.Invoke(reasonMessageListItem);
|
||
});
|
||
messageContent = chunk;
|
||
var (matched, remaining) = ExtractMatchedPart(chunk, toolPattern);
|
||
if (matched == "")
|
||
{
|
||
var (matchedPrompt, remainingPrompt) = ExtractMatchedPart(chunk, promptPattern);
|
||
if (matchedPrompt == "")
|
||
{
|
||
//普通消息文本
|
||
MessageListItem chatMessageListItem = new ChatMessageItem()
|
||
{
|
||
content = remainingPrompt,
|
||
role = "assistant",
|
||
type = MessageType.CHAT_MESSAGE,
|
||
id = timestamp.ToString()
|
||
};
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
callback?.Invoke(chatMessageListItem);
|
||
});
|
||
}
|
||
else
|
||
{
|
||
//包含Prompt调用请求的消息
|
||
MessageListItem chatMessageListItem = new ChatMessageItem()
|
||
{
|
||
content = remainingPrompt,
|
||
role = "assistant",
|
||
type = MessageType.CHAT_MESSAGE,
|
||
id = timestamp.ToString()
|
||
};
|
||
callback?.Invoke(chatMessageListItem);
|
||
XElement promptUse = XElement.Parse(matchedPrompt);
|
||
string promptKey = promptUse.Element("name")?.Value;
|
||
string promptArgs = promptUse.Element("arguments")?.Value;
|
||
Dictionary<string, string> promptParams = JsonConvert.DeserializeObject<Dictionary<string, string>>(promptArgs);
|
||
promptRequests = new List<PromptRequest>();
|
||
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<string, object> toolParams = JsonConvert.DeserializeObject<Dictionary<string, object>>(toolArgs);
|
||
string serverName = fullToolName.Contains(":") ? fullToolName.Split(':')[0] : fullToolName;
|
||
string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName;
|
||
McpServer mcpServer = mcpServerList.GetServer(serverName);
|
||
//将工具调用请求添加至列表中待一次回答完整后再统一进行调用
|
||
MessageListItem chatMessageListItem = new ChatMessageItem()
|
||
{
|
||
content = remaining,
|
||
role = "assistant",
|
||
type = MessageType.CHAT_MESSAGE,
|
||
id = timestamp.ToString()
|
||
};
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
callback?.Invoke(chatMessageListItem);
|
||
});
|
||
if (!executedTool)
|
||
{
|
||
MessageListItem toolMessageListItem = new ToolMessageItem()
|
||
{
|
||
content = toolName,
|
||
result = "工具运行中" + toolName,
|
||
toolName = toolName,
|
||
toolParams = toolParams,
|
||
role = "user",
|
||
type = MessageType.TOOL_MESSAGE,
|
||
id = (timestamp+1).ToString(),
|
||
status = "loading"
|
||
};
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
callback?.Invoke(toolMessageListItem);
|
||
});
|
||
}
|
||
mcpToolRequests = new List<McpToolRequest>();
|
||
McpToolRequest mcpToolRequest = new McpToolRequest()
|
||
{
|
||
McpServer = mcpServer,
|
||
ToolName = toolName,
|
||
ToolArgs = toolParams,
|
||
};
|
||
mcpToolRequests.Add(mcpToolRequest);
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Console.WriteLine(e);
|
||
log.Error(e.Message);
|
||
}
|
||
}
|
||
}catch (Exception e)
|
||
{
|
||
log.Error(e.Message);
|
||
MessageListItem endMessageListItem2 = new ChatMessageItem
|
||
{
|
||
type = MessageType.ERROR,
|
||
content = $"请求大模型出错:{e.Message}",
|
||
id = timestamp.ToString(),
|
||
role = "system"
|
||
};
|
||
callback?.Invoke(endMessageListItem2);
|
||
MessageListItem endMessageListItem1 = new ChatMessageItem
|
||
{
|
||
type = MessageType.END_TAG,
|
||
content = "",
|
||
id = (timestamp + 3).ToString(),
|
||
role = "assistant"
|
||
};
|
||
callback?.Invoke(endMessageListItem1);
|
||
// MessageBox.Show(e.Message, "请求大模型出错");
|
||
break;
|
||
}
|
||
if (messageContent != "")
|
||
{
|
||
messages.Add(new Message
|
||
{
|
||
Role = "assistant",
|
||
Content = messageContent
|
||
});
|
||
}
|
||
/*统一处理本次请求中的MCP工具调用需求*/
|
||
foreach (McpToolRequest mcpToolRequest in mcpToolRequests)
|
||
{
|
||
executedTool = true;
|
||
try
|
||
{
|
||
McpServer mcpServer = mcpToolRequest.McpServer;
|
||
string toolName = mcpToolRequest.ToolName;
|
||
Dictionary<string, object> toolParams = mcpToolRequest.ToolArgs;
|
||
if (mcpServer is SseMcpServer)
|
||
{
|
||
SseMcpServer sseMcpServer = mcpServer as SseMcpServer;
|
||
SseMcpClient client = new SseMcpClient(sseMcpServer.BaseUrl);
|
||
CallToolResponse toolResponse = await client.CallToolAsync(toolName, toolParams);
|
||
MessageListItem toolMessageItem1 = new ToolMessageItem
|
||
{
|
||
toolName = toolName,
|
||
toolParams = toolParams,
|
||
type = MessageType.TOOL_MESSAGE,
|
||
status = toolResponse.IsError ? "fail" : "success",
|
||
content = JsonConvert.SerializeObject(toolResponse),
|
||
result = JsonConvert.SerializeObject(toolResponse),
|
||
id = (timestamp + 1).ToString(),
|
||
role = "user"
|
||
};
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = toolResponse.IsError
|
||
? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse))
|
||
: SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse))
|
||
});
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
callback?.Invoke(toolMessageItem1);
|
||
});
|
||
}
|
||
else if (mcpServer is StdioMcpServer)
|
||
{
|
||
StdioMcpServer stdioMcpServer = mcpServer as StdioMcpServer;
|
||
StdioMcpClient client = new StdioMcpClient(stdioMcpServer.Command, stdioMcpServer.Args);
|
||
CallToolResponse toolResponse = await client.CallToolAsync(toolName, toolParams);
|
||
MessageListItem toolMessageItem1 = new ToolMessageItem
|
||
{
|
||
toolName = toolName,
|
||
toolParams = toolParams,
|
||
type = MessageType.TOOL_MESSAGE,
|
||
status = toolResponse.IsError ? "fail" : "success",
|
||
content = JsonConvert.SerializeObject(toolResponse),
|
||
result = JsonConvert.SerializeObject(toolResponse),
|
||
id = (timestamp + 1).ToString(),
|
||
role = "user"
|
||
};
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = toolResponse.IsError
|
||
? SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(toolResponse))
|
||
: SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(toolResponse))
|
||
});
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
callback?.Invoke(toolMessageItem1);
|
||
});
|
||
}
|
||
else if (mcpServer is InnerMcpServer)
|
||
{
|
||
InnerMcpServer innerMcpServer = mcpServer as InnerMcpServer;
|
||
string mcpServerName = innerMcpServer.Name;
|
||
if (toolName == "ArcGisProTool" && queriedKnowledge == false)
|
||
{
|
||
JsonRpcResultEntity knowledgeResult = await KnowledgeBase.QueryArcgisToolDoc(toolParams["toolName"].ToString());
|
||
if (knowledgeResult is JsonRpcSuccessEntity)
|
||
{
|
||
JsonRpcSuccessEntity knowledgeSuccessResult = knowledgeResult as JsonRpcSuccessEntity;
|
||
MessageListItem toolMessageItem = new ToolMessageItem
|
||
{
|
||
toolName = "QueryArcgisToolDoc",
|
||
toolParams = new Dictionary<string, object>(){{"query",toolParams["toolName"].ToString()}},
|
||
type = MessageType.TOOL_MESSAGE,
|
||
status = "success",
|
||
content = JsonConvert.SerializeObject(knowledgeSuccessResult.Result),
|
||
result = JsonConvert.SerializeObject(knowledgeSuccessResult.Result),
|
||
id = (timestamp + 4).ToString(),
|
||
role = "user"
|
||
};
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = SystemPrompt.ToolContinuePrompt(JsonConvert.SerializeObject(knowledgeSuccessResult))
|
||
});
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
callback?.Invoke(toolMessageItem);
|
||
});
|
||
queriedKnowledge = true;
|
||
}
|
||
else
|
||
{
|
||
JsonRpcErrorEntity knowledgeErrorResult = knowledgeResult as JsonRpcErrorEntity;
|
||
MessageListItem toolMessageItem = new ToolMessageItem
|
||
{
|
||
toolName = "QueryArcgisToolDoc",
|
||
toolParams = new Dictionary<string, object>(){{"query",toolParams["toolName"].ToString()}},
|
||
type = MessageType.TOOL_MESSAGE,
|
||
status = "fail",
|
||
content = JsonConvert.SerializeObject(knowledgeErrorResult.Error),
|
||
result = JsonConvert.SerializeObject(knowledgeErrorResult.Error),
|
||
id = (timestamp + 4).ToString(),
|
||
role = "user"
|
||
};
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(knowledgeErrorResult))
|
||
});
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
callback?.Invoke(toolMessageItem);
|
||
});
|
||
}
|
||
continue;
|
||
}
|
||
Type type = Type.GetType("LinkToolAddin.client.tool." + (mcpServer as InnerMcpServer).Name);
|
||
MethodInfo method = type.GetMethod(toolName, BindingFlags.Public | BindingFlags.Static);
|
||
var methodParams = toolParams.Values.ToArray();
|
||
object[] args = new object[methodParams.Length];
|
||
for (int i = 0; i < methodParams.Length; i++)
|
||
{
|
||
if (methodParams[i].GetType() == typeof(JArray))
|
||
{
|
||
List<string> list = new List<string>();
|
||
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>;
|
||
JsonRpcResultEntity innerResult = await task;
|
||
string displayToolName = toolName;
|
||
if (displayToolName == "ArcGisProTool")
|
||
{
|
||
displayToolName = "【GP】"+toolParams["toolName"].ToString();
|
||
}
|
||
queriedKnowledge = false;
|
||
if (innerResult is JsonRpcErrorEntity)
|
||
{
|
||
MessageListItem toolMessageItem1 = new ToolMessageItem
|
||
{
|
||
toolName = displayToolName,
|
||
toolParams = toolParams,
|
||
type = MessageType.TOOL_MESSAGE,
|
||
status = "fail",
|
||
content = JsonConvert.SerializeObject(innerResult),
|
||
result = JsonConvert.SerializeObject(innerResult),
|
||
id = (timestamp + 1).ToString(),
|
||
role = "user"
|
||
};
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = SystemPrompt.ErrorPrompt(JsonConvert.SerializeObject(innerResult))
|
||
});
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
callback?.Invoke(toolMessageItem1);
|
||
});
|
||
}
|
||
else if (innerResult is JsonRpcSuccessEntity)
|
||
{
|
||
MessageListItem toolMessageItem1 = new ToolMessageItem
|
||
{
|
||
toolName = displayToolName,
|
||
toolParams = toolParams,
|
||
type = MessageType.TOOL_MESSAGE,
|
||
status = "success",
|
||
content = JsonConvert.SerializeObject(innerResult),
|
||
result = JsonConvert.SerializeObject(innerResult),
|
||
id = (timestamp + 1).ToString(),
|
||
role = "user"
|
||
};
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = SystemPrompt.ContinuePrompt(JsonConvert.SerializeObject(innerResult))
|
||
});
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
callback?.Invoke(toolMessageItem1);
|
||
});
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine(ex.Message);
|
||
log.Error(ex.Message);
|
||
}
|
||
|
||
}
|
||
/*统一处理本次请求中的Prompt调用需求*/
|
||
foreach (PromptRequest promptRequest in promptRequests)
|
||
{
|
||
try
|
||
{
|
||
string promptContent = DynamicPrompt.GetPrompt(promptRequest.PromptName, promptRequest.PromptArgs);
|
||
messages.Add(new Message
|
||
{
|
||
Role = "user",
|
||
Content = promptContent
|
||
});
|
||
MessageListItem toolMessageItem = new ToolMessageItem
|
||
{
|
||
toolName = "调用提示词",
|
||
toolParams = new Dictionary<string, object>(),
|
||
type = MessageType.TOOL_MESSAGE,
|
||
status = "success",
|
||
content = "成功调用提示词:"+promptRequest.PromptName,
|
||
id = (timestamp+1).ToString(),
|
||
result = "成功调用提示词:"+promptRequest.PromptName,
|
||
role = "user"
|
||
};
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
callback?.Invoke(toolMessageItem);
|
||
});
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Console.WriteLine(e);
|
||
log.Error(e.Message);
|
||
}
|
||
}
|
||
MessageListItem endMessageListItem = new ChatMessageItem
|
||
{
|
||
type = MessageType.END_TAG,
|
||
content = "",
|
||
id = (timestamp+3).ToString(),
|
||
role = "assistant"
|
||
};
|
||
callback?.Invoke(endMessageListItem);
|
||
}
|
||
|
||
MessageBox.Show("本轮回复完成", "结束提示");
|
||
}
|
||
|
||
private static async Task<string> GetToolInfos(McpServerList mcpServerList,Action<MessageListItem> callback)
|
||
{
|
||
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||
StringBuilder toolInfos = new StringBuilder();
|
||
int i = 0;
|
||
List<int> failedMcp = new List<int>();
|
||
List<string> failedMcpString = new List<string>();
|
||
foreach (McpServer mcpServer in mcpServerList.GetAllServers())
|
||
{
|
||
i++;
|
||
try
|
||
{
|
||
if (mcpServer is InnerMcpServer)
|
||
{
|
||
InnerMcpServer innerMcpServer = (InnerMcpServer)mcpServer;
|
||
Type type = Type.GetType("LinkToolAddin.client.tool." + innerMcpServer.Name);
|
||
MethodInfo[] methods = type.GetMethods();
|
||
foreach (MethodInfo method in methods)
|
||
{
|
||
if (method.IsPublic && method.IsStatic)
|
||
{
|
||
string methodName = method.Name;
|
||
string methodDescription = method.GetCustomAttribute<DescriptionAttribute>()?.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<McpClientTool> 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<McpClientTool> tools = await client.GetToolListAsync();
|
||
foreach (McpClientTool tool in tools)
|
||
{
|
||
string toolName = (mcpServer as StdioMcpServer).Name + ":" + tool.Name;;
|
||
string toolDescription = tool.Description;
|
||
string toolParamSchema = tool.JsonSchema.ToString();
|
||
McpToolDefinition toolDefinition = new McpToolDefinition
|
||
{
|
||
Tool = new Tool
|
||
{
|
||
Name = toolName,
|
||
Description = toolDescription,
|
||
Arguments = CompressJson(toolParamSchema)
|
||
}
|
||
};
|
||
toolInfos.AppendLine(JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition)).ToString());
|
||
toolInfos.AppendLine();
|
||
}
|
||
}
|
||
}catch (Exception e)
|
||
{
|
||
log.Error(e.Message);
|
||
failedMcp.Add(i);
|
||
failedMcpString.Add(mcpServerList.GetAllServerNames()[i-1]);
|
||
}
|
||
}
|
||
|
||
List<UserPrompt> prompts = DynamicPrompt.GetAllPrompts();
|
||
foreach (UserPrompt userPrompt in prompts)
|
||
{
|
||
try
|
||
{
|
||
XNode node = JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(new PromptDefinition(){UserPrompt = userPrompt}));
|
||
toolInfos.AppendLine(node.ToString());
|
||
toolInfos.AppendLine();
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
log.Error(e.Message);
|
||
}
|
||
}
|
||
|
||
if (!failedMcp.IsNullOrEmpty())
|
||
{
|
||
MessageListItem endMessageListItem2 = new ChatMessageItem
|
||
{
|
||
type = MessageType.WARNING,
|
||
content = $"读取失败的MCP服务有:{JsonConvert.SerializeObject(failedMcpString)}",
|
||
id = timestamp.ToString(),
|
||
role = "system"
|
||
};
|
||
callback?.Invoke(endMessageListItem2);
|
||
// MessageBox.Show($"读取失败的MCP服务有:{JsonConvert.SerializeObject(failedMcpString)}","MCP读取错误");
|
||
}
|
||
return toolInfos.ToString();
|
||
}
|
||
|
||
public static string CompressJson(string json)
|
||
{
|
||
// 解析JSON并自动去除无关空白
|
||
var token = JToken.Parse(json);
|
||
// 序列化为无格式紧凑字符串
|
||
return token.ToString(Newtonsoft.Json.Formatting.None);
|
||
}
|
||
|
||
private static string GenerateMethodParamSchema(MethodInfo method)
|
||
{
|
||
var generator = new JSchemaGenerator
|
||
{
|
||
// 启用属性注解处理
|
||
DefaultRequired = Required.DisallowNull,
|
||
SchemaReferenceHandling = SchemaReferenceHandling.None
|
||
};
|
||
|
||
var paramSchema = new JSchema { Type = JSchemaType.Object };
|
||
|
||
foreach (ParameterInfo param in method.GetParameters())
|
||
{
|
||
// 生成参数类型的基础Schema
|
||
JSchema typeSchema = generator.Generate(param.ParameterType);
|
||
|
||
// 添加Description描述
|
||
var descriptionAttr = param.GetCustomAttribute<DescriptionAttribute>();
|
||
if (descriptionAttr != null)
|
||
{
|
||
typeSchema.Description = descriptionAttr.Description; // 网页6的Description特性处理
|
||
}
|
||
|
||
paramSchema.Properties.Add(param.Name, typeSchema);
|
||
}
|
||
var settings = new JsonSerializerSettings {
|
||
Formatting = Newtonsoft.Json.Formatting.None, // 关键设置:禁用缩进和换行
|
||
NullValueHandling = NullValueHandling.Ignore // 可选:忽略空值
|
||
};
|
||
return JsonConvert.SerializeObject(paramSchema, settings);;
|
||
}
|
||
|
||
public static async void TestChatMessage(string message, string model, string gdbPath,
|
||
Action<MessageListItem> 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<MessageListItem> callback)
|
||
{
|
||
MessageListItem toolListItem = new ToolMessageItem
|
||
{
|
||
content = message,
|
||
type = MessageType.TOOL_MESSAGE,
|
||
toolName = "arcgis_pro.executeTool",
|
||
toolParams = new Dictionary<string, object>
|
||
{
|
||
{"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<MessageListItem> 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<string, object>
|
||
{
|
||
{"gp_name","analysis.Buffer"},
|
||
{"gp_params","[\"C:\\test.gdb\\river\",\"30 Meters\"]"}
|
||
},
|
||
id = "testtool123456",
|
||
status = "success",
|
||
role = "user",
|
||
result = "成功创建缓冲区"
|
||
};
|
||
callback?.Invoke(toolListItem);
|
||
}
|
||
} |