修复重复调用工具的问题,优化流程结束逻辑

This commit is contained in:
PeterZhong 2025-05-31 17:33:33 +08:00
parent af43d0d774
commit 602fd94fc0
4 changed files with 66 additions and 27 deletions

View File

@ -274,6 +274,7 @@ public class Gateway
string promptPattern = "<prompt>([\\s\\S]*?)<name>([\\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<McpToolRequest> mcpToolRequests = new List<McpToolRequest>();
List<string> promptKeys = new List<string>();
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 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<string, object> 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);
}
}
}

View File

@ -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<string, object> ToolArgs { get; set; }
}

View File

@ -4,7 +4,7 @@ public class SystemPrompt
{
public static string SysPromptTemplate =
"现在你是一个精通地理信息分析和ArcGIS Pro软件的专家请以此身份回答用户的问题。" +
"指令:您可以使用一组工具来回答用户的问题。还可以通过<prompt></prompt>调用用户提示词,从而使你更好地理解和完成用户的任务。整个流程结束之后单独输出一条内容为'[DONE]'的消息,前后不要有任何说明文字。" +
"指令:您可以使用一组工具来回答用户的问题。还可以通过<prompt></prompt>调用用户提示词,从而使你更好地理解和完成用户的任务。" +
"调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过<prompt></prompt>的方式来调用用户提示词,你能更好地理解和解决用户的问题。" +
"工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。" +
"输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化以单独信息格式输出,前后没有文字信息。" +
@ -13,12 +13,7 @@ public class SystemPrompt
"工具名称:需与所使用工具的精确名称一致。" +
"参数:应为包含工具所需参数的 JSON 对象。例如:<tool_use>\\n <name>gaode:maps_geo</name>\\n <arguments>{\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}</arguments>\\n</tool_use>" +
"你必须严格遵守以下输出规则:" +
"1.XML工具调用格式必须在下一条单独输出如果前面有文字将永远无法调用工具。" +
//"只有单独一条的调用请求文本程序才能识别并调用,从而将结果反馈给你。" +
"2.文字说明如果紧跟着工具调用的XML将暴露程序XML会在下一条单独输出因此文字描述后面一定不能输出工具调用的XML格式。" +
"3.用户时间宝贵,不得重复调用上一次已成功执行的工具调用,除非有新的参数或上下文变化。" +
"4.如果目前工作已经完成无法知道用户其他需求时,一定要单独输出一条内容为'[DONE]'的消息表示工具调用结束。不要在文本的末尾。" +
"5.不得在同一消息中混合说明文字与工具调用。" +
"结果:用户将以以下格式返回工具调用结果:<tool_use_result>\n <name>{tool_name}</name>\n <result>{result}</result>\n</tool_use_result>。应为字符串类型,可以表示文件或其他输出类型。" +
"工具调用示例:MCP工具调用的格式要求示例以下是使用虚拟工具的示例<tool_use>\\n <name>gaode:maps_geo</name>\\n <arguments>{\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}</arguments>\\n</tool_use>"+
"特别地需要调用ArcGIS Pro工具前必须先查询帮助文档、标准调用名和参数后再进行调用";

View File

@ -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 =
"<tool_use>\n<name>ArcGisPro:ArcGisProTool</name>\n<arguments>{\"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\"]}</arguments>\n</tool_use>";
XElement toolUse = XElement.Parse(xmlStr);