diff --git a/packages/global/core/chat/type.d.ts b/packages/global/core/chat/type.d.ts index 73b5747fc..9d69c968c 100644 --- a/packages/global/core/chat/type.d.ts +++ b/packages/global/core/chat/type.d.ts @@ -15,6 +15,7 @@ import type { FlowNodeInputItemType } from '../workflow/type/io'; import type { FlowNodeTemplateType } from '../workflow/type/node.d'; import { ChatCompletionMessageParam } from '../ai/type'; import type { RequireOnlyOne } from '../../common/type/utils'; +import type { AgentPlanType } from '../../../service/core/workflow/dispatch/ai/agent/sub/plan/type'; /* --------- chat ---------- */ export type ChatSchemaType = { @@ -95,17 +96,10 @@ export type AIChatItemValueItemType = { }; tool: ToolModuleResponseItemType; interactive: WorkflowInteractiveResponseType; - - text?: { - content: string; - }; - reasoning?: { - content: string; - }; - interactive?: WorkflowInteractiveResponseType; + agentPlan: AgentPlanType; // Abandon - tools?: ToolModuleResponseItemType[]; + tools: ToolModuleResponseItemType[]; }>; export type AIChatItemType = { obj: ChatRoleEnum.AI; diff --git a/packages/global/core/workflow/runtime/constants.ts b/packages/global/core/workflow/runtime/constants.ts index 8c9ddbe91..da5965a15 100644 --- a/packages/global/core/workflow/runtime/constants.ts +++ b/packages/global/core/workflow/runtime/constants.ts @@ -15,7 +15,8 @@ export enum SseResponseEventEnum { flowResponses = 'flowResponses', // sse response request updateVariables = 'updateVariables', - interactive = 'interactive' // user select + interactive = 'interactive', // user select + agentPlan = 'agentPlan' // agent plan } export enum DispatchNodeResponseKeyEnum { diff --git a/packages/global/core/workflow/template/system/interactive/type.ts b/packages/global/core/workflow/template/system/interactive/type.ts index c36da39a6..4a69a6846 100644 --- a/packages/global/core/workflow/template/system/interactive/type.ts +++ b/packages/global/core/workflow/template/system/interactive/type.ts @@ -20,13 +20,13 @@ type InteractiveNodeType = { nodeOutputs?: NodeOutputItemType[]; }; -type ChildrenInteractive = InteractiveNodeType & { +export type ChildrenInteractive = InteractiveNodeType & { type: 'childrenInteractive'; params: { childrenResponse: WorkflowInteractiveResponseType; }; }; -type ToolCallChildrenInteractive = InteractiveNodeType & { +export type ToolCallChildrenInteractive = InteractiveNodeType & { type: 'toolChildrenInteractive'; params: { childrenResponse: WorkflowInteractiveResponseType; @@ -38,7 +38,7 @@ type ToolCallChildrenInteractive = InteractiveNodeType & { }; // Loop bode -type LoopInteractive = InteractiveNodeType & { +export type LoopInteractive = InteractiveNodeType & { type: 'loopInteractive'; params: { loopResult: any[]; diff --git a/packages/service/core/ai/llm/agentCall.ts b/packages/service/core/ai/llm/agentCall.ts deleted file mode 100644 index 1ca5357c2..000000000 --- a/packages/service/core/ai/llm/agentCall.ts +++ /dev/null @@ -1,213 +0,0 @@ -import type { - ChatCompletionMessageParam, - ChatCompletionTool, - ChatCompletionMessageToolCall -} from '@fastgpt/global/core/ai/type'; -import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; -import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; -import type { AIChatItemType, AIChatItemValueItemType } from '@fastgpt/global/core/chat/type'; -import type { - InteractiveNodeResponseType, - WorkflowInteractiveResponseType -} from '@fastgpt/global/core/workflow/template/system/interactive/type'; -import type { CreateLLMResponseProps, ResponseEvents } from './request'; -import { createLLMResponse } from './request'; -import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; -import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; -import { countGptMessagesTokens, countPromptTokens } from '../../../common/string/tiktoken/index'; -import { addLog } from '../../../common/system/log'; -import type { AgentPlanStepType } from '../../workflow/dispatch/ai/agent/sub/plan/type'; -import { calculateCompressionThresholds } from './compress/constants'; -import { compressRequestMessages, compressToolcallResponse } from './compress'; - -type RunAgentCallProps = { - maxRunAgentTimes: number; - interactiveEntryToolParams?: WorkflowInteractiveResponseType['toolParams']; - currentStep: AgentPlanStepType; - - body: { - messages: ChatCompletionMessageParam[]; - model: LLMModelItemType; - temperature?: number; - top_p?: number; - stream?: boolean; - subApps: ChatCompletionTool[]; - }; - - userKey?: CreateLLMResponseProps['userKey']; - isAborted?: CreateLLMResponseProps['isAborted']; - - getToolInfo: (id: string) => { - name: string; - avatar: string; - }; - handleToolResponse: (e: { - call: ChatCompletionMessageToolCall; - messages: ChatCompletionMessageParam[]; - }) => Promise<{ - response: string; - usages: ChatNodeUsageType[]; - interactive?: InteractiveNodeResponseType; - }>; -} & ResponseEvents; - -type RunAgentResponse = { - completeMessages: ChatCompletionMessageParam[]; - assistantResponses: AIChatItemValueItemType[]; - interactiveResponse?: InteractiveNodeResponseType; - - // Usage - inputTokens: number; - outputTokens: number; - subAppUsages: ChatNodeUsageType[]; -}; - -export const runAgentCall = async ({ - maxRunAgentTimes, - interactiveEntryToolParams, - currentStep, - body: { model, messages, stream, temperature, top_p, subApps }, - userKey, - isAborted, - - handleToolResponse, - getToolInfo, - - onReasoning, - onStreaming, - onToolCall, - onToolParam -}: RunAgentCallProps): Promise => { - let runTimes = 0; - - const assistantResponses: AIChatItemValueItemType[] = []; - let interactiveResponse: InteractiveNodeResponseType | undefined; - - let requestMessages = messages; - - let inputTokens: number = 0; - let outputTokens: number = 0; - const subAppUsages: ChatNodeUsageType[] = []; - - // TODO: interactive rewrite messages - - while (runTimes < maxRunAgentTimes) { - // TODO: 费用检测 - runTimes++; - - // 对请求的 requestMessages 进行压缩 - const taskDescription = currentStep.description || currentStep.title; - if (taskDescription) { - const result = await compressRequestMessages(requestMessages, model, taskDescription); - requestMessages = result.messages; - inputTokens += result.usage?.inputTokens || 0; - outputTokens += result.usage?.outputTokens || 0; - } - - // Request LLM - let { - reasoningText: reasoningContent, - answerText: answer, - toolCalls = [], - usage, - getEmptyResponseTip, - completeMessages - } = await createLLMResponse({ - body: { - model, - messages: requestMessages, - tool_choice: 'auto', - toolCallMode: model.toolChoice ? 'toolChoice' : 'prompt', - tools: subApps, - parallel_tool_calls: true, - stream, - temperature, - top_p - }, - userKey, - isAborted, - onReasoning, - onStreaming, - onToolCall, - onToolParam - }); - - if (!answer && !reasoningContent && !toolCalls.length) { - return Promise.reject(getEmptyResponseTip()); - } - - const requestMessagesLength = requestMessages.length; - requestMessages = completeMessages.slice(); - - for await (const tool of toolCalls) { - // TODO: 加入交互节点处理 - - // Call tool and compress tool response - const { response, usages, interactive } = await handleToolResponse({ - call: tool, - messages: requestMessages.slice(0, requestMessagesLength) - }).then(async (res) => { - const thresholds = calculateCompressionThresholds(model.maxContext); - const toolTokenCount = await countPromptTokens(res.response); - - const response = await (async () => { - if (toolTokenCount > thresholds.singleTool.threshold && currentStep) { - const taskDescription = currentStep.description || currentStep.title; - return await compressToolcallResponse( - res.response, - model, - tool.function.name, - taskDescription, - thresholds.singleTool.target - ); - } else { - return res.response; - } - })(); - - return { - ...res, - response - }; - }); - - requestMessages.push({ - tool_call_id: tool.id, - role: ChatCompletionRequestMessageRoleEnum.Tool, - content: response - }); - - subAppUsages.push(...usages); - - if (interactive) { - interactiveResponse = interactive; - } - } - - // TODO: 移动到工作流里 assistantResponses concat - const currentAssistantResponses = GPTMessages2Chats({ - messages: requestMessages.slice(requestMessagesLength), - getToolInfo - })[0] as AIChatItemType; - if (currentAssistantResponses) { - assistantResponses.push(...currentAssistantResponses.value); - } - - // Usage concat - inputTokens += usage.inputTokens; - outputTokens += usage.outputTokens; - - if (toolCalls.length === 0) { - break; - } - } - - return { - inputTokens, - outputTokens, - completeMessages: requestMessages, - assistantResponses, - subAppUsages, - interactiveResponse - }; -}; diff --git a/packages/service/core/ai/llm/request.ts b/packages/service/core/ai/llm/request.ts index b721d935a..eb9317c64 100644 --- a/packages/service/core/ai/llm/request.ts +++ b/packages/service/core/ai/llm/request.ts @@ -35,7 +35,7 @@ export type ResponseEvents = { onStreaming?: (e: { text: string }) => void; onReasoning?: (e: { text: string }) => void; onToolCall?: (e: { call: ChatCompletionMessageToolCall }) => void; - onToolParam?: (e: { call: ChatCompletionMessageToolCall; params: string }) => void; + onToolParam?: (e: { tool: ChatCompletionMessageToolCall; params: string }) => void; }; export type CreateLLMResponseProps = { @@ -260,7 +260,7 @@ export const createStreamResponse = async ({ if (currentTool && arg) { currentTool.function.arguments += arg; - onToolParam?.({ call: currentTool, params: arg }); + onToolParam?.({ tool: currentTool, params: arg }); } } }); diff --git a/packages/service/core/workflow/dispatch/ai/agent/constants.ts b/packages/service/core/workflow/dispatch/ai/agent/constants.ts new file mode 100644 index 000000000..91ef5b6b2 --- /dev/null +++ b/packages/service/core/workflow/dispatch/ai/agent/constants.ts @@ -0,0 +1,196 @@ +import type { AgentPlanStepType } from './sub/plan/type'; +import { getLLMModel } from '../../../../ai/model'; +import { countPromptTokens } from '../../../../../common/string/tiktoken/index'; +import { createLLMResponse } from '../../../../ai/llm/request'; +import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; +import { addLog } from '../../../../../common/system/log'; +import { calculateCompressionThresholds } from '../../../../ai/llm/compress/constants'; + +export const getMasterAgentSystemPrompt = async ({ + steps, + step, + userInput, + background = '', + model +}: { + steps: AgentPlanStepType[]; + step: AgentPlanStepType; + userInput: string; + background?: string; + model: string; +}) => { + /** + * 压缩步骤提示词(Depends on) + * 当 stepPrompt 的 token 长度超过模型最大长度的 15% 时,调用 LLM 压缩到 12% + */ + const compressStepPrompt = async ( + stepPrompt: string, + model: string, + currentDescription: string + ): Promise => { + if (!stepPrompt) return stepPrompt; + + const modelData = getLLMModel(model); + if (!modelData) return stepPrompt; + + const tokenCount = await countPromptTokens(stepPrompt); + const thresholds = calculateCompressionThresholds(modelData.maxContext); + const maxTokenThreshold = thresholds.dependsOn.threshold; + + if (tokenCount <= maxTokenThreshold) { + return stepPrompt; + } + + const targetTokens = thresholds.dependsOn.target; + + const compressionSystemPrompt = ` +你是工作流步骤历史压缩专家,擅长从多个已执行步骤的结果中提取关键信息。 +你的任务是对工作流的执行历史进行智能压缩,在保留关键信息的同时,大幅降低 token 消耗。 + + + + 输入内容是按照"步骤ID → 步骤标题 → 执行结果"格式组织的多个步骤记录。 + 你需要根据当前任务目标,对这些历史记录进行分级压缩。 + + + + **第一阶段:快速扫描与相关性评估** + + 在开始压缩前,请先在内心完成以下思考(不需要输出): + 1. 浏览所有步骤,识别每个步骤与当前任务目标的相关性 + 2. 为每个步骤标注相关性等级: + - [高]:直接支撑当前任务,包含关键数据或结论 + - [中]:间接相关,提供背景信息或辅助判断 + - [低]:弱相关或无关,可大幅精简或省略 + 3. 确定压缩策略:基于相关性等级,决定每个步骤的保留程度 + + **第二阶段:执行分级压缩** + + 根据第一阶段的评估,按以下策略压缩: + + 1. **高相关步骤**(保留度 80-100%) + - 完整保留:步骤ID、标题、核心执行结果 + - 保留所有:具体数据、关键结论、链接引用、重要发现 + - 仅精简:去除啰嗦的过程描述和冗余表达 + + 2. **中等相关步骤**(保留度 40-60%) + - 保留:步骤ID、标题、核心要点 + - 提炼:将执行结果浓缩为 2-3 句话 + - 去除:详细过程、重复信息、次要细节 + + 3. **低相关步骤**(保留度 10-20%) + - 保留:步骤ID、标题 + - 极简化:一句话总结(或直接省略执行结果) + - 判断:如果完全无关,可整体省略该步骤 + + + + - 删繁就简:移除重复、冗长的描述性内容 + - 去粗取精:针对当前任务目标,保留最相关的要素 + - 保数据留结论:优先保留具体数据、关键结论、链接引用 + - 保持时序:按原始顺序输出,不要打乱逻辑 + - 可追溯性:保留必要的步骤标识,确保能理解信息来源 + - 识别共性:如果连续多个步骤结果相似,可合并描述 + + + + 压缩完成后,请自我检查: + 1. 是否达到了目标压缩比例? + 2. 当前任务所需的关键信息是否都保留了? + 3. 压缩后的内容是否仍能让后续步骤理解发生了什么? + 4. 步骤的时序关系是否清晰? + `; + + const userPrompt = `请对以下工作流步骤的执行历史进行压缩,保留与当前任务最相关的信息。 + +**当前任务目标**:${currentDescription} + +**需要压缩的步骤历史**: +${stepPrompt} + +**压缩要求**: +- 原始长度:${tokenCount} tokens +- 目标长度:约 ${targetTokens} tokens(压缩到原长度的 ${Math.round((targetTokens / tokenCount) * 100)}%) + +**输出格式要求**: +1. 保留步骤结构:每个步骤使用"# 步骤ID: [id]\\n\\t - 步骤标题: [title]\\n\\t - 执行结果: [精简后的结果]"的格式 +2. 根据相关性分级处理: + - 与当前任务高度相关的步骤:保留完整的关键信息(数据、结论、链接等) + - 中等相关的步骤:提炼要点,移除冗余描述 + - 低相关的步骤:仅保留一句话总结或省略执行结果 +3. 保持步骤顺序:按原始顺序输出,不要打乱 +4. 提取共性:如果连续多个步骤结果相似,可以适当合并描述 + +**质量标准**: +- 压缩后的内容能让后续步骤理解前置步骤做了什么、得到了什么结果 +- 保留所有对当前任务有价值的具体数据和关键结论 +- 移除重复、啰嗦的描述性文字 + +请直接输出压缩后的步骤历史:`; + + try { + const { answerText } = await createLLMResponse({ + body: { + model: modelData, + messages: [ + { + role: ChatCompletionRequestMessageRoleEnum.System, + content: compressionSystemPrompt + }, + { + role: ChatCompletionRequestMessageRoleEnum.User, + content: userPrompt + } + ], + temperature: 0.1, + stream: false + } + }); + + return answerText || stepPrompt; + } catch (error) { + console.error('压缩 stepPrompt 失败:', error); + // 压缩失败时返回原始内容 + return stepPrompt; + } + }; + + let stepPrompt = steps + .filter((item) => step.depends_on && step.depends_on.includes(item.id)) + .map( + (item) => + `# 步骤ID: ${item.id}\n\t - 步骤标题: ${item.title}\n\t - 执行结果: ${item.response}` + ) + .filter(Boolean) + .join('\n'); + addLog.debug(`Step call depends_on (LLM): ${step.id}`, step.depends_on); + // 压缩依赖的上下文 + stepPrompt = await compressStepPrompt(stepPrompt, model, step.description || step.title); + + return `请根据任务背景、之前步骤的执行结果和当前步骤要求选择并调用相应的工具。如果是一个总结性质的步骤,请整合之前步骤的结果进行总结。 + 【任务背景】 + 目标: ${userInput} + 前置信息: ${background} + + 【当前步骤】 + 步骤ID: ${step.id} + 步骤标题: ${step.title} + + ${ + stepPrompt + ? `【之前步骤的执行结果】 + ${stepPrompt}` + : '' + } + + 【执行指导】 + 1. 仔细阅读前面步骤的执行结果,理解已经获得的信息 + 2. 根据当前步骤描述和前面的结果,分析需要使用的工具 + 3. 从可用工具列表中选择最合适的工具 + 4. 基于前面步骤的结果为工具生成合理的参数 + 5. 如果需要多个工具,可以同时调用 + 6. 确保当前步骤的执行能够有效利用和整合前面的结果 + 7. 如果是总结的步骤,请利用之前步骤的信息进行全面总结 + + 请严格按照步骤描述执行,确保完成所有要求的子任务。`; +}; diff --git a/packages/service/core/workflow/dispatch/ai/agent/index.ts b/packages/service/core/workflow/dispatch/ai/agent/index.ts new file mode 100644 index 000000000..657be6a34 --- /dev/null +++ b/packages/service/core/workflow/dispatch/ai/agent/index.ts @@ -0,0 +1,487 @@ +import type { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { + ConfirmPlanAgentText, + DispatchNodeResponseKeyEnum, + SseResponseEventEnum +} from '@fastgpt/global/core/workflow/runtime/constants'; +import type { + DispatchNodeResultType, + ModuleDispatchProps, + RuntimeNodeItemType +} from '@fastgpt/global/core/workflow/runtime/type'; +import { getLLMModel } from '../../../../ai/model'; +import { getNodeErrResponse, getHistories } from '../../utils'; +import type { AIChatItemValueItemType, ChatItemType } from '@fastgpt/global/core/chat/type'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { + chats2GPTMessages, + chatValue2RuntimePrompt, + GPTMessages2Chats +} from '@fastgpt/global/core/chat/adapt'; +import { formatModelChars2Points } from '../../../../../support/wallet/usage/utils'; +import { filterMemoryMessages } from '../utils'; +import { systemSubInfo } from './sub/constants'; +import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; +import { dispatchPlanAgent, dispatchReplanAgent } from './sub/plan'; +import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node'; +import { getSubApps, rewriteSubAppsToolset } from './sub'; + +import { getFileInputPrompt } from './sub/file/utils'; +import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type'; +import type { AgentPlanType } from './sub/plan/type'; +import type { localeType } from '@fastgpt/global/common/i18n/type'; +import { stepCall } from './master/call'; +import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; +import { addLog } from '../../../../../common/system/log'; +import { checkTaskComplexity } from './master/taskComplexity'; + +export type DispatchAgentModuleProps = ModuleDispatchProps<{ + [NodeInputKeyEnum.history]?: ChatItemType[]; + [NodeInputKeyEnum.userChatInput]: string; + + [NodeInputKeyEnum.fileUrlList]?: string[]; + [NodeInputKeyEnum.aiModel]: string; + [NodeInputKeyEnum.aiSystemPrompt]: string; + [NodeInputKeyEnum.aiChatTemperature]?: number; + [NodeInputKeyEnum.aiChatTopP]?: number; + + [NodeInputKeyEnum.subApps]?: FlowNodeTemplateType[]; + [NodeInputKeyEnum.isAskAgent]?: boolean; + [NodeInputKeyEnum.isPlanAgent]?: boolean; +}>; + +type Response = DispatchNodeResultType<{ + [NodeOutputKeyEnum.answerText]: string; +}>; + +export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise => { + let { + node: { nodeId, name, isEntry, version, inputs }, + lang, + runtimeNodes, + histories, + query, + requestOrigin, + chatConfig, + lastInteractive, + runningUserInfo, + runningAppInfo, + externalProvider, + stream, + workflowDispatchDeep, + workflowStreamResponse, + usagePush, + params: { + model, + systemPrompt, + userChatInput, + history = 6, + fileUrlList: fileLinks, + temperature, + aiChatTopP, + subApps = [], + isPlanAgent = true, + isAskAgent = true + } + } = props; + const agentModel = getLLMModel(model); + const chatHistories = getHistories(history, histories); + const historiesMessages = chats2GPTMessages({ + messages: chatHistories, + reserveId: false, + reserveTool: false + }); + + const planMessagesKey = `planMessages-${nodeId}`; + const replanMessagesKey = `replanMessages-${nodeId}`; + const agentPlanKey = `agentPlan-${nodeId}`; + + // 交互模式进来的话,这个值才是交互输入的值 + const interactiveInput = lastInteractive ? chatValue2RuntimePrompt(query).text : ''; + + // Get history messages + let { planHistoryMessages, replanMessages, agentPlan } = (() => { + const lastHistory = chatHistories[chatHistories.length - 1]; + if (lastHistory && lastHistory.obj === ChatRoleEnum.AI) { + return { + planHistoryMessages: (lastHistory.memories?.[planMessagesKey] || + []) as ChatCompletionMessageParam[], + replanMessages: (lastHistory.memories?.[replanMessagesKey] || + []) as ChatCompletionMessageParam[], + agentPlan: (lastHistory.memories?.[agentPlanKey] || []) as AgentPlanType + }; + } + return { + planHistoryMessages: undefined, + replanMessages: undefined, + agentPlan: undefined + }; + })(); + + try { + // Get files + const fileUrlInput = inputs.find((item) => item.key === NodeInputKeyEnum.fileUrlList); + if (!fileUrlInput || !fileUrlInput.value || fileUrlInput.value.length === 0) { + fileLinks = undefined; + } + const { filesMap, prompt: fileInputPrompt } = getFileInputPrompt({ + fileUrls: fileLinks, + requestOrigin, + maxFiles: chatConfig?.fileSelectConfig?.maxFiles || 20, + histories: chatHistories + }); + + // Get sub apps + const { subAppList, subAppsMap, getSubAppInfo } = await useSubApps({ + subApps, + lang, + filesMap + }); + + /* ===== AI Start ===== */ + + /* ===== Check task complexity ===== */ + const taskIsComplexity = await (async () => { + // Check task complexity: 第一次进入任务时候进行判断。(有 plan了,说明已经开始执行任务了) + const isCheckTaskComplexityStep = isPlanAgent && !agentPlan && !planHistoryMessages; + // if (isCheckTaskComplexityStep) { + // const res = await checkTaskComplexity({ + // model, + // userChatInput + // }); + // if (res.usage) { + // usagePush([res.usage]); + // } + // return res.complex; + // } + + // 对轮运行时候,代表都是进入复杂流程 + return true; + })(); + + if (taskIsComplexity) { + /* ===== Plan Agent ===== */ + const planCallFn = async () => { + // 点了确认。此时肯定有 agentPlans + if ( + lastInteractive?.type === 'agentPlanCheck' && + interactiveInput === ConfirmPlanAgentText && + agentPlan + ) { + planHistoryMessages = undefined; + } else { + const { answerText, plan, completeMessages, usages, interactiveResponse } = + await dispatchPlanAgent({ + historyMessages: planHistoryMessages || historiesMessages, + userInput: lastInteractive ? interactiveInput : userChatInput, + interactive: lastInteractive, + subAppList, + getSubAppInfo, + systemPrompt, + model, + temperature, + top_p: aiChatTopP, + stream, + isTopPlanAgent: workflowDispatchDeep === 1 + }); + + const assistantResponses: AIChatItemValueItemType[] = [ + ...(answerText + ? [ + { + text: { + content: answerText + } + } + ] + : []), + ...(plan + ? [ + { + agentPlan: plan + } + ] + : []) + ]; + + // SSE response + if (answerText) { + workflowStreamResponse?.({ + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + text: answerText + }) + }); + } + if (plan) { + workflowStreamResponse?.({ + event: SseResponseEventEnum.agentPlan, + data: { agentPlan: plan } + }); + } + + agentPlan = plan; + + usagePush(usages); + // Sub agent plan 不会有交互响应。Top agent plan 肯定会有。 + if (interactiveResponse) { + return { + [DispatchNodeResponseKeyEnum.assistantResponses]: assistantResponses, + [DispatchNodeResponseKeyEnum.memories]: { + [planMessagesKey]: filterMemoryMessages(completeMessages), + [agentPlanKey]: agentPlan + }, + [DispatchNodeResponseKeyEnum.interactive]: interactiveResponse + }; + } else { + planHistoryMessages = undefined; + } + } + }; + const replanCallFn = async ({ plan }: { plan: AgentPlanType }) => { + if (!agentPlan) return; + + addLog.debug(`Replan step`); + + const { + answerText, + plan: rePlan, + completeMessages, + usages, + interactiveResponse + } = await dispatchReplanAgent({ + historyMessages: replanMessages || historiesMessages, + userInput: lastInteractive ? interactiveInput : userChatInput, + plan, + interactive: lastInteractive, + subAppList, + getSubAppInfo, + systemPrompt, + model, + temperature, + top_p: aiChatTopP, + stream, + isTopPlanAgent: workflowDispatchDeep === 1 + }); + + if (rePlan) { + agentPlan.steps.push(...rePlan.steps); + agentPlan.replan = rePlan.replan; + } + + const assistantResponses: AIChatItemValueItemType[] = [ + ...(answerText + ? [ + { + text: { + content: answerText + } + } + ] + : []), + ...(rePlan + ? [ + { + agentPlan: plan + } + ] + : []) + ]; + + // SSE response + if (answerText) { + workflowStreamResponse?.({ + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + text: answerText + }) + }); + } + if (rePlan) { + workflowStreamResponse?.({ + event: SseResponseEventEnum.agentPlan, + data: { agentPlan: plan } + }); + } + + usagePush(usages); + // Sub agent plan 不会有交互响应。Top agent plan 肯定会有。 + if (interactiveResponse) { + return { + [DispatchNodeResponseKeyEnum.assistantResponses]: assistantResponses, + [DispatchNodeResponseKeyEnum.memories]: { + [replanMessagesKey]: filterMemoryMessages(completeMessages), + [agentPlanKey]: agentPlan + }, + [DispatchNodeResponseKeyEnum.interactive]: interactiveResponse + }; + } else { + replanMessages = undefined; + } + }; + + // Plan step: 需要生成 plan,且还没有完整的 plan + const isPlanStep = isPlanAgent && (!agentPlan || planHistoryMessages); + // Replan step: 已有 plan,且有 replan 历史消息 + const isReplanStep = isPlanAgent && agentPlan && replanMessages; + + // 执行 Plan/replan + if (isPlanStep) { + const result = await planCallFn(); + // 有 result 代表 plan 有交互响应(check/ask) + if (result) return result; + } else if (isReplanStep) { + const result = await replanCallFn({ + plan: agentPlan! + }); + if (result) return result; + } + + addLog.debug(`Start master agent`, { + agentPlan: JSON.stringify(agentPlan, null, 2) + }); + + /* ===== Master agent, 逐步执行 plan ===== */ + if (!agentPlan) return Promise.reject('没有 plan'); + + let assistantResponses: AIChatItemValueItemType[] = []; + + while (agentPlan.steps!.filter((item) => !item.response)!.length) { + for await (const step of agentPlan?.steps) { + if (step.response) continue; + addLog.debug(`Step call: ${step.id}`, step); + + // Temp code + workflowStreamResponse?.({ + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + text: `\n # ${step.id}: ${step.title}\n` + }) + }); + const tmpAssistantResponses: AIChatItemValueItemType = { + text: { + content: `\n # ${step.id}: ${step.title}\n` + } + }; + assistantResponses.push(tmpAssistantResponses); + + // Step call + const result = await stepCall({ + ...props, + getSubAppInfo, + steps: agentPlan.steps, // 传入所有步骤,而不仅仅是未执行的步骤 + subAppList, + step, + filesMap, + subAppsMap + }); + + // Merge response + const assistantResponse = GPTMessages2Chats({ + messages: result.assistantMessages, + reserveTool: true, + getToolInfo: getSubAppInfo + }) + .map((item) => item.value as AIChatItemValueItemType[]) + .flat(); + step.response = result.rawResponse; + step.summary = result.summary; + assistantResponses.push(...assistantResponse); + } + + // Call replan + if (agentPlan?.replan === true) { + // 内部会修改 agentPlan.steps 的内容,从而使循环重复触发 + const replanResult = await replanCallFn({ + plan: agentPlan + }); + // Replan 里有需要用户交互的内容,直接 return + if (replanResult) return replanResult; + } + } + + return { + // 目前 Master 不会触发交互 + // [DispatchNodeResponseKeyEnum.interactive]: interactiveResponse, + // TODO: 需要对 memoryMessages 单独建表存储 + [DispatchNodeResponseKeyEnum.memories]: { + [agentPlanKey]: agentPlan, + [planMessagesKey]: undefined, + [replanMessagesKey]: undefined + }, + [DispatchNodeResponseKeyEnum.assistantResponses]: assistantResponses, + [DispatchNodeResponseKeyEnum.nodeResponse]: { + // 展示的积分消耗 + // totalPoints: totalPointsUsage, + // toolCallInputTokens: inputTokens, + // toolCallOutputTokens: outputTokens, + // childTotalPoints: toolTotalPoints, + // model: modelName, + query: userChatInput, + // toolDetail: dispatchFlowResponse, + mergeSignId: nodeId + } + }; + } + + // 简单 tool call 模式(一轮对话就结束了,不会多轮,所以不会受到连续对话的 taskIsComplexity 影响) + return Promise.reject('目前未支持简单模式'); + } catch (error) { + return getNodeErrResponse({ error }); + } +}; + +export const useSubApps = async ({ + subApps, + lang, + filesMap +}: { + subApps: FlowNodeTemplateType[]; + lang?: localeType; + filesMap: Record; +}) => { + // Get sub apps + const runtimeSubApps = await rewriteSubAppsToolset({ + subApps: subApps.map((node) => { + return { + nodeId: node.id, + name: node.name, + avatar: node.avatar, + intro: node.intro, + toolDescription: node.toolDescription, + flowNodeType: node.flowNodeType, + showStatus: node.showStatus, + isEntry: false, + inputs: node.inputs, + outputs: node.outputs, + pluginId: node.pluginId, + version: node.version, + toolConfig: node.toolConfig, + catchError: node.catchError + }; + }), + lang + }); + + const subAppList = getSubApps({ + subApps: runtimeSubApps, + addReadFileTool: Object.keys(filesMap).length > 0 + }); + + const subAppsMap = new Map(runtimeSubApps.map((item) => [item.nodeId, item])); + const getSubAppInfo = (id: string) => { + const toolNode = subAppsMap.get(id) || systemSubInfo[id]; + return { + name: toolNode?.name || '', + avatar: toolNode?.avatar || '', + toolDescription: toolNode?.toolDescription || toolNode?.name || '' + }; + }; + + return { + subAppList, + subAppsMap, + getSubAppInfo + }; +}; diff --git a/packages/service/core/workflow/dispatch/ai/tool/master/call.ts b/packages/service/core/workflow/dispatch/ai/agent/master/call.ts similarity index 89% rename from packages/service/core/workflow/dispatch/ai/tool/master/call.ts rename to packages/service/core/workflow/dispatch/ai/agent/master/call.ts index 9b50e24d6..4711f6143 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/master/call.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/master/call.ts @@ -16,7 +16,7 @@ import { } from '@fastgpt/global/core/workflow/runtime/utils'; import { getWorkflowChildResponseWrite } from '../../../utils'; import { SubAppIds } from '../sub/constants'; -import { parseToolArgs } from '../../utils'; +import { parseToolArgs } from '../../../../../ai/utils'; import { dispatchFileRead } from '../sub/file'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; @@ -25,8 +25,6 @@ import { dispatchApp, dispatchPlugin } from '../sub/app'; import { getErrText } from '@fastgpt/global/common/error/utils'; import type { DispatchAgentModuleProps } from '..'; import { getLLMModel } from '../../../../../ai/model'; -import { createLLMResponse } from '../../../../../ai/llm/request'; -import { addLog } from '../../../../../../common/system/log'; import { getStepDependon } from './dependon'; import { getResponseSummary } from './responseSummary'; @@ -47,6 +45,7 @@ export const stepCall = async ({ subAppsMap: Map; }) => { const { + res, node: { nodeId }, runtimeNodes, chatConfig, @@ -55,7 +54,6 @@ export const stepCall = async ({ variables, externalProvider, stream, - res, workflowStreamResponse, usagePush, params: { userChatInput, systemPrompt, model, temperature, aiChatTopP } @@ -74,15 +72,17 @@ export const stepCall = async ({ step.depends_on = depends; } - // addLog.debug(`Step information`, steps); + // Step call system prompt + // TODO: 需要把压缩的 usage 返回 const systemPromptContent = await getMasterAgentSystemPrompt({ steps, step, userInput: userChatInput, - model - // background: systemPrompt + model, + background: systemPrompt }); + // Step call request messages const requestMessages = chats2GPTMessages({ messages: [ { @@ -109,24 +109,22 @@ export const stepCall = async ({ // 'Step call requestMessages', // JSON.stringify({ requestMessages, subAppList }, null, 2) // ); - // TODO: 阶段性推送账单 - const { assistantResponses, inputTokens, outputTokens, subAppUsages, interactiveResponse } = + + const { assistantMessages, inputTokens, outputTokens, subAppUsages, interactiveResponse } = await runAgentCall({ maxRunAgentTimes: 100, - currentStep: step, - // interactiveEntryToolParams: lastInteractive?.toolParams, body: { messages: requestMessages, model: getLLMModel(model), temperature, stream, top_p: aiChatTopP, - subApps: subAppList + tools: subAppList }, userKey: externalProvider.openaiAccount, isAborted: res ? () => res.closed : undefined, - getToolInfo: getSubAppInfo, + // childrenInteractiveParams onReasoning({ text }) { workflowStreamResponse?.({ @@ -160,9 +158,9 @@ export const stepCall = async ({ } }); }, - onToolParam({ call, params }) { + onToolParam({ tool, params }) { workflowStreamResponse?.({ - id: call.id, + id: tool.id, event: SseResponseEventEnum.toolParams, data: { tool: { @@ -172,6 +170,7 @@ export const stepCall = async ({ }); }, + // TODO: 对齐最新的方案 handleToolResponse: async ({ call, messages }) => { const toolId = call.function.name; const childWorkflowStreamResponse = getWorkflowChildResponseWrite({ @@ -338,12 +337,34 @@ export const stepCall = async ({ return { response, + assistantMessages: [], // TODO usages }; + }, + handleInteractiveTool: async ({ toolParams }) => { + return { + response: 'Interactive tool not supported', + assistantMessages: [], // TODO + usages: [] + }; } }); - const answerText = assistantResponses.map((item) => item.text?.content).join('\n'); + const answerText = assistantMessages + .map((item) => { + if (item.role === 'assistant' && item.content) { + if (typeof item.content === 'string') { + return item.content; + } else { + return item.content + .map((content) => (content.type === 'text' ? content.text : '')) + .join('\n'); + } + } + return ''; + }) + .join('\n'); + // Get step response summary const { answerText: summary, usage: summaryUsage } = await getResponseSummary({ response: answerText, model @@ -355,6 +376,6 @@ export const stepCall = async ({ return { rawResponse: answerText, summary, - assistantResponses + assistantMessages }; }; diff --git a/packages/service/core/workflow/dispatch/ai/tool/master/dependon.ts b/packages/service/core/workflow/dispatch/ai/agent/master/dependon.ts similarity index 98% rename from packages/service/core/workflow/dispatch/ai/tool/master/dependon.ts rename to packages/service/core/workflow/dispatch/ai/agent/master/dependon.ts index 30cf8f3e5..8b17fe6e4 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/master/dependon.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/master/dependon.ts @@ -2,7 +2,7 @@ import { getLLMModel } from '../../../../../ai/model'; import type { AgentPlanStepType } from '../sub/plan/type'; import { addLog } from '../../../../../../common/system/log'; import { createLLMResponse } from '../../../../../ai/llm/request'; -import { parseToolArgs } from '../../utils'; +import { parseToolArgs } from '../../../../../ai/utils'; import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; import { formatModelChars2Points } from '../../../../../../support/wallet/usage/utils'; diff --git a/packages/service/core/workflow/dispatch/ai/tool/master/responseSummary.ts b/packages/service/core/workflow/dispatch/ai/agent/master/responseSummary.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/master/responseSummary.ts rename to packages/service/core/workflow/dispatch/ai/agent/master/responseSummary.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/master/taskComplexity.ts b/packages/service/core/workflow/dispatch/ai/agent/master/taskComplexity.ts similarity index 98% rename from packages/service/core/workflow/dispatch/ai/tool/master/taskComplexity.ts rename to packages/service/core/workflow/dispatch/ai/agent/master/taskComplexity.ts index 8c62068ee..cc03b41b0 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/master/taskComplexity.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/master/taskComplexity.ts @@ -1,5 +1,5 @@ import { createLLMResponse } from '../../../../../ai/llm/request'; -import { parseToolArgs } from '../../utils'; +import { parseToolArgs } from '../../../../../ai/utils'; import { addLog } from '../../../../../../common/system/log'; import { formatModelChars2Points } from '../../../../../../support/wallet/usage/utils'; import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/app/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/app/index.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/constants.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/constants.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/constants.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/constants.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/dataset/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/dataset/index.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/dataset/index.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/dataset/index.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/file/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/file/index.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/file/index.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/file/index.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/file/utils.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/file/utils.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/file/utils.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/file/utils.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/index.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/index.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/index.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/model/constants.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/model/constants.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/model/constants.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/model/constants.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/model/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/model/index.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/model/index.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/model/index.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/plan/ask/constants.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/ask/constants.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/plan/ask/constants.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/plan/ask/constants.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/plan/constants.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/constants.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/plan/constants.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/plan/constants.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/plan/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/index.ts similarity index 98% rename from packages/service/core/workflow/dispatch/ai/tool/sub/plan/index.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/plan/index.ts index e71fae964..5c7fe5e50 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/sub/plan/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/index.ts @@ -16,7 +16,7 @@ import type { InteractiveNodeResponseType, WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; -import { parseToolArgs } from '../../../utils'; +import { parseToolArgs } from '../../../../../../ai/utils'; import { PlanAgentAskTool, type AskAgentToolParamsType } from './ask/constants'; import { PlanCheckInteractive } from './constants'; import type { AgentPlanType } from './type'; @@ -93,7 +93,7 @@ export const dispatchPlanAgent = async ({ tool_call_id: lastMessages.tool_calls[0].id, content: userInput }); - // TODO: 是否合理 + // TODO: 是否合理,以及模型兼容性问题 requestMessages.push({ role: 'assistant', content: '请基于以上收集的用户信息,重新生成完整的计划,严格按照 JSON Schema 输出。' diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/plan/prompt.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/prompt.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/plan/prompt.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/plan/prompt.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/plan/type.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/type.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/plan/type.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/plan/type.ts diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/tool/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/tool/index.ts similarity index 100% rename from packages/service/core/workflow/dispatch/ai/tool/sub/tool/index.ts rename to packages/service/core/workflow/dispatch/ai/agent/sub/tool/index.ts diff --git a/packages/service/core/workflow/dispatch/ai/agent/type.ts b/packages/service/core/workflow/dispatch/ai/agent/type.ts new file mode 100644 index 000000000..be7218172 --- /dev/null +++ b/packages/service/core/workflow/dispatch/ai/agent/type.ts @@ -0,0 +1,19 @@ +import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; +import type { JSONSchemaInputType } from '@fastgpt/global/core/app/jsonschema'; +import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; + +export type ToolNodeItemType = RuntimeNodeItemType & { + toolParams: RuntimeNodeItemType['inputs']; + jsonSchema?: JSONSchemaInputType; +}; + +export type DispatchSubAppResponse = { + response: string; + usages?: ChatNodeUsageType[]; +}; + +export type GetSubAppInfoFnType = (id: string) => { + name: string; + avatar: string; + toolDescription: string; +}; diff --git a/packages/service/core/workflow/dispatch/ai/agent/utils.ts b/packages/service/core/workflow/dispatch/ai/agent/utils.ts new file mode 100644 index 000000000..38458d645 --- /dev/null +++ b/packages/service/core/workflow/dispatch/ai/agent/utils.ts @@ -0,0 +1,31 @@ +/* + 匹配 {{@toolId@}},转化成: @name 的格式。 +*/ +export const parseSystemPrompt = ({ + systemPrompt, + getSubAppInfo +}: { + systemPrompt?: string; + getSubAppInfo: (id: string) => { + name: string; + avatar: string; + toolDescription: string; + }; +}): string => { + if (!systemPrompt) return ''; + + // Match pattern {{@toolId@}} and convert to @name format + const pattern = /\{\{@([^@]+)@\}\}/g; + + 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; +}; diff --git a/packages/service/core/workflow/dispatch/ai/tool/constants.ts b/packages/service/core/workflow/dispatch/ai/tool/constants.ts index 84ed862dc..c4a178a42 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/constants.ts +++ b/packages/service/core/workflow/dispatch/ai/tool/constants.ts @@ -1,197 +1,14 @@ -import type { AgentPlanStepType } from './sub/plan/type'; -import type { AgentPlanType } from './sub/plan/type'; -import { getLLMModel } from '../../../../ai/model'; -import { countPromptTokens } from '../../../../../common/string/tiktoken/index'; -import { createLLMResponse } from '../../../../ai/llm/request'; -import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; -import { addLog } from '../../../../../common/system/log'; -import { calculateCompressionThresholds } from '../../../../ai/llm/compress/constants'; +import { replaceVariable } from '@fastgpt/global/common/string/tools'; -export const getMasterAgentSystemPrompt = async ({ - steps, - step, - userInput, - background = '', - model -}: { - steps: AgentPlanStepType[]; - step: AgentPlanStepType; - userInput: string; - background?: string; - model: string; +export const getMultiplePrompt = (obj: { + fileCount: number; + imgCount: number; + question: string; }) => { - /** - * 压缩步骤提示词(Depends on) - * 当 stepPrompt 的 token 长度超过模型最大长度的 15% 时,调用 LLM 压缩到 12% - */ - const compressStepPrompt = async ( - stepPrompt: string, - model: string, - currentDescription: string - ): Promise => { - if (!stepPrompt) return stepPrompt; - - const modelData = getLLMModel(model); - if (!modelData) return stepPrompt; - - const tokenCount = await countPromptTokens(stepPrompt); - const thresholds = calculateCompressionThresholds(modelData.maxContext); - const maxTokenThreshold = thresholds.dependsOn.threshold; - - if (tokenCount <= maxTokenThreshold) { - return stepPrompt; - } - - const targetTokens = thresholds.dependsOn.target; - - const compressionSystemPrompt = ` -你是工作流步骤历史压缩专家,擅长从多个已执行步骤的结果中提取关键信息。 -你的任务是对工作流的执行历史进行智能压缩,在保留关键信息的同时,大幅降低 token 消耗。 - - - - 输入内容是按照"步骤ID → 步骤标题 → 执行结果"格式组织的多个步骤记录。 - 你需要根据当前任务目标,对这些历史记录进行分级压缩。 - - - - **第一阶段:快速扫描与相关性评估** - - 在开始压缩前,请先在内心完成以下思考(不需要输出): - 1. 浏览所有步骤,识别每个步骤与当前任务目标的相关性 - 2. 为每个步骤标注相关性等级: - - [高]:直接支撑当前任务,包含关键数据或结论 - - [中]:间接相关,提供背景信息或辅助判断 - - [低]:弱相关或无关,可大幅精简或省略 - 3. 确定压缩策略:基于相关性等级,决定每个步骤的保留程度 - - **第二阶段:执行分级压缩** - - 根据第一阶段的评估,按以下策略压缩: - - 1. **高相关步骤**(保留度 80-100%) - - 完整保留:步骤ID、标题、核心执行结果 - - 保留所有:具体数据、关键结论、链接引用、重要发现 - - 仅精简:去除啰嗦的过程描述和冗余表达 - - 2. **中等相关步骤**(保留度 40-60%) - - 保留:步骤ID、标题、核心要点 - - 提炼:将执行结果浓缩为 2-3 句话 - - 去除:详细过程、重复信息、次要细节 - - 3. **低相关步骤**(保留度 10-20%) - - 保留:步骤ID、标题 - - 极简化:一句话总结(或直接省略执行结果) - - 判断:如果完全无关,可整体省略该步骤 - - - - - 删繁就简:移除重复、冗长的描述性内容 - - 去粗取精:针对当前任务目标,保留最相关的要素 - - 保数据留结论:优先保留具体数据、关键结论、链接引用 - - 保持时序:按原始顺序输出,不要打乱逻辑 - - 可追溯性:保留必要的步骤标识,确保能理解信息来源 - - 识别共性:如果连续多个步骤结果相似,可合并描述 - - - - 压缩完成后,请自我检查: - 1. 是否达到了目标压缩比例? - 2. 当前任务所需的关键信息是否都保留了? - 3. 压缩后的内容是否仍能让后续步骤理解发生了什么? - 4. 步骤的时序关系是否清晰? - `; - - const userPrompt = `请对以下工作流步骤的执行历史进行压缩,保留与当前任务最相关的信息。 - -**当前任务目标**:${currentDescription} - -**需要压缩的步骤历史**: -${stepPrompt} - -**压缩要求**: -- 原始长度:${tokenCount} tokens -- 目标长度:约 ${targetTokens} tokens(压缩到原长度的 ${Math.round((targetTokens / tokenCount) * 100)}%) - -**输出格式要求**: -1. 保留步骤结构:每个步骤使用"# 步骤ID: [id]\\n\\t - 步骤标题: [title]\\n\\t - 执行结果: [精简后的结果]"的格式 -2. 根据相关性分级处理: - - 与当前任务高度相关的步骤:保留完整的关键信息(数据、结论、链接等) - - 中等相关的步骤:提炼要点,移除冗余描述 - - 低相关的步骤:仅保留一句话总结或省略执行结果 -3. 保持步骤顺序:按原始顺序输出,不要打乱 -4. 提取共性:如果连续多个步骤结果相似,可以适当合并描述 - -**质量标准**: -- 压缩后的内容能让后续步骤理解前置步骤做了什么、得到了什么结果 -- 保留所有对当前任务有价值的具体数据和关键结论 -- 移除重复、啰嗦的描述性文字 - -请直接输出压缩后的步骤历史:`; - - try { - const { answerText } = await createLLMResponse({ - body: { - model: modelData, - messages: [ - { - role: ChatCompletionRequestMessageRoleEnum.System, - content: compressionSystemPrompt - }, - { - role: ChatCompletionRequestMessageRoleEnum.User, - content: userPrompt - } - ], - temperature: 0.1, - stream: false - } - }); - - return answerText || stepPrompt; - } catch (error) { - console.error('压缩 stepPrompt 失败:', error); - // 压缩失败时返回原始内容 - return stepPrompt; - } - }; - - let stepPrompt = steps - .filter((item) => step.depends_on && step.depends_on.includes(item.id)) - .map( - (item) => - `# 步骤ID: ${item.id}\n\t - 步骤标题: ${item.title}\n\t - 执行结果: ${item.response}` - ) - .filter(Boolean) - .join('\n'); - addLog.debug(`Step call depends_on (LLM): ${step.id}`, step.depends_on); - // 压缩依赖的上下文 - stepPrompt = await compressStepPrompt(stepPrompt, model, step.description || step.title); - - return `请根据任务背景、之前步骤的执行结果和当前步骤要求选择并调用相应的工具。如果是一个总结性质的步骤,请整合之前步骤的结果进行总结。 - 【任务背景】 - 目标: ${userInput} - 前置信息: ${background} - - 【当前步骤】 - 步骤ID: ${step.id} - 步骤标题: ${step.title} - - ${ - stepPrompt - ? `【之前步骤的执行结果】 - ${stepPrompt}` - : '' - } - - 【执行指导】 - 1. 仔细阅读前面步骤的执行结果,理解已经获得的信息 - 2. 根据当前步骤描述和前面的结果,分析需要使用的工具 - 3. 从可用工具列表中选择最合适的工具 - 4. 基于前面步骤的结果为工具生成合理的参数 - 5. 如果需要多个工具,可以同时调用 - 6. 确保当前步骤的执行能够有效利用和整合前面的结果 - 7. 如果是总结的步骤,请利用之前步骤的信息进行全面总结 - - 请严格按照步骤描述执行,确保完成所有要求的子任务。`; + const prompt = `Number of session file inputs: +Document:{{fileCount}} +Image:{{imgCount}} +------ +{{question}}`; + return replaceVariable(prompt, obj); }; diff --git a/packages/service/core/workflow/dispatch/ai/tool/index.ts b/packages/service/core/workflow/dispatch/ai/tool/index.ts index d862b1b75..8cbd71be2 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/index.ts +++ b/packages/service/core/workflow/dispatch/ai/tool/index.ts @@ -1,20 +1,23 @@ -import type { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import { - ConfirmPlanAgentText, - DispatchNodeResponseKeyEnum, - SseResponseEventEnum -} from '@fastgpt/global/core/workflow/runtime/constants'; +import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import type { + ChatDispatchProps, DispatchNodeResultType, - ModuleDispatchProps, RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; import { getLLMModel } from '../../../../ai/model'; -import { getNodeErrResponse, getHistories } from '../../utils'; -import type { AIChatItemValueItemType, ChatItemType } from '@fastgpt/global/core/chat/type'; +import { filterToolNodeIdByEdges, getNodeErrResponse, getHistories } from '../../utils'; +import { runToolCall } from './toolCall'; +import { type DispatchToolModuleProps, type ToolNodeItemType } from './type'; +import { type ChatItemType, type UserChatItemValueItemType } from '@fastgpt/global/core/chat/type'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; -import { chats2GPTMessages, chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt'; +import { + GPTMessages2Chats, + chatValue2RuntimePrompt, + chats2GPTMessages, + getSystemPrompt_ChatItemType, + runtimePrompt2ChatsValue +} from '@fastgpt/global/core/chat/adapt'; import { formatModelChars2Points } from '../../../../../support/wallet/usage/utils'; import { getHistoryPreview } from '@fastgpt/global/core/chat/utils'; import { replaceVariable } from '@fastgpt/global/common/string/tools'; @@ -34,23 +37,19 @@ type Response = DispatchNodeResultType<{ [NodeOutputKeyEnum.answerText]: string; }>; -export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise => { +export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise => { let { node: { nodeId, name, isEntry, version, inputs }, - lang, runtimeNodes, + runtimeEdges, histories, query, requestOrigin, chatConfig, lastInteractive, runningUserInfo, - runningAppInfo, externalProvider, - stream, - workflowDispatchDeep, - workflowStreamResponse, - usagePush, + usageId, params: { model, systemPrompt, @@ -62,111 +61,49 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise isResponseAnswerText = true } } = props; - const agentModel = getLLMModel(model); - const chatHistories = getHistories(history, histories); - const historiesMessages = chats2GPTMessages({ - messages: chatHistories, - reserveId: false, - reserveTool: false - }); - - const planMessagesKey = `planMessages-${nodeId}`; - const replanMessagesKey = `replanMessages-${nodeId}`; - const agentPlanKey = `agentPlan-${nodeId}`; - - // 交互模式进来的话,这个值才是交互输入的值 - const interactiveInput = lastInteractive ? chatValue2RuntimePrompt(query).text : ''; - - // Get history messages - let { planHistoryMessages, replanMessages, agentPlan } = (() => { - const lastHistory = chatHistories[chatHistories.length - 1]; - if (lastHistory && lastHistory.obj === ChatRoleEnum.AI) { - return { - planHistoryMessages: (lastHistory.memories?.[planMessagesKey] || - []) as ChatCompletionMessageParam[], - replanMessages: (lastHistory.memories?.[replanMessagesKey] || - []) as ChatCompletionMessageParam[], - agentPlan: (lastHistory.memories?.[agentPlanKey] || []) as AgentPlanType - }; - } - return { - planHistoryMessages: undefined, - replanMessages: undefined, - agentPlan: undefined - }; - })(); - - // Check task complexity: 第一次进入任务时候进行判断。(有 plan了,说明已经开始执行任务了) - const isCheckTaskComplexityStep = isPlanAgent && !agentPlan && !planHistoryMessages; try { - // Get files + const toolModel = getLLMModel(model); + const useVision = aiChatVision && toolModel.vision; + const chatHistories = getHistories(history, histories); + + props.params.aiChatVision = aiChatVision && toolModel.vision; + props.params.aiChatReasoning = aiChatReasoning && toolModel.reasoning; const fileUrlInput = inputs.find((item) => item.key === NodeInputKeyEnum.fileUrlList); if (!fileUrlInput || !fileUrlInput.value || fileUrlInput.value.length === 0) { fileLinks = undefined; } -<<<<<<< HEAD - const { filesMap, prompt: fileInputPrompt } = getFileInputPrompt({ - fileUrls: fileLinks, - requestOrigin, - maxFiles: chatConfig?.fileSelectConfig?.maxFiles || 20, - histories: chatHistories - }); - - // Get sub apps - const { subAppList, subAppsMap, getSubAppInfo } = await useSubApps({ - subApps, - lang, - filesMap - }); - - /* ===== AI Start ===== */ - - /* ===== Check task complexity ===== */ - const taskIsComplexity = await (async () => { - // if (isCheckTaskComplexityStep) { - // const res = await checkTaskComplexity({ - // model, - // userChatInput - // }); - // if (res.usage) { - // usagePush([res.usage]); - // } - // return res.complex; - // } - - // 对轮运行时候,代表都是进入复杂流程 - return true; - })(); - - if (taskIsComplexity) { - /* ===== Plan Agent ===== */ - const planCallFn = async () => { - // 点了确认。此时肯定有 agentPlans - if ( - lastInteractive?.type === 'agentPlanCheck' && - interactiveInput === ConfirmPlanAgentText && - agentPlan - ) { - planHistoryMessages = undefined; - } else { - // 临时代码 - const tmpText = '正在进行规划生成...\n'; - workflowStreamResponse?.({ - event: SseResponseEventEnum.answer, - data: textAdaptGptResponse({ - text: tmpText - }) - }); - -<<<<<<<< HEAD:packages/service/core/workflow/dispatch/ai/tool/index.ts - const { - toolWorkflowInteractiveResponse, - toolDispatchFlowResponses, // tool flow response -======= const toolNodeIds = filterToolNodeIdByEdges({ nodeId, edges: runtimeEdges }); - const toolNodes = getToolNodesByIds({ toolNodeIds, runtimeNodes }); + + // Gets the module to which the tool is connected + const toolNodes = toolNodeIds + .map((nodeId) => { + const tool = runtimeNodes.find((item) => item.nodeId === nodeId); + return tool; + }) + .filter(Boolean) + .map((tool) => { + const toolParams: FlowNodeInputItemType[] = []; + // Raw json schema(MCP tool) + let jsonSchema: JSONSchemaInputType | undefined = undefined; + tool?.inputs.forEach((input) => { + if (input.toolDescription) { + toolParams.push(input); + } + + if (input.key === NodeInputKeyEnum.toolData || input.key === 'toolData') { + const value = input.value as McpToolDataType; + jsonSchema = value.inputSchema; + } + }); + + return { + ...(tool as RuntimeNodeItemType), + toolParams, + jsonSchema + }; + }); // Check interactive entry props.node.isEntry = false; @@ -183,6 +120,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise customPdfParse: chatConfig?.fileSelectConfig?.customPdfParse, fileLinks, inputFiles: globalFiles, +<<<<<<< HEAD <<<<<<< HEAD hasReadFilesTool, usageId, @@ -192,6 +130,10 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise ======= hasReadFilesTool >>>>>>> a48ad2abe (squash: compress all commits into one) +======= + hasReadFilesTool, + usageId +>>>>>>> daaea654e (feat: plan response in ui) }); const concatenateSystemPrompt = [ @@ -250,16 +192,11 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise const { toolWorkflowInteractiveResponse, - dispatchFlowResponse, // tool flow response ->>>>>>> 757253617 (squash: compress all commits into one) + toolDispatchFlowResponses, // tool flow response toolCallInputTokens, toolCallOutputTokens, completeMessages = [], // The actual message sent to AI(just save text) assistantResponses = [], // FastGPT system store assistant.value response -<<<<<<< HEAD -======= - runTimes, ->>>>>>> 757253617 (squash: compress all commits into one) finish_reason } = await (async () => { const adaptMessages = chats2GPTMessages({ @@ -267,162 +204,20 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise reserveId: false // reserveTool: !!toolModel.toolChoice }); -<<<<<<< HEAD return runToolCall({ ...props, -======= - const requestParams = { ->>>>>>> 757253617 (squash: compress all commits into one) runtimeNodes, runtimeEdges, toolNodes, toolModel, messages: adaptMessages, -<<<<<<< HEAD childrenInteractiveParams: lastInteractive?.type === 'toolChildrenInteractive' ? lastInteractive.params : undefined -======== - const { answerText, plan, completeMessages, usages, interactiveResponse } = - await dispatchPlanAgent({ - historyMessages: planHistoryMessages || historiesMessages, - userInput: lastInteractive ? interactiveInput : userChatInput, - interactive: lastInteractive, - subAppList, - getSubAppInfo, - systemPrompt, - model, - temperature, - top_p: aiChatTopP, - stream, - isTopPlanAgent: workflowDispatchDeep === 1 - }); - - const text = `${answerText}${plan ? `\n\`\`\`json\n${JSON.stringify(plan, null, 2)}\n\`\`\`` : ''}`; - workflowStreamResponse?.({ - event: SseResponseEventEnum.answer, - data: textAdaptGptResponse({ - text - }) - }); - - agentPlan = plan; - - usagePush(usages); - // Sub agent plan 不会有交互响应。Top agent plan 肯定会有。 - if (interactiveResponse) { - return { - [DispatchNodeResponseKeyEnum.answerText]: `${tmpText}${text}`, - [DispatchNodeResponseKeyEnum.memories]: { - [planMessagesKey]: filterMemoryMessages(completeMessages), - [agentPlanKey]: agentPlan - }, - [DispatchNodeResponseKeyEnum.interactive]: interactiveResponse - }; - } else { - planHistoryMessages = undefined; - } - } - }; - const replanCallFn = async ({ plan }: { plan: AgentPlanType }) => { - if (!agentPlan) return; - - addLog.debug(`Replan step`); - // 临时代码 - const tmpText = '\n # 正在重新进行规划生成...\n'; - workflowStreamResponse?.({ - event: SseResponseEventEnum.answer, - data: textAdaptGptResponse({ - text: tmpText - }) - }); - - const { - answerText, - plan: rePlan, - completeMessages, - usages, - interactiveResponse - } = await dispatchReplanAgent({ - historyMessages: replanMessages || historiesMessages, - userInput: lastInteractive ? interactiveInput : userChatInput, - plan, - interactive: lastInteractive, - subAppList, - getSubAppInfo, - systemPrompt, - model, - temperature, - top_p: aiChatTopP, - stream, - isTopPlanAgent: workflowDispatchDeep === 1 - }); - - if (rePlan) { - agentPlan.steps.push(...rePlan.steps); - agentPlan.replan = rePlan.replan; - } - - const text = `${answerText}${agentPlan ? `\n\`\`\`json\n${JSON.stringify(agentPlan, null, 2)}\n\`\`\`\n` : ''}`; - workflowStreamResponse?.({ - event: SseResponseEventEnum.answer, - data: textAdaptGptResponse({ - text - }) - }); - - usagePush(usages); - // Sub agent plan 不会有交互响应。Top agent plan 肯定会有。 - if (interactiveResponse) { - return { - [DispatchNodeResponseKeyEnum.answerText]: `${tmpText}${text}`, - [DispatchNodeResponseKeyEnum.memories]: { - [replanMessagesKey]: filterMemoryMessages(completeMessages), - [agentPlanKey]: agentPlan - }, - [DispatchNodeResponseKeyEnum.interactive]: interactiveResponse - }; - } else { - replanMessages = undefined; - } - }; - - // Plan step: 需要生成 plan,且还没有完整的 plan - const isPlanStep = isPlanAgent && (!agentPlan || planHistoryMessages); - // Replan step: 已有 plan,且有 replan 历史消息 - const isReplanStep = isPlanAgent && agentPlan && replanMessages; - - // 执行 Plan/replan - if (isPlanStep) { - const result = await planCallFn(); - // 有 result 代表 plan 有交互响应(check/ask) - if (result) return result; - } else if (isReplanStep) { - const result = await replanCallFn({ - plan: agentPlan! - }); - if (result) return result; - } - - addLog.debug(`Start master agent`, { - agentPlan: JSON.stringify(agentPlan, null, 2) ->>>>>>>> 757253617 (squash: compress all commits into one):packages/service/core/workflow/dispatch/ai/agent/index.ts - }); - -<<<<<<<< HEAD:packages/service/core/workflow/dispatch/ai/tool/index.ts - // Usage computed -======= - interactiveEntryToolParams: lastInteractive?.toolParams - }; - - return runToolCall({ - ...props, - ...requestParams, - maxRunToolTimes: 100 }); })(); ->>>>>>> 757253617 (squash: compress all commits into one) + // Usage computed const { totalPoints: modelTotalPoints, modelName } = formatModelChars2Points({ model, inputTokens: toolCallInputTokens, @@ -430,29 +225,13 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise }); const modelUsage = externalProvider.openaiAccount?.key ? 0 : modelTotalPoints; -<<<<<<< HEAD const toolUsages = toolDispatchFlowResponses.map((item) => item.flowUsages).flat(); const toolTotalPoints = toolUsages.reduce((sum, item) => sum + item.totalPoints, 0); -======== - /* ===== Master agent, 逐步执行 plan ===== */ - if (!agentPlan) return Promise.reject('没有 plan'); - - let assistantResponses: AIChatItemValueItemType[] = []; ->>>>>>>> 757253617 (squash: compress all commits into one):packages/service/core/workflow/dispatch/ai/agent/index.ts - - while (agentPlan.steps!.filter((item) => !item.response)!.length) { - const pendingSteps = agentPlan?.steps!.filter((item) => !item.response)!; - -<<<<<<<< HEAD:packages/service/core/workflow/dispatch/ai/tool/index.ts - // Preview assistant responses -======= - const toolUsages = dispatchFlowResponse.map((item) => item.flowUsages).flat(); - const toolTotalPoints = toolUsages.reduce((sum, item) => sum + item.totalPoints, 0); // concat tool usage const totalPointsUsage = modelUsage + toolTotalPoints; ->>>>>>> 757253617 (squash: compress all commits into one) + // Preview assistant responses const previewAssistantResponses = filterToolResponseToPreview(assistantResponses); return { @@ -462,11 +241,11 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise .map((item) => item.text?.content || '') .join('') }, -<<<<<<< HEAD [DispatchNodeResponseKeyEnum.runTimes]: toolDispatchFlowResponses.reduce( (sum, item) => sum + item.runTimes, 0 ), +<<<<<<< HEAD <<<<<<< HEAD [DispatchNodeResponseKeyEnum.assistantResponses]: isResponseAnswerText ? previewAssistantResponses @@ -475,6 +254,8 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise ======= [DispatchNodeResponseKeyEnum.runTimes]: runTimes, >>>>>>> 757253617 (squash: compress all commits into one) +======= +>>>>>>> daaea654e (feat: plan response in ui) [DispatchNodeResponseKeyEnum.assistantResponses]: previewAssistantResponses, >>>>>>> a48ad2abe (squash: compress all commits into one) [DispatchNodeResponseKeyEnum.nodeResponse]: { @@ -490,11 +271,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise 10000, useVision ), -<<<<<<< HEAD toolDetail: toolDispatchFlowResponses.map((item) => item.flowResponses).flat(), -======= - toolDetail: dispatchFlowResponse.map((item) => item.flowResponses).flat(), ->>>>>>> 757253617 (squash: compress all commits into one) mergeSignId: nodeId, finishReason: finish_reason }, @@ -506,134 +283,17 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise totalPoints: modelUsage, inputTokens: toolCallInputTokens, outputTokens: toolCallOutputTokens -<<<<<<< HEAD -======== - for await (const step of pendingSteps) { - addLog.debug(`Step call: ${step.id}`, step); - - workflowStreamResponse?.({ - event: SseResponseEventEnum.answer, - data: textAdaptGptResponse({ - text: `\n # ${step.id}: ${step.title}\n` - }) - }); - - const result = await stepCall({ - ...props, - getSubAppInfo, - steps: agentPlan.steps, // 传入所有步骤,而不仅仅是未执行的步骤 - subAppList, - step, - filesMap, - subAppsMap - }); - - step.response = result.rawResponse; - step.summary = result.summary; - assistantResponses.push(...result.assistantResponses); - } - - if (agentPlan?.replan === true) { - const replanResult = await replanCallFn({ - plan: agentPlan - }); - if (replanResult) return replanResult; - } - } - - return { - // 目前 Master 不会触发交互 - // [DispatchNodeResponseKeyEnum.interactive]: interactiveResponse, - // TODO: 需要对 memoryMessages 单独建表存储 - [DispatchNodeResponseKeyEnum.memories]: { - [agentPlanKey]: agentPlan, - [planMessagesKey]: undefined, - [replanMessagesKey]: undefined ->>>>>>>> 757253617 (squash: compress all commits into one):packages/service/core/workflow/dispatch/ai/agent/index.ts - }, - [DispatchNodeResponseKeyEnum.assistantResponses]: assistantResponses, - [DispatchNodeResponseKeyEnum.nodeResponse]: { - // 展示的积分消耗 - // totalPoints: totalPointsUsage, - // toolCallInputTokens: inputTokens, - // toolCallOutputTokens: outputTokens, - // childTotalPoints: toolTotalPoints, - // model: modelName, - query: userChatInput, - // toolDetail: dispatchFlowResponse, - mergeSignId: nodeId - } - }; - } - - // 简单 tool call 模式(一轮对话就结束了,不会多轮,所以不会受到连续对话的 taskIsComplexity 影响) - return Promise.reject('目前未支持简单模式'); -======= }, // 工具的消耗 ...toolUsages ], [DispatchNodeResponseKeyEnum.interactive]: toolWorkflowInteractiveResponse }; ->>>>>>> 757253617 (squash: compress all commits into one) } catch (error) { return getNodeErrResponse({ error }); } }; -<<<<<<< HEAD -export const useSubApps = async ({ - subApps, - lang, - filesMap -}: { - subApps: FlowNodeTemplateType[]; - lang?: localeType; - filesMap: Record; -}) => { - // Get sub apps - const runtimeSubApps = await rewriteSubAppsToolset({ - subApps: subApps.map((node) => { - return { - nodeId: node.id, - name: node.name, - avatar: node.avatar, - intro: node.intro, - toolDescription: node.toolDescription, - flowNodeType: node.flowNodeType, - showStatus: node.showStatus, - isEntry: false, - inputs: node.inputs, - outputs: node.outputs, - pluginId: node.pluginId, - version: node.version, - toolConfig: node.toolConfig, - catchError: node.catchError - }; - }), - lang - }); - - const subAppList = getSubApps({ - subApps: runtimeSubApps, - addReadFileTool: Object.keys(filesMap).length > 0 - }); - - const subAppsMap = new Map(runtimeSubApps.map((item) => [item.nodeId, item])); - const getSubAppInfo = (id: string) => { - const toolNode = subAppsMap.get(id) || systemSubInfo[id]; - return { - name: toolNode?.name || '', - avatar: toolNode?.avatar || '', - toolDescription: toolNode?.toolDescription || toolNode?.name || '' - }; - }; - - return { - subAppList, - subAppsMap, - getSubAppInfo -======= const getMultiInput = async ({ runningUserInfo, histories, @@ -642,6 +302,7 @@ const getMultiInput = async ({ maxFiles, customPdfParse, inputFiles, +<<<<<<< HEAD <<<<<<< HEAD hasReadFilesTool, usageId, @@ -651,6 +312,10 @@ const getMultiInput = async ({ ======= hasReadFilesTool >>>>>>> a48ad2abe (squash: compress all commits into one) +======= + hasReadFilesTool, + usageId +>>>>>>> daaea654e (feat: plan response in ui) }: { runningUserInfo: ChatDispatchProps['runningUserInfo']; histories: ChatItemType[]; @@ -660,6 +325,7 @@ const getMultiInput = async ({ customPdfParse?: boolean; inputFiles: UserChatItemValueItemType['file'][]; hasReadFilesTool: boolean; +<<<<<<< HEAD <<<<<<< HEAD usageId?: string; appId: string; @@ -667,6 +333,9 @@ const getMultiInput = async ({ uId: string; ======= >>>>>>> a48ad2abe (squash: compress all commits into one) +======= + usageId?: string; +>>>>>>> daaea654e (feat: plan response in ui) }) => { // Not file quote if (!fileLinks || hasReadFilesTool) { @@ -693,6 +362,7 @@ const getMultiInput = async ({ requestOrigin, maxFiles, customPdfParse, + usageId, teamId: runningUserInfo.teamId, tmbId: runningUserInfo.tmbId }); @@ -700,6 +370,53 @@ const getMultiInput = async ({ return { documentQuoteText: text, userFiles: fileLinks.map((url) => parseUrlToFileType(url)).filter(Boolean) ->>>>>>> 757253617 (squash: compress all commits into one) }; }; + +/* +Tool call, auth add file prompt to question。 +Guide the LLM to call tool. +*/ +const toolCallMessagesAdapt = ({ + userInput, + skip +}: { + userInput: UserChatItemValueItemType[]; + skip?: boolean; +}): UserChatItemValueItemType[] => { + if (skip) return userInput; + + const files = userInput.filter((item) => item.file); + + if (files.length > 0) { + const filesCount = files.filter((file) => file.file?.type === 'file').length; + const imgCount = files.filter((file) => file.file?.type === 'image').length; + + if (userInput.some((item) => item.text)) { + return userInput.map((item) => { + if (item.text) { + const text = item.text?.content || ''; + + return { + ...item, + text: { + content: getMultiplePrompt({ fileCount: filesCount, imgCount, question: text }) + } + }; + } + return item; + }); + } + + // Every input is a file + return [ + { + text: { + content: getMultiplePrompt({ fileCount: filesCount, imgCount, question: '' }) + } + } + ]; + } + + return userInput; +}; diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/context/index.ts b/packages/service/core/workflow/dispatch/ai/tool/sub/context/index.ts deleted file mode 100644 index 6b704f1ea..000000000 --- a/packages/service/core/workflow/dispatch/ai/tool/sub/context/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { DispatchSubAppResponse } from '../../type'; - -export const dispatchContextAgent = async (props: {}): Promise => { - return { - response: '' - }; -}; diff --git a/packages/service/core/workflow/dispatch/ai/tool/sub/stop/constants.ts b/packages/service/core/workflow/dispatch/ai/tool/sub/stop/constants.ts deleted file mode 100644 index b070be8b6..000000000 --- a/packages/service/core/workflow/dispatch/ai/tool/sub/stop/constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type'; -import { SubAppIds } from '../constants'; - -export const StopAgentTool: ChatCompletionTool = { - type: 'function', - function: { - name: SubAppIds.stop, - description: '如果完成了所有的任务,可调用此工具。' - } -}; diff --git a/packages/service/core/workflow/dispatch/ai/tool/toolCall.ts b/packages/service/core/workflow/dispatch/ai/tool/toolCall.ts index 96617ef6e..b5eb860db 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/toolCall.ts +++ b/packages/service/core/workflow/dispatch/ai/tool/toolCall.ts @@ -1,6 +1,4 @@ -<<<<<<< HEAD import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type'; -import { responseWriteController } from '../../../../../common/response'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { runWorkflow } from '../../index'; @@ -18,88 +16,6 @@ import { runAgentCall } from '../../../../ai/llm/agentCall'; export const runToolCall = async (props: DispatchToolModuleProps): Promise => { const { messages, toolNodes, toolModel, childrenInteractiveParams, ...workflowProps } = props; const { -======= -import { filterGPTMessageByMaxContext } from '../../../../ai/llm/utils'; -import type { - ChatCompletionToolMessageParam, - ChatCompletionMessageParam, - ChatCompletionTool -} from '@fastgpt/global/core/ai/type'; -import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; -import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; -import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; -import { runWorkflow } from '../../index'; -import type { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type'; -import type { DispatchFlowResponse } from '../../type'; -import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; -import type { AIChatItemType } from '@fastgpt/global/core/chat/type'; -import { formatToolResponse, parseToolArgs } from '../utils'; -import { initToolNodes, initToolCallEdges } from './utils'; -import { computedMaxToken } from '../../../../ai/utils'; -import { sliceStrStartEnd } from '@fastgpt/global/common/string/tools'; -import type { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; -import { getErrText } from '@fastgpt/global/common/error/utils'; -import { createLLMResponse } from '../../../../ai/llm/request'; -import { toolValueTypeList, valueTypeJsonSchemaMap } from '@fastgpt/global/core/workflow/constants'; - -type ToolRunResponseType = { - toolRunResponse?: DispatchFlowResponse; - toolMsgParams: ChatCompletionToolMessageParam; -}[]; - -/* - 调用思路: - 先Check 是否是交互节点触发 - - 交互模式: - 1. 从缓存中获取工作流运行数据 - 2. 运行工作流 - 3. 检测是否有停止信号或交互响应 - - 无:汇总结果,递归运行工具 - - 有:缓存结果,结束调用 - - 非交互模式: - 1. 组合 tools - 2. 过滤 messages - 3. Load request llm messages: system prompt, histories, human question, (assistant responses, tool responses, assistant responses....) - 4. 请求 LLM 获取结果 - - - 有工具调用 - 1. 批量运行工具的工作流,获取结果(工作流原生结果,工具执行结果) - 2. 合并递归中,所有工具的原生运行结果 - 3. 组合 assistants tool 响应 - 4. 组合本次 request 和 llm response 的 messages,并计算出消耗的 tokens - 5. 组合本次 request、llm response 和 tool response 结果 - 6. 组合本次的 assistant responses: history assistant + tool assistant + tool child assistant - 7. 判断是否还有停止信号或交互响应 - - 无:递归运行工具 - - 有:缓存结果,结束调用 - - 无工具调用 - 1. 汇总结果,递归运行工具 - 2. 计算 completeMessages 和 tokens 后返回。 - - 交互节点额外缓存结果包括: - 1. 入口的节点 id - 2. toolCallId: 本次工具调用的 ID,可以找到是调用了哪个工具,入口并不会记录工具的 id - 3. messages:本次递归中,assistants responses 和 tool responses -*/ - -export const runToolCall = async ( - props: DispatchToolModuleProps & { - maxRunToolTimes: number; - }, - response?: RunToolResponse -): Promise => { - const { - messages, - toolNodes, - toolModel, - maxRunToolTimes, - interactiveEntryToolParams, - ...workflowProps - } = props; - let { ->>>>>>> 757253617 (squash: compress all commits into one) res, checkIsStopping, requestOrigin, @@ -122,105 +38,7 @@ export const runToolCall = async ( } } = workflowProps; -<<<<<<< HEAD // 构建 tools 参数 -======= - if (maxRunToolTimes <= 0 && response) { - return response; - } - - // Interactive - if (interactiveEntryToolParams) { - initToolNodes(runtimeNodes, interactiveEntryToolParams.entryNodeIds); - initToolCallEdges(runtimeEdges, interactiveEntryToolParams.entryNodeIds); - - // Run entry tool - const toolRunResponse = await runWorkflow({ - ...workflowProps, - usageId: undefined, - isToolCall: true - }); - const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses); - - // Response to frontend - workflowStreamResponse?.({ - event: SseResponseEventEnum.toolResponse, - data: { - tool: { - id: interactiveEntryToolParams.toolCallId, - toolName: '', - toolAvatar: '', - params: '', - response: sliceStrStartEnd(stringToolResponse, 5000, 5000) - } - } - }); - - // Check stop signal - const hasStopSignal = toolRunResponse.flowResponses?.some((item) => item.toolStop); - // Check interactive response(Only 1 interaction is reserved) - const workflowInteractiveResponse = toolRunResponse.workflowInteractiveResponse; - - const requestMessages = [ - ...messages, - ...interactiveEntryToolParams.memoryMessages.map((item) => - item.role === 'tool' && item.tool_call_id === interactiveEntryToolParams.toolCallId - ? { - ...item, - content: stringToolResponse - } - : item - ) - ]; - - if (hasStopSignal || workflowInteractiveResponse) { - // Get interactive tool data - const toolWorkflowInteractiveResponse: WorkflowInteractiveResponseType | undefined = - workflowInteractiveResponse - ? { - ...workflowInteractiveResponse, - toolParams: { - entryNodeIds: workflowInteractiveResponse.entryNodeIds, - toolCallId: interactiveEntryToolParams.toolCallId, - memoryMessages: interactiveEntryToolParams.memoryMessages - } - } - : undefined; - - return { - dispatchFlowResponse: [toolRunResponse], - toolCallInputTokens: 0, - toolCallOutputTokens: 0, - completeMessages: requestMessages, - assistantResponses: toolRunResponse.assistantResponses, - runTimes: toolRunResponse.runTimes, - toolWorkflowInteractiveResponse - }; - } - - return runToolCall( - { - ...props, - interactiveEntryToolParams: undefined, - maxRunToolTimes: maxRunToolTimes - 1, - // Rewrite toolCall messages - messages: requestMessages - }, - { - dispatchFlowResponse: [toolRunResponse], - toolCallInputTokens: 0, - toolCallOutputTokens: 0, - assistantResponses: toolRunResponse.assistantResponses, - runTimes: toolRunResponse.runTimes - } - ); - } - - // ------------------------------------------------------------ - - const assistantResponses = response?.assistantResponses || []; - ->>>>>>> 757253617 (squash: compress all commits into one) const toolNodesMap = new Map(); const tools: ChatCompletionTool[] = toolNodes.map((item) => { toolNodesMap.set(item.nodeId, item); @@ -272,7 +90,6 @@ export const runToolCall = async ( } }; }); -<<<<<<< HEAD const getToolInfo = (name: string) => { const toolNode = toolNodesMap.get(name); return { @@ -281,8 +98,6 @@ export const runToolCall = async ( }; }; - // SSE 响应实例 - const write = res ? responseWriteController({ res, readStream: stream }) : undefined; // 工具响应原始值 const toolRunResponses: DispatchFlowResponse[] = []; @@ -311,74 +126,12 @@ export const runToolCall = async ( requestOrigin, retainDatasetCite, useVision: aiChatVision -======= - - const max_tokens = computedMaxToken({ - model: toolModel, - maxToken, - min: 100 - }); - - // Filter histories by maxToken - const filterMessages = ( - await filterGPTMessageByMaxContext({ - messages, - maxContext: toolModel.maxContext - (max_tokens || 0) // filter token. not response maxToken - }) - ).map((item) => { - if (item.role === 'assistant' && item.tool_calls) { - return { - ...item, - tool_calls: item.tool_calls.map((tool) => ({ - id: tool.id, - type: tool.type, - function: tool.function - })) - }; - } - return item; - }); - - let { - reasoningText: reasoningContent, - answerText: answer, - toolCalls = [], - finish_reason, - usage, - getEmptyResponseTip, - assistantMessage, - completeMessages - } = await createLLMResponse({ - body: { - model: toolModel.model, - stream, - messages: filterMessages, - tool_choice: 'auto', - toolCallMode: toolModel.toolChoice ? 'toolChoice' : 'prompt', - tools, - parallel_tool_calls: true, - temperature, - max_tokens, - top_p: aiChatTopP, - stop: aiChatStopSign, - response_format: { - type: aiChatResponseFormat as any, - json_schema: aiChatJsonSchema - }, - retainDatasetCite, - useVision: aiChatVision, - requestOrigin ->>>>>>> 757253617 (squash: compress all commits into one) }, isAborted: checkIsStopping, userKey: externalProvider.openaiAccount, onReasoning({ text }) { if (!aiChatReasoning) return; workflowStreamResponse?.({ -<<<<<<< HEAD - write, -======= ->>>>>>> 757253617 (squash: compress all commits into one) event: SseResponseEventEnum.answer, data: textAdaptGptResponse({ reasoning_content: text @@ -388,10 +141,6 @@ export const runToolCall = async ( onStreaming({ text }) { if (!isResponseAnswerText) return; workflowStreamResponse?.({ -<<<<<<< HEAD - write, -======= ->>>>>>> 757253617 (squash: compress all commits into one) event: SseResponseEventEnum.answer, data: textAdaptGptResponse({ text @@ -401,7 +150,6 @@ export const runToolCall = async ( onToolCall({ call }) { if (!isResponseAnswerText) return; const toolNode = toolNodesMap.get(call.function.name); -<<<<<<< HEAD if (toolNode) { workflowStreamResponse?.({ event: SseResponseEventEnum.toolCall, @@ -421,34 +169,10 @@ export const runToolCall = async ( onToolParam({ tool, params }) { if (!isResponseAnswerText) return; workflowStreamResponse?.({ - write, event: SseResponseEventEnum.toolParams, data: { tool: { id: tool.id, -======= - if (!toolNode) return; - workflowStreamResponse?.({ - event: SseResponseEventEnum.toolCall, - data: { - tool: { - id: call.id, - toolName: toolNode.name, - toolAvatar: toolNode.avatar, - functionName: call.function.name, - params: call.function.arguments ?? '', - response: '' - } - } - }); - }, - onToolParam({ call, params }) { - workflowStreamResponse?.({ - event: SseResponseEventEnum.toolParams, - data: { - tool: { - id: call.id, ->>>>>>> 757253617 (squash: compress all commits into one) toolName: '', toolAvatar: '', params, @@ -456,7 +180,6 @@ export const runToolCall = async ( } } }); -<<<<<<< HEAD }, handleToolResponse: async ({ call, messages }) => { const toolNode = toolNodesMap.get(call.function?.name); @@ -589,213 +312,4 @@ export const runToolCall = async ( finish_reason, toolWorkflowInteractiveResponse: interactiveResponse }; -======= - } - }); - - if (!answer && !reasoningContent && !toolCalls.length) { - return Promise.reject(getEmptyResponseTip()); - } - - /* Run the selected tool by LLM. - Since only reference parameters are passed, if the same tool is run in parallel, it will get the same run parameters - */ - const toolsRunResponse: ToolRunResponseType = []; - for await (const tool of toolCalls) { - try { - const toolNode = toolNodesMap.get(tool.function?.name); - - if (!toolNode) continue; - - const startParams = parseToolArgs(tool.function.arguments); - - initToolNodes(runtimeNodes, [toolNode.nodeId], startParams); - const toolRunResponse = await runWorkflow({ - ...workflowProps, - usageId: undefined, - isToolCall: true - }); - - const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses); - const toolMsgParams: ChatCompletionToolMessageParam = { - tool_call_id: tool.id, - role: ChatCompletionRequestMessageRoleEnum.Tool, - content: stringToolResponse - }; - - workflowStreamResponse?.({ - event: SseResponseEventEnum.toolResponse, - data: { - tool: { - id: tool.id, - toolName: '', - toolAvatar: '', - params: '', - response: sliceStrStartEnd(stringToolResponse, 5000, 5000) - } - } - }); - - toolsRunResponse.push({ - toolRunResponse, - toolMsgParams - }); - } catch (error) { - const err = getErrText(error); - workflowStreamResponse?.({ - event: SseResponseEventEnum.toolResponse, - data: { - tool: { - id: tool.id, - toolName: '', - toolAvatar: '', - params: '', - response: sliceStrStartEnd(err, 5000, 5000) - } - } - }); - - toolsRunResponse.push({ - toolRunResponse: undefined, - toolMsgParams: { - tool_call_id: tool.id, - role: ChatCompletionRequestMessageRoleEnum.Tool, - content: sliceStrStartEnd(err, 5000, 5000) - } - }); - } - } - - const flatToolsResponseData = toolsRunResponse - .map((item) => item.toolRunResponse) - .flat() - .filter(Boolean) as DispatchFlowResponse[]; - // concat tool responses - const dispatchFlowResponse = response - ? response.dispatchFlowResponse.concat(flatToolsResponseData) - : flatToolsResponseData; - - const inputTokens = response - ? response.toolCallInputTokens + usage.inputTokens - : usage.inputTokens; - const outputTokens = response - ? response.toolCallOutputTokens + usage.outputTokens - : usage.outputTokens; - - if (toolCalls.length > 0) { - /* - ... - user - assistant: tool data - tool: tool response - */ - const nextRequestMessages: ChatCompletionMessageParam[] = [ - ...completeMessages, - ...toolsRunResponse.map((item) => item?.toolMsgParams) - ]; - - /* - Get tool node assistant response - - history assistant - - current tool assistant - - tool child assistant - */ - const toolNodeAssistant = GPTMessages2Chats({ - messages: [...assistantMessage, ...toolsRunResponse.map((item) => item?.toolMsgParams)], - getToolInfo: (id) => { - const toolNode = toolNodesMap.get(id); - return { - name: toolNode?.name || '', - avatar: toolNode?.avatar || '' - }; - } - })[0] as AIChatItemType; - const toolChildAssistants = flatToolsResponseData - .map((item) => item.assistantResponses) - .flat() - .filter((item) => !item.interactive); // 交互节点留着下次记录 - const concatAssistantResponses = [ - ...assistantResponses, - ...toolNodeAssistant.value, - ...toolChildAssistants - ]; - - const runTimes = - (response?.runTimes || 0) + - flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0); - - // Check stop signal - const hasStopSignal = flatToolsResponseData.some( - (item) => !!item.flowResponses?.find((item) => item.toolStop) - ); - // Check interactive response(Only 1 interaction is reserved) - const workflowInteractiveResponseItem = toolsRunResponse.find( - (item) => item.toolRunResponse?.workflowInteractiveResponse - ); - if (hasStopSignal || workflowInteractiveResponseItem) { - // Get interactive tool data - const workflowInteractiveResponse = - workflowInteractiveResponseItem?.toolRunResponse?.workflowInteractiveResponse; - - // Flashback traverses completeMessages, intercepting messages that know the first user - const firstUserIndex = nextRequestMessages.findLastIndex((item) => item.role === 'user'); - const newMessages = nextRequestMessages.slice(firstUserIndex + 1); - - const toolWorkflowInteractiveResponse: WorkflowInteractiveResponseType | undefined = - workflowInteractiveResponse - ? { - ...workflowInteractiveResponse, - toolParams: { - entryNodeIds: workflowInteractiveResponse.entryNodeIds, - toolCallId: workflowInteractiveResponseItem?.toolMsgParams.tool_call_id, - memoryMessages: newMessages - } - } - : undefined; - - return { - dispatchFlowResponse, - toolCallInputTokens: inputTokens, - toolCallOutputTokens: outputTokens, - completeMessages: nextRequestMessages, - assistantResponses: concatAssistantResponses, - toolWorkflowInteractiveResponse, - runTimes, - finish_reason - }; - } - - return runToolCall( - { - ...props, - maxRunToolTimes: maxRunToolTimes - 1, - messages: nextRequestMessages - }, - { - dispatchFlowResponse, - toolCallInputTokens: inputTokens, - toolCallOutputTokens: outputTokens, - assistantResponses: concatAssistantResponses, - runTimes, - finish_reason - } - ); - } else { - // concat tool assistant - const toolNodeAssistant = GPTMessages2Chats({ - messages: assistantMessage - })[0] as AIChatItemType; - - return { - dispatchFlowResponse: response?.dispatchFlowResponse || [], - toolCallInputTokens: inputTokens, - toolCallOutputTokens: outputTokens, - - completeMessages, - assistantResponses: [...assistantResponses, ...toolNodeAssistant.value], - runTimes: (response?.runTimes || 0) + 1, - finish_reason - }; - } ->>>>>>> 757253617 (squash: compress all commits into one) }; diff --git a/packages/service/core/workflow/dispatch/ai/tool/type.d.ts b/packages/service/core/workflow/dispatch/ai/tool/type.d.ts deleted file mode 100644 index 8e0714a8e..000000000 --- a/packages/service/core/workflow/dispatch/ai/tool/type.d.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { - ChatCompletionMessageParam, - CompletionFinishReason -} from '@fastgpt/global/core/ai/type'; -import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import type { - ModuleDispatchProps, - DispatchNodeResponseType -} from '@fastgpt/global/core/workflow/runtime/type'; -import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; -import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; -import type { DispatchFlowResponse } from '../../type'; -import type { AIChatItemValueItemType } from '@fastgpt/global/core/chat/type'; -import { ChatItemValueItemType } from '@fastgpt/global/core/chat/type'; -import type { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; -import type { - ToolCallChildrenInteractive, - InteractiveNodeResponseType, - WorkflowInteractiveResponseType -} from '@fastgpt/global/core/workflow/template/system/interactive/type'; -import type { LLMModelItemType } from '@fastgpt/global/core/ai/model'; -import type { JSONSchemaInputType } from '@fastgpt/global/core/app/jsonschema'; - -export type DispatchToolModuleProps = ModuleDispatchProps<{ - [NodeInputKeyEnum.history]?: ChatItemType[]; - [NodeInputKeyEnum.userChatInput]: string; - - [NodeInputKeyEnum.fileUrlList]?: string[]; - [NodeInputKeyEnum.aiModel]: string; - [NodeInputKeyEnum.aiSystemPrompt]: string; - [NodeInputKeyEnum.aiChatTemperature]: number; - [NodeInputKeyEnum.aiChatMaxToken]: number; - [NodeInputKeyEnum.aiChatIsResponseText]: boolean; - [NodeInputKeyEnum.aiChatVision]?: boolean; - [NodeInputKeyEnum.aiChatReasoning]?: boolean; - [NodeInputKeyEnum.aiChatTopP]?: number; - [NodeInputKeyEnum.aiChatStopSign]?: string; - [NodeInputKeyEnum.aiChatResponseFormat]?: string; - [NodeInputKeyEnum.aiChatJsonSchema]?: string; -}> & { - messages: ChatCompletionMessageParam[]; - toolNodes: ToolNodeItemType[]; - toolModel: LLMModelItemType; - childrenInteractiveParams?: ToolCallChildrenInteractive['params']; -}; - -export type RunToolResponse = { - toolDispatchFlowResponses: DispatchFlowResponse[]; - toolCallInputTokens: number; - toolCallOutputTokens: number; - completeMessages: ChatCompletionMessageParam[]; - assistantResponses: AIChatItemValueItemType[]; - finish_reason: CompletionFinishReason; - toolWorkflowInteractiveResponse?: ToolCallChildrenInteractive; -}; -export type ToolNodeItemType = RuntimeNodeItemType & { - toolParams: RuntimeNodeItemType['inputs']; - jsonSchema?: JSONSchemaInputType; -}; diff --git a/packages/service/core/workflow/dispatch/ai/tool/type.ts b/packages/service/core/workflow/dispatch/ai/tool/type.ts index be7218172..3a843b4fa 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/type.ts +++ b/packages/service/core/workflow/dispatch/ai/tool/type.ts @@ -1,19 +1,48 @@ +import type { + ChatCompletionMessageParam, + CompletionFinishReason +} from '@fastgpt/global/core/ai/type'; +import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; +import type { DispatchFlowResponse } from '../../type'; +import type { AIChatItemValueItemType, ChatItemType } from '@fastgpt/global/core/chat/type'; +import type { ToolCallChildrenInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type'; +import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; import type { JSONSchemaInputType } from '@fastgpt/global/core/app/jsonschema'; -import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; +export type DispatchToolModuleProps = ModuleDispatchProps<{ + [NodeInputKeyEnum.history]?: ChatItemType[]; + [NodeInputKeyEnum.userChatInput]: string; + + [NodeInputKeyEnum.fileUrlList]?: string[]; + [NodeInputKeyEnum.aiModel]: string; + [NodeInputKeyEnum.aiSystemPrompt]: string; + [NodeInputKeyEnum.aiChatTemperature]: number; + [NodeInputKeyEnum.aiChatMaxToken]: number; + [NodeInputKeyEnum.aiChatVision]?: boolean; + [NodeInputKeyEnum.aiChatReasoning]?: boolean; + [NodeInputKeyEnum.aiChatTopP]?: number; + [NodeInputKeyEnum.aiChatStopSign]?: string; + [NodeInputKeyEnum.aiChatResponseFormat]?: string; + [NodeInputKeyEnum.aiChatJsonSchema]?: string; +}> & { + messages: ChatCompletionMessageParam[]; + toolNodes: ToolNodeItemType[]; + toolModel: LLMModelItemType; + childrenInteractiveParams?: ToolCallChildrenInteractive['params']; +}; + +export type RunToolResponse = { + toolDispatchFlowResponses: DispatchFlowResponse[]; + toolCallInputTokens: number; + toolCallOutputTokens: number; + completeMessages: ChatCompletionMessageParam[]; + assistantResponses: AIChatItemValueItemType[]; + finish_reason: CompletionFinishReason; + toolWorkflowInteractiveResponse?: ToolCallChildrenInteractive; +}; export type ToolNodeItemType = RuntimeNodeItemType & { toolParams: RuntimeNodeItemType['inputs']; jsonSchema?: JSONSchemaInputType; }; - -export type DispatchSubAppResponse = { - response: string; - usages?: ChatNodeUsageType[]; -}; - -export type GetSubAppInfoFnType = (id: string) => { - name: string; - avatar: string; - toolDescription: string; -}; diff --git a/packages/service/core/workflow/dispatch/ai/tool/utils.ts b/packages/service/core/workflow/dispatch/ai/tool/utils.ts index 290638e76..529d4182c 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/utils.ts +++ b/packages/service/core/workflow/dispatch/ai/tool/utils.ts @@ -1,34 +1,41 @@ -<<<<<<< HEAD -/* - 匹配 {{@toolId@}},转化成: @name 的格式。 -*/ -export const parseSystemPrompt = ({ - systemPrompt, - getSubAppInfo +import { sliceStrStartEnd } from '@fastgpt/global/common/string/tools'; +import { type AIChatItemValueItemType } from '@fastgpt/global/core/chat/type'; +import { type FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; +import { type RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; +import { type RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; + +export const updateToolInputValue = ({ + params, + inputs }: { - systemPrompt?: string; - getSubAppInfo: (id: string) => { - name: string; - avatar: string; - toolDescription: string; - }; -}): string => { - if (!systemPrompt) return ''; + params: Record; + inputs: FlowNodeInputItemType[]; +}) => { + return inputs.map((input) => ({ + ...input, + value: params[input.key] ?? input.value + })); +}; - // Match pattern {{@toolId@}} and convert to @name format - const pattern = /\{\{@([^@]+)@\}\}/g; - - 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 +export const filterToolResponseToPreview = (response: AIChatItemValueItemType[]) => { + return response.map((item) => { + if (item.tools) { + const formatTools = item.tools?.map((tool) => { + return { + ...tool, + response: sliceStrStartEnd(tool.response, 500, 500) + }; + }); + return { + ...item, + tools: formatTools + }; } - return `@${toolInfo.name}`; + return item; }); +}; -<<<<<<<< HEAD:packages/service/core/workflow/dispatch/ai/tool/utils.ts export const formatToolResponse = (toolResponses: any) => { if (typeof toolResponses === 'object') { return JSON.stringify(toolResponses, null, 2); @@ -37,42 +44,6 @@ export const formatToolResponse = (toolResponses: any) => { return toolResponses ? String(toolResponses) : 'none'; }; -======= -import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; -import type { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; -import type { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; - -export const initToolNodes = ( - nodes: RuntimeNodeItemType[], - entryNodeIds: string[], - startParams?: Record -) => { - const updateToolInputValue = ({ - params, - inputs - }: { - params: Record; - inputs: FlowNodeInputItemType[]; - }) => { - return inputs.map((input) => ({ - ...input, - value: params[input.key] ?? input.value - })); - }; - - nodes.forEach((node) => { - if (entryNodeIds.includes(node.nodeId)) { - node.isEntry = true; - node.isStart = true; - if (startParams) { - node.inputs = updateToolInputValue({ params: startParams, inputs: node.inputs }); - } - } else { - node.isStart = false; - } - }); -}; ->>>>>>> 757253617 (squash: compress all commits into one) // 在原参上改变值,不修改原对象,tool workflow 中,使用的还是原对象 export const initToolCallEdges = (edges: RuntimeEdgeItemType[], entryNodeIds: string[]) => { edges.forEach((edge) => { @@ -81,7 +52,6 @@ export const initToolCallEdges = (edges: RuntimeEdgeItemType[], entryNodeIds: st } }); }; -<<<<<<< HEAD export const initToolNodes = ( nodes: RuntimeNodeItemType[], @@ -96,9 +66,4 @@ export const initToolNodes = ( } } }); -======== - return processedPrompt; ->>>>>>>> 757253617 (squash: compress all commits into one):packages/service/core/workflow/dispatch/ai/agent/utils.ts }; -======= ->>>>>>> 757253617 (squash: compress all commits into one) diff --git a/packages/service/core/workflow/dispatch/ai/utils.ts b/packages/service/core/workflow/dispatch/ai/utils.ts index 9a220e216..b72691408 100644 --- a/packages/service/core/workflow/dispatch/ai/utils.ts +++ b/packages/service/core/workflow/dispatch/ai/utils.ts @@ -139,11 +139,3 @@ export const getToolNodesByIds = ({ }; }); }; - -export const parseToolArgs = >(toolArgs: string) => { - try { - return json5.parse(sliceJsonStr(toolArgs)) as T; - } catch { - return; - } -}; diff --git a/packages/web/i18n/en/chat.json b/packages/web/i18n/en/chat.json index 4e6a4208b..1b4804fd8 100644 --- a/packages/web/i18n/en/chat.json +++ b/packages/web/i18n/en/chat.json @@ -29,8 +29,8 @@ "config_input_guide_lexicon": "Set Up Lexicon", "config_input_guide_lexicon_title": "Set Up Lexicon", "confirm_clear_input_value": "Are you sure to clear the form content? \nDefault values ​​will be restored!", + "confirm_plan": "Confirm plan", "confirm_to_clear_share_chat_history": "Are you sure you want to clear all chat history?", - "confirm_plan_agent": "Please confirm whether the change plan meets expectations. If you need to modify it, you can send the modification requirements in the input box at the bottom.", "content_empty": "No Content", "contextual": "{{num}} Contexts", "contextual_preview": "Contextual Preview {{num}} Items", @@ -81,6 +81,7 @@ "not_query": "Missing query content", "not_select_file": "No file selected", "plan_agent": "Plan agent", + "plan_check_tip": "Please confirm your plan or enter new requirements in the input box", "plugins_output": "Plugin Output", "press_to_speak": "Hold down to speak", "query_extension_IO_tokens": "Problem Optimization Input/Output Tokens", diff --git a/packages/web/i18n/zh-CN/chat.json b/packages/web/i18n/zh-CN/chat.json index cc63e331a..5acdda229 100644 --- a/packages/web/i18n/zh-CN/chat.json +++ b/packages/web/i18n/zh-CN/chat.json @@ -29,8 +29,8 @@ "config_input_guide_lexicon": "配置词库", "config_input_guide_lexicon_title": "配置词库", "confirm_clear_input_value": "确认清空表单内容?将会恢复默认值!", + "confirm_plan": "确认计划", "confirm_to_clear_share_chat_history": "确认清空所有聊天记录?", - "confirm_plan_agent": "请确认改计划是否符合预期,如需修改,可在底部输入框中发送修改要求。", "content_empty": "内容为空", "contextual": "{{num}}条上下文", "contextual_preview": "上下文预览 {{num}} 条", @@ -81,6 +81,7 @@ "not_query": "缺少查询内容", "not_select_file": "未选择文件", "plan_agent": "任务规划", + "plan_check_tip": "请确认计划或在输入框中输入新要求", "plugins_output": "插件输出", "press_to_speak": "按住说话", "query_extension_IO_tokens": "问题优化输入/输出 Tokens", diff --git a/packages/web/i18n/zh-Hant/chat.json b/packages/web/i18n/zh-Hant/chat.json index f3d6d1ab2..d76952b04 100644 --- a/packages/web/i18n/zh-Hant/chat.json +++ b/packages/web/i18n/zh-Hant/chat.json @@ -29,8 +29,8 @@ "config_input_guide_lexicon": "設定詞彙庫", "config_input_guide_lexicon_title": "設定詞彙庫", "confirm_clear_input_value": "確認清空表單內容?\n將會恢復默認值!", + "confirm_plan": "確認計劃", "confirm_to_clear_share_chat_history": "確認清空所有聊天記錄?", - "confirm_plan_agent": "請確認改計劃是否符合預期,如需修改,可在底部輸入框中發送修改要求。", "content_empty": "無內容", "contextual": "{{num}} 筆上下文", "contextual_preview": "上下文預覽 {{num}} 筆", @@ -81,6 +81,7 @@ "not_query": "缺少查詢內容", "not_select_file": "尚未選取檔案", "plan_agent": "任務規劃", + "plan_check_tip": "請確認計劃或在輸入框中輸入新要求", "plugins_output": "外掛程式輸出", "press_to_speak": "按住說話", "query_extension_IO_tokens": "問題最佳化輸入/輸出 Tokens", diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx index 492d7e79f..7bcc13c55 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx @@ -31,6 +31,8 @@ import { addStatisticalDataToHistoryItem } from '@/global/core/chat/utils'; import dynamic from 'next/dynamic'; import { useMemoizedFn } from 'ahooks'; import ChatBoxDivider from '../../../Divider'; +import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus'; +import { ConfirmPlanAgentText } from '@fastgpt/global/core/workflow/runtime/constants'; import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance'; const ResponseTags = dynamic(() => import('./ResponseTags')); @@ -58,6 +60,7 @@ type Props = { }; questionGuides?: string[]; children?: React.ReactNode; + hasPlanCheck?: boolean; } & ChatControllerProps; const RenderQuestionGuide = ({ questionGuides }: { questionGuides: string[] }) => { @@ -122,7 +125,7 @@ const AIContentCard = React.memo(function AIContentCard({ ); }); -const ChatItem = (props: Props) => { +const ChatItem = ({ hasPlanCheck, ...props }: Props) => { const { avatar, statusBoxData, children, isLastChild, questionGuides = [], chat } = props; const { t } = useTranslation(); @@ -163,7 +166,7 @@ const ChatItem = (props: Props) => { const outLinkAuthData = useContextSelector(WorkflowRuntimeContext, (v) => v.outLinkAuthData); const isShowReadRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource); - const { totalQuoteList: quoteList = [] } = useMemo( + const { totalQuoteList: quoteList = [] } = useMemoEnhance( () => addStatisticalDataToHistoryItem(chat), [chat] ); @@ -172,7 +175,7 @@ const ChatItem = (props: Props) => { const { copyData } = useCopyData(); - const chatStatusMap = useMemo(() => { + const chatStatusMap = useMemoEnhance(() => { if (!statusBoxData?.status) return; return colorMap[statusBoxData.status]; }, [statusBoxData?.status]); @@ -181,8 +184,12 @@ const ChatItem = (props: Props) => { 1. The interactive node is divided into n dialog boxes. 2. Auto-complete the last textnode */ - const splitAiResponseResults = useMemo(() => { - if (chat.obj === ChatRoleEnum.Human) return [chat.value]; + const { responses: splitAiResponseResults } = useMemo(() => { + if (chat.obj === ChatRoleEnum.Human) { + return { + responses: [chat.value] + }; + } if (chat.obj === ChatRoleEnum.AI) { // Remove empty text node @@ -200,7 +207,11 @@ const ChatItem = (props: Props) => { let currentGroup: AIChatItemValueItemType[] = []; filterList.forEach((value) => { + // 每次遇到交互节点,则推送一个全新的分组 if (value.interactive) { + if (value.interactive.type === 'agentPlanCheck') { + return; + } if (currentGroup.length > 0) { groupedValues.push(currentGroup); currentGroup = []; @@ -235,10 +246,14 @@ const ChatItem = (props: Props) => { } } - return groupedValues; + return { + responses: groupedValues + }; } - return []; + return { + responses: [] + }; }, [chat.obj, chat.value, isChatting]); const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData); @@ -283,8 +298,6 @@ const ChatItem = (props: Props) => { } ); - const aiSubApps = 'subApps' in chat ? chat.subApps : undefined; - return ( { ); })} + + {hasPlanCheck && isLastChild && ( + + + + )} ); }; diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx index e57e6ba24..0ec9971d8 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx @@ -14,7 +14,7 @@ import type { } from '@fastgpt/global/core/chat/type.d'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { getErrText } from '@fastgpt/global/common/error/utils'; -import { Box, Checkbox, Flex } from '@chakra-ui/react'; +import { Box, Button, Checkbox, Flex } from '@chakra-ui/react'; import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus'; import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt'; import { useForm } from 'react-hook-form'; @@ -249,6 +249,7 @@ const ChatBox = ({ tool, subAppId, interactive, + agentPlan, variables, nodeResponse, durationSeconds, @@ -488,6 +489,14 @@ const ChatBox = ({ value: item.value.concat(val) }; } + if (event === SseResponseEventEnum.agentPlan && agentPlan) { + return { + ...item, + value: item.value.concat({ + agentPlan + }) + }; + } if (event === SseResponseEventEnum.workflowDuration && durationSeconds) { return { ...item, @@ -1134,6 +1143,8 @@ const ChatBox = ({ }, [chatType, chatRecords.length, chatStartedWatch]); //chat history + const hasPlanCheck = + lastInteractive?.type === 'agentPlanCheck' && !lastInteractive.params.confirmed; const RecordsBox = useMemo(() => { return ( @@ -1166,6 +1177,7 @@ const ChatBox = ({ avatar={appAvatar} chat={item} isLastChild={index === chatRecords.length - 1} + hasPlanCheck={hasPlanCheck} {...{ showVoiceIcon, statusBoxData, @@ -1236,7 +1248,8 @@ const ChatBox = ({ t, showMarkIcon, itemRefs, - onCloseCustomFeedback + onCloseCustomFeedback, + hasPlanCheck ]); // Child box diff --git a/projects/app/src/components/core/chat/ChatContainer/type.d.ts b/projects/app/src/components/core/chat/ChatContainer/type.d.ts index 7a80ee6ff..60d2dddf6 100644 --- a/projects/app/src/components/core/chat/ChatContainer/type.d.ts +++ b/projects/app/src/components/core/chat/ChatContainer/type.d.ts @@ -8,6 +8,7 @@ import type { import { ChatSiteItemType } from '@fastgpt/global/core/chat/type'; import type { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import type { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; +import type { AgentPlanType } from '@fastgpt/service/core/workflow/dispatch/ai/agent/sub/plan/type'; export type generatingMessageProps = { event: SseResponseEventEnum; @@ -19,6 +20,7 @@ export type generatingMessageProps = { status?: 'running' | 'finish'; tool?: ToolModuleResponseItemType; interactive?: WorkflowInteractiveResponseType; + agentPlan?: AgentPlanType; variables?: Record; nodeResponse?: ChatHistoryItemResType; durationSeconds?: number; diff --git a/projects/app/src/components/core/chat/components/AIResponseBox.tsx b/projects/app/src/components/core/chat/components/AIResponseBox.tsx index 292e9512a..9b4c5545f 100644 --- a/projects/app/src/components/core/chat/components/AIResponseBox.tsx +++ b/projects/app/src/components/core/chat/components/AIResponseBox.tsx @@ -26,18 +26,15 @@ import type { import { isEqual } from 'lodash'; import { useTranslation } from 'next-i18next'; import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus'; -import { - SelectOptionsComponent, - FormInputComponent, - AgentPlanCheckComponent -} from './Interactive/InteractiveComponents'; +import { SelectOptionsComponent, FormInputComponent } from './Interactive/InteractiveComponents'; import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils'; import { useContextSelector } from 'use-context-selector'; import { type OnOpenCiteModalProps } from '@/web/core/chat/context/chatItemContext'; import { WorkflowRuntimeContext } from '../ChatContainer/context/workflowRuntimeContext'; import { useCreation } from 'ahooks'; import { useSafeTranslation } from '@fastgpt/web/hooks/useSafeTranslation'; -import { ConfirmPlanAgentText } from '@fastgpt/global/core/workflow/runtime/constants'; +import type { AgentPlanType } from '@fastgpt/service/core/workflow/dispatch/ai/agent/sub/plan/type'; +import MyDivider from '@fastgpt/web/components/common/MyDivider'; const accordionButtonStyle = { w: 'auto', @@ -334,6 +331,33 @@ const RenderPaymentPauseInteractive = React.memo(function RenderPaymentPauseInte ); }); +const RenderAgentPlan = React.memo(function RenderAgentPlan({ + agentPlan +}: { + agentPlan: AgentPlanType; +}) { + const { t } = useTranslation(); + return ( + + + {agentPlan.task} + + + {agentPlan.steps.map((step, index) => ( + + + {`${index + 1}. ${step.title}`} + + {step.description} + + ))} + + + {t('chat:plan_check_tip')} + + ); +}); + const AIResponseBox = ({ chatItemDataId, value, @@ -391,14 +415,7 @@ const AIResponseBox = ({ ); } if (interactive.type === 'agentPlanCheck') { - return ( - { - onSendPrompt(ConfirmPlanAgentText); - }} - /> - ); + return null; } if (interactive.type === 'agentPlanAskQuery') { return {interactive.params.content}; @@ -407,6 +424,9 @@ const AIResponseBox = ({ return ; } } + if ('agentPlan' in value && value.agentPlan) { + return ; + } // Abandon if ('tools' in value && value.tools) { diff --git a/projects/app/src/components/core/chat/components/Interactive/InteractiveComponents.tsx b/projects/app/src/components/core/chat/components/Interactive/InteractiveComponents.tsx index 70a7c1a08..a79ae74e7 100644 --- a/projects/app/src/components/core/chat/components/Interactive/InteractiveComponents.tsx +++ b/projects/app/src/components/core/chat/components/Interactive/InteractiveComponents.tsx @@ -247,23 +247,3 @@ export const FormInputComponent = React.memo(function FormInputComponent({ ); }); - -// Agent interactive -export const AgentPlanCheckComponent = React.memo(function AgentPlanCheckComponent({ - interactiveParams, - onConfirm -}: { - interactiveParams: AgentPlanCheckInteractive['params']; - onConfirm: () => void; -}) { - const { t } = useTranslation(); - return interactiveParams?.confirmed ? ( - // TODO:临时 UI - 已确认计划 - ) : ( - - {t('chat:confirm_plan_agent')} - - - ); -}); diff --git a/projects/app/src/pageComponents/dashboard/agent/context.tsx b/projects/app/src/pageComponents/dashboard/agent/context.tsx index ff370b4f8..7617730d2 100644 --- a/projects/app/src/pageComponents/dashboard/agent/context.tsx +++ b/projects/app/src/pageComponents/dashboard/agent/context.tsx @@ -87,7 +87,7 @@ const AppListContextProvider = ({ children }: { children: ReactNode }) => { // agent page if (router.pathname.includes('/agent')) { return !type || type === 'all' - ? [AppTypeEnum.folder, AppTypeEnum.simple, AppTypeEnum.workflow] + ? [AppTypeEnum.folder, AppTypeEnum.simple, AppTypeEnum.workflow, AppTypeEnum.agent] : [AppTypeEnum.folder, type]; } diff --git a/projects/app/src/pageComponents/dashboard/constant.ts b/projects/app/src/pageComponents/dashboard/constant.ts index ab28def94..6bcee9b86 100644 --- a/projects/app/src/pageComponents/dashboard/constant.ts +++ b/projects/app/src/pageComponents/dashboard/constant.ts @@ -2,6 +2,10 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { i18nT } from '@fastgpt/web/i18n/utils'; export const appTypeTagMap = { + [AppTypeEnum.agent]: { + label: 'Agent', + icon: 'core/app/type/mcpTools' + }, [AppTypeEnum.simple]: { label: i18nT('app:type.Chat_Agent'), icon: 'core/app/type/simple' @@ -28,6 +32,5 @@ export const appTypeTagMap = { }, [AppTypeEnum.tool]: undefined, [AppTypeEnum.folder]: undefined, - [AppTypeEnum.hidden]: undefined, - [AppTypeEnum.agent]: undefined + [AppTypeEnum.hidden]: undefined }; diff --git a/projects/app/src/web/common/api/fetch.ts b/projects/app/src/web/common/api/fetch.ts index 64ae4339f..70ba990ae 100644 --- a/projects/app/src/web/common/api/fetch.ts +++ b/projects/app/src/web/common/api/fetch.ts @@ -12,6 +12,7 @@ import { formatTime2YMDHMW } from '@fastgpt/global/common/string/time'; import { getWebReqUrl } from '@fastgpt/web/common/system/utils'; import type { OnOptimizePromptProps } from '@/components/common/PromptEditor/OptimizerPopover'; import type { OnOptimizeCodeProps } from '@/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeCode/Copilot'; +import type { AgentPlanType } from '@fastgpt/service/core/workflow/dispatch/ai/agent/sub/plan/type'; type StreamFetchProps = { url?: string; @@ -36,6 +37,12 @@ type ResponseQueueItemType = event: SseResponseEventEnum.interactive; [key: string]: any; } + | { + responseValueId?: string; + subAppId?: string; + event: SseResponseEventEnum.agentPlan; + agentPlan: AgentPlanType; + } | { responseValueId?: string; subAppId?: string; @@ -256,6 +263,13 @@ export const streamFetch = ({ event, ...rest }); + } else if (event === SseResponseEventEnum.agentPlan) { + pushDataToQueue({ + responseValueId, + subAppId, + event, + agentPlan: rest.agentPlan + }); } else if (event === SseResponseEventEnum.error) { if (rest.statusText === TeamErrEnum.aiPointsNotEnough) { useSystemStore.getState().setNotSufficientModalType(TeamErrEnum.aiPointsNotEnough);