Ch35: FastGPT 源码走读——FlowNode 与 QA 对索引
Part 9 · 前置:Ch18 FastGPT 精读 · 后续:Ch36 AnythingLLM
结论先行:FastGPT 是 TypeScript Monorepo——大师读 packages/service/core/dataset/training/ 的 QA 对拆分 与 packages/service/support/permission/ 的 多租户 RBAC。核心差异化在 索引增强(LLM 生成 Q-A 对再 embed),不在 retriever 数学。企业场景卖的是「Team 隔离 + 工作流 + 配额」,WeKnora 可并行做 QA 索引而不替代三路检索。
一、日常类比
传统段落索引像 把说明书整页贴墙上:用户问「怎么连 Wi-Fi?」,墙上写的是「本设备支持蓝牙 5.0 和 Wi-Fi 6…」——问句和陈述句对不上。FastGPT 的 QA 拆分像 额外贴一墙便利贴:「Q: 支持哪些无线? A: 蓝牙5.0和Wi-Fi6」——检索时 问题对问题,口语 FAQ(E 组)更容易命中。
多租户像 同一栋写字楼不同公司:电梯(平台)共用,但每家公司只能进自己的楼层(teamId filter),API Key 和知识库不能串。
二、Commit / 版本 Pin 说明
| 项 | 建议 |
|---|---|
| 仓库 | labring/FastGPT |
| 分支 | main + clone 日 2026-06 的 commit hash 写入笔记 |
| 栈 | Next.js + MongoDB + PostgreSQL(pgvector) |
| 部署 | Sealos 模板或 pnpm i && pnpm dev(以仓库 README 为准) |
git clone https://github.com/labring/FastGPT.git
cd FastGPT && git rev-parse HEAD > ~/fastgpt-pin.txt
pnpm install && pnpm dev # 或 docker 一键
Monorepo 路径以 packages/ 为准;若仓库结构调整,以 pin commit 下 rg FlowNode 结果为准。
三、目录地图(QA + 多租户)
| 路径 | 职责 |
|---|---|
packages/global/core/workflow/type.d.ts |
FlowNode 类型枚举、边与变量 |
packages/service/core/workflow/dispatch/ |
各节点执行器 dispatchDatasetSearch, dispatchLLM |
packages/service/core/dataset/ |
Dataset / Collection / Data CRUD |
packages/service/core/dataset/training/ |
generateQA() QA 对生成与向量化 |
packages/service/core/dataset/search/ |
pgvector + Mongo 全文双路 |
packages/service/support/permission/ |
authDataset(), RBAC |
packages/service/support/user/ |
User / Team / MemberGroup |
projects/app/ |
Next.js 前端 |
四、一条具体调用链(文档上传 → QA 索引 → 检索)
projects/app 上传 UI
→ projects/app/src/pages/api/core/dataset/collection/create.ts # API 路由
→ packages/service/support/permission/auth/dataset.ts # teamId 鉴权
→ packages/service/core/dataset/collection/controller.ts # 创建 collection
→ packages/service/core/dataset/training/generateQA.ts # 调 LLM 生成多组 (Q,A)
→ packages/service/core/ai/openai/index.ts # chatCompletion 抽 QA
→ packages/service/core/dataset/training/generateVector.ts # question 字段 embed
→ PostgreSQL pgvector + MongoDB 全文索引
--- 用户问答 ---
→ packages/service/core/workflow/dispatch/index.ts # dispatchWorkFlow
→ packages/service/core/workflow/dispatch/datasetSearch.ts # 双路召回
→ packages/service/core/dataset/search/controller.ts # embeddingSearch + fullTextSearch
→ packages/service/core/workflow/dispatch/llm.ts # 拼 context → 生成
大师理解:QA 拆分发生在 training 链,检索仍走 dataset/search/——是索引增强,不是替换向量检索。
五、grep 起手式
cd FastGPT
rg -n "generateQA|generateVector" packages/service/core/dataset/
rg -n "FlowNodeTypeEnum|datasetNode" packages/global/core/workflow/
rg -n "teamId|authDataset" packages/service/support/
rg -l "fullTextSearch|embeddingSearch" packages/service/core/dataset/search/
rg -i "qa.*split|question.*answer" packages/ --glob '*.{ts,tsx}' | head -20
六、与 Dify / WeKnora / Lab
| 维度 | FastGPT | Dify | WeKnora |
|---|---|---|---|
| 栈 | TS 全栈 | Py + Next | Go + Py |
| 强项 | QA 索引 + 多租户 SaaS | Provider + Workflow | 三路 + RRF |
| FAQ 场景 | E 组预期优 | 中 | 中(可加 QA 索引) |
Lab:queries-smoke-20.jsonl E 组 + faq-version-conflict.md → Ch37。
七、动手练习(45–60 分钟)
| 时段 | 任务 |
|---|---|
| 0–15 min | 本地起 FastGPT,创建 Team + Dataset,开启 QA 拆分(若 UI 有开关则打开) |
| 15–30 min | 上传 faq-version-conflict.md,在数据预览看是否生成 Q-A 对 |
| 30–45 min | 读 generateQA.ts,找出 prompt 模板位置与写入字段(question vs content) |
| 45–60 min | 用两个不同 Team 账号验证 A 团队看不到 B 团队 Dataset;写 5 行:QA 索引如何解决「问句 vs 陈述句」鸿沟 |
章末自检
- 解释 QA 对索引与「纯段落 chunk」在召回上的差异
- 说出
teamId在查询链路的 filter 注入点(≥1 个文件名) - 从 upload API 指到
generateQA的连续 3 个文件 - 对比 FastGPT 与 WeKnora:为何 TS Monorepo 利于 SaaS 交付