diff --git a/packages/service/core/workflow/dispatch/ai/agent/index.ts b/packages/service/core/workflow/dispatch/ai/agent/index.ts index 1e75c1999..a9dc830ab 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/index.ts @@ -1,9 +1,4 @@ -import { - NodeInputKeyEnum, - NodeOutputKeyEnum, - toolValueTypeList, - valueTypeJsonSchemaMap -} from '@fastgpt/global/core/workflow/constants'; +import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { DispatchNodeResponseKeyEnum, SseResponseEventEnum @@ -14,7 +9,7 @@ import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; import { getLLMModel } from '../../../../ai/model'; -import { filterToolNodeIdByEdges, getNodeErrResponse, getHistories } from '../../utils'; +import { getNodeErrResponse, getHistories } from '../../utils'; import { runAgentCall } from '../../../../ai/llm/agentCall'; import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type'; import { type ChatItemType } from '@fastgpt/global/core/chat/type'; @@ -27,14 +22,7 @@ import { } from '@fastgpt/global/core/chat/adapt'; import { formatModelChars2Points } from '../../../../../support/wallet/usage/utils'; import { getHistoryPreview } from '@fastgpt/global/core/chat/utils'; -import { - filterMemoryMessages, - filterToolResponseToPreview, - formatToolResponse, - getToolNodesByIds, - initToolNodes, - parseToolArgs -} from '../utils'; +import { filterMemoryMessages, filterToolResponseToPreview, parseToolArgs } from '../utils'; import { SubAppIds } from './sub/constants'; import { getReferenceVariableValue, @@ -42,19 +30,18 @@ import { textAdaptGptResponse, valueTypeFormat } from '@fastgpt/global/core/workflow/runtime/utils'; -import { sliceStrStartEnd } from '@fastgpt/global/common/string/tools'; import { dispatchPlanAgent } from './sub/plan'; import { dispatchModelAgent } from './sub/model'; import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node'; import { getSubApps, rewriteSubAppsToolset } from './sub'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; -import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; import { dispatchTool } from './sub/tool'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { getMasterAgentDefaultPrompt } from './constants'; import { addFilePrompt2Input, getFileInputPrompt } from './sub/file/utils'; import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type'; import { dispatchFileRead } from './sub/file'; +import { dispatchApp } from './sub/app'; export type DispatchAgentModuleProps = ModuleDispatchProps<{ [NodeInputKeyEnum.history]?: ChatItemType[]; @@ -298,23 +285,6 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise } }); - const lastPlanCallIndex = messages - .slice(0, -1) - .findLastIndex( - (c) => - c.role === 'assistant' && - c.tool_calls?.some((tc) => tc.function?.name === SubAppIds.plan) - ); - const originalContent = - lastPlanCallIndex !== -1 - ? (messages[lastPlanCallIndex + 1].content as string) - : ''; - - // const applyedContent = applyDiff({ - // original: originalContent, - // patch: content - // }); - return { response, usages, @@ -449,6 +419,16 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise usages, isEnd: false }; + } else if (node.flowNodeType === FlowNodeTypeEnum.appModule) { + const { response, usages } = await dispatchApp({ + ...props, + node + }); + return { + response, + usages, + isEnd: false + }; } else { return { response: 'Can not find the tool', diff --git a/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts index 0945e2a01..2522a9112 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts @@ -1,3 +1,163 @@ +import type { DispatchSubAppResponse } from '../../type'; +import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; +import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type'; +import { + filterSystemVariables, + getHistories +} from '../../../../../../../core/workflow/dispatch/utils'; +import { authAppByTmbId } from '../../../../../../../support/permission/app/auth'; +import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; +import { getAppVersionById } from '../../../../../../../core/app/version/controller'; +import { getRunningUserInfoByTmbId } from '../../../../../../../support/user/team/utils'; +import { runWorkflow } from '../../../../../../../core/workflow/dispatch'; +import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; +import { + getWorkflowEntryNodeIds, + rewriteNodeOutputByHistories, + storeEdges2RuntimeEdges, + storeNodes2RuntimeNodes, + textAdaptGptResponse +} from '@fastgpt/global/core/workflow/runtime/utils'; +import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/global/core/chat/adapt'; +import { getUserChatInfoAndAuthTeamPoints } from '../../../../../../../support/permission/auth/team'; +import { parseUrlToFileType } from '@fastgpt/global/common/file/tools'; + export type appConfigType = { variables: Record; }; + +type Props = ModuleDispatchProps<{ + [NodeInputKeyEnum.userChatInput]: string; + [NodeInputKeyEnum.history]?: ChatItemType[] | number; + [NodeInputKeyEnum.fileUrlList]?: string[]; + [NodeInputKeyEnum.forbidStream]?: boolean; + [NodeInputKeyEnum.fileUrlList]?: string[]; +}>; + +export const dispatchApp = async (props: Props): Promise => { + const { + runningAppInfo, + histories, + query, + lastInteractive, + node: { pluginId: appId, version }, + workflowStreamResponse, + params, + variables, + stream + } = props; + const { + system_forbid_stream = false, + userChatInput, + history, + fileUrlList, + ...childrenAppVariables + } = params; + + const { files } = chatValue2RuntimePrompt(query); + + const userInputFiles = (() => { + if (fileUrlList) { + return fileUrlList.map((url) => parseUrlToFileType(url)).filter(Boolean); + } + // Adapt version 4.8.13 upgrade + return files; + })(); + + if (!userChatInput && !userInputFiles) { + return Promise.reject(new Error('Input is empty')); + } + if (!appId) { + return Promise.reject(new Error('pluginId is empty')); + } + + try { + // Auth the app by tmbId(Not the user, but the workflow user) + const { app: appData } = await authAppByTmbId({ + appId: appId, + tmbId: runningAppInfo.tmbId, + per: ReadPermissionVal + }); + const { nodes, edges, chatConfig } = await getAppVersionById({ + appId, + versionId: version, + app: appData + }); + + const childStreamResponse = system_forbid_stream ? false : stream; + // Auto line + if (childStreamResponse) { + workflowStreamResponse?.({ + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + text: '\n' + }) + }); + } + + const chatHistories = getHistories(history, histories); + + // Rewrite children app variables + const systemVariables = filterSystemVariables(variables); + const { externalProvider } = await getUserChatInfoAndAuthTeamPoints(appData.tmbId); + const childrenRunVariables = { + ...systemVariables, + ...childrenAppVariables, + histories: chatHistories, + appId: String(appData._id), + ...(externalProvider ? externalProvider.externalWorkflowVariables : {}) + }; + + const childrenInteractive = + lastInteractive?.type === 'childrenInteractive' + ? lastInteractive.params.childrenResponse + : undefined; + const runtimeNodes = rewriteNodeOutputByHistories( + storeNodes2RuntimeNodes( + nodes, + getWorkflowEntryNodeIds(nodes, childrenInteractive || undefined) + ), + childrenInteractive + ); + + const runtimeEdges = storeEdges2RuntimeEdges(edges, childrenInteractive); + const theQuery = childrenInteractive + ? query + : runtimePrompt2ChatsValue({ files: userInputFiles, text: userChatInput }); + + const { assistantResponses } = await runWorkflow({ + ...props, + lastInteractive: childrenInteractive, + // Rewrite stream mode + ...(system_forbid_stream + ? { + stream: false, + workflowStreamResponse: undefined + } + : {}), + runningAppInfo: { + id: String(appData._id), + teamId: String(appData.teamId), + tmbId: String(appData.tmbId), + isChildApp: true + }, + runningUserInfo: await getRunningUserInfoByTmbId(appData.tmbId), + runtimeNodes, + runtimeEdges, + histories: chatHistories, + variables: childrenRunVariables, + query: theQuery, + chatConfig + }); + + const { text } = chatValue2RuntimePrompt(assistantResponses); + + return { + response: text, + usages: [] + }; + } catch (error) { + return Promise.reject('dispatch app failed'); + } +}; diff --git a/packages/service/core/workflow/dispatch/ai/agent/sub/plan/prompt.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/prompt.ts index 8c52f8041..b9f279425 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/sub/plan/prompt.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/prompt.ts @@ -1,6 +1,6 @@ export const getPlanAgentPrompt = (background?: string) => { return ` -你是一个专业的项目规划助手,擅长将复杂任务分解为结构化的执行计划;同时支持对既有计划进行“最小差异(+- Diff)”式修改。你会严格遵循指定的注释标记格式,并在修改模式下输出可直接应用的补丁(patch)。 +你是一个专业的项目规划助手,擅长将复杂任务分解为结构化的执行计划。 ${ @@ -11,63 +11,22 @@ ${ : '' } - -- 自动识别两种模式: - 1) 创建模式(create):用户未提供现有计划时,生成全新的计划文档。 - 2) 修改模式(patch):用户提供现有计划或明确提出“增删改”需求时,输出 Diff Patch。 -- 若用户提供了现有计划文本(含注释标记),一律进入修改模式;否则进入创建模式。 - - -根据用户提供的主题或目标,生成或修改一份详细、可执行的项目计划文档,包含合理的阶段划分与具体待办事项;修改时以“补丁”为最小输出单元,确保变更可定位、可回放、可审计。 +根据用户提供的主题或目标,生成一份详细、可执行的项目计划文档,包含合理的阶段划分与具体待办事项。 -- 用户输入:一个需要制定或更新的主题、目标或任务描述;可选的现有计划文档;可选的变更请求(自然语言或指令式)。 -- 输入格式:自然语言描述,可能包含背景、目标、约束、优先级、本地化偏好、以及现有计划全文。 +- 用户输入:一个需要制定的主题、目标或任务描述。 +- 输入格式:自然语言描述,可能包含背景、目标、约束、优先级、本地化偏好。 -通用步骤 1. 解析用户输入,提取核心目标、关键要素、约束与本地化偏好。 2. 评估任务复杂度(简单:2-3 步;复杂:4-7 步),据此确定阶段数量。 3. 各阶段生成 3-5 条可执行 Todo,动词开头,MECE 且无重叠。 4. 语言风格本地化(根据用户输入语言进行术语与语序调整)。 - -创建模式(create) 5. 产出完整计划,严格使用占位符 [主题] 与标记体系;确保编号连续、标签闭合、结构清晰。 - -修改模式(patch) -5. 解析“现有计划”(锚点优先级:\`\`、\`\`、\`\` 等)。 -6. 将用户变更需求映射为原子操作(见 ),生成最小必要的行级 Diff: - - 仅对变更涉及的行输出 \`+\`(新增)或 \`-\`(删除);未变更行不重复输出。 - - 修改视为“-旧行”与“+新行”的并列呈现。 - - 插入请贴靠最稳固的锚点(如 \`\` 下的标题或 \`\` 前后)。 -7. 自动重排:对步骤编号 N、待办编号 X 做连续性校正;若移动/插入造成编号漂移,补丁中体现校正后的行。 -8. 校验:所有必须标签完整闭合;编号连续;每步 3-5 条待办;MECE;无空段落;无悬挂标记。 -9. 产出补丁;如 render=full,则在补丁后附上“更新后的完整文档”。 - - -支持的原子操作(内部推理用,输出仍为行级 Diff): -- ADD_STEP(after N | before N | at end) 新增步骤(含标题、描述、Todos、可选备注) -- REMOVE_STEP(N) 删除步骤 -- UPDATE_STEP(N, title/desc/note=…) 更新步骤标题/描述/备注 -- MOVE_STEP(N -> M) 移动步骤至序号 M(重排后编号连续) -- ADD_TODO(N, at k) 在步骤 N 的第 k 个位置插入 Todo -- REMOVE_TODO(N.k) 删除 Todo -- UPDATE_TODO(N.k, text=…) 更新 Todo 文本 -- MOVE_TODO(N.k -> M.t) 移动 Todo 到其他步骤/位置 -- RENAME_THEME(text=…) 更新主标题或整体描述中的 [主题] 文本描述(占位仍用 [主题]) -说明:若用户未显式给出操作,你需从自然语言中归纳为上述操作的序列并生成对应行级 Diff。 - - - -- 补丁仅包含变更行,以“前缀字符 +|-”表示新增/删除。 -- 不在上述集合内的行(例如空行)若因结构需要调整,可一并纳入补丁。 -- 修改必须保持“标记尾注不变更其语义角色”,即:当你替换内容时,保留原注释标签并只更改标签左侧的可读文本。 -- 重排后编号以补丁中的最新数字为准;不要在同一补丁里出现对同一元素的多次相互抵消的改动。 -- 若原文缺少稳固锚点,优先在最近的 \`\` 或 \`\` 相邻位置插入。 - + - 必须严格遵循以下注释标记格式: @@ -80,7 +39,7 @@ ${ * 标记单个待办事项 * 添加重要注释或备注 - 步骤数量随复杂度自动调整;每步 3-5 条 Todo。 -- 编号(N、X)必须连续、准确,修改模式下需自动校正。 +- 编号(N、X)必须连续、准确。 - 描述语言简洁、专业、可操作;各阶段逻辑递进、MECE。 - 进行本地化调整(术语、量词、表达习惯)。 @@ -89,11 +48,10 @@ ${ - 不生成违法、不道德或有害内容;敏感主题输出合规替代方案。 - 避免过于具体的时间/预算承诺与无法验证的保证。 - 保持中立、客观;必要时指出风险与依赖。 -- 你拥有的记忆是通过别的 Agent 共享给你的, 你只需要专注于输出内容, 不必担心上下文的完整性。 - + # [主题] 深度调研计划 全面了解 [主题] 的 [核心维度描述] @@ -121,15 +79,7 @@ ${ [可选备注] - - - - # 仅输出变更行,以 +- 表示;无代码块围栏;保持原有缩进与空行风格。确保旧行的准确性和完整性 - # 如果是 todo list 的变更,请确保 todo 前的 - [ ] 符号正确。删除或替换的行前用 - - [ ] 来表示 todo 的变更 - # 禁止输出代码块标记\`\`\` - + 新增或替换后的行 - - 被删除或被替换的旧行 - + - - -- 例:标记 todo 2.1 完成状态 - -- - [ ] 完成数据采集与清洗 -+ - [x] 完成数据采集与清洗 - - `; };