perf: agent plan

This commit is contained in:
archer
2025-09-16 21:28:52 +08:00
parent fc7df5f364
commit 7227569142
9 changed files with 369 additions and 235 deletions

View File

@@ -1,4 +1,5 @@
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import type { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import {
ConfirmPlanAgentText,
DispatchNodeResponseKeyEnum,
@@ -17,6 +18,7 @@ import { type ChatItemType } from '@fastgpt/global/core/chat/type';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import {
GPTMessages2Chats,
chatValue2RuntimePrompt,
chats2GPTMessages,
getSystemPrompt_ChatItemType,
runtimePrompt2ChatsValue
@@ -43,6 +45,7 @@ import { addFilePrompt2Input, getFileInputPrompt } from './sub/file/utils';
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';
export type DispatchAgentModuleProps = ModuleDispatchProps<{
[NodeInputKeyEnum.history]?: ChatItemType[];
@@ -84,7 +87,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
params: {
model,
systemPrompt,
userChatInput,
userChatInput: taskInput,
history = 6,
fileUrlList: fileLinks,
temperature,
@@ -119,6 +122,9 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
// Check interactive entry
props.node.isEntry = false;
// 交互模式进来的话,这个值才是交互输入的值
const interactiveInput = lastInteractive ? chatValue2RuntimePrompt(query).text : '';
try {
// Get files
const fileUrlInput = inputs.find((item) => item.key === NodeInputKeyEnum.fileUrlList);
@@ -165,15 +171,90 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
const toolNode = subAppsMap.get(id) || systemSubInfo[id];
return {
name: toolNode?.name || '',
avatar: toolNode?.avatar || ''
avatar: toolNode?.avatar || '',
toolDescription: toolNode?.toolDescription || toolNode?.name || ''
};
};
/* ===== Plan Agent ===== */
let planMessages: ChatCompletionMessageParam[] = [];
/*
Top agent
1. 首轮执行:有
2. 交互/check
3. Confirmed plan: 无
4. masteragent 的交互时间: 无
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)
) {
// 临时代码
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 text = `${answerText}${planList ? `\n\`\`\`json\n${JSON.stringify(planList, null, 2)}\n\`\`\`` : ''}`;
workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text
})
});
planMessages = completeMessages;
masterPlanToolCallMessages = planToolCallMessages;
// TODO: usage 合并
// Sub agent plan 不会有交互响应。Top agent plan 肯定会有。
if (interactiveResponse) {
return {
[DispatchNodeResponseKeyEnum.answerText]: `${tmpText}${text}`,
[DispatchNodeResponseKeyEnum.memories]: {
[planMessagesKey]: filterMemoryMessages(planMessages)
},
[DispatchNodeResponseKeyEnum.interactive]: interactiveResponse
};
}
}
/* ===== Master agent ===== */
// Get master request messages
const systemMessages = chats2GPTMessages({
messages: getSystemPrompt_ChatItemType(
workflowDispatchDeep === 1 ? getMasterAgentDefaultPrompt() : systemPrompt
),
messages: getSystemPrompt_ChatItemType(getMasterAgentDefaultPrompt()),
reserveId: false
});
const historyMessages: ChatCompletionMessageParam[] = (() => {
@@ -184,87 +265,24 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
return chats2GPTMessages({ messages: chatHistories, reserveId: false });
})();
if (lastInteractive?.type !== 'userSelect' && lastInteractive?.type !== 'userInput') {
userChatInput = query[0].text?.content ?? userChatInput;
}
const userMessages = chats2GPTMessages({
messages: [
{
obj: ChatRoleEnum.Human,
value: runtimePrompt2ChatsValue({
text: addFilePrompt2Input({ query: userChatInput, filePrompt: fileInputPrompt }),
text: addFilePrompt2Input({ query: taskInput, filePrompt: fileInputPrompt }),
files: []
})
}
],
reserveId: false
});
const requestMessages = [...systemMessages, ...historyMessages, ...userMessages];
// TODO: 执行 plan function(只有lastInteractive userselect/userInput 时候,才不需要进入 plan)
if (
lastInteractive?.type !== 'userSelect' &&
lastInteractive?.type !== 'userInput' &&
userChatInput !== ConfirmPlanAgentText &&
workflowDispatchDeep === 1
) {
const planRequestMessages = [...planHistoryMessages, ...userMessages];
const { completeMessages, toolMessages, usages, interactiveResponse } =
await dispatchPlanAgent({
messages: planRequestMessages,
subApps: subAppList,
model,
temperature,
top_p: aiChatTopP,
systemPrompt,
stream,
onReasoning: ({ text }: { text: string }) => {
workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
reasoning_content: text
})
});
},
onStreaming: ({ text }: { text: string }) => {
workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text
})
});
}
});
if (toolMessages) requestMessages.push(...toolMessages);
return {
[DispatchNodeResponseKeyEnum.memories]: {
[masterMessagesKey]: filterMemoryMessages(requestMessages),
[planMessagesKey]: filterMemoryMessages(completeMessages)
},
[DispatchNodeResponseKeyEnum.interactive]: interactiveResponse
// Mock: 返回 plan user input
// [DispatchNodeResponseKeyEnum.interactive]: {
// type: 'agentPlanAskUserForm',
// params: {
// description: '测试',
// inputForm: [
// {
// type: FlowNodeInputTypeEnum.input,
// key: 'test1',
// label: '测试1',
// value: '',
// valueType: WorkflowIOValueTypeEnum.string,
// required: true
// }
// ]
// }
// }
};
}
const requestMessages = [
...systemMessages,
...historyMessages,
...masterPlanToolCallMessages,
...userMessages
];
const dispatchFlowResponse: ChatHistoryItemResType[] = [];
const {
@@ -371,7 +389,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
isEnd: true
};
}
// TODO: 现在是程序中强制执行的 Plan
// TODO: 可能会再次触发 plan
// else if (toolId === SubAppIds.plan) {
// const { completeMessages, response, usages, interactiveResponse } =
// await dispatchPlanAgent({
@@ -393,18 +411,26 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
// };
// }
else if (toolId === SubAppIds.model) {
const { systemPrompt, task } = parseToolArgs<{
const params = parseToolArgs<{
systemPrompt: string;
task: string;
}>(call.function.arguments);
if (!params) {
return {
response: 'params is not object',
usages: [],
isEnd: false
};
}
const { response, usages } = await dispatchModelAgent({
model,
temperature,
top_p: aiChatTopP,
stream,
systemPrompt,
task,
systemPrompt: params.systemPrompt,
task: params.task,
onReasoning,
onStreaming
});
@@ -414,10 +440,17 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
isEnd: false
};
} else if (toolId === SubAppIds.fileRead) {
const { file_indexes } = parseToolArgs<{
const params = parseToolArgs<{
file_indexes: string[];
}>(call.function.arguments);
if (!Array.isArray(file_indexes)) {
if (!params) {
return {
response: 'params is not object',
usages: [],
isEnd: false
};
}
if (!Array.isArray(params.file_indexes)) {
return {
response: 'file_indexes is not array',
usages: [],
@@ -425,7 +458,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
};
}
const files = file_indexes.map((index) => ({
const files = params.file_indexes.map((index) => ({
index,
url: filesMap[index]
}));
@@ -453,6 +486,15 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
}
const toolCallParams = parseToolArgs(call.function.arguments);
if (!toolCallParams) {
return {
response: 'params is not object',
usages: [],
isEnd: false
};
}
// Get params
const requestParams = (() => {
const params: Record<string, any> = toolCallParams;
@@ -582,16 +624,12 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
const previewAssistantResponses = filterToolResponseToPreview(assistantResponses);
return {
data: {
[NodeOutputKeyEnum.answerText]: previewAssistantResponses
.filter((item) => item.text?.content)
.map((item) => item.text?.content || '')
.join('')
},
// 目前 Master 不会触发交互
// [DispatchNodeResponseKeyEnum.interactive]: interactiveResponse,
// TODO: 需要对 memoryMessages 单独建表存储
[DispatchNodeResponseKeyEnum.memories]: {
[masterMessagesKey]: filterMemoryMessages(completeMessages),
[planMessagesKey]: [filterMemoryMessages(planHistoryMessages)]
[planMessagesKey]: [filterMemoryMessages(planMessages)]
},
[DispatchNodeResponseKeyEnum.assistantResponses]: previewAssistantResponses,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
@@ -601,7 +639,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
toolCallOutputTokens: outputTokens,
childTotalPoints: toolTotalPoints,
model: modelName,
query: userChatInput,
query: taskInput,
historyPreview: getHistoryPreview(
GPTMessages2Chats({ messages: completeMessages, reserveTool: false }),
10000,
@@ -621,8 +659,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
},
// Tool usage
...subAppUsages
],
[DispatchNodeResponseKeyEnum.interactive]: interactiveResponse
]
};
} catch (error) {
return getNodeErrResponse({ error });

View File

@@ -1,4 +1,3 @@
import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type';
import { i18nT } from '../../../../../../../web/i18n/utils';
export enum SubAppIds {
@@ -9,25 +8,33 @@ export enum SubAppIds {
fileRead = 'file_read'
}
export const systemSubInfo: Record<string, { name: string; avatar: string }> = {
export const systemSubInfo: Record<
string,
{ name: string; avatar: string; toolDescription: string }
> = {
[SubAppIds.plan]: {
name: i18nT('chat:plan_agent'),
avatar: 'common/detail'
avatar: 'common/detail',
toolDescription: '分析和拆解用户问题,制定分步计划。'
},
[SubAppIds.fileRead]: {
name: i18nT('chat:file_parse'),
avatar: 'core/workflow/template/readFiles'
avatar: 'core/workflow/template/readFiles',
toolDescription: '读取文件内容,并返回文件内容。'
},
[SubAppIds.ask]: {
name: 'Ask Agent',
avatar: 'core/workflow/template/agent'
avatar: 'core/workflow/template/agent',
toolDescription: '询问用户问题,并返回用户回答。'
},
[SubAppIds.stop]: {
name: 'Stop Agent',
avatar: 'core/workflow/template/agent'
avatar: 'core/workflow/template/agent',
toolDescription: '停止当前任务。'
},
[SubAppIds.model]: {
name: 'Model Agent',
avatar: 'core/workflow/template/agent'
avatar: 'core/workflow/template/agent',
toolDescription: '调用 LLM 模型完成一些通用任务。'
}
};

View File

@@ -1,19 +1,28 @@
import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type';
import { SubAppIds } from '../../constants';
export type AskAgentToolParamsType = Partial<{
mode: 'select' | 'formInput' | 'input';
prompt: string;
export type AskAgentToolParamsType =
| {
mode: 'select';
prompt?: string;
options: string[];
}
| {
mode: 'formInput';
prompt?: string;
form: {
field: string;
type: 'textInput' | 'numberInput' | 'singleSelect' | 'multiSelect';
required: boolean;
options: string[];
}[];
}>;
}
| {
mode: 'input';
prompt: string;
};
export const AskAgentTool: ChatCompletionTool = {
export const PlanAgentAskTool: ChatCompletionTool = {
type: 'function',
function: {
name: SubAppIds.ask,
@@ -30,7 +39,6 @@ export const AskAgentTool: ChatCompletionTool = {
2. mode = "input"
- 用于自由文本输入,适合用户提供个性化或开放式回答。
- prompt: 展示的问题提示,引导用户填写。
- options: 此模式下通常留空或忽略。
- 场景示例:
* 需要用户补充说明原因、填写备注、输入 URL/编号等。
* 当 "select" 的选项无法覆盖用户真实答案时,可以再调用一次 "input" 追问。

View File

@@ -1,11 +1,18 @@
import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type';
import { SubAppIds } from '../constants';
import { SubAppIds, systemSubInfo } from '../constants';
import type { InteractiveNodeResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
export const PlanCheckInteractive: InteractiveNodeResponseType = {
type: 'agentPlanCheck',
params: {
confirmed: false
}
};
export const PlanAgentTool: ChatCompletionTool = {
type: 'function',
function: {
name: SubAppIds.plan,
description: '分析和拆解用户问题,制定分步计划。',
description: systemSubInfo[SubAppIds.plan].toolDescription,
parameters: {}
}
};

View File

@@ -2,17 +2,21 @@ import type {
ChatCompletionMessageParam,
ChatCompletionTool
} from '@fastgpt/global/core/ai/type.d';
import { createLLMResponse, type ResponseEvents } from '../../../../../../ai/llm/request';
import { createLLMResponse } from '../../../../../../ai/llm/request';
import { getPlanAgentPrompt } from './prompt';
import { getLLMModel } from '../../../../../../ai/model';
import { formatModelChars2Points } from '../../../../../../../support/wallet/usage/utils';
import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
import { SubAppIds } from '../constants';
import type { InteractiveNodeResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import type {
InteractiveNodeResponseType,
WorkflowInteractiveResponseType
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { parseToolArgs } from '../../../utils';
import { AskAgentTool, type AskAgentToolParamsType } from './ask/constants';
import { PlanAgentAskTool, type AskAgentToolParamsType } from './ask/constants';
import { PlanCheckInteractive } from './constants';
import type { AgentPlanType } from './type';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
type PlanAgentConfig = {
model: string;
@@ -23,49 +27,67 @@ type PlanAgentConfig = {
};
type DispatchPlanAgentProps = PlanAgentConfig & {
messages: ChatCompletionMessageParam[];
subApps: ChatCompletionTool[];
onReasoning: ResponseEvents['onReasoning'];
onStreaming: ResponseEvents['onStreaming'];
historyMessages: ChatCompletionMessageParam[];
userInput: string;
interactive?: WorkflowInteractiveResponseType;
subAppPrompt: string;
isTopPlanAgent: boolean;
};
type DispatchPlanAgentResponse = {
response: string;
usages: ChatNodeUsageType[];
answerText: string;
planList?: AgentPlanType;
planToolCallMessages: ChatCompletionMessageParam[];
completeMessages: ChatCompletionMessageParam[];
toolMessages?: ChatCompletionMessageParam[];
usages: ChatNodeUsageType[];
interactiveResponse?: InteractiveNodeResponseType;
};
export const dispatchPlanAgent = async ({
messages,
subApps,
historyMessages,
userInput,
interactive,
subAppPrompt,
model,
systemPrompt,
temperature,
top_p,
stream,
onReasoning,
onStreaming
isTopPlanAgent
}: DispatchPlanAgentProps): Promise<DispatchPlanAgentResponse> => {
const modelData = getLLMModel(model);
const requestMessages: ChatCompletionMessageParam[] = [
{
role: 'system',
content: getPlanAgentPrompt(systemPrompt)
content: getPlanAgentPrompt(subAppPrompt, systemPrompt)
},
...messages.filter((item) => item.role !== 'system')
...historyMessages.filter((item) => item.role !== 'system')
];
// TODO: 考虑一下 plan 要不要挂上 master 的工具组
// const filterPlanTools = subApps.filter((item) => item.function.name !== SubAppIds.plan);
// filterPlanTools.push(AskAgentTool);
const tools = [AskAgentTool];
// 分类query/user select/user form
const lastMessages = requestMessages[requestMessages.length - 1];
if (
(interactive?.type === 'agentPlanAskUserSelect' ||
interactive?.type === 'agentPlanAskUserForm') &&
lastMessages.role === 'assistant' &&
lastMessages.tool_calls
) {
requestMessages.push({
role: 'tool',
tool_call_id: lastMessages.tool_calls[0].id,
content: userInput
});
} else {
requestMessages.push({
role: 'user',
content: userInput
});
}
console.log(JSON.stringify({ requestMessages }, null, 2));
const {
reasoningText,
answerText,
toolCalls = [],
usage,
@@ -79,73 +101,50 @@ export const dispatchPlanAgent = async ({
top_p,
stream,
tools,
tools: isTopPlanAgent ? [PlanAgentAskTool] : [],
tool_choice: 'auto',
toolCallMode: modelData.toolChoice ? 'toolChoice' : 'prompt',
parallel_tool_calls: true
},
onReasoning,
onStreaming
parallel_tool_calls: false
}
});
if (!answerText && !reasoningText && !toolCalls.length) {
if (!answerText && !toolCalls.length) {
return Promise.reject(getEmptyResponseTip());
}
// TODO: 需要考虑多个 Interactive 并发的情况
let interactiveResponse: InteractiveNodeResponseType = {
type: 'agentPlanCheck',
params: {}
/*
正常输出情况:
1. text: 正常生成plan
2. toolCall: 调用ask工具
3. text + toolCall: 可能生成 plan + 调用ask工具
*/
// Text: 回答的文本planList: 结构化的plan只能有其中一个有值
const { text, planList } = (() => {
if (!answerText)
return {
text: '',
planList: undefined
};
for await (const call of toolCalls) {
const toolId = call.function.name;
if (toolId === SubAppIds.ask) {
const params = parseToolArgs<AskAgentToolParamsType>(call.function.arguments);
if (params.mode === 'select') {
interactiveResponse = {
type: 'agentPlanAskUserSelect',
params: {
description: params?.prompt ?? '选择选项',
userSelectOptions: params?.options?.map((v, i) => {
return { key: `option${i}`, value: v };
})
}
} as InteractiveNodeResponseType;
}
if (params.mode === 'input') {
interactiveResponse = {
type: 'agentPlanAskQuery',
params: {
content: params?.prompt ?? '输入详细信息'
}
const params = parseToolArgs<AgentPlanType>(answerText);
if (!params || !params.task || !params.steps) {
return {
text: answerText,
planList: undefined
};
}
completeMessages.push({
tool_call_id: call.id,
role: ChatCompletionRequestMessageRoleEnum.Tool,
content: '等待用户输入内容'
});
}
}
const { totalPoints, modelName } = formatModelChars2Points({
model: modelData.model,
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens
});
const toolMessages: ChatCompletionMessageParam[] = [];
if (answerText) {
const toolId = getNanoid(6);
const toolCall: ChatCompletionMessageParam = {
role: ChatCompletionRequestMessageRoleEnum.Assistant,
return {
text: '',
planList: params
};
})();
const callPlanId = getNanoid(6);
const planToolCallMessages: ChatCompletionMessageParam[] = [
{
role: 'assistant',
tool_calls: [
{
id: toolId,
id: callPlanId,
type: 'function',
function: {
name: SubAppIds.plan,
@@ -153,17 +152,56 @@ export const dispatchPlanAgent = async ({
}
}
]
},
{
role: 'tool',
tool_call_id: callPlanId,
content: planList ? JSON.stringify(planList) : text || 'Create plan error'
}
];
// 只有顶层有交互模式
const interactiveResponse: InteractiveNodeResponseType | undefined = (() => {
if (!isTopPlanAgent) return;
const tooCall = toolCalls[0];
if (tooCall) {
const params = parseToolArgs<AskAgentToolParamsType>(tooCall.function.arguments);
if (params?.mode === 'select') {
return {
type: 'agentPlanAskUserSelect',
params: {
description: params.prompt ?? '',
userSelectOptions: params.options.filter(Boolean).map((v, i) => {
return { key: `option${i}`, value: v };
})
}
};
const toolCallResponse: ChatCompletionMessageParam = {
role: ChatCompletionRequestMessageRoleEnum.Tool,
tool_call_id: toolId,
content: answerText
}
if (params?.mode === 'input' && params.prompt) {
return {
type: 'agentPlanAskQuery',
params: {
content: params.prompt ?? ''
}
};
toolMessages.push(toolCall, toolCallResponse);
}
}
// Plan 没有主动交互,则强制触发 check
return PlanCheckInteractive;
})();
const { totalPoints, modelName } = formatModelChars2Points({
model: modelData.model,
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens
});
return {
response: answerText,
answerText: text,
planList,
planToolCallMessages,
usages: [
{
moduleName: modelName,
@@ -174,7 +212,6 @@ export const dispatchPlanAgent = async ({
}
],
completeMessages,
toolMessages,
interactiveResponse
};
};

View File

@@ -1,24 +1,52 @@
export const getPlanAgentPrompt = (background?: string) => {
import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type';
import { SubAppIds } from '../constants';
export const getSubAppsPrompt = ({
subAppList,
getSubAppInfo
}: {
subAppList: ChatCompletionTool[];
getSubAppInfo: (id: string) => {
name: string;
avatar: string;
toolDescription: string;
};
}) => {
return subAppList
.map((item) => {
const info = getSubAppInfo(item.function.name);
if (!info) return '';
return `@${info.name}(${info.toolDescription})`;
})
.filter(Boolean)
.join('; ');
};
/*
subAppsPrompt
@名字(功能); @名字(功能)
*/
export const getPlanAgentPrompt = (subAppsPrompt: string, systemPrompt?: string) => {
return `<role>
你是一个专业的项目规划助手,擅长将复杂任务分解为结构化的执行计划。
</role>
${
background
? `<user_role>
${background}
</user_role>`
systemPrompt
? `<user_required>
${systemPrompt}
</user_required>`
: ''
}
<process>
1. 解析用户输入,提取核心目标、关键要素、约束与本地化偏好。
2. 评估任务复杂度, 据此确定阶段数量。
3. 禁止调用除"ask_agent"以外的任何工具.
4. 语言风格本地化(根据用户输入语言进行术语与语序调整)。
5. 严格按照 JSON Schema 生成完整计划,不得输出多余内容
6. 仅在缺少关键信息时使用"ask_agent"工具询问用户(如:未指定目的地、预算、时间等必要细节)
7. 如果信息充足或用户已回答询问必须直接输出JSON格式的完整计划不再调用工具
- 解析用户输入,提取核心目标、关键要素、约束与本地化偏好。
- 在缺少完成任务的关键信息时,使用 [${SubAppIds.ask}] 工具来询问用户(如:未指定目的地、预算、时间等必要细节)
- 你还可以使用这些工具来设计本轮执行计划: """${subAppsPrompt}"""。注意,你只有这些工具可以进行调用。
${systemPrompt ? '- 制定本轮计划时,严格参考 <user_required></user_required> 中的内容进行设计,设计的计划不偏离<user_required></user_required>。' : ''}
- 输出语言风格本地化(根据用户输入语言进行术语与语序调整)
- 严格按照 JSON Schema 生成完整计划,不得输出多余内容。
</process>
<requirements>
@@ -34,21 +62,21 @@ ${background}
},
"steps": {
"type": "array",
"description": "阶段步骤列表",
"description": "完成任务的步骤列表",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "唯一标识"
"description": "步骤的唯一标识"
},
"title": {
"type": "string",
"description": "阶段标题"
"description": "步骤标题"
},
"description": {
"type": "string",
"description": "阶段描述, 并在末尾@对应任务将要移交使用的工具/子智能体"
"description": "步骤的具体描述, 可以使用@符号声明需要用到的工具。"
},
},
"required": ["id", "title", "description"]
@@ -66,23 +94,21 @@ ${background}
- 保持中立、客观;必要时指出风险与依赖。
</guardrails>
<output>
<format>
<example>
{
"task": "[主题] 深度调研计划",
"steps": [
{
"id": "[id]",
"title": "[阶段名称]",
"description": "[阶段描述] @sub_agent"
"id": "step1",
"title": "[步骤名称]",
"description": "[步骤描述] @网络搜索"
},
{
"id": "[id]",
"title": "[阶段名称]",
"description": "[阶段描述] @sub_agent"
"id": "step2",
"title": "[步骤名称]",
"description": "[步骤描述] @webhook机器人"
}
]
}
</output>
`;
</example>`;
};

View File

@@ -0,0 +1,9 @@
export type AgentPlanStepType = {
id: string;
title: string;
description: string;
};
export type AgentPlanType = {
task: string;
steps: AgentPlanStepType[];
};

View File

@@ -23,7 +23,6 @@ import {
} from '@fastgpt/global/core/ai/type';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { ModelTypeEnum } from '../../../../../global/core/ai/model';
import {
getExtractJsonPrompt,
getExtractJsonToolPrompt

View File

@@ -1,4 +1,8 @@
import { replaceVariable, sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
import {
replaceVariable,
sliceJsonStr,
sliceStrStartEnd
} from '@fastgpt/global/common/string/tools';
import type {
AIChatItemValueItemType,
UserChatItemValueItemType
@@ -174,10 +178,10 @@ export const getToolNodesByIds = ({
});
};
export const parseToolArgs = <T = Record<string, any>>(toolArgs: string): T => {
export const parseToolArgs = <T = Record<string, any>>(toolArgs: string) => {
try {
return json5.parse(toolArgs) as T;
return json5.parse(sliceJsonStr(toolArgs)) as T;
} catch {
return {} as T;
return;
}
};