Commit 445a5dc4 by YeYuheng Committed by GitHub

fix: optimize top agent prompt (#7095)

* fix: optimize top agent prompt

* chore: trigger image rebuild
parent a5364a45
......@@ -59,16 +59,16 @@ export const generateResourceList = async ({
resourceList: string;
}> => {
const getPrompt = ({ tool, dataset }: { tool: string; dataset: string }) => {
return `## 可用资源列表
return `## 可用工具与知识库
### 工具
${tool}
### 知识库
${dataset}
### 系统功能
- **file_upload**: 文件上传功能,允许用户在对话中上传文件,让 Agent 读取私有文件内容
- **sandbox**: 虚拟机执行环境,为 Agent 提供代码运行能力(Python、Shell 等),适用于数据处理、科学计算、代码执行等场景
## 可配置前端开关(不是工具,不能 @ 引用)
- **file_upload**: 文件上传开关,允许用户在对话中上传文件
- **sandbox**: 虚拟机开关,允许 Agent 使用虚拟机执行环境
`;
};
......@@ -115,7 +115,6 @@ ${dataset}
});
const allTools = [...systemTools, ...myTools, ...builtinTools];
return {
resourceList: getPrompt({
tool: allTools.length > 0 ? allTools.join('\n') : '暂无已安装的工具',
......@@ -175,19 +174,21 @@ export const buildSystemPrompt = (data: TopAgentGenerationAnswerType): string =>
let description = step.description;
// 替换 description 中的资源引用:
// - 工具: @工具ID -> {{@工具ID@}}
// - 知识库: @知识库ID -> {{@dataset_search@}}
// - 工具: @工具ID / @工具ID@ / @[工具ID] -> {{@工具ID@}}
// - 知识库: @知识库ID / @知识库ID@ / @[知识库ID] -> {{@dataset_search@}}
if (step.expectedTools && step.expectedTools.length > 0) {
step.expectedTools.forEach((resourceRef) => {
const replaceId =
resourceRef.type === 'knowledge' ? SubAppIds.datasetSearch : resourceRef.id;
const regex = new RegExp(
`@${resourceRef.id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}@?`,
'g'
);
const escapedId = resourceRef.id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`@(?:\\[${escapedId}\\]|${escapedId}@?)`, 'g');
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}`);
// if (step.expectedTools && step.expectedTools.length > 0) {
......
import { beforeEach, describe, expect, it, vi } from 'vitest';
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';
const { createLLMResponseMock } = vi.hoisted(() => ({
......@@ -69,6 +70,78 @@ describe('dispatchTopAgent', () => {
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 () => {
createLLMResponseMock.mockResolvedValue({
answerText: JSON.stringify({
......@@ -134,8 +207,99 @@ describe('dispatchTopAgent', () => {
event: SseResponseEventEnum.topAgentConfig,
data: expect.objectContaining({
tools: [AGENT_SANDBOX_TOOLSET_ID],
systemPrompt: expect.stringContaining(`{{@${AGENT_SANDBOX_TOOLSET_ID}@}}`),
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