perf: agent call code

This commit is contained in:
archer
2025-09-16 23:11:54 +08:00
parent b31345a710
commit f1c5efa6cb
7 changed files with 133 additions and 163 deletions

View File

@@ -1,37 +1,40 @@
import { SubAppIds } from './sub/constants';
export const getMasterAgentDefaultPrompt = () => {
return `## 角色定位
是一个高级任务调度器Task Orchestrator负责分析用户需求、拆解并分派子任务、整合多Agent成果确保最终产出具备高度专业性、系统性与洞见力。
return `## 工作流程
需要严格按照 [${SubAppIds.plan}] 提供的步骤逐步完成执行任务。`;
// return `## 角色定位
// 你是一个高级任务调度器Task Orchestrator负责分析用户需求、拆解并分派子任务、整合多Agent成果确保最终产出具备高度专业性、系统性与洞见力。
## 核心职责
### 粒度控制
- **原子任务原则**:每个子任务必须是单一、明确、可独立完成的
- **专业匹配原则**根据任务属性将子任务分配给最合适的专业Agent
- **避免任务过载**不得将多个复杂任务打包给单个Agent
// ## 核心职责
// ### 粒度控制
// - **原子任务原则**:每个子任务必须是单一、明确、可独立完成的
// - **专业匹配原则**根据任务属性将子任务分配给最合适的专业Agent
// - **避免任务过载**不得将多个复杂任务打包给单个Agent
### 过程管理
- 制定清晰的执行顺序,保证任务逻辑链条完整
- 动态监控子任务进展,必要时调整分工或策略
- 保持信息透明确保上下游Agent输出可直接衔接
// ### 过程管理
// - 制定清晰的执行顺序,保证任务逻辑链条完整
// - 动态监控子任务进展,必要时调整分工或策略
// - 保持信息透明确保上下游Agent输出可直接衔接
### 结果验证
- 校验每个子任务是否达成预期目标
- 严格检查输出的准确性、完整性和格式规范
- 确保所有结果的一致性、逻辑连贯性和互补性
// ### 结果验证
// - 校验每个子任务是否达成预期目标
// - 严格检查输出的准确性、完整性和格式规范
// - 确保所有结果的一致性、逻辑连贯性和互补性
## 最终产出标准
1. **专业性**:输出必须具备专家级深度,避免表面化结论
2. **系统性**:结果需结构化呈现,覆盖任务的各个核心维度
3. **多视角**:从战略、执行、风险、机遇等不同角度进行分析
4. **洞见力**:在满足需求的基础上,提出更高层次的见解、趋势判断或改进建议
5. **可操作性**:输出不仅是分析,还应包含可执行的步骤、优先级和资源需求
6. **用户导向**:确保最终成果切合用户需求,且超出用户的预期价值
// ## 最终产出标准
// 1. **专业性**:输出必须具备专家级深度,避免表面化结论
// 2. **系统性**:结果需结构化呈现,覆盖任务的各个核心维度
// 3. **多视角**:从战略、执行、风险、机遇等不同角度进行分析
// 4. **洞见力**:在满足需求的基础上,提出更高层次的见解、趋势判断或改进建议
// 5. **可操作性**:输出不仅是分析,还应包含可执行的步骤、优先级和资源需求
// 6. **用户导向**:确保最终成果切合用户需求,且超出用户的预期价值
## 关键原则
1. **分而治之**复杂任务必须拆解禁止直接交由单个Agent
2. **专业对口**匹配最合适的Agent来执行对应任务
3. **循序渐进**:按照逻辑顺序推进,确保结果可堆叠与传递
4. **结果导向**:始终聚焦最终输出的质量与用户满意度
5. **灵活调整**:在任务推进中,根据结果反馈动态优化策略
6. **高标准产出**:最终结果必须是专业、详细、系统且具备洞见的完整方案
`;
// ## 关键原则
// 1. **分而治之**复杂任务必须拆解禁止直接交由单个Agent
// 2. **专业对口**匹配最合适的Agent来执行对应任务
// 3. **循序渐进**:按照逻辑顺序推进,确保结果可堆叠与传递
// 4. **结果导向**:始终聚焦最终输出的质量与用户满意度
// 5. **灵活调整**:在任务推进中,根据结果反馈动态优化策略
// 6. **高标准产出**:最终结果必须是专业、详细、系统且具备洞见的完整方案`;
};

View File

@@ -46,6 +46,7 @@ import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
import { dispatchFileRead } from './sub/file';
import { dispatchApp, dispatchPlugin } from './sub/app';
import { getSubAppsPrompt } from './sub/plan/prompt';
import { parseAgentSystemPrompt } from './utils';
export type DispatchAgentModuleProps = ModuleDispatchProps<{
[NodeInputKeyEnum.history]?: ChatItemType[];
@@ -101,21 +102,25 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
const masterMessagesKey = `masterMessages-${nodeId}`;
const planMessagesKey = `planMessages-${nodeId}`;
const planToolCallMessagesKey = `planToolCallMessages-${nodeId}`;
// Get history messages
const { masterHistoryMessages, planHistoryMessages } = (() => {
let { masterHistoryMessages, planHistoryMessages, planToolCallMessages } = (() => {
const lastHistory = chatHistories[chatHistories.length - 1];
if (lastHistory && lastHistory.obj === ChatRoleEnum.AI) {
return {
masterHistoryMessages: (lastHistory.memories?.[masterMessagesKey] ||
[]) as ChatCompletionMessageParam[],
planHistoryMessages: (lastHistory.memories?.[planMessagesKey] ||
[]) as ChatCompletionMessageParam[],
planToolCallMessages: (lastHistory.memories?.[planToolCallMessagesKey] ||
[]) as ChatCompletionMessageParam[]
};
}
return {
masterHistoryMessages: [],
planHistoryMessages: []
planHistoryMessages: [],
planToolCallMessages: []
};
})();
@@ -188,65 +193,69 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
Sub agent
只会执行一次,肯定有。
*/
let masterPlanToolCallMessages: ChatCompletionMessageParam[] = [];
if (
// 不为 userSelect 和 userInput或者如果为 agentPlanCheck 并且 interactiveInput !== ConfirmPlanAgentText 都会执行
(lastInteractive?.type !== 'userSelect' &&
lastInteractive?.type !== 'userInput' &&
lastInteractive?.type !== 'agentPlanCheck') ||
(lastInteractive?.type === 'agentPlanCheck' && interactiveInput !== ConfirmPlanAgentText)
// 如果是 userSelect 和 userInput说明不是 plan 触发的交互。
lastInteractive?.type !== 'userSelect' &&
lastInteractive?.type !== 'userInput'
) {
// 临时代码
const tmpText = '正在进行规划生成...\n';
workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: tmpText
})
});
// Confirm 操作
if (lastInteractive?.type === 'agentPlanCheck' && interactiveInput === ConfirmPlanAgentText) {
} else {
// 临时代码
const tmpText = '正在进行规划生成...\n';
workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: tmpText
})
});
const {
answerText,
planList,
planToolCallMessages,
completeMessages,
usages,
interactiveResponse
} = await dispatchPlanAgent({
historyMessages: planHistoryMessages,
userInput: lastInteractive ? interactiveInput : taskInput,
interactive: lastInteractive,
subAppPrompt: getSubAppsPrompt({ subAppList, getSubAppInfo }),
model,
systemPrompt,
temperature,
top_p: aiChatTopP,
stream,
isTopPlanAgent: workflowDispatchDeep === 1
});
const {
answerText,
planList,
planToolCallMessages: newPlanToolCallMessages,
completeMessages,
usages,
interactiveResponse
} = await dispatchPlanAgent({
historyMessages: planHistoryMessages,
userInput: lastInteractive ? interactiveInput : taskInput,
interactive: lastInteractive,
subAppPrompt: getSubAppsPrompt({ subAppList, getSubAppInfo }),
model,
systemPrompt: parseAgentSystemPrompt({
systemPrompt,
getSubAppInfo
}),
temperature,
top_p: aiChatTopP,
stream,
isTopPlanAgent: workflowDispatchDeep === 1
});
const text = `${answerText}${planList ? `\n\`\`\`json\n${JSON.stringify(planList, null, 2)}\n\`\`\`` : ''}`;
workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text
})
});
const text = `${answerText}${planList ? `\n\`\`\`json\n${JSON.stringify(planList, null, 2)}\n\`\`\`` : ''}`;
workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text
})
});
planMessages = completeMessages;
masterPlanToolCallMessages = planToolCallMessages;
planMessages = completeMessages;
planToolCallMessages = newPlanToolCallMessages;
// TODO: usage 合并
// Sub agent plan 不会有交互响应。Top agent plan 肯定会有。
if (interactiveResponse) {
return {
[DispatchNodeResponseKeyEnum.answerText]: `${tmpText}${text}`,
[DispatchNodeResponseKeyEnum.memories]: {
[planMessagesKey]: filterMemoryMessages(planMessages)
},
[DispatchNodeResponseKeyEnum.interactive]: interactiveResponse
};
// TODO: usage 合并
// Sub agent plan 不会有交互响应。Top agent plan 肯定会有。
if (interactiveResponse) {
return {
[DispatchNodeResponseKeyEnum.answerText]: `${tmpText}${text}`,
[DispatchNodeResponseKeyEnum.memories]: {
[planMessagesKey]: filterMemoryMessages(planMessages),
[planToolCallMessagesKey]: planToolCallMessages
},
[DispatchNodeResponseKeyEnum.interactive]: interactiveResponse
};
}
}
}
@@ -262,7 +271,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
return masterHistoryMessages;
}
return chats2GPTMessages({ messages: chatHistories, reserveId: false });
return [];
})();
const userMessages = chats2GPTMessages({
@@ -280,9 +289,10 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
const requestMessages = [
...systemMessages,
...historyMessages,
...masterPlanToolCallMessages,
...userMessages
...userMessages,
...planToolCallMessages
];
console.log(JSON.stringify({ requestMessages }, null, 2));
const dispatchFlowResponse: ChatHistoryItemResType[] = [];
const {

View File

@@ -15,7 +15,7 @@ export const systemSubInfo: Record<
[SubAppIds.plan]: {
name: i18nT('chat:plan_agent'),
avatar: 'common/detail',
toolDescription: '分析和拆解用户问题,制定分步计划。'
toolDescription: '分析和拆解用户问题,制定执行步骤。'
},
[SubAppIds.fileRead]: {
name: i18nT('chat:file_parse'),

View File

@@ -1,5 +1,4 @@
import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type';
import { PlanAgentTool } from './plan/constants';
import { readFileTool } from './file/utils';
import type { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import type { JSONSchemaInputType } from '@fastgpt/global/core/app/jsonschema';
@@ -79,7 +78,11 @@ export const getSubApps = ({
];
// Node Tools
const nodeTools = subApps.map<ChatCompletionTool>((item) => {
const unitNodeTools = subApps.filter(
(item, index, array) => array.findIndex((app) => app.pluginId === item.pluginId) === index
);
const nodeTools = unitNodeTools.map<ChatCompletionTool>((item) => {
const toolParams: FlowNodeInputItemType[] = [];
let jsonSchema: JSONSchemaInputType | undefined;

View File

@@ -1,7 +1,4 @@
import type {
ChatCompletionMessageParam,
ChatCompletionTool
} from '@fastgpt/global/core/ai/type.d';
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
import { createLLMResponse } from '../../../../../../ai/llm/request';
import { getPlanAgentPrompt } from './prompt';
import { getLLMModel } from '../../../../../../ai/model';
@@ -85,7 +82,7 @@ export const dispatchPlanAgent = async ({
content: userInput
});
}
console.log(JSON.stringify({ requestMessages }, null, 2));
// console.log(JSON.stringify({ requestMessages }, null, 2));
const {
answerText,

View File

@@ -1,64 +1,30 @@
import type {
ChatCompletionAssistantMessageParam,
ChatCompletionMessageParam
} from '@fastgpt/global/core/ai/type';
import type { ToolNodeItemType } from './type';
const namespaceMap = new Map<string, string>([
['a', '子应用'],
['t', '工具'],
['d', '知识库'],
['m', '模型']
]);
// e.g: {{@a.appId@}} -> a.appId
const buildPattern = (options?: { prefix?: string }): RegExp => {
const config = {
prefix: '@',
...options
};
const escapedPrefix = config.prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return new RegExp(`\\{\\{${escapedPrefix}([^${escapedPrefix}]+)${escapedPrefix}\\}\\}`, 'g');
};
export const getSubIdsByAgentSystem = (
systemPrompt: string,
options?: { prefix?: string }
): string[] => {
const pattern = buildPattern(options);
const ids: string[] = [];
let match;
while ((match = pattern.exec(systemPrompt)) !== null) {
const fullName = match[1];
const [, id] = fullName.split('.');
if (id) {
ids.push(id);
}
}
return ids;
};
export const parseAgentSystem = ({
/*
匹配 {{@toolId@}},转化成: @name 的格式。
*/
export const parseAgentSystemPrompt = ({
systemPrompt,
toolNodesMap,
options
getSubAppInfo
}: {
systemPrompt: string;
toolNodesMap: Map<string, ToolNodeItemType>;
options?: { prefix?: string };
getSubAppInfo: (id: string) => {
name: string;
avatar: string;
toolDescription: string;
};
}): string => {
const pattern = buildPattern(options);
if (!systemPrompt) return '';
const processedPrompt = systemPrompt.replace(pattern, (_, toolName) => {
const [namespace, id] = toolName.split('.');
const toolNode = toolNodesMap.get(id);
const name = toolNode?.name || toolNode?.toolDescription || toolNode?.intro || 'unknown';
// Match pattern {{@toolId@}} and convert to @name format
const pattern = /\{\{@([^@]+)@\}\}/g;
const prefix = namespaceMap.get(namespace) ?? 'unknown';
return `${prefix}:${name}`;
const processedPrompt = systemPrompt.replace(pattern, (match, toolId) => {
const toolInfo = getSubAppInfo(toolId);
if (!toolInfo) {
console.warn(`Tool not found for ID: ${toolId}`);
return match; // Return original match if tool not found
}
return `@${toolInfo.name}`;
});
return processedPrompt;

View File

@@ -14,13 +14,12 @@ import type {
AIChatItemValueItemType,
ToolModuleResponseItemType
} from '@fastgpt/global/core/chat/type';
import React, { useState, useCallback, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@fastgpt/web/components/common/Avatar';
import type {
InteractiveBasicType,
PaymentPauseInteractive,
InteractiveNodeResponseType,
UserInputInteractive,
UserSelectInteractive
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
@@ -148,7 +147,6 @@ const RenderTool = React.memo(
onOpenCiteModal?: (e?: OnOpenCiteModalProps) => void;
}) {
const { t } = useSafeTranslation();
const [userOnchange, setUserOnchange] = useState(false);
const formatJson = (string: string) => {
try {
return JSON.stringify(JSON.parse(string), null, 2);
@@ -160,14 +158,7 @@ const RenderTool = React.memo(
const response = formatJson(tool.response);
return (
<Accordion
key={tool.id}
allowToggle
index={userOnchange ? undefined : showAnimation ? 0 : undefined}
onChange={() => {
setUserOnchange(true);
}}
>
<Accordion key={tool.id} allowToggle>
<AccordionItem borderTop={'none'} borderBottom={'none'}>
<AccordionButton {...accordionButtonStyle}>
<Avatar src={tool.toolAvatar} w={'1.25rem'} h={'1.25rem'} borderRadius={'sm'} />
@@ -200,7 +191,7 @@ ${params}`}
${response}`}
/>
)}
{subAppValue && (
{subAppValue && subAppValue.length > 0 && (
<Box bg={'white'} p={2}>
{subAppValue.map((value, index) => (
<AIResponseBox