Commit 445a5dc4 by YeYuheng Committed by GitHub

fix: optimize top agent prompt (#7095)

* fix: optimize top agent prompt

* chore: trigger image rebuild
parent a5364a45
...@@ -49,7 +49,7 @@ ${sections.join('\n')} ...@@ -49,7 +49,7 @@ ${sections.join('\n')}
**重要提示**: **重要提示**:
- 在规划阶段,优先使用预设知识库,但必须保证与任务语义相关 - 在规划阶段,优先使用预设知识库,但必须保证与任务语义相关
- 禁止把明显不相关的知识库纳入步骤(例如医疗知识库用于旅游规划) - 禁止把明显不相关的知识库纳入步骤
- 若预设知识库不匹配任务,可从可访问知识库中选择更相关者 - 若预设知识库不匹配任务,可从可访问知识库中选择更相关者
`; `;
})(); })();
...@@ -97,7 +97,7 @@ ${existsInfoPrompt} ...@@ -97,7 +97,7 @@ ${existsInfoPrompt}
- 为后续信息收集确定方向 - 为后续信息收集确定方向
**⚠️ 2. 能力边界确认**(最关键,必须优先) **⚠️ 2. 能力边界确认**(最关键,必须优先)
- **系统能力**:基于“可用资源列表”自行判断可用工具及其能力边界 - **系统能力**:基于“可用工具与知识库”自行判断可用工具及其能力边界
- **不支持的功能**:哪些功能无法实现、哪些操作缺少工具支持 - **不支持的功能**:哪些功能无法实现、哪些操作缺少工具支持
- **技术约束**:数据格式/大小限制、第三方服务依赖、权限和资源约束 - **技术约束**:数据格式/大小限制、第三方服务依赖、权限和资源约束
...@@ -122,7 +122,7 @@ ${existsInfoPrompt} ...@@ -122,7 +122,7 @@ ${existsInfoPrompt}
- ✅ 同一问题不要重复问;若用户已答复则进入下一步 - ✅ 同一问题不要重复问;若用户已答复则进入下一步
- ✅ 优先选择题(尤其多选),尽量减少用户打字 - ✅ 优先选择题(尤其多选),尽量减少用户打字
- ✅ 能用选项就不用开放式输入,只有必要时才用输入框 - ✅ 能用选项就不用开放式输入,只有必要时才用输入框
- ✅ 不要求用户提供工具/知识库 ID(你应根据可用资源列表自行选择并规划) - ✅ 不要求用户提供工具/知识库 ID(你应根据可用工具与知识库自行选择并规划)
- ✅ 不向搭建者收集最终用户的具体输入内容/样本(这类信息属于运行时由最终用户提供) - ✅ 不向搭建者收集最终用户的具体输入内容/样本(这类信息属于运行时由最终用户提供)
- ✅ 能用系统已有信息推断的,不再追问 - ✅ 能用系统已有信息推断的,不再追问
...@@ -239,118 +239,32 @@ ${existsInfoPrompt} ...@@ -239,118 +239,32 @@ ${existsInfoPrompt}
当处于配置信息生成阶段时: 当处于配置信息生成阶段时:
<resource_definitions> <resource_definitions>
**系统资源定义**(重要:理解三类资源的本质区别) **资源只分三类,请严格区分:**
- **工具 [工具]**:执行动作、调用服务、处理数据、生成内容。
**工具 (Tools)**: - **知识库 [知识库]**:检索已存储的信息,提供领域知识。
- 定义:可以执行特定功能的能力模块 - **系统功能**:平台前端开关,只能影响交互方式,不是工具或知识库。
- 功能:执行操作、调用API、处理数据、生成内容等
- 特点:主动执行,产生结果或副作用 **硬性边界:**
- 示例:搜索引擎、数据库操作、邮件发送、内容生成 - 模型不能自造工具、知识库或资源 ID。
- expectedTools 只能从下方“可用工具与知识库”候选列表中选择带 [工具] 或 [知识库] 标签的真实资源。
**知识库 (Knowledges)**: - description 中只能用 @资源ID 引用带 [工具] 或 [知识库] 标签的真实资源。
- 定义:系统上已经搭建好的文件存储系统,包含特定领域的结构化信息 - file_upload 和 sandbox 不是 expectedTools,也不能写成 @file_upload、@sandbox 或其他 @系统功能ID。
- 功能:存储和检索信息,提供领域知识查询 - file_upload 和 sandbox 只作为 resources.system_features 下的前端开关;需要时启用开关,并在步骤中搭配真实 [工具]/[知识库] 资源。
- 特点:被动查询,返回已存储的信息
- 示例:产品文档库、技术手册、行业知识库
**系统功能 (System Features)**:
- 定义:平台级的功能开关,控制执行流程的特殊能力
- 功能:影响任务执行方式的系统级配置
- 特点:开关控制,改变交互模式
- 示例:文件上传、用户交互、实时数据流
**关键区别**:
- 工具 = "做事情"(执行动作、调用服务、处理数据)
- 知识库 = "查信息"(检索已有知识、获取领域信息)
- 系统功能 = "改变模式"(启用特殊交互方式、系统级能力)
**选择建议**:
- 需要执行操作(搜索、发送、计算、转换)→ 选择工具
- 需要查询特定领域的信息(产品资料、技术文档、行业知识)→ 选择知识库
- 需要用户提供文件/特殊交互方式 → 启用系统功能
- 三者可以配合使用:例如用搜索工具获取实时信息,用知识库补充领域知识,启用文件上传让用户提供私有数据
</resource_definitions> </resource_definitions>
**可用资源列表**: **可用工具与知识库 / 可配置前端开关**:
""" """
${resourceList} ${resourceList}
""" """
**配置生成要求**: **配置生成前的内部检查(不要输出):**
1. 严格按照JSON格式输出 1. 任务目标、角色、输入输出和关键约束是否足够明确。
2. **严格确保所有引用的资源都在可用资源列表中** - 这是硬性要求 2. 每个执行步骤是否有真实可用能力支撑;无法实现的能力不要伪造工具补齐。
3. 考虑搭建者的实际约束条件(时间、资源、技能等) 3. 工具/知识库是否来自“可用工具与知识库”,并按标签设置 type:
4. **绝不要使用任何不在可用资源列表中的资源** - 违背此项将导致配置被拒绝 - [工具] → {"id": "资源ID", "type": "tool"}
- [知识库] → {"id": "资源ID", "type": "knowledge"}
**🚨 资源使用严格限制(极其重要)**: 4. 同类工具只选最合适的一个;知识库必须和任务语义相关,不能为凑数量加入。
5. 如果需要用户上传私有文件,启用 resources.system_features.file_upload;如果需要代码执行、复杂计算或数据转换,启用 resources.system_features.sandbox。
**资源识别规则**:
1. 在上面的"## 可用资源列表"中查找所有可用资源
2. 资源的组成部分是: - **资源 id** [资源标签]: 资源名称 - 资源描述
3. 每个资源ID后面都有标签:[工具] 或 [知识库]
4. 输出时必须根据标签确定 type 值:
- 标签是 [工具] → "type": "tool"
- 标签是 [知识库] → "type": "knowledge"
**输出格式要求**:
- ✅ 必须使用对象数组格式:[{"id": "...", "type": "..."}]
- ✅ 资源ID必须完全匹配列表中的ID(包括大小写、特殊字符)
- ❌ 不要使用字符串数组格式:["...", "..."]
- ❌ 不要猜测 type 值,必须根据列表中的标签确定
**输出前的自我检查步骤**:
1. 查看你选择的每个资源ID,它在列表中的标签是什么?
2. 如果标签是 [工具] → 设置 "type": "tool"
3. 如果标签是 [知识库] → 设置 "type": "knowledge"
4. 确保每个资源都有 id 和 type 两个字段
**常见错误避免**:
- ❌ 不要凭空想象资源名称
- ❌ 不要使用通用描述如"数据库工具"而不指定具体ID
- ❌ 不要引用"可能"存在但未在列表中明确的资源
- ❌ 不要输出字符串数组,必须是对象数组
- ❌ 不要把 [知识库] 标签的资源设置为 type: "tool"
- ❌ **不要选择多个同类型的工具**
- ✅ 必须根据列表中的标签准确设置 type 值
- ✅ 基于实际可用的资源进行规划
- ✅ **同一类型的工具只选择最合适的一个**
**深度分析框架**(内部思考过程,不输出):
**知识库选择约束**:
- 候选范围必须来自“可用资源列表”中的全部 [知识库] 项
- 若存在“预选高优先级”知识库,应优先从中选择
- 若预选知识库与任务语义不匹配,应选择更相关的知识库,禁止为凑数量强行加入
🔍 第一层:任务本质分析
- 识别用户的核心目标和真实意图
- 分析任务的复杂度、范围和关键约束
- 确定主要的功能需求和预期成果
📋 第二层:资源需求识别
根据任务特点,识别需要的三类资源:
- 需要哪些工具来执行操作?(搜索、计算、生成、发送等)
- 需要哪些知识库来获取领域知识?(产品资料、技术文档等)
- 需要哪些系统功能来改变交互模式?(是否需要用户上传文件?)
🎯 第三层:精确资源匹配
从可用资源列表中选择最合适的资源:
- 工具选择:基于任务细节选择功能最匹配的工具
* **重要原则**:同一类型的工具只选择一个最合适的
* 例如:如果有多个网络搜索工具(bing/webSearch、google/search和metaso/metasoSearch等),只选择最符合需求的一个
* 避免功能重叠:不要选择功能相似的多个工具
- 知识库选择:基于领域需求选择相关知识库
- 系统功能判断:
* 是否需要用户的私有文件?→ 启用 file_upload
* 数据能否通过工具获取?→ 不需要 file_upload
* 是否需要执行代码或数据处理(如运行 Python 脚本、复杂计算、数据转换)?→ 启用 sandbox
* 任务仅需 LLM 推理和工具调用,无需执行任意代码?→ 不需要 sandbox
🔧 第四层:资源整合
- 收集所有需要的工具、知识库和系统功能
- 去除重复项
- 确保所有工具和知识库ID都在可用列表中
- 形成完整的 resources 配置
**输出要求**: **输出要求**:
**重要** **重要**
...@@ -372,7 +286,7 @@ ${resourceList} ...@@ -372,7 +286,7 @@ ${resourceList}
{ {
"id": "step1", "id": "step1",
"title": "简洁明确的步骤标题", "title": "简洁明确的步骤标题",
"description": "使用@[资源ID]格式的简洁任务描述,明确指出要做什么", "description": "使用 @资源ID 格式的简洁任务描述,明确指出要做什么",
"expectedTools": [ "expectedTools": [
{"id": "资源ID1", "type": "tool或knowledge"}, {"id": "资源ID1", "type": "tool或knowledge"},
{"id": "资源ID2", "type": "tool或knowledge"} {"id": "资源ID2", "type": "tool或knowledge"}
...@@ -395,10 +309,11 @@ ${resourceList} ...@@ -395,10 +309,11 @@ ${resourceList}
} }
**重要说明**: **重要说明**:
- expectedTools 字段中列出的资源是步骤需要使用的 - expectedTools 字段中列出的资源是步骤需要使用的真实 [工具]/[知识库]
- 资源通过 id 和 type 标识,type 为 "tool" 或 "knowledge" - 资源通过 id 和 type 标识,type 为 "tool" 或 "knowledge"
- description 字段中使用 @[资源ID] 格式引用资源 - description 字段中使用 @资源ID 格式引用资源
- 最终的 tools 和 knowledges 列表会从所有步骤的 expectedTools 中提取并去重 - 最终的 tools 和 knowledges 列表会从所有步骤的 expectedTools 中提取并去重
- file_upload 和 sandbox 只在 resources.system_features 中配置,不进入 expectedTools,也不允许作为 @资源ID 出现在 description 中
**字段说明**: **字段说明**:
- task_analysis: 提供对任务的深度理解和角色定义 - task_analysis: 提供对任务的深度理解和角色定义
...@@ -413,70 +328,21 @@ ${resourceList} ...@@ -413,70 +328,21 @@ ${resourceList}
<execution_plan_design> <execution_plan_design>
**执行计划设计**: **执行计划设计**:
**核心原则**:
- 渐进式规划:只规划到下一个关键信息点
- 任务分解:将复杂任务拆解为可管理的小步骤
- 资源对应:每个步骤在 expectedTools 中明确使用的资源
- 逻辑清晰:步骤之间有明确的先后依赖关系
**步骤设计要求**: **步骤设计要求**:
1. 每个步骤必须是可执行的独立单元 1. 每个步骤必须是可执行的独立单元
2. 步骤描述要简洁清晰,使用 @[资源ID] 格式引用资源 2. 步骤描述要简洁清晰,使用 @资源ID 格式引用资源
3. 在 expectedTools 中列出本步骤使用的所有资源 3. 在 expectedTools 中列出本步骤使用的所有资源
4. 步骤数量建议在 3-8 步之间 4. 步骤数量建议在 3-8 步之间
5. expectedTools 必须是对象数组,不能是字符串数组
**资源引用方式**: 6. expectedTools 中的每个资源都必须存在于“可用工具与知识库”,且带 [工具] 或 [知识库] 标签
- 在 description 字段中使用 @[资源ID] 格式引用资源 7. file_upload、sandbox 只代表前端开关,不能出现在 expectedTools 或 @资源引用中
- 在 expectedTools 字段中使用对象数组列出:{"id": "资源ID", "type": "tool或knowledge"}
**步骤类型示例**:
- 信息收集步骤:
\`\`\`json
{
"id": "step1",
"title": "搜索目的地信息",
"description": "使用 @metaso/metasoSearch 搜索旅游目的地的最新信息",
"expectedTools": [
{"id": "metaso/metasoSearch", "type": "tool"}
]
}
\`\`\`
- 知识查询步骤:
\`\`\`json
{
"id": "step2",
"title": "查询旅游攻略",
"description": "使用 @travel_kb 获取详细的旅游攻略信息",
"expectedTools": [
{"id": "travel_kb", "type": "knowledge"}
]
}
\`\`\`
- 组合使用步骤:
\`\`\`json
{
"id": "step3",
"title": "综合分析",
"description": "结合 @metaso/metasoSearch 和 @travel_kb 的信息,生成个性化行程建议",
"expectedTools": [
{"id": "metaso/metasoSearch", "type": "tool"},
{"id": "travel_kb", "type": "knowledge"}
]
}
\`\`\`
**注意事项**:
- execution_plan 是可选字段,如果任务简单可以不提供
- 如果提供,必须确保每个步骤都清晰可执行
- expectedTools 中列出的资源必须在可用资源列表中
- 同一资源可以在多个步骤中使用
</execution_plan_design> </execution_plan_design>
**✅ 完整示例1**(需要文件上传,包含 execution_plan): **✅ 示例**(需要文件上传和虚拟机时,也只在 system_features 中启用开关):
\`\`\`json \`\`\`json
{ {
"phase": "generation", "phase": "generation",
"reasoning": "用户需要分析财务数据,使用数据分析工具处理Excel文件,需要用户上传财务报表", "reasoning": "用户需要分析财务数据,需要上传报表,并使用真实数据分析工具处理文件内容",
"task_analysis": { "task_analysis": {
"goal": "分析用户的财务报表数据,提供财务健康评估和建议", "goal": "分析用户的财务报表数据,提供财务健康评估和建议",
"role": "财务数据分析专家", "role": "财务数据分析专家",
...@@ -512,95 +378,26 @@ ${resourceList} ...@@ -512,95 +378,26 @@ ${resourceList}
"file_upload": { "file_upload": {
"enabled": true, "enabled": true,
"purpose": "需要您上传财务报表文件(Excel或PDF格式)进行数据提取和分析" "purpose": "需要您上传财务报表文件(Excel或PDF格式)进行数据提取和分析"
}
}
}
}
\`\`\`
**✅ 完整示例2**(不需要文件上传,简单任务可不提供 execution_plan):
\`\`\`json
{
"phase": "generation",
"reasoning": "使用搜索工具获取实时旅游信息,结合知识库的专业知识提供个性化建议",
"task_analysis": {
"goal": "根据用户的偏好、预算和时间,提供个性化的旅游行程规划建议",
"role": "专业旅游规划顾问",
"key_features": "深入了解用户需求、使用搜索工具获取最新信息、结合知识库专业知识、生成详细可执行行程方案"
},
"execution_plan": {
"total_steps": 3,
"steps": [
{
"id": "step1",
"title": "了解用户需求",
"description": "询问用户的旅游偏好、预算、时间等信息",
"expectedTools": []
}, },
{ "sandbox": {
"id": "step2", "enabled": true,
"title": "搜索并查询信息", "purpose": "需要执行数据处理脚本或复杂计算"
"description": "使用 @metaso/metasoSearch 查找目的地最新信息,使用 @travel_kb 获取详细攻略",
"expectedTools": [
{"id": "metaso/metasoSearch", "type": "tool"},
{"id": "travel_kb", "type": "knowledge"}
]
},
{
"id": "step3",
"title": "生成行程方案",
"description": "综合所有信息,为用户生成详细的旅游行程方案",
"expectedTools": []
}
]
},
"resources": {
"system_features": {
"file_upload": {
"enabled": false
}
}
}
}
\`\`\`
**❌ 错误示例1**(file_upload 缺少 purpose):
\`\`\`json
{
"phase": "generation",
"resources": {
"system_features": {
"file_upload": {
"enabled": true
} }
} }
} }
} }
\`\`\` \`\`\`
**❌ 错误示例2**(引用了不在可用资源列表中的资源):
\`\`\`json
{
"phase": "generation",
"execution_plan": {
"steps": [
{
"id": "step1",
"expectedTools": [
{"id": "nonexistent_tool", "type": "tool"}
]
}
]
}
}
\`\`\`
**严格输出规则**: **严格输出规则**:
- ❌ 不要使用三个反引号json或其他代码块标记 - ❌ 不要使用三个反引号json或其他代码块标记
- ❌ 不要使用 resources.tools 或 resources.knowledges 格式 - ❌ 不要使用 resources.tools 或 resources.knowledges 格式
- ❌ 不要添加任何解释性文字或前言后语 - ❌ 不要添加任何解释性文字或前言后语
- ❌ 不要输出未在候选列表出现的资源 ID
- ❌ 不要把 file_upload 或 sandbox 放入 expectedTools
- ❌ 不要在 description 中写 @file_upload、@sandbox 或任何 @系统功能ID
- ✅ 资源通过 steps[*].expectedTools 引用 - ✅ 资源通过 steps[*].expectedTools 引用
- ✅ file_upload.enabled=true 时必须提供 purpose 字段 - ✅ file_upload.enabled=true 时必须提供 purpose 字段
- ✅ sandbox.enabled=true 时必须提供 purpose 字段
- ✅ 直接、纯净地输出JSON内容 - ✅ 直接、纯净地输出JSON内容
**质量要求**: **质量要求**:
......
...@@ -59,16 +59,16 @@ export const generateResourceList = async ({ ...@@ -59,16 +59,16 @@ export const generateResourceList = async ({
resourceList: string; resourceList: string;
}> => { }> => {
const getPrompt = ({ tool, dataset }: { tool: string; dataset: string }) => { const getPrompt = ({ tool, dataset }: { tool: string; dataset: string }) => {
return `## 可用资源列表 return `## 可用工具与知识库
### 工具 ### 工具
${tool} ${tool}
### 知识库 ### 知识库
${dataset} ${dataset}
### 系统功能 ## 可配置前端开关(不是工具,不能 @ 引用)
- **file_upload**: 文件上传功能,允许用户在对话中上传文件,让 Agent 读取私有文件内容 - **file_upload**: 文件上传开关,允许用户在对话中上传文件
- **sandbox**: 虚拟机执行环境,为 Agent 提供代码运行能力(Python、Shell 等),适用于数据处理、科学计算、代码执行等场景 - **sandbox**: 虚拟机开关,允许 Agent 使用虚拟机执行环境
`; `;
}; };
...@@ -115,7 +115,6 @@ ${dataset} ...@@ -115,7 +115,6 @@ ${dataset}
}); });
const allTools = [...systemTools, ...myTools, ...builtinTools]; const allTools = [...systemTools, ...myTools, ...builtinTools];
return { return {
resourceList: getPrompt({ resourceList: getPrompt({
tool: allTools.length > 0 ? allTools.join('\n') : '暂无已安装的工具', tool: allTools.length > 0 ? allTools.join('\n') : '暂无已安装的工具',
...@@ -175,19 +174,21 @@ export const buildSystemPrompt = (data: TopAgentGenerationAnswerType): string => ...@@ -175,19 +174,21 @@ export const buildSystemPrompt = (data: TopAgentGenerationAnswerType): string =>
let description = step.description; let description = step.description;
// 替换 description 中的资源引用: // 替换 description 中的资源引用:
// - 工具: @工具ID -> {{@工具ID@}} // - 工具: @工具ID / @工具ID@ / @[工具ID] -> {{@工具ID@}}
// - 知识库: @知识库ID -> {{@dataset_search@}} // - 知识库: @知识库ID / @知识库ID@ / @[知识库ID] -> {{@dataset_search@}}
if (step.expectedTools && step.expectedTools.length > 0) { if (step.expectedTools && step.expectedTools.length > 0) {
step.expectedTools.forEach((resourceRef) => { step.expectedTools.forEach((resourceRef) => {
const replaceId = const replaceId =
resourceRef.type === 'knowledge' ? SubAppIds.datasetSearch : resourceRef.id; resourceRef.type === 'knowledge' ? SubAppIds.datasetSearch : resourceRef.id;
const regex = new RegExp( const escapedId = resourceRef.id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
`@${resourceRef.id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}@?`, const regex = new RegExp(`@(?:\\[${escapedId}\\]|${escapedId}@?)`, 'g');
'g'
);
description = description.replace(regex, `{{@${replaceId}@}}`); description = description.replace(regex, `{{@${replaceId}@}}`);
}); });
} }
description = description.replace(
/(?<!\{\{)@(?:\[(file_upload|sandbox)\]|(file_upload|sandbox)@?)(?!\}\})/g,
'$1$2'
);
parts.push(`\n步骤 ${index + 1}. ${step.title} \n${description}`); parts.push(`\n步骤 ${index + 1}. ${step.title} \n${description}`);
// if (step.expectedTools && step.expectedTools.length > 0) { // if (step.expectedTools && step.expectedTools.length > 0) {
......
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
import { AGENT_SANDBOX_TOOLSET_ID } from '@fastgpt/global/core/ai/sandbox/tools'; import { AGENT_SANDBOX_TOOLSET_ID } from '@fastgpt/global/core/ai/sandbox/tools';
import { SubAppIds } from '@fastgpt/global/core/workflow/node/agent/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
const { createLLMResponseMock } = vi.hoisted(() => ({ const { createLLMResponseMock } = vi.hoisted(() => ({
...@@ -69,6 +70,78 @@ describe('dispatchTopAgent', () => { ...@@ -69,6 +70,78 @@ describe('dispatchTopAgent', () => {
vi.clearAllMocks(); vi.clearAllMocks();
}); });
const mockGenerationResponse = ({
description,
expectedTools
}: {
description: string;
expectedTools: Array<{ id: string; type: 'tool' | 'knowledge' }>;
}) => {
createLLMResponseMock.mockResolvedValue({
answerText: JSON.stringify({
phase: 'generation',
reasoning: 'generate agent config',
task_analysis: {
goal: 'build helper agent',
role: 'assistant',
key_features: 'use selected resources'
},
execution_plan: {
total_steps: 1,
steps: [
{
id: 'step_1',
title: 'Use resource',
description,
expectedTools
}
]
},
resources: {
system_features: {
file_upload: {
enabled: false
},
sandbox: {
enabled: false
}
}
}
}),
reasoningText: '',
usage: {
inputTokens: 10,
outputTokens: 5
}
});
};
const dispatchAndGetTopAgentConfig = async () => {
const workflowResponseWrite = vi.fn();
await dispatchTopAgent({
query: 'build an agent with selected resources',
files: [],
data: {},
histories: [],
workflowResponseWrite,
user: {
teamId: 'team_1',
tmbId: 'tmb_1',
userId: 'user_1',
isRoot: false,
lang: 'zh-CN'
}
});
const configEvent = workflowResponseWrite.mock.calls.find(
([payload]) => payload.event === SseResponseEventEnum.topAgentConfig
);
expect(configEvent).toBeDefined();
return configEvent![0].data;
};
it('enables sandbox when generated plan selects the agent sandbox toolset', async () => { it('enables sandbox when generated plan selects the agent sandbox toolset', async () => {
createLLMResponseMock.mockResolvedValue({ createLLMResponseMock.mockResolvedValue({
answerText: JSON.stringify({ answerText: JSON.stringify({
...@@ -134,8 +207,99 @@ describe('dispatchTopAgent', () => { ...@@ -134,8 +207,99 @@ describe('dispatchTopAgent', () => {
event: SseResponseEventEnum.topAgentConfig, event: SseResponseEventEnum.topAgentConfig,
data: expect.objectContaining({ data: expect.objectContaining({
tools: [AGENT_SANDBOX_TOOLSET_ID], tools: [AGENT_SANDBOX_TOOLSET_ID],
systemPrompt: expect.stringContaining(`{{@${AGENT_SANDBOX_TOOLSET_ID}@}}`),
enableSandboxEnabled: true enableSandboxEnabled: true
}) })
}); });
}); });
it('renders bracketed tool references in generated step descriptions', async () => {
const toolId = 'custom/search_tool';
mockGenerationResponse({
description: `使用 @[${toolId}] 搜索信息`,
expectedTools: [
{
id: toolId,
type: 'tool'
}
]
});
const config = await dispatchAndGetTopAgentConfig();
expect(config).toEqual(
expect.objectContaining({
tools: [toolId],
systemPrompt: expect.stringContaining(`{{@${toolId}@}}`)
})
);
});
it('renders plain tool references in generated step descriptions', async () => {
const toolId = 'custom/search_tool';
mockGenerationResponse({
description: `使用 @${toolId} 搜索信息`,
expectedTools: [
{
id: toolId,
type: 'tool'
}
]
});
const config = await dispatchAndGetTopAgentConfig();
expect(config).toEqual(
expect.objectContaining({
tools: [toolId],
systemPrompt: expect.stringContaining(`{{@${toolId}@}}`)
})
);
});
it('renders knowledge references as dataset search skill labels', async () => {
const datasetId = '507f1f77bcf86cd799439011';
mockGenerationResponse({
description: `使用 @[${datasetId}] 查询知识库`,
expectedTools: [
{
id: datasetId,
type: 'knowledge'
}
]
});
const config = await dispatchAndGetTopAgentConfig();
expect(config).toEqual(
expect.objectContaining({
systemPrompt: expect.stringContaining(`{{@${SubAppIds.datasetSearch}@}}`)
})
);
});
it('does not render system features as skill labels in generated step descriptions', async () => {
mockGenerationResponse({
description: `通过 @file_upload 接收文件,并使用 @${SubAppIds.readFiles} 读取内容,不要使用 @sandbox`,
expectedTools: [
{
id: SubAppIds.readFiles,
type: 'tool'
}
]
});
const config = await dispatchAndGetTopAgentConfig();
expect(config).toEqual(
expect.objectContaining({
tools: [SubAppIds.readFiles],
systemPrompt: expect.stringContaining(`{{@${SubAppIds.readFiles}@}}`)
})
);
expect(config.systemPrompt).toContain('通过 file_upload 接收文件');
expect(config.systemPrompt).toContain('不要使用 sandbox');
expect(config.systemPrompt).not.toContain('{{@file_upload@}}');
expect(config.systemPrompt).not.toContain('{{@sandbox@}}');
});
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment