FastGPT SkillEdit 复用标准 Chat 会话:sourceType/sourceId 双资源模型设计与实践
FastGPT SkillEdit 复用标准 Chat 会话sourceType/sourceId 双资源模型设计与实践【免费下载链接】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 中Skill EditSkill 调试/预览复用标准 Chat 会话这一核心改造展开讲解在保留历史appId物理字段的前提下通过业务语义层新增sourceType sourceId双资源模型让 App 会话与 Skill Edit 会话共用chats、chatitems、chat_item_responses三张标准会话表。读完本文你将掌握该模型的枚举定义、统一查询/写入 helper、OpenAPI 双层 schema、前端 Chat Target 分层、sandbox/S3/stop-resume 的资源隔离规则以及索引迁移与历史数据兼容策略可直接用于理解或复刻类似的多资源类型复用单套会话存储改造。一、背景为什么不能直接改字段FastGPT 的标准 Chat 体系由三张表构成chats会话、chatitems消息、chat_item_responses节点响应。历史上这三张表的物理字段叫appId所有 App 会话都通过它归属到具体应用。当 Skill Edit 调试会话需要复用这套标准 Chat 能力标准 chat service、标准 chat API时面临两个约束历史数据量巨大历史 App 会话数据量很大不适合为了接入 Skill Edit 做大规模字段重命名或全量回填物理字段语义单一appId字段名承载不了资源类型的语义直接把 SkillId 塞进appId会导致 Skill 会话与 App 会话在同一字段下混存、无法区分。因此最终方案是在业务语义层新增sourceType sourceIdApp 会话sourceTypeappsourceIdappIdSkill Edit 会话sourceTypeskillEditsourceIdskillId。Mongo 第一阶段继续保留物理字段appId但把它视为历史字段名业务含义统一为sourceId。这样既不动存量数据又能在语义层完成资源隔离。二、目标与非目标目标App 和 Skill Edit 共用chats、chatitems、chat_item_responses标准 chat API 对外继续使用业务字段appId或skillId不暴露内部sourceType/sourceIdAPI route 使用parseApiInput和 runtime schema 把appId/skillId转换为sourceType/sourceIdAPI handler 之后的业务层统一接收sourceType/sourceId禁止继续传 API 原始字段appId/skillIdApp 历史数据在缺失sourceType的情况下仍可读取、更新和删除Skill Edit 的 usage 写入usage.skillId不污染usage.appIdSkill Edit 不写入 App 最近使用、App 统计日志和 App 看板stop、resume、nodeResponse、S3、sandbox 都按sourceType/sourceId隔离。非目标第一阶段的明确边界不做 Mongo 字段appId - sourceId的物理重命名不强制回填几亿历史 App chat 数据不长期兼容旧 Skill Debug chat上线初始化阶段清理掉旧数据不让ChatSourceEnum承担资源类型语义——它继续表示入口来源test、api、online、share等不把 Skill Edit 接入 App 最近使用和 App chat logs。三、核心模型ChatSourceTypeEnum 与统一 helper枚举定义在 packages/global/core/chat/constants.ts 中定义了资源类型枚举与表示入口来源的ChatSourceEnum明确区分export enum ChatSourceTypeEnum { app app, skillEdit skillEdit, chatAgentHelper chatAgentHelper }说明源码中的枚举比设计初稿多了一个chatAgentHelper成员HelperBot 独立命名空间注释也明确写道ChatSourceEnum表示对话入口来源如 test/api/onlineChatSourceTypeEnum表示会话归属资源类型用于在同一套 chat 表中隔离 App 和 Skill Edit。这正对应设计中不让 ChatSourceEnum 承担资源类型语义的约束。sourceId是所属资源的真实 ObjectIdsourceTypeapp时sourceId是 AppIdsourceTypeskillEdit时sourceId是 SkillId。统一查询/写入 helper所有新代码必须通过统一 helper 构造查询和写入字段实现在 packages/service/core/chat/source.tsexport function buildChatSourceWriteFields({ sourceType, sourceId }: ChatSourceParams) { return { sourceType, appId: sourceId }; } export function buildChatSourceQuery({ sourceType, sourceId }: ChatSourceParams) { if (sourceType ChatSourceTypeEnum.app) { return { appId: sourceId, $or: [{ sourceType: ChatSourceTypeEnum.app }, { sourceType: { $exists: false } }] }; } if ( sourceType ChatSourceTypeEnum.skillEdit || sourceType ChatSourceTypeEnum.chatAgentHelper ) { return { appId: sourceId, sourceType }; } const exhaustiveCheck: never sourceType; throw new Error(Unsupported chat source type: ${exhaustiveCheck}); }核心语义App 查询默认兼容历史数据$or同时匹配sourceTypeapp与sourceType字段缺失{ $exists: false }的记录保证存量 App 会话长期可读Skill Edit 查询必须精确匹配sourceTypeskillEdit避免 Skill 与 App 复用同一物理appId字段时串记录非法sourceType走never穷尽检查并抛错从编译期和运行期双重防呆。源码中还额外提供了buildChatSourceAggregateMatch用于聚合管线场景——因为 Mongoose 不会自动 cast aggregate$match它会把合法的 ObjectId 字符串显式转换成Types.ObjectId避免聚合查询命中不了历史appId物理字段见 source.ts。四、API 设计对外 appId/skillId对内 sourceType/sourceId入参互斥规则标准 chat API 对外只接受appId或skillId二者其一{ appId: 68ad85a7463006c963799a05, chatId: chat_xxx }{ skillId: 68ad85a7463006c963799a06, chatId: chat_xxx }规则appId和skillId必须且只能传一个只传appId转换为sourceTypeapp、sourceIdappId只传skillId转换为sourceTypeskillEdit、sourceIdskillIdZod transform 后的业务层不再保留顶层appId/skillId不使用字段名type表示资源类型避免和已有业务枚举如ChatSourceEnum冲突。OpenAPI schema 分层withChatTarget一类 runtime schema 带 transform不能直接用于 OpenAPI 文档生成。因此每个标准 chat API 使用两层 schemaRaw schema不带 transform对外描述appId/skillId用于 OpenAPI path 和前端请求类型Runtime schema基于 raw schema transformAPI route 的parseApiInput使用输出sourceType/sourceId。OpenAPI 只注册 raw schema。互斥约束由 raw schema 的superRefine与 API route 的parseApiInput在运行时共同保证。这一分层在 packages/global/openapi/core/chat/api.ts 中有完整实现四个 schema 构造器覆盖了必填/可选与外链鉴权两种维度helper语义createChatTargetInputSchema(shape)必填 targetappId/skillId互斥用于 OpenAPI pathcreateOptionalChatTargetInputSchema(shape)可选 target仅用于可从外链鉴权上下文反推 App 的接口createOutLinkChatTargetInputSchema(shape)必填 target 外链鉴权字段createOptionalOutLinkChatTargetInputSchema(shape)可选 target 外链鉴权字段对应的 runtime 转换 helper 为withChatTarget、withOptionalChatTarget、withOutLinkChatTarget、withOptionalOutLinkChatTarget它们内部调用transformChatTargetInput/transformChatAuthTargetInput等转换函数。源码注释特别强调不能用OutLinkChatAuthSchema.extend(createChatTargetInputSchema(...).shape)拼接否则会丢失 chat target 互斥校验。互斥校验本身由refineChatAuthTargetInput完成api.ts它覆盖了这些规则appId和skillId不能同时提供appId/skillId/share auth三选一sourceTypechatAgentHelper必须带appId、不能带skillIdshare 模式下shareId与outLinkUid必须成对出现skillId不能与 share auth 混用必填模式下三者必须提供其一。对应的运行时转换transformChatTargetInputapi.ts逻辑如下sourceId appId || skillId若传了skillId则sourceTypeskillEdit否则按chatAgentHelper/app判断。标准接口覆盖范围以下接口统一支持appId/skillIdraw input并在 route 中解析为sourceType/sourceId会话初始化与续跑/api/core/chat/init、/api/core/chat/resume停止/api/v2/chat/stop记录/api/core/chat/record/getRecords_v2、getPaginationRecords、getResData、delete、getQuote、getCollectionQuote历史/api/core/chat/history/getHistories、getHistoryStatus、markRead、updateHistory、delHistory、clearHistories、batchDelete反馈/api/core/chat/feedback/updateUserFeedback、updateFeedbackReadStatus、adminUpdate、closeCustom、getFeedbackRecordIds文件/api/core/chat/file/presignChatFilePostUrl、presignChatFileGetUrl语音/api/v1/audio/transcriptions历史 wrapper route 如果保留必须复用同一个 source-aware handler 和 schema。App-only 接口不接入 Skill Edit以下接口保持 App-onlyoutLink init、team init、inputGuide、recentlyUsed、公开 OpenAPI chat completions、/api/core/chat/chatTest、helperBot、App chat logs。App-only 接口内部如调用标准 chat service必须显式传sourceTypeapp和sourceIdappId。特别说明/api/core/chat/chatTest它的入参是 App workflow test 协议依赖nodes/edges/chatConfig/appName和authAppSkill Edit 调试使用 Skill 专属协议构造 runtime nodes 和编辑沙盒上下文不应仅通过给chatTest增加skillId来混用两套请求结构。若后续要彻底移除 Skill debug 专属接口需要单独设计生成入口转换层而不是把skillId直接塞进现有ChatTestPropsSchema。五、前端 Chat Target 设计双层 target前端分为两层 target实现见 projects/app/src/web/core/chat/utils.tstype ChatSourceTarget { sourceType: app | skillEdit; sourceId: string; }; type ChatApiTarget { appId: string } | { skillId: string };规则ChatSourceTarget是前端标准 chat 组件内部 targetChatBox、WorkflowRuntimeContext和标准 chat 请求都以它为准ChatApiTarget是 OpenAPI/API 边界 raw target只在请求发出前由toChatApiTarget(sourceTarget)派生App 页面传sourceTarget{{ sourceType: app, sourceId: appId }}Skill Preview 传sourceTarget{{ sourceType: skillEdit, sourceId: skillId }}ChatBox runtime 状态 key 不暴露成 prop统一用getChatSourceKey(sourceTarget)生成形如${sourceType}:${sourceId}Skill Preview 下 input guide、TTS、语音识别入口和 ChatBox 内的 App 沙盒入口不展示、不调用所有标准 chat API 调用只能从sourceTarget派生ChatApiTarget不能从真实 App-onlyappId或 Skill ID 推导前端组件不要自行拼 API raw target统一走toChatApiTarget(sourceTarget)。核心转换实现export const toChatApiTarget (target: ChatSourceTarget): ChatTargetInputType { if (target.sourceType ChatSourceTypeEnum.skillEdit) { return { skillId: target.sourceId }; } if (target.sourceType ChatSourceTypeEnum.chatAgentHelper) { return { appId: target.sourceId, sourceType: ChatSourceTypeEnum.chatAgentHelper }; } return { appId: target.sourceId }; };源码中还提供了useChatApiTarget(target)useMemo 包装的派生 hook和getChatSourceKey前者供标准请求统一取 API raw target后者生成${sourceType}:${sourceId}运行时 key。此外utils.ts还提供了toChatSourceTargetraw target → 内部 source target供 SandboxEditor 等 API 边界场景反向使用和toChatAuthApiTargetsource target → 带 outLinkAuthData 的 raw targetshare 模式只传outLinkAuthData。ChatBox 最终前端方案ChatBox不再感知appId/skillId只接收标准内部 targetChatBox sourceTarget{{ sourceType, sourceId }} features{features} onStartChat{onStartChat} onChatGenerateStatusChange{onChatGenerateStatusChange} /边界划分sourceTarget用于 record/history/feedback/file/quote/resume/stop/delete 等标准 chat 能力features只控制功能展示和能力开关如 feedback、mark、voice、tts、inputGuide、sandbox、workorder、autoResume、markRead、quickReplies、footer actionsonStartChat保留外部注入因为 App/Home/Share/ChatTest/Skill Preview 的生成编排不同暂时不能统一onChatGenerateStatusChange只作为事件通知外部页面ChatBox 不直接读写侧栏 history、最近使用、路由状态等外部模型onStopChat移除外部 override统一走 source-aware/api/v2/chat/stoponDeleteChatItem移除外部 override统一走 source-aware chat item delete 接口ChatBox 目录内禁止直接依赖ChatContext、useChatStore、最近使用等外部页面状态需要影响外部时通过 props 回调由页面层承接App/Home/Share 侧栏历史同步放在页面层 hook 中消费onChatGenerateStatusChangeSkill Preview 不传该回调。迁移顺序新增ChatSourceTarget、getChatSourceKey、toChatApiTargetWorkflowRuntimeContext改为暴露sourceTarget/sourceKey/appId/chatId其中appId只表示真实 App-only 能力所需的 AppIdChatBoxprops 改为sourceTarget features onStartChat不保留feedbackType/showMarkIcon/showVoiceIcon/...等旧 feature props标准 chat 请求统一改用toChatApiTarget(sourceTarget)删除onStopChat/onDeleteChatItem两个 propsSkill Preview 改走通用 stop/deleteApp-only 功能全部从appId判断改为features控制外部 history/recently used/router 等状态同步迁到页面层 props 回调ChatBox 内只保留自身 UI 状态最后扫ChatBox目录内appId/skillId/chatTarget/chatTargetId确保只剩入口页面或 App-only 能力使用。最大注意点onStartChat不是 feature也不是标准 CRUD先保留。六、权限设计source-aware 鉴权入口新增标准 chat target 鉴权入口测试见 projects/app/test/service/support/permission/auth/chat.test.tstype AuthChatTargetParams { sourceType: ChatSourceTypeEnum; sourceId: string; chatId?: string; };规则sourceTypeapp复用现有authChatCrud/authAppsourceTypeskillEdit走authSkill并在传入chatId时用source-aware 查询校验 chat 属于当前 skill 和团队团队不匹配必须拒绝对应测试用例outLink、share、team domain 等 App 专属入口保持sourceTypeapp。所有 chat 存在性校验必须使用 source-aware 查询禁止裸查{ appId, chatId }否则 Skill 会话会被误判为 App 会话或在新索引启用前命中错误记录。七、数据模型三表字段演进chats新增sourceType字段第一阶段不设置required: true也不设置 schema default。原因有三历史 App 数据缺失sourceType新写入必须通过buildChatSourceWriteFields显式带sourceType不能让漏传在 Mongoose 层静默默认成 App待可选回填完成后再评估是否收紧 schema 校验。在 packages/global/core/chat/type.ts 的ChatSchema中appId保留为物理字段名但 meta 注释明确说明其业务语义是sourceId可能是 appId 或 skillIdsourceType的 meta 说明旧数据可能缺失业务查询层按 app 兼容。注意这里的 z.default 仅作用于类型层空值归一Mongo schema 层不设 default配合source.ts测试断言sourceType可缺失但无默认值防止新写入漏传时被静默归为 App。chatitems新增同样的sourceType字段。Human/AI 占位写入、AI 消息更新、软删除、反馈、记录读取都必须带 source-aware 条件。ChatItemDBSchema中同样保留物理appId字段见 type.ts。chat_item_responses新增同样的sourceType字段。createWorkflowEntryNodeResponseWriter写入和读取都接收sourceType/sourceId避免 App 与 Skill Edit 在极端 ID 碰撞时串数据实现见 packages/service/core/chat/nodeResponseStorage.ts。ChatItemResponseSchema位于 type.ts。usages新增skillId字段。写入规则App chat写usage.appId不写usage.skillIdSkill Edit chat写usage.skillId不写usage.appId。usages和usage_items是计费审计数据不能随 chat 删除。app_chat_logs不新增sourceType。该表语义是 App 统计日志Skill Edit 不写入。app_chat_logs不纳入统一 chat 资源删除函数由 App 删除流程自行处理。八、Workflow RuntimerunningAppInfo 收敛runningAppInfo不保留 deprecated 的id或sandboxId字段最终结构为type RunningAppInfo { sourceType: ChatSourceTypeEnum; sourceId: string; teamId: string; tmbId: string; name: string; isChildApp?: boolean; };使用规则chat 持久化使用sourceType/sourceId计费App 写usage.appIdsourceIdSkill Edit 写usage.skillIdsourceIdnodeResponse使用sourceType/sourceId写入和读取stop/resume使用sourceType/sourceId/chatId作为 Redis namespaceApp 专属逻辑只能在sourceTypeapp时把sourceId当 AppId 使用Skill Edit 专属逻辑只能在sourceTypeskillEdit时把sourceId当 SkillId 使用。streamAgentSandboxInitStatus不再接收appId或sandboxId只接收sourceType/sourceId/userId/chatId内部调用getRunningSandboxId计算实际 sandboxId 后推送状态。相关测试覆盖可见 packages/service/test/core/workflow/workflowStatus.test.ts。九、Sandboxid 统一计算Sandbox id 统一由getRunningSandboxId计算function getRunningSandboxId({ sourceType, sourceId, userId, chatId }) { if (sourceType ChatSourceTypeEnum.app) { return generateSandboxId(sourceId, userId, chatId); } if (sourceType ChatSourceTypeEnum.skillEdit) { return getEditDebugSandboxId(sourceId); } const exhaustiveCheck: never sourceType; throw new Error(Unsupported chat source type: ${exhaustiveCheck}); }规则App chatgenerateSandboxId(appId, userId, chatId)Skill Edit固定getEditDebugSandboxId(skillId)编辑态沙盒与 Skill 生命周期绑定不随 chat 变化ensureAgentSandboxRuntime内部计算 sandboxId不从runningAppInfo读取底层 sandbox schema 如仍叫appId调用层必须集中封装避免业务代码把它误认为真实 AppId。在 packages/global/core/ai/sandbox/constants.ts 中generateSandboxId的 v2 实现为${sourceType.toLowerCase()}-${hashStr(${sourceId}-${userId}).slice(0, 16)}即带 sourceType 前缀的稳定物理资源 ID。skillEdit 编辑态实例SandboxTypeEnum.editDebug则通过 Skill 删除链路处理。十、systemVar 与 S3 文件隔离systemVarSkill Edit 场景不伪造appIdApp 场景继续注入appIdsourceIdSkill Edit 场景不注入appId内部运行态可以携带sourceType/sourceId是否暴露到变量面板另行评估。S3 文件 key新上传统一使用 source-aware keychat/${sourceType}/${sourceId}/${uid}/${chatId}/${filename}兼容规则旧 App 文件 keychat/${appId}/${uid}/${chatId}/${filename}继续可读新 App 文件使用chat/app/${appId}/...新 Skill Edit 文件使用chat/skillEdit/${skillId}/...历史 chat item 中保存的旧 key 不重写legacy key 只允许在sourceTypeapp的鉴权上下文中通过Skill Edit 不默认读取 legacy App key。在文件预览接口/api/core/chat/file/presignChatFileGetUrl上鉴权时同时校验sourceType/sourceId/uid/chatId与 S3 key 归属错误chatId预览返回unAuthChat见 projects/app/test/pages/api/core/chat/file/presignChatFileGetUrl.test.ts。S3 key 构造与解析测试位于 packages/service/test/common/s3/key.test.ts。十一、stop/resume 的 Redis key 隔离stop keyagent_runtime_stopping:${sourceType}:${sourceId}:${chatId}stream resume keystream:resume:data:${teamId}:${sourceType}:${sourceId}:${chatId} stream:resume:unavailable:${teamId}:${sourceType}:${sourceId}:${chatId} stream:resume:active:${teamId}:${sourceType}:${sourceId}:${chatId}stop、resume、catchUp、runtime status 的 key 构造必须集中到 helper禁止各处手写。对应的 key 格式测试在 packages/service/test/core/workflow/workflowStatus.test.ts 中专门防止 stop key 回退为裸sourceId/chatId。十二、删除与清理统一 source-aware 删除函数标准 chat 会话资源统一由deleteChatResourcesBySource处理实现见 packages/service/core/chat/delete.tschatschatitemschat_item_responseschat S3 文件chat 绑定的 sandbox 实例和资源不纳入统一函数app_chat_logsApp 日志域由 App 删除流程处理usages/usage_items计费审计域不能删除chat_input_guidesApp 配置域HelperBot chat独立命名空间。App 删除调用await deleteSandboxesByAppId(appId); deleteChatResourcesBySource({ sourceType: ChatSourceTypeEnum.app, sourceId: appId, includeLegacyApp: true, deleteSandboxResources: false });App 删除流程先按 App 维度删除 sandbox再调用统一 chat 资源删除函数统一函数此时不再重复删 chat 绑定 sandbox。App 日志仍由 App 删除流程单独删除。App 日志批量硬删 chat 调用deleteChatResourcesBySource({ sourceType: ChatSourceTypeEnum.app, sourceId: appId, chatIds, includeLegacyApp: true });该场景会删除指定 chat 绑定的 App sandbox对应测试 packages/service/test/core/chat/delete.test.ts 与 projects/app/test/api/core/chat/history/batchDelete.test.ts 确认批量删除 Skill Edit chat 不会误删 App chat sandbox。Skill 删除调用deleteChatResourcesBySource({ sourceType: ChatSourceTypeEnum.skillEdit, sourceId: skillId });旧 Skill Debug 初始化清理调用deleteChatResourcesBySource({ sourceType: ChatSourceTypeEnum.skillEdit, sourceId: skillId, legacySkillDebug: true });legacySkillDebugtrue只匹配{ appId: skillId, source: ChatSourceEnum.test, sourceType: { $exists: false } }十三、旧 Skill Debug 数据清理策略旧 Skill Debug chat不做迁移初始化阶段一次性硬删。原因旧数据缺少sourceType会和历史 App 兼容逻辑冲突旧唯一索引{ appId: 1, chatId: 1 }存在时旧 Skill row 会挡住新 Skill Edit rowSkill Preview 可能从 localStorage 复用旧chatId不清理会触发 duplicate key。清理识别规则扫描agentSkills._idSkill 数量预计不超过 1000用 skillId 集合匹配 legacy chats{ appId: { $in: skillIds }, source: test, sourceType: { $exists: false } }与apps._id做审计比对输出重复 ID 报告第一阶段不把碰撞作为自动剔除条件按 skillId 和 chat 游标分批硬删 chats/items/responses/S3Skill Edit 编辑沙盒由 Skill 删除链路处理不由 legacy chat 清理函数处理脚本支持dry-run、断点续跑和幂等重试。上线后保留一次 duplicate 兜底Skill Edit 创建 chat 遇到 duplicate 时如果确认是 legacy Skill Debug row则清理该单 chat 后重试一次。清理逻辑测试见 packages/service/test/core/chat/legacySkillDebugCleanup.test.ts。十四、索引与迁移上线前先创建新索引。chats新唯一索引db.chats.createIndex( { sourceType: 1, appId: 1, chatId: 1 }, { unique: true, name: sourceType_1_appId_1_chatId_1 } );创建前先做重复审计本地审计通过DUPLICATE_SOURCE_ROWS0、DUPLICATE_LEGACY_APP_ROWS0。旧{ appId: 1, chatId: 1 }唯一索引稳定后再删除。关键风险如果不回填历史 App 数据删除旧唯一索引后 DB 不会阻止以下逻辑重复{ appId, chatId, sourceType: { $exists: false } } { appId, chatId, sourceType: app }因此App 写入路径必须先用 source-aware query 命中 legacy row不能盲插新 App row。这也是为什么buildChatSourceQuery对 App 必须带$or兼容条件——先查询命中已有 legacy row 再做 upsert避免重复。chatitems和chat_item_responses需要补 source-aware非唯一复合索引用于记录读取、分页、删除和 nodeResponse 查询。旧索引先保留用于 legacy App 查询、rollback 和 explain 对比。schema 索引声明测试见 packages/service/test/core/chat/schema.test.ts它断言 chat 三表sourceType可缺失但无默认值并覆盖sourceType_1_appId_1_chatId_1唯一索引声明。十五、风险清单历史 App 数据漏查App 查询必须长期兼容sourceType缺失直到可选回填完成且确认不再需要 legacy 查询。旧唯一索引删除后的重复写入删除旧唯一索引前必须确认 App 写入路径不会在同一appId/chatId下创建 legacy row 和sourceTypeapprow 两份逻辑重复。usage 归因错误Skill Edit 只能写usage.skillId不能写usage.appId。浏览器集成复验中已确认Skill Preview 会话的chats/chatitems.sourceTypeskillEdit、物理appIdskillIdusage 只写skillId且appId为空App 会话则相反usage 写appId且skillIdnull。runningAppInfo 字段误用不保留runningAppInfo.id和runningAppInfo.sandboxId。所有运行态调用只使用sourceType/sourceIdsandboxId 统一计算。S3 或删除串源新文件 key、预览授权和删除前缀都必须按 source-aware 规则处理。App 删除清理 legacy new App 前缀Skill 删除只清理chat/skillEdit/${skillId}。十六、验证体系与测试路径整个改造有完整的测试与集成验证支撑主要测试路径如下可供排查问题时直接复用OpenAPI target schemapackages/global/test/openapi/core/chat/targetSchema.test.ts——覆盖 App/Skill target transform、必填 target 缺失、appIdskillId同传拒绝、可选 target 缺省/歧义以及/v1/audio/transcriptionsraw form schema 与 runtime transform 分层chat 三表 schema 与删除packages/service/test/core/chat/schema.test.ts、delete.test.ts、legacySkillDebugCleanup.test.tschat 主链路packages/service/test/core/chat/nodeResponseStorage.test.ts、saveChat.test.ts、controller.test.ts、title.test.ts文件上传/预览与鉴权projects/app/test/pages/api/core/chat/file/presignChatFilePostUrl.test.ts、presignChatFileGetUrl.test.ts、projects/app/test/service/support/permission/auth/chat.test.tsSkill Debug 回归projects/app/test/api/core/ai/skill/debugChat.test.ts 与debugSession/*系列list/records/delete/stop/chatItemDelete.permissionsandbox 与 workflowpackages/service/test/core/ai/sandbox/runtime/index.test.ts、packages/service/test/core/workflow/workflowStatus.test.ts。全量测试结果文档记录fastgpt/global81 个文件、1688 个测试fastgpt/app138 个文件、1018 个测试fastgpt/service217 个文件、2903 个测试跳过 2 个外部集成文件、35 个测试并完成pnpm --filter fastgpt/app typecheck与git diff --check复核。浏览器集成复验的关键结论包括App Chat 标准请求体为 OpenAPI raw{ appId, chatId }Skill Preview 请求体为{ skillId, chatId }均未向前端暴露内部sourceType/sourceIdSkill Preview 下不出现语音、TTS、input guide、ChatBox 内 App 沙盒入口编辑沙盒 ticket 请求体为{ skillId, chatId: edit-debug }keepalive 显示sourceTypeskillEdit/sourceId.../appIdNone/chatIdedit-debug本地 MongoDB 中chats/chatitems/chat_item_responses均写入正确的sourceTypeusage 归因正确。小结这套sourceType/sourceId双资源模型的核心价值在于用最小的存储迁移成本不重命名字段、不回填历史数据完成了最大的语义隔离收益——App 与 Skill Edit 共享整套标准 Chat 能力却互不污染 usage、日志、sandbox、S3 与 stop/resume 运行时状态。其设计精髓可归纳为四条原则对外 API 只暴露appId/skillId业务字段、对内业务层统一sourceType/sourceId、App 查询长期兼容缺失sourceType的 legacy 数据、所有新代码禁止绕过统一 helper 手写查询或 key 构造。对于需要在单套会话存储上承载多种资源类型的类似系统这套方案具备很高的直接参考价值。【免费下载链接】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),仅供参考