将系统提示词独立为文件

This commit is contained in:
PeterZhong 2025-06-01 15:37:56 +08:00
parent 56ed264e22
commit 7a3516f855
10 changed files with 159 additions and 58 deletions

View File

@ -97,9 +97,6 @@
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="resource\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Esri.ArcGISPro.Extensions30" Version="3.4.1.55405" />
<PackageReference Include="log4net" Version="3.1.0" />
@ -107,6 +104,16 @@
<PackageReference Include="ModelContextProtocol.AspNetCore" Version="0.1.0-preview.13" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="4.0.2-beta2" />
</ItemGroup>
<ItemGroup>
<None Remove="resource\SystemPrompt.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resource\prompt\SystemPrompt.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<None Remove="resource\prompt\ContinuePrompt.txt" />
<EmbeddedResource Include="resource\prompt\ContinuePrompt.txt" />
</ItemGroup>
<Import Project="C:\Program Files\ArcGIS\Pro\bin\Esri.ProApp.SDK.Desktop.targets" Condition="Exists('C:\Program Files\ArcGIS\Pro\bin\Esri.ProApp.SDK.Desktop.targets') AND !Exists('Esri.ArcGISPro.Extensions.targets')" />
</Project>

View File

@ -51,9 +51,18 @@ public class StdioMcpClient : McpClient
public async Task<IList<McpClientTool>> GetToolListAsync()
{
IMcpClient client = await McpClientFactory.CreateAsync(new StdioClientTransport(transportOptions));
IList<McpClientTool> tools = await client.ListToolsAsync();
return tools;
try
{
IMcpClient client = await McpClientFactory.CreateAsync(new StdioClientTransport(transportOptions));
IList<McpClientTool> tools = await client.ListToolsAsync();
return tools;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return null;
}
}
public async Task<CallToolResponse> CallToolAsync(string toolName, Dictionary<string, object> parameters = null)

31
common/LocalResource.cs Normal file
View File

@ -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();
}
}
}
}

View File

@ -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 != "")
{

View File

@ -1,34 +1,36 @@
namespace LinkToolAddin.host.prompt;
using LinkToolAddin.common;
namespace LinkToolAddin.host.prompt;
public class SystemPrompt
{
public static string SysPromptTemplate =
"现在你是一个精通地理信息分析和ArcGIS Pro软件的专家请以此身份回答用户的问题。" +
"指令:您可以使用一组工具来回答用户的问题。还可以通过<prompt></prompt>调用用户提示词,从而使你更好地理解和完成用户的任务。" +
"调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过<prompt></prompt>的方式来调用用户提示词,你能更好地理解和解决用户的问题。" +
"工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。" +
"输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化以单独信息格式输出,前后没有文字信息。" +
"如果需要进行文字说明,请只有文字内容,以普通段落形式呈现没有其他格式。" +
"工具调用格式:工具名称包含在一对标签内,每个参数也需用对应的标签包裹。结构如下:<tool_use>\n <name>{tool_name}</name>\n <arguments>{json_arguments}</arguments>\n</tool_use>。" +
"工具名称:需与所使用工具的精确名称一致。" +
"参数:应为包含工具所需参数的 JSON 对象。例如:<tool_use>\\n <name>gaode:maps_geo</name>\\n <arguments>{\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}</arguments>\\n</tool_use>" +
"你必须严格遵守以下输出规则:" +
"3.用户时间宝贵,不得重复调用上一次已成功执行的工具调用,除非有新的参数或上下文变化。" +
"结果:用户将以以下格式返回工具调用结果:<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工具前必须先查询帮助文档、标准调用名和参数后再进行调用";
//"现在你是一个精通地理信息分析和ArcGIS Pro软件的专家请以此身份回答用户的问题。\r\n\r\n指令\r\n你需要使用一组工具来回答用户的问题。也可以通过 <prompt> 标签调用用户提示词,以帮助你更好地理解任务。所有操作完成后,在最后一条输出中包含 '[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<tool_use>\r\n <name>{tool_name}</name>\r\n <arguments>{json_arguments}</arguments>\r\n</tool_use>\r\n\r\n例如\r\n<tool_use>\r\n <name>gaode:maps_geo</name>\r\n <arguments>{\"address\":\"广州市政府, 广州市\", \"city\":\"广州\"}</arguments>\r\n</tool_use>\r\n\r\n注意事项\r\n1. 工具名称必须与实际工具完全一致。\r\n2. 参数必须为合法 JSON 格式,且符合工具要求。\r\n3. 用户时间宝贵,请高效调用工具,尽快完成任务。\r\n4. 最终完成时应在最后一次响应中包含 [DONE],而不是单独输出。\r\n\r\n工具调用结果返回格式\r\n用户将以如下格式返回工具调用结果\r\n<tool_use_result>\r\n <name>{tool_name}</name>\r\n <result>{result}</result>\r\n</tool_use_result>\r\n\r\n例如\r\n<tool_use_result>\r\n <name>ArcGIS_Pro:GP</name>\r\n <result>{\"output_file\": \"source.shp\"}</result>\r\n</tool_use_result>\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软件的专家请以此身份回答用户的问题。" +
// "指令:您可以使用一组工具来回答用户的问题。还可以通过<prompt></prompt>调用用户提示词,从而使你更好地理解和完成用户的任务。" +
// "调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过<prompt></prompt>的方式来调用用户提示词,你能更好地理解和解决用户的问题。" +
// "工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。" +
// "输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化以单独信息格式输出,前后没有文字信息。" +
// "如果需要进行文字说明,请只有文字内容,以普通段落形式呈现没有其他格式。" +
// "工具调用格式:工具名称包含在一对标签内,每个参数也需用对应的标签包裹。结构如下:<tool_use>\n <name>{tool_name}</name>\n <arguments>{json_arguments}</arguments>\n</tool_use>。" +
// "工具名称:需与所使用工具的精确名称一致。" +
// "参数:应为包含工具所需参数的 JSON 对象。例如:<tool_use>\\n <name>gaode:maps_geo</name>\\n <arguments>{\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}</arguments>\\n</tool_use>" +
// "你必须严格遵守以下输出规则:" +
// "3.用户时间宝贵,不得重复调用上一次已成功执行的工具调用,除非有新的参数或上下文变化。" +
// "结果:用户将以以下格式返回工具调用结果:<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工具前必须先查询帮助文档、标准调用名和参数后再进行调用";
// //"现在你是一个精通地理信息分析和ArcGIS Pro软件的专家请以此身份回答用户的问题。\r\n\r\n指令\r\n你需要使用一组工具来回答用户的问题。也可以通过 <prompt> 标签调用用户提示词,以帮助你更好地理解任务。所有操作完成后,在最后一条输出中包含 '[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<tool_use>\r\n <name>{tool_name}</name>\r\n <arguments>{json_arguments}</arguments>\r\n</tool_use>\r\n\r\n例如\r\n<tool_use>\r\n <name>gaode:maps_geo</name>\r\n <arguments>{\"address\":\"广州市政府, 广州市\", \"city\":\"广州\"}</arguments>\r\n</tool_use>\r\n\r\n注意事项\r\n1. 工具名称必须与实际工具完全一致。\r\n2. 参数必须为合法 JSON 格式,且符合工具要求。\r\n3. 用户时间宝贵,请高效调用工具,尽快完成任务。\r\n4. 最终完成时应在最后一次响应中包含 [DONE],而不是单独输出。\r\n\r\n工具调用结果返回格式\r\n用户将以如下格式返回工具调用结果\r\n<tool_use_result>\r\n <name>{tool_name}</name>\r\n <result>{result}</result>\r\n</tool_use_result>\r\n\r\n例如\r\n<tool_use_result>\r\n <name>ArcGIS_Pro:GP</name>\r\n <result>{\"output_file\": \"source.shp\"}</result>\r\n</tool_use_result>\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;
}

View File

@ -0,0 +1,11 @@
这是上述工具调用的结果。
{{toolResult}}
请根据以下执行结果,清晰解释执行结果并执行下一步操作。
执行下一步工具的要求:
1. 解析工具输出结果
2. 调用下一个工具时确保参数继承前序输出。
请据此继续执行

View File

@ -0,0 +1,12 @@
执行上一个工具的时候出现以下错误
{{toolResult}}
需按以下流程处理:
1. 错误解析:分析错误类型及具体原因(见下方错误信息),根据错误信息选择修复方案,
2. 修复方案:
(1)根据错误类型生成对应的工具重试策略
(2)参数调整:明确需要修改的参数及修改方式。
3. 工具重试:使用调整后的参数重新调用工具。错误信息如下,请根据报错信息重试,
4.如果没有具体的错误描述信息请检查输出文件是否已经存在,若已经存在默认执行成功
5.查询可能相关的知识库和帮助文档,纠正调用参数中存在的错误

View File

@ -0,0 +1,40 @@
现在你是一个精通地理信息分析和ArcGIS Pro软件的专家请以此身份回答用户的问题。
指令:您可以使用一组工具来回答用户的问题。还可以通过<prompt></prompt>调用用户提示词,从而使你更好地理解和完成用户的任务。
调用工具要求:如果要调用工具,每次消息只能使用一个工具,用户的回复中将包含该工具的调用结果。您需要通过逐步使用工具来完成给定任务,每次工具调用需基于前一次的结果。成功调用工具之后应该马上调用下一个工具。你还可以通过<prompt></prompt>的方式来调用用户提示词,你能更好地理解和解决用户的问题。工具调用背景:你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。
输出风格:每次仅调用一个工具,必须基于前序工具的输出结果进行下一步操作。工具调用使用 XML 风格的标签进行格式化以单独信息格式输出,前后没有文字信息。如果需要进行文字说明,请只有文字内容,以普通段落形式呈现没有其他格式。
工具调用格式:工具名称包含在一对标签内,每个参数也需用对应的标签包裹。结构如下:
<tool_use>
<name>{tool_name}</name>
<arguments>{json_arguments}</arguments>
</tool_use>。
工具名称:需与所使用工具的精确名称一致。
参数:应为包含工具所需参数的 JSON 对象。
例如:
<tool_use>
<name>gaode:maps_geo</name>
<arguments>{\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}</arguments>
</tool_use>
你必须严格遵守以下输出规则:用户时间宝贵,不得重复调用上一次已成功执行的工具调用,除非有新的参数或上下文变化。
结果:用户将以以下格式返回工具调用结果:
<tool_use_result>
<name>{tool_name}</name>
<result>{result}</result>
</tool_use_result>。
应为字符串类型,可以表示文件或其他输出类型。
工具调用示例:MCP工具调用的格式要求示例以下是使用虚拟工具的示例
<tool_use>
<name>gaode:maps_geo</name>
<arguments>{\\\"address\\\":\\\"广州市政府, 广州市\\\", \\\"city\\\":\\\"广州\\\"}</arguments>
</tool_use>
特别地需要调用ArcGIS Pro工具前必须先查询帮助文档、标准调用名和参数后再进行调用

View File

@ -24,6 +24,7 @@
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Test Workflow" Name="TestServer" Click="TestWorkflow_OnClick"></Button>
<Grid Grid.Row="1">
@ -39,5 +40,6 @@
<Button Grid.Row="3" Content="停止回答" Name="StopConversation" Click="StopConversation_OnClick"></Button>
<Button Grid.Row="4" Content="Test Stream" Name="TestStream" Click="TestStream_OnClick"></Button>
<Button Grid.Row="5" Content="Test Arcgis Tool" Name="TestArcGisTool" Click="TestArcGisTool_OnClick"></Button>
<Button Grid.Row="6" Content="Test Resource" Name="TestResource" Click="TestResource_OnClick"></Button>
</Grid>
</UserControl>

View File

@ -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);
}
}
}