ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FastGPT 交互节点开发指南:工作流暂停-恢复机制与自定义交互节点全流程实现

FastGPT 交互节点开发指南:工作流暂停-恢复机制与自定义交互节点全流程实现 FastGPT 交互节点开发指南工作流暂停-恢复机制与自定义交互节点全流程实现【免费下载链接】FastGPTFastGPT is a knowledge-based platform built on the LLMs, offers a comprehensive suite of out-of-the-box capabilities such as data processing, RAG retrieval, and visual AI workflow orchestration, letting you easily develop and deploy complex question-answering systems without the need for extensive setup or configuration.项目地址: https://gitcode.com/GitHub_Trending/fa/FastGPT导读FastGPT 工作流支持多种交互节点类型允许在工作流执行过程中暂停并等待用户输入从而实现“AI 主动发问 → 用户作答 → 工作流继续”的人机协作闭环。本文将基于仓库内 交互节点开发指南 的完整脉络结合packages/global类型定义、packages/service后端执行逻辑与projects/app前端渲染组件等源码实现系统讲解交互节点的架构设计、二阶段执行机制isEntry 标志、开发新交互节点的 10 个完整步骤、测试清单与常见问题排查。读完本文你将掌握从类型定义、节点模板、后端 dispatch 到前端交互组件、国际化与历史记录管理的端到端开发能力。说明文中代码路径均以仓库根目录为起点文中“交互类型”指后端与前端共同识别的交互标识如userSelect、userInput、childrenInteractive等。现有交互节点类型当前系统支持以下交互节点类型部分为旧版标识已在代码中以deprecated标注类型标识说明源码位置userSelect用户选择节点单选userSelect 类型定义、userSelect 执行逻辑userInput旧文档称formInput表单输入节点多字段表单userInput 类型定义、formInput 执行逻辑childrenInteractive子工作流交互ChildrenInteractivetoolChildrenInteractive工具调用场景下的子工作流交互携带toolCallId用于替换 tool 的 responseToolCallChildrenInteractiveloopInteractive/loopRunInteractive循环交互记录loopResult、currentIndex/iterationLoopInteractivepaymentPause欠费暂停交互PaymentPauseInteractiveagentAskAgent 多问题询问questions数量 1–3AgentAskInteractiveagentPlanAskQuery旧版 Agent 单问题询问已废弃使用AgentAskInteractive替代AgentPlanAskQueryInteractive所有交互类型最终通过WorkflowInteractiveResponseTypeInteractiveBasicType与InteractiveNodeResponseType的交集对外暴露见 type.ts。交互节点架构核心类型定义交互节点的类型定义位于 packages/global/core/workflow/template/system/interactive/type.ts注意旧文档中的路径为type.d.ts当前仓库实际文件名为type.ts。该文件同时使用zod 运行时校验 Schema与TypeScript 类型双轨定义既能保证编译期类型安全又能保证运行时数据合法性。基础交互结构InteractiveBasicType如下const InteractiveBasicTypeSchema z.object({ entryNodeIds: z.array(z.string()), interactiveId: z.string().optional(), nodeResponseId: z.string().optional(), memoryEdges: z.array(RuntimeEdgeItemTypeSchema), nodeOutputs: z.array(NodeOutputItemSchema), skipNodeQueue: z .array(z.object({ id: z.string(), skippedNodeIdList: z.array(z.string()) })) .optional(), // 需要记录目前在 queue 里的节点 usageId: z.string().optional() });各字段含义entryNodeIds入口节点 ID 列表工作流恢复时从这些节点重新进入执行interactiveId交互唯一标识可选nodeResponseId节点响应 ID可选用于关联节点响应记录memoryEdges需要记忆的边。交互暂停时保存的memoryEdges可能指向 ToolSet 展开产生的临时节点因此续跑前的孤儿边过滤必须在工具集展开完成后执行详见 dispatch/index.ts 中的注释nodeOutputs节点输出列表skipNodeQueue跳过的节点队列idskippedNodeIdList。交互节点触发时系统会保存该队列恢复时用于跳过已处理的节点usageId用量记录 ID。而InteractiveNodeType是其中各字段均可选optional的“轻量版”供具体交互节点继承const InteractiveNodeTypeSchema z.object({ entryNodeIds: z.array(z.string()).optional(), interactiveId: z.string().optional(), nodeResponseId: z.string().optional(), memoryEdges: z.array(RuntimeEdgeItemTypeSchema).optional(), nodeOutputs: z.array(NodeOutputItemSchema).optional() });具体交互节点的定义模式以表单输入为例export const UserInputInteractiveSchema z.object({ type: z.literal(userInput), params: z.object({ description: z.string(), inputForm: z.array(UserInputFormItemSchema), submitted: z.boolean().optional() }) }); export type UserInputInteractive z.infertypeof UserInputInteractiveSchema;其中UserInputFormItemSchema定义了表单字段的完整约束继承自AppFileSelectConfigTypeSchemaexport const UserInputFormItemSchema AppFileSelectConfigTypeSchema.extend({ type: z.enum(FlowNodeInputTypeEnum), key: z.string(), label: z.string(), value: z.any(), valueType: z.enum(WorkflowIOValueTypeEnum), description: z.string().optional(), defaultValue: z.any().optional(), required: z.boolean(), maxLength: z.number().optional(), // input textarea minLength: z.number().optional(), // password max: z.number().optional(), // numberInput min: z.number().optional(), // numberInput list: z.array(z.object({ label: z.string(), value: z.string() })).optional(), // select canLocalUpload: z.boolean().optional(), canUrlUpload: z.boolean().optional() });所有交互类型通过zoddiscriminatedUnion(type, ...)收拢为联合类型InteractiveNodeResponseType见 type.ts#L201-L217新增交互类型时需将其 Schema 加入该联合。工作流执行机制isEntry 标志与二阶段执行交互节点在工作流执行中的特殊处理位于 packages/service/core/workflow/dispatch/index.ts// Start process width initInput const entryNodes data.runtimeNodes.filter((item) item.isEntry); // Reset entry data.runtimeNodes.forEach((item) { // Interactively nodes will use the isEntry, which does not need to be updated if ( item.flowNodeType ! FlowNodeTypeEnum.userSelect item.flowNodeType ! FlowNodeTypeEnum.formInput item.flowNodeType ! FlowNodeTypeEnum.toolCall ) { item.isEntry false; } });这段逻辑的要点是普通节点在工作流重新执行前会被清空isEntry而交互类节点userSelect、formInput以及作为工具调用载体的toolCall保留isEntry标志以便通过isEntry判断该节点是“首次进入需要发起交互”还是“用户提交数据后恢复执行需要处理输入”。注意当前仓库源码中isEntry白名单包含的是userSelect、formInput、toolCall而非文档示例中的agent如果你开发的交互节点类型也需要在恢复时保留入口标志必须把它加入这个白名单。交互节点的执行天然分为两个阶段第一次执行发起交互节点不是入口节点或lastInteractive不是对应交互类型时返回interactive响应并暂停工作流第二次执行处理用户输入用户提交后工作流从入口节点恢复节点命中isEntry且lastInteractive.type匹配进入数据处理分支并重置node.isEntry false。开发新交互节点的步骤步骤 1定义节点类型文件packages/global/core/workflow/template/system/interactive/type.ts首先定义输入项结构与交互节点 Schema并将其加入InteractiveNodeResponseType联合类型export type YourInputItemType { // 定义输入项的结构 key: string; label: string; value: any; // ... 其他字段 }; // 建议同时给出 zod Schema供运行时校验仓库采用双轨定义 export const YourInteractiveNodeSchema z.object({ type: z.literal(yourNodeType), params: z.object({ description: z.string(), yourInputField: z.array(z.any()), submitted: z.boolean().optional() }) }); type YourInteractiveNode InteractiveNodeType z.infertypeof YourInteractiveNodeSchema; // 添加到 discriminatedUnion 联合类型 export const InteractiveNodeResponseTypeSchema z.intersection( z.discriminatedUnion(type, [ UserSelectInteractiveSchema, UserInputInteractiveSchema, ChildrenInteractiveSchema, ToolCallChildrenInteractiveSchema, LoopInteractiveSchema, LoopRunInteractiveSchema, PaymentPauseInteractiveSchema, AgentPlanAskQueryInteractiveSchema, AgentAskInteractiveSchema, YourInteractiveNodeSchema // 新增 ]), z.object({ askId: z.string().nullish() }) );步骤 2定义节点枚举可选文件packages/global/core/workflow/node/constant.ts如果不需要为节点模板注册新的FlowNodeTypeEnum例如只是在已有节点上增加交互分支逻辑则无需修改此文件export enum FlowNodeTypeEnum { // ... 现有类型 yourNodeType yourNodeType, // 新增节点类型 }步骤 3创建节点模板可选文件packages/global/core/workflow/template/system/interactive/yourNode.ts节点模板负责描述节点在编辑器中的外观、输入输出端口与默认值。仓库中已有的两个模板是极佳范本userSelect 模板showSourceHandle: false单选无分支输出实际通过skipHandleId实现分支效果、showTargetHandle: true、isTool: true默认选项为Confirm/CancelformInput 模板输入输出双向连接、isTool: trueuserInputForms默认值为空数组。新建模板的基本骨架如下import { i18nT } from ../../../../../common/i18n/utils; import { FlowNodeTemplateTypeEnum, NodeInputKeyEnum, NodeOutputKeyEnum, WorkflowIOValueTypeEnum } from ../../../constants; import { FlowNodeInputTypeEnum, FlowNodeOutputTypeEnum, FlowNodeTypeEnum } from ../../../node/constant; import { createHideInContext } from ../../context; import { type FlowNodeTemplateType } from ../../../type/node; export const YourNode: FlowNodeTemplateType { id: FlowNodeTypeEnum.yourNodeType, templateType: FlowNodeTemplateTypeEnum.interactive, flowNodeType: FlowNodeTypeEnum.yourNodeType, showSourceHandle: true, // 是否显示源连接点 showTargetHandle: true, // 是否显示目标连接点 avatar: core/workflow/template/yourNode, name: i18nT(app:workflow.your_node), intro: i18nT(app:workflow.your_node_tip), isTool: true, // 标记为工具节点可被 Agent/ToolCall 调用 // 可结合 createHideInContext 控制节点在特定父级如并行分支中的可见性 inputs: [ { key: NodeInputKeyEnum.description, renderTypeList: [FlowNodeInputTypeEnum.textarea], valueType: WorkflowIOValueTypeEnum.string, label: i18nT(app:workflow.node_description), placeholder: i18nT(app:workflow.your_node_placeholder) }, { key: NodeInputKeyEnum.yourInputField, renderTypeList: [FlowNodeInputTypeEnum.custom], valueType: WorkflowIOValueTypeEnum.any, label: , value: [] // 默认值 } ], outputs: [ { id: NodeOutputKeyEnum.yourResult, key: NodeOutputKeyEnum.yourResult, required: true, label: i18nT(workflow:your_result), valueType: WorkflowIOValueTypeEnum.object, type: FlowNodeOutputTypeEnum.static } ] };其中isShowInContext: createHideInContext([{ parentType: FlowNodeTypeEnum.parallelRun }])是现有两个交互模板都使用的配置见 userSelect.ts#L29表示该交互节点在并行分支内不可用新节点模板建议保持一致。步骤 4创建节点执行逻辑文件packages/service/core/workflow/dispatch/interactive/yourNode.ts执行逻辑是实现二阶段交互的核心。真实参考实现见userSelect.ts第一阶段返回interactive第二阶段解析query文本得到选中值命中后node.isEntry false并通过skipHandleId跳过未选中的分支getHandleId(nodeId, source, item.key)同时返回rewriteHistories: histories.slice(0, -2)移除当前交互记录formInput.ts第一阶段返回userInput交互第二阶段JSON.parse用户提交的 JSON 字符串并对password类型字段做解密anyValueDecrypt、对fileSelect类型字段做文件规范化处理预览 URL 生成、文件数量上限控制等。新节点的骨架import { DispatchNodeResponseKeyEnum } from fastgpt/global/core/workflow/runtime/constants; import type { DispatchNodeResultType, ModuleDispatchProps } from fastgpt/global/core/workflow/runtime/type; import { NodeInputKeyEnum, NodeOutputKeyEnum } from fastgpt/global/core/workflow/constants; import { chatValue2RuntimePrompt } from fastgpt/global/core/chat/adapt; type Props ModuleDispatchProps{ [NodeInputKeyEnum.description]: string; [NodeInputKeyEnum.yourInputField]: YourInputItemType[]; }; type YourNodeResponse DispatchNodeResultType{ [NodeOutputKeyEnum.yourResult]?: Recordstring, any; }; export const dispatchYourNode async (props: Props): PromiseYourNodeResponse { const { histories, node, params: { description, yourInputField }, query, lastInteractive } props; const { isEntry } node; // 第一阶段非入口节点或不是对应的交互类型返回交互请求暂停工作流 if (!isEntry || lastInteractive?.type ! yourNodeType) { return { [DispatchNodeResponseKeyEnum.interactive]: { type: yourNodeType, params: { description, yourInputField } } }; } // 第二阶段处理用户提交的数据 node.isEntry false; // 重要重置入口标志 const { text } chatValue2RuntimePrompt(query); const userInputVal (() { try { return JSON.parse(text); // 用户输入以 JSON 字符串形式进入 query 的 text } catch (error) { return {}; } })(); return { data: { [NodeOutputKeyEnum.yourResult]: userInputVal }, // 移除当前交互的历史记录用户问题 系统响应最后 2 条 [DispatchNodeResponseKeyEnum.rewriteHistories]: histories.slice(0, -2), [DispatchNodeResponseKeyEnum.toolResponse]: userInputVal, [DispatchNodeResponseKeyEnum.nodeResponse]: { yourResult: userInputVal } }; };关键约定与源码一致用户提交的内容以 JSON 字符串形式写入query.text通过chatValue2RuntimePrompt(query)取出后JSON.parse还原见 formInput.ts#L108-L116 的注释与实现。返回值中的data将写入节点输出供后续节点引用。步骤 5注册节点回调文件packages/service/core/workflow/dispatch/constants.tscallbackMap是FlowNodeTypeEnum → dispatch 函数的注册表类型为RecordFlowNodeTypeEnum | typeof internalRuntimeNodeType, ...。现有交互节点注册方式[FlowNodeTypeEnum.userSelect]: dispatchUserSelect, [FlowNodeTypeEnum.formInput]: dispatchFormInput,新增节点时import { dispatchYourNode } from ./interactive/yourNode; export const callbackMap: RecordFlowNodeTypeEnum, any { // ... 现有节点 [FlowNodeTypeEnum.yourNodeType]: dispatchYourNode, };步骤 6创建前端渲染组件6.1 聊天界面交互组件文件projects/app/src/components/core/chat/components/Interactive/InteractiveComponents.tsx该文件已实现SelectOptionsComponent单选基于LeftRadio渲染选项列表见 L45-L79与FormInputComponent表单基于react-hook-form的useForm/Controller支持文件上传状态判定isFileUploading与错误提示见 L81-L130。新组件可参考以下骨架export const YourNodeComponent React.memo(function YourNodeComponent({ interactiveParams: { description, yourInputField, submitted }, defaultValues {}, SubmitButton }: { interactiveParams: YourInteractiveNode[params]; defaultValues?: Recordstring, any; SubmitButton: (e: { onSubmit: UseFormHandleSubmitRecordstring, any }) React.JSX.Element; }) { const { handleSubmit, control } useForm({ defaultValues }); return ( Box DescriptionBox description{description} / Flex flexDirection{column} gap{3} {yourInputField.map((input) ( Box key{input.key} {/* 渲染你的输入组件 */} Controller control{control} name{input.key} render{({ field: { onChange, value } }) ( YourInputComponent value{value} onChange{onChange} isDisabled{submitted} / )} / /Box ))} /Flex {!submitted ( Flex justifyContent{flex-end} mt{4} SubmitButton onSubmit{handleSubmit} / /Flex )} /Box ); });6.2 工作流编辑器节点组件文件projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeYourNode.tsx编辑器节点组件基于reactflow的NodeProps与NodeCard封装通过RenderInput/RenderOutput渲染输入输出并可通过CustomComponent映射自定义输入渲染import React, { useMemo } from react; import { type NodeProps } from reactflow; import { Box } from chakra-ui/react; import NodeCard from ./render/NodeCard; import { type FlowNodeItemType } from fastgpt/global/core/workflow/type/node; import Container from ../components/Container; import RenderInput from ./render/RenderInput; import RenderOutput from ./render/RenderOutput; import { NodeInputKeyEnum } from fastgpt/global/core/workflow/constants; import { useTranslation } from next-i18next; import { type FlowNodeInputItemType } from fastgpt/global/core/workflow/type/io; import { useContextSelector } from use-context-selector; import IOTitle from ../components/IOTitle; import { WorkflowActionsContext } from ../../context/workflowActionsContext; const NodeYourNode ({ data, selected }: NodePropsFlowNodeItemType) { const { t } useTranslation(); const { nodeId, inputs, outputs } data; const onChangeNode useContextSelector(WorkflowActionsContext, (v) v.onChangeNode); const CustomComponent useMemo( () ({ [NodeInputKeyEnum.yourInputField]: (v: FlowNodeInputItemType) { // 自定义渲染逻辑 return Box{/* 你的自定义UI */}/Box; } }), [nodeId, onChangeNode, t] ); return ( NodeCard minW{400px} selected{selected} {...data} Container RenderInput nodeId{nodeId} flowInputList{inputs} CustomComponent{CustomComponent} / /Container Container IOTitle text{t(common:Output)} / RenderOutput nodeId{nodeId} flowOutputList{outputs} / /Container /NodeCard ); }; export default React.memo(NodeYourNode);步骤 7注册节点组件需要在节点注册表中注册你的节点组件具体位置根据项目配置而定使新节点出现在工作流编辑器的节点面板中并关联到NodeYourNode渲染组件。步骤 8添加国际化文件packages/web/i18n/zh-CN/app.json、en/app.json、zh-Hant/app.json等语言文件节点模板中通过i18nT(app:workflow.your_node)引用翻译 key因此必须补齐所有语言的文案{ workflow: { your_node: 你的节点名称, your_node_tip: 节点功能说明, your_node_placeholder: 提示文本 } }步骤 9调整保存对话记录逻辑文件packages/service/core/chat/saveChat.ts修改updateInteractiveChat方法使新交互在保存对话时能被正确处理交互消息的类型、内容序列化与恢复格式均在此登记。步骤 10根据历史记录获取/设置交互状态文件projects/app/src/components/core/chat/ChatContainer/ChatBox/utils.tspackages/global/core/workflow/runtime/utils.ts调整setInteractiveResultToHistories、getInteractiveByHistories和getLastInteractiveValue方法使新交互能够写入历史记录保存时、从历史记录中还原交互状态刷新/恢复时、获取最后一次交互的值供入口节点判断lastInteractive.type。关键注意事项1. isEntry 标志管理交互节点需要保持isEntry标志在工作流恢复时有效因此必须把新节点类型加入白名单否则恢复执行时入口标志被清除交互数据永远无法被第二阶段消费// 在 packages/service/core/workflow/dispatch/index.ts 中 // 确保你的节点类型被添加到白名单 if ( item.flowNodeType ! FlowNodeTypeEnum.userSelect item.flowNodeType ! FlowNodeTypeEnum.formInput item.flowNodeType ! FlowNodeTypeEnum.toolCall item.flowNodeType ! FlowNodeTypeEnum.yourNodeType // 新增 ) { item.isEntry false; }2. 交互响应流程交互节点有两个执行阶段这是整个机制的基石第一次执行返回interactive响应暂停工作流第二次执行接收用户输入继续工作流。// 第一阶段 if (!isEntry || lastInteractive?.type ! yourNodeType) { return { [DispatchNodeResponseKeyEnum.interactive]: { type: yourNodeType, params: { /* ... */ } } }; } // 第二阶段 node.isEntry false; // 重要重置标志 // 处理用户输入...3. 历史记录管理交互节点需要正确处理历史记录第二阶段返回时通过rewriteHistories移除“用户问题 系统响应”这两条交互记录histories.slice(0, -2)避免它们被当作普通上下文传给后续节点return { // 移除交互对话的历史记录用户问题 系统响应 [DispatchNodeResponseKeyEnum.rewriteHistories]: histories.slice(0, -2), // ... 其他返回值 };4. Skip 节点队列交互节点触发时系统会保存skipNodeQueue每个交互暂停时刻的节点队列快照恢复时将其作为defaultSkipNodeQueue传入新的WorkflowQueue从而跳过已处理的节点见 dispatch/index.ts#L1672-L1673。5. 工具调用支持如果节点需要在工具调用中使用例如被 Agent 节点作为工具触发设置isTool: true。同时注意toolCall也处于isEntry白名单中工具类交互toolChildrenInteractive依赖该标志恢复执行。测试清单开发完成后请测试以下场景节点在工作流编辑器中正常显示节点配置保存和加载正确交互请求正确发送到前端前端组件正确渲染交互界面用户输入正确传回后端工作流正确恢复并继续执行历史记录正确更新节点输出正确连接到后续节点错误情况处理正确多语言支持完整仓库中已存在针对表单输入交互的测试用例formInput.test.ts可作为新节点单元测试的编写参考。参考实现开发新节点时可直接对照以下两个现成的完整实现简单单选userSelect节点类型定义type.ts#L127-L141节点模板userSelect.ts执行逻辑userSelect.ts前端组件InteractiveComponents.tsx#L45-L79复杂表单formInput节点类型定义type.ts#L143-L172节点模板formInput.ts执行逻辑formInput.ts前端组件InteractiveComponents.tsx#L81-L130常见问题Q交互节点执行了两次A这是正常的。第一次返回交互请求暂停第二次处理用户输入恢复。确保在第二次执行时设置node.isEntry false否则后续恢复会重复进入处理分支。Q工作流恢复后没有继续执行A检查你的节点类型是否在isEntry白名单中dispatch/index.ts同时确认lastInteractive?.type与你的交互类型一致。Q用户输入格式不对A检查chatValue2RuntimePrompt的返回值。用户提交内容以 JSON 字符串形式进入query.text按你的数据格式JSON.parse解析解析失败时返回空对象兜底参考 formInput.ts#L108-L116。Q如何支持多个交互节点串联A每个交互节点都会暂停工作流用户完成后会自动继续到下一个节点。entryNodeIds与skipNodeQueue会记录每个暂停点的状态恢复时从对应入口继续从而天然支持多交互串联。文件清单总结开发新交互节点需要修改/创建以下文件后端核心文件packages/global/core/workflow/template/system/interactive/type.ts— 类型定义与 zod Schemapackages/global/core/workflow/node/constant.ts— 节点枚举可选packages/global/core/workflow/template/system/interactive/yourNode.ts— 节点模板可选packages/service/core/workflow/dispatch/interactive/yourNode.ts— 执行逻辑packages/service/core/workflow/dispatch/constants.ts— 回调注册packages/service/core/workflow/dispatch/index.ts— isEntry 白名单前端组件文件projects/app/src/components/core/chat/components/Interactive/InteractiveComponents.tsx— 聊天交互组件projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeYourNode.tsx— 工作流编辑器组件国际化文件packages/web/i18n/zh-CN/app.json— 中文翻译packages/web/i18n/en/app.json— 英文翻译packages/web/i18n/zh-Hant/app.json— 繁体中文翻译附录关键输入输出键定义如果需要新的输入输出键在以下文件中定义文件packages/global/core/workflow/constants.tsexport enum NodeInputKeyEnum { // ... 现有键 yourInputKey yourInputKey, } export enum NodeOutputKeyEnum { // ... 现有键 yourOutputKey yourOutputKey, }小结FastGPT 的交互节点机制围绕“isEntry 标志 二阶段 dispatch 交互历史记录”三个核心构件展开isEntry白名单决定节点在恢复时是否保留入口身份dispatch 函数依据isEntry与lastInteractive.type区分“发起交互”与“处理输入”两个阶段rewriteHistories/skipNodeQueue/memoryEdges则共同保证暂停与恢复之间的状态一致。按照本文的 10 个步骤类型定义 → 枚举 → 模板 → 执行逻辑 → 回调注册 → 前端组件 → 组件注册 → 国际化 → 对话保存 → 历史状态并对照userSelect、formInput两个参考实现即可在 FastGPT 工作流中快速落地新的自定义交互节点。【免费下载链接】FastGPTFastGPT is a knowledge-based platform built on the LLMs, offers a comprehensive suite of out-of-the-box capabilities such as data processing, RAG retrieval, and visual AI workflow orchestration, letting you easily develop and deploy complex question-answering systems without the need for extensive setup or configuration.项目地址: https://gitcode.com/GitHub_Trending/fa/FastGPT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进