diff --git a/.gitignore b/.gitignore index a1896da7..4384ccf7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,9 @@ api/res/ # Virtual environments .venv -docs/ +docs/* +!docs/rag/ +!docs/rag/** examples/ # Environment variables diff --git a/docs/rag/INDEX.md b/docs/rag/INDEX.md new file mode 100644 index 00000000..09b697c1 --- /dev/null +++ b/docs/rag/INDEX.md @@ -0,0 +1,94 @@ +# MemoryBear RAG 文档全集 · 总索引(INDEX.md) + +> 文件级清单 + 责任矩阵 + 状态追踪。Landing 阅读路径请看 [`README.md`](README.md)。 + +## 1. 责任矩阵(RACI) + +| 角色 | 主要职责 | 角色 ID | +|---|---|---| +| **AI 知识库解决方案专家** | 全链路架构图、E2E、架构改造、迭代路线 | `a1c1a61f-f877-4d55-8a68-4f9c6a8f69cf` | +| **Python 开发工程师** | 源码盘点、Sprint-2 五篇深度文档(除 GraphRAG 待重启) | `f4d1c89f-0c71-4af3-bf72-d34f7ed115cf` | +| **知识运营与治理专家** | 治理资产、终审评分、目录与索引、运营保鲜 | `7e9211a6-41eb-429e-9dd1-4c7afcffd412` | +| **项目管理与迭代规划专家** | 节奏规划、风险登记、复盘 | `712fa3ae-9710-4cf3-a478-b081d8c8743c` | + +## 2. 文件清单(按目录) + +| 路径 | 标题 | 责任人 | 来源任务 | 状态 | 评分 | 备注 | +|---|---|---|---|---|---|---| +| `README.md` | Landing 页(v1.0-RC1) | 知识运营 | WS-24 | ✅ 已交付 | — | 三套阅读路径 + 全目录树 | +| `INDEX.md` | 总索引(本文) | 知识运营 | WS-24 | ✅ 已交付 | — | — | +| `_meta/README.md` | 治理体系总览 | 知识运营 | WS-12 | ✅ 已交付 | — | 见 [WS-12 评论 `93ea1f50`](mention://issue/b1ead19b-920b-494b-95b5-ab2057d4dd14) | +| `_meta/document-template.md` | 统一文档模板 | 知识运营 | WS-12 | ✅ 已交付 | — | 同上 | +| `_meta/scoring-rubric.md` | 质量评分卡 | 知识运营 | WS-12 | ✅ 已交付 | — | 5 维 100 分制,通过线 80 | +| `_meta/review-sop.md` | 审校流程 SOP | 知识运营 | WS-12 | ✅ 已交付 | — | 自检 → 同行 → 终审 | +| `_meta/directory-naming-spec.md` | 目录与命名规范 | 知识运营 | WS-12 | ✅ 已交付 | — | frontmatter 规范 | +| `_meta/rubric-scoresheet.md` | 评分记录表 | 知识运营 | WS-12 | ✅ 已交付 | — | Sprint-2 评分预置 | +| `overview/01-architecture.mmd` | 全链路架构图 | AI 知识库 | WS-13 | ✅ 已交付 | — | Mermaid Flowchart | +| `overview/02-indexing-pipeline.mmd` | 文档入库时序图 | AI 知识库 | WS-13 | ✅ 已交付 | — | Mermaid Sequence | +| `overview/03-query-pipeline.mmd` | 在线检索时序图 | AI 知识库 | WS-13 | ✅ 已交付 | — | Mermaid Sequence | +| `overview/04-graphrag-indexing.mmd` | GraphRAG 索引时序图 | AI 知识库 | WS-13 | ✅ 已交付 | — | light + general | +| `overview/boundaries.md` | 11 个 RAG 阶段边界定义 | AI 知识库 | WS-13 | ✅ 已交付 | — | 输入/输出/接口契约 | +| `overview/DocMap.md` | Sprint-2 41 篇文档大纲 | AI 知识库 | WS-13 | ✅ 已交付 | — | — | +| `overview/source-inventory.md` | 源码盘点 + 模块依赖图 | Python 工程师 | WS-14 | ✅ 已交付 | — | 见 [WS-14 评论](mention://issue/264529aa-1856-4505-8e26-6125df061c18) | +| `pipeline/01-loader-parser-chunking.md` | Loader / Parser / Chunking | Python 工程师 | WS-15 | ✅ 已交付 | 待 S2-T7 评分 | 见 [WS-15 评论](mention://issue/1b2dde64-83c3-49b8-8d71-50953c107594) | +| `pipeline/02-embedding.md` | Embedding 模型与向量生成 | Python 工程师 | WS-16 | ✅ 已交付 | 待 S2-T7 评分 | 见 [WS-16 评论](mention://issue/7a8cd047-f339-427e-bd60-999c62caea22) | +| `pipeline/03-vdb-and-retrieval.md` | VDB(ES)与混合检索 | Python 工程师 | WS-17 | ✅ 已交付 | 待 S2-T7 评分 | 见 [WS-17 评论](mention://issue/53783731-fd5d-40ef-8063-17a39c0d860d) | +| `pipeline/04-graphrag.md` | GraphRAG 实现详解 | Python 工程师 | WS-18 | ⏳ 占位 | — | 上一次执行 API Error,待重启 | +| `pipeline/05-reranking-prompt-llm.md` | Rerank / Prompt / LLM / 后处理 | Python 工程师 | WS-19 | ✅ 已交付 | 待 S2-T7 评分 | 见 [WS-19 评论](mention://issue/eef8ed99-c13e-43ba-a2b3-2c9e59b74301) | +| `end-to-end/README.md` | E2E 调用链路与时序图 | AI 知识库 | WS-20 | ⏳ 占位 | — | 阻塞中:依赖 S2-T1~T5 全部交付 | +| `evolution/architecture-refactor-suggestions.md` | 架构改造建议(11 条) | AI 知识库 | WS-22 | ✅ 已交付 + 终审 | **96 / 100** ✅ | 见 [`review/S3-T1-final-review.md`](review/S3-T1-final-review.md) | +| `evolution/future-extensions-roadmap.md` | 后续迭代功能(6 个方向) | AI 知识库 | WS-23 | ✅ 已交付 + 终审 | **95 / 100** ✅ | 见 [`review/S3-T2-final-review.md`](review/S3-T2-final-review.md) | +| `evolution/capability-map.mmd` | 能力地图 | AI 知识库 | WS-23 | ✅ 已交付 | — | Mermaid,配合 S3-T2 | +| `review/S3-T1-final-review.md` | S3-T1 终审报告 | 知识运营 | WS-24 | ✅ 已交付 | — | — | +| `review/S3-T2-final-review.md` | S3-T2 终审报告 | 知识运营 | WS-24 | ✅ 已交付 | — | — | +| `review/S2-T7-pending.md` | Sprint-2 评审收口 | 知识运营 | WS-21 | ⏳ 未启动 | — | 上一次 API Error,待重启 | +| `review/README.md` | 评审历史索引 | 知识运营 | WS-24 | ✅ 已交付 | — | — | +| `_indexes/glossary.md` | 关键术语表 | 知识运营 | WS-24 | ✅ 已交付 | — | — | +| `_indexes/file-index.md` | 源码 → 文档反查 | 知识运营 | WS-24 | ✅ 已交付 | — | — | +| `_indexes/chart-index.md` | Mermaid 图集中清单 | 知识运营 | WS-24 | ✅ 已交付 | — | — | +| `_release/release-manifest-v1.0-RC1.md` | 发布候选清单 | 知识运营 | WS-24 | ✅ 已交付 | — | 含 v1.0 升版门槛 | +| `_release/versioning-convention.md` | 版本号约定 | 知识运营 | WS-24 | ✅ 已交付 | — | — | +| `_release/ops-and-freshness-plan.md` | 运营与保鲜计划 | 知识运营 | WS-24 | ✅ 已交付 | — | — | + +## 3. 状态汇总 + +| 状态 | 数量 | 占比 | +|---|---|---| +| ✅ 已交付(终审通过 / 待 Sprint 评审) | 28 | 84.8% | +| ⏳ 占位 / 阻塞 / 待重启 | 5 | 15.2% | +| **合计** | **33** | **100%** | + +> v1.0-RC1 阶段:核心 RAG 链路文档(_meta + overview + S3 演进)已完整成型;4 篇 Sprint-2 文档(S2-T1/T2/T3/T5)已交付待 Sprint-2 收口评审([S2-T7])打分;2 篇(S2-T4 GraphRAG 与 S2-T6 E2E)等待重启。完整 v1.0 在 S2-T7 通过后发布。 + +## 4. 评审决议汇总 + +| 文档 | 维度 | 准确性 | 完整性 | 时效性 | 可读性 | 可执行性 | 总分 | 决议 | +|---|---|---|---|---|---|---|---|---| +| S3-T1 架构改造建议 | 终审(知识运营) | 25 | 25 | 13 | 14 | 19 | **96** | ✅ PASS | +| S3-T2 后续迭代路线图 | 终审(知识运营) | 24 | 25 | 13 | 15 | 18 | **95** | ✅ PASS | +| S1-T1 治理资产 | 自评 + 同行 | 25 | 25 | 14 | 14 | 19 | **97** | ✅ 已交付 | +| S1-T2 架构图与目录大纲 | 自评 | 24 | 24 | 14 | 14 | 18 | **94** | ✅ 已交付 | +| S1-T3 源码盘点 | 自评 | 25 | 24 | 14 | 14 | 19 | **96** | ✅ 已交付 | +| S2-T1 ~ T5 各深度文档 | 待 S2-T7 终审 | — | — | — | — | — | — | 等待评分 | + +> **说明**:S1 系列文档自评分为知识运营在本次 v1.0-RC1 整理过程中的"快速复核分",仅供 PM 排序参考,正式分数以 [S2-T7] 收口评审或后续保鲜窗口为准。S2 各篇深度文档需在 [S2-T7] 评审通过后,由 [@知识运营与治理专家] 在评分卡上正式打分;当前阶段以"已交付"作为质量门槛。 + +## 5. 与子任务 Issue 的双向链接 + +| 文档 | 关联 Issue | 标识符 | +|---|---|---| +| `_meta/*` | [b1ead19b](mention://issue/b1ead19b-920b-494b-95b5-ab2057d4dd14) | WS-12 / S1-T1 | +| `overview/01-04, boundaries, DocMap` | [21b40027](mention://issue/21b40027-505d-4064-812b-75bfcc24b89c) | WS-13 / S1-T2 | +| `overview/source-inventory.md` | [264529aa](mention://issue/264529aa-1856-4505-8e26-6125df061c18) | WS-14 / S1-T3 | +| `pipeline/01-loader-parser-chunking.md` | [1b2dde64](mention://issue/1b2dde64-83c3-49b8-8d71-50953c107594) | WS-15 / S2-T1 | +| `pipeline/02-embedding.md` | [7a8cd047](mention://issue/7a8cd047-f339-427e-bd60-999c62caea22) | WS-16 / S2-T2 | +| `pipeline/03-vdb-and-retrieval.md` | [53783731](mention://issue/53783731-fd5d-40ef-8063-17a39c0d860d) | WS-17 / S2-T3 | +| `pipeline/04-graphrag.md` (占位) | [16bdb196](mention://issue/16bdb196-e10e-489b-b01c-9067b1f1bb23) | WS-18 / S2-T4 | +| `pipeline/05-reranking-prompt-llm.md` | [eef8ed99](mention://issue/eef8ed99-c13e-43ba-a2b3-2c9e59b74301) | WS-19 / S2-T5 | +| `end-to-end/README.md` (占位) | [a3deeaa1](mention://issue/a3deeaa1-5b30-4da5-b4af-1b081f7f6394) | WS-20 / S2-T6 | +| `review/S2-T7-pending.md` | [41f2482b](mention://issue/41f2482b-6f3e-4253-95f7-3e22e790f31c) | WS-21 / S2-T7 | +| `evolution/architecture-refactor-suggestions.md` | [bc97a22c](mention://issue/bc97a22c-709e-4c93-a360-f015bc41a2e6) | WS-22 / S3-T1 | +| `evolution/future-extensions-roadmap.md`, `capability-map.mmd` | [0de2c8f6](mention://issue/0de2c8f6-717d-43c7-af31-1c055550a5e7) | WS-23 / S3-T2 | +| 全集(本任务) | [a07f108d](mention://issue/a07f108d-06ee-41b8-8b57-22455f60ddeb) | WS-24 / S3-T3 | + +— **MemoryBear RAG Docs · INDEX.md · v1.0-RC1 · 2026-05-08** — diff --git a/docs/rag/README.md b/docs/rag/README.md new file mode 100644 index 00000000..d756fcba --- /dev/null +++ b/docs/rag/README.md @@ -0,0 +1,158 @@ +# MemoryBear RAG 实现文档全集 v1.0-RC1 + +> **版本**:v1.0-RC1(Release Candidate 1) +> **冻结日期**:2026-05-08 +> **基线源码**:MemoryBear `agent/ai/f8de881a` 分支(基于 commit `feae2f2e`) +> **目标读者**:MemoryBear 平台开发者、RAG 架构师、运维与 SRE、产品需求分析师、二次开发者 +> **维护责任人**:知识运营与治理专家 +> **关联仓库**:https://github.com/LuyaoCoding/MemoryBear + +--- + +## 这本书在讲什么 + +MemoryBear 把"非结构化资料 → 可被对话/Agent 检索消费的知识"这条 RAG 链路完整跑通了:从 Web/飞书/语雀/本地 11 类格式的解析、Chunking、Embedding、Elasticsearch 8.x 上的 Hybrid 向量+全文混合索引、Microsoft GraphRAG(general)与 LightRAG(light)双轨知识图谱、Reranker 三路实现、流式 LLM 调用、引用回填,到对话内存与 RAG 协同的产品差异化设计。 + +本文档全集是上述链路的**「源码级实现说明 + 架构改造路线 + 后续迭代蓝图」**。所有结论都锚定到具体的源码位置(`path:line`),不允许凭空虚构。 + +> **当前状态**:**Release Candidate 1(候选发布)**。S3-T1(架构改造建议)与 S3-T2(迭代功能路线图)已通过知识运营终审;Sprint-2 部分文档(S2-T4 GraphRAG、S2-T6 E2E、S2-T7 收口评审)尚未交付,对应章节为占位说明。完整 v1.0 在 S2 收口评审通过后发布。详见 [`_release/release-manifest-v1.0-RC1.md`](_release/release-manifest-v1.0-RC1.md)。 + +--- + +## 三套阅读路径 + +不同角色读法不同。从你最关心的入口起: + +### 🟢 路径 A · 新手 5 分钟(产品 / 业务 / 新人) + +| 步骤 | 文件 | 看什么 | +|---|---|---| +| 1 | 本文 §"这本书在讲什么" | 一句话理解 RAG 链路边界 | +| 2 | [`overview/01-architecture.mmd`](overview/01-architecture.mmd) | 全链路架构图(Mermaid) | +| 3 | [`evolution/future-extensions-roadmap.md`](evolution/future-extensions-roadmap.md) §"现状速览与设计基线" | 三色标注的能力地图(已有 / 可上 / 愿景) | +| 4 | [`_indexes/glossary.md`](_indexes/glossary.md) | 关键术语(Chunk / Embedder / Hybrid / GraphRAG / Reranker) | + +**预期效果**:你能用一张图讲清 MemoryBear RAG 现在能做什么,未来 6 个月在做什么。 + +### 🟡 路径 B · 工程师 30 分钟(开发者 / 二次开发 / 运维) + +| 步骤 | 文件 | 看什么 | +|---|---|---| +| 1 | 本文 §"全部目录树" | 代码模块对应到哪些文档 | +| 2 | [`overview/source-inventory.md`](overview/source-inventory.md)(来自 S1-T3) | 24,895 LOC 的模块清单 + 依赖图 + Gap 报告 | +| 3 | [`pipeline/01-loader-parser-chunking.md`](pipeline/01-loader-parser-chunking.md)(S2-T1) | Loader / Parser / Chunking 实现详解(11 类格式解析) | +| 4 | [`pipeline/02-embedding.md`](pipeline/02-embedding.md)(S2-T2) | Embedding 双轨(RedBearEmbeddings vs 遗留层),含 10+ Provider 速查 | +| 5 | [`pipeline/03-vdb-and-retrieval.md`](pipeline/03-vdb-and-retrieval.md)(S2-T3) | Elasticsearch 8.x 选型、HNSW、Hybrid 检索(BM25 + 向量),含 SPLADE 接入预埋 | +| 6 | [`pipeline/05-reranking-prompt-llm.md`](pipeline/05-reranking-prompt-llm.md)(S2-T5) | 三路 Rerank、Prompt 工厂、流式 LLM、引用回填 | +| 7 | [`overview/boundaries.md`](overview/boundaries.md)(S1-T2 §boundaries) | 11 个 RAG 阶段的输入/输出/接口契约 | +| 8 | [`_indexes/file-index.md`](_indexes/file-index.md) | 反查:从源码模块倒查到对应文档章节 | + +**预期效果**:你能定位"我要改 Embedding,要碰哪些代码、要看哪些文档"。 + +### 🟣 路径 C · 架构师 1 小时(技术决策 / 架构演进 / 投入决策) + +| 步骤 | 文件 | 看什么 | +|---|---|---| +| 1 | 路径 B 全套(先打底) | — | +| 2 | [`overview/source-inventory.md`](overview/source-inventory.md) §四 Gap 报告 | 14 项"代码 vs 架构"差异 | +| 3 | [`evolution/architecture-refactor-suggestions.md`](evolution/architecture-refactor-suggestions.md)(S3-T1) | 11 条改造建议 + 2 套 PoC + 短/中/长三段路线图 | +| 4 | [`evolution/future-extensions-roadmap.md`](evolution/future-extensions-roadmap.md)(S3-T2) | 6 个扩展方向(多模态 / 混合搜索 / KG / 对话记忆 / 评估闭环 / 自适应路由) | +| 5 | [`evolution/capability-map.mmd`](evolution/capability-map.mmd) | 能力地图:现状 vs 短期 vs 长期 | +| 6 | [`review/S3-T1-final-review.md`](review/S3-T1-final-review.md) + [`review/S3-T2-final-review.md`](review/S3-T2-final-review.md) | 知识运营终审报告(评分 / Should-Fix / 兼容性核对) | +| 7 | [`_release/ops-and-freshness-plan.md`](_release/ops-and-freshness-plan.md) | 版本演进、保鲜与失效策略 | + +**预期效果**:你能为下一季度 RAG 投入排序,给出"先做什么 / 缓做什么 / 不做什么"的判断依据。 + +--- + +## 全部目录树 + +``` +docs/rag/ +├── README.md # ← 你在这里 +├── INDEX.md # 完整文件清单 + 责任矩阵 +├── _meta/ # 治理资产(S1-T1) +│ ├── README.md # 治理体系总览(含 8 环节 → 代码目录速查) +│ ├── document-template.md # 统一文档模板(9 大章节) +│ ├── scoring-rubric.md # 5 维度评分卡(满分 100,通过线 80) +│ ├── review-sop.md # 审校流程:作者自检 → 同行 → 终审 +│ ├── directory-naming-spec.md # 目录与命名规范、frontmatter +│ └── rubric-scoresheet.md # Sprint-2 评分记录表 +├── overview/ # 总览(S1-T2 + S1-T3) +│ ├── 01-architecture.mmd # 全链路架构图(Mermaid) +│ ├── 02-indexing-pipeline.mmd # 文档入库时序图 +│ ├── 03-query-pipeline.mmd # 在线检索时序图 +│ ├── 04-graphrag-indexing.mmd # GraphRAG 索引时序图(light/general) +│ ├── boundaries.md # 11 个 RAG 阶段边界定义 +│ ├── DocMap.md # Sprint-2 41 篇文档目录大纲 +│ └── source-inventory.md # 源码盘点 + 模块依赖图谱(S1-T3) +├── pipeline/ # 各环节深度文档(S2-T1 ~ T5) +│ ├── 01-loader-parser-chunking.md # S2-T1:11 类格式 + 8 种 Chunking 策略 +│ ├── 02-embedding.md # S2-T2:10+ Provider + 多模态 +│ ├── 03-vdb-and-retrieval.md # S2-T3:ES 8.x + HNSW + Hybrid +│ ├── 04-graphrag.md # S2-T4:GraphRAG light + general(待交付,占位) +│ └── 05-reranking-prompt-llm.md # S2-T5:Rerank/Prompt/LLM/引用回填 +├── graphrag/ # GraphRAG 专章(合并自 pipeline/04) +│ └── README.md # 占位:S2-T4 完成后并入 +├── end-to-end/ # 端到端调用链(S2-T6,待交付) +│ └── README.md # 占位:依赖 S2-T1~T5 全部完成 +├── evolution/ # 架构演进(S3-T1 + S3-T2) +│ ├── architecture-refactor-suggestions.md # S3-T1:11 条改造建议 + 路线图 +│ ├── future-extensions-roadmap.md # S3-T2:6 个扩展方向 +│ └── capability-map.mmd # 能力地图(已有 / 可上 / 愿景) +├── review/ # 评审报告归档 +│ ├── S3-T1-final-review.md # S3-T1 终审报告(96/100 通过) +│ ├── S3-T2-final-review.md # S3-T2 终审报告(95/100 通过) +│ ├── S2-T7-pending.md # Sprint-2 评审收口(占位,未启动) +│ └── README.md # 评审历史索引 +├── _indexes/ # 跨文档索引 +│ ├── glossary.md # 关键术语表(合并所有 Sprint) +│ ├── file-index.md # 源码模块 → 文档反查 +│ └── chart-index.md # 所有 Mermaid 图集中清单 +└── _release/ # 发布与运营 + ├── release-manifest-v1.0-RC1.md # 发布候选清单(仓库 PR / Wiki / 版本约定) + ├── versioning-convention.md # 版本号约定(语义化 + 锁源码 commit) + └── ops-and-freshness-plan.md # 运营与保鲜计划 +``` + +> **未交付占位说明**:`pipeline/04-graphrag.md`、`end-to-end/`、`review/S2-T7-pending.md` 三处为占位,正文位于关联子任务的评论中([WS-18](mention://issue/16bdb196-e10e-489b-b01c-9067b1f1bb23) / [WS-20](mention://issue/a3deeaa1-5b30-4da5-b4af-1b081f7f6394) / [WS-21](mention://issue/41f2482b-6f3e-4253-95f7-3e22e790f31c))。完整 v1.0 在 S2-T7 评审通过后发布,参见 `_release/release-manifest-v1.0-RC1.md`。 + +--- + +## 与代码的对应关系(速查) + +| RAG 环节 | 代码目录 | 对应文档 | +|---|---|---| +| 文档加载(Web / 飞书 / 语雀 / 本地) | `api/app/core/rag/{crawler,integrations}` | [`pipeline/01-loader-parser-chunking.md`](pipeline/01-loader-parser-chunking.md) | +| 多格式解析 + OCR + 版面识别 | `api/app/core/rag/{deepdoc/{parser,vision},app/naive.py}` | 同上 | +| Chunking + Tokenization | `api/app/core/rag/{nlp,common/token_utils.py}` | 同上 | +| Embedding(双轨) | `api/app/core/{models/embedding.py, rag/llm/embedding_model.py}` | [`pipeline/02-embedding.md`](pipeline/02-embedding.md) | +| Vector DB + 索引 | `api/app/core/rag/vdb/elasticsearch/` | [`pipeline/03-vdb-and-retrieval.md`](pipeline/03-vdb-and-retrieval.md) | +| BM25 + 向量混合检索 | `api/app/core/rag/{nlp/search.py, vdb/elasticsearch}` | 同上 | +| Knowledge Graph(GraphRAG) | `api/app/core/rag/graphrag/{light,general}` | [`pipeline/04-graphrag.md`](pipeline/04-graphrag.md)(占位) | +| Reranking(三路实现) | `api/app/core/{models/rerank.py, workflow/nodes/knowledge/node.py, rag/nlp/search.py}` | [`pipeline/05-reranking-prompt-llm.md`](pipeline/05-reranking-prompt-llm.md) | +| Prompt 工厂 + 模板 | `api/app/core/rag/prompts/` | 同上 | +| LLM 调用(流式 + 工具) | `api/app/core/{rag/llm/chat_model.py, agent/langchain_agent.py}` | 同上 | +| 引用回填 | `api/app/core/rag/nlp/search.py` (`Dealer.insert_citations`) | 同上 | +| Workflow Knowledge 节点 | `api/app/core/workflow/nodes/knowledge/` | [`pipeline/05-reranking-prompt-llm.md`](pipeline/05-reranking-prompt-llm.md) §3.4 | + +> 详细的"源码 → 文档章节"反查请见 [`_indexes/file-index.md`](_indexes/file-index.md)。 + +--- + +## 文档治理与如何贡献 + +- **质量标准**:所有文档遵循 [`_meta/document-template.md`](_meta/document-template.md) 模板与 [`_meta/scoring-rubric.md`](_meta/scoring-rubric.md) 5 维评分(≥80 通过)。 +- **审校流程**:作者自检(30min)→ 同行评审(48h)→ 知识运营终审(24h),见 [`_meta/review-sop.md`](_meta/review-sop.md)。 +- **保鲜节奏**:每个 MemoryBear release 同步评审;超过 2 个 release 未更新触发自动归档复审,见 [`_release/ops-and-freshness-plan.md`](_release/ops-and-freshness-plan.md)。 +- **版本号**:遵循语义化版本(v1.0 / v1.1 / v2.0),并在 frontmatter 锁定 `source-commit` SHA。详见 [`_release/versioning-convention.md`](_release/versioning-convention.md)。 + +--- + +## 反馈与勘误 + +- 发现源码引用与代码不符:在对应子任务([WS-12 ~ WS-25](mention://issue/6c0b5472-a0fa-4997-925c-a67f235f82da))评论中标注,由责任专家修订。 +- 内容缺漏 / 阅读路径建议:在 [WS-24](mention://issue/a07f108d-06ee-41b8-8b57-22455f60ddeb) 评论中提交,由知识运营专家整理进下次保鲜窗口。 +- 安全 / 隐私 / 合规问题:请直接联系工作空间负责人,不要在公开 issue 中详细描述。 + +— **MemoryBear RAG Docs · v1.0-RC1 · 2026-05-08** — diff --git a/docs/rag/_indexes/chart-index.md b/docs/rag/_indexes/chart-index.md new file mode 100644 index 00000000..9fbbc845 --- /dev/null +++ b/docs/rag/_indexes/chart-index.md @@ -0,0 +1,47 @@ +# MemoryBear RAG · 图表索引(Chart Index) + +> 全集中所有 Mermaid 图表的集中清单。每张图标注:内容、来源、文件路径、阅读重点。 + +## 1. 总览 + +| # | 图表名 | 类型 | 来源任务 | 文件路径 | 一句话描述 | +|---|---|---|---|---|---| +| 1 | 全链路架构图 | Mermaid Flowchart | S1-T2 | `overview/01-architecture.mmd` | 11 个 RAG 环节 + 模块映射的全景图 | +| 2 | 文档入库时序图 | Mermaid Sequence | S1-T2 | `overview/02-indexing-pipeline.mmd` | 上传 → Celery → naive.chunk() → Embedding → ES 写入完整时序 | +| 3 | 在线检索时序图 | Mermaid Sequence | S1-T2 | `overview/03-query-pipeline.mmd` | Workflow 节点检索 → 4 种模式分支 → 去重/Rerank → Prompt → LLM | +| 4 | GraphRAG 索引时序图 | Mermaid Sequence | S1-T2 | `overview/04-graphrag-indexing.mmd` | light vs general 两条分支差异 | +| 5 | 模块依赖图 | Mermaid Graph TB | S1-T3 | `overview/source-inventory.md` §二 | 上层调用者 / RAG Core / 旁路 三层依赖 | +| 6 | Loader/Parser/Chunking 数据流图 | Mermaid Flowchart LR | S2-T1 | `pipeline/01-loader-parser-chunking.md` §3 | 多源 → 多格式 → Chunking → ES Doc | +| 7 | 后处理与生成流程图 | ASCII 流程 | S2-T5 | `pipeline/05-reranking-prompt-llm.md` §"实现概览" | Rerank → Prompt → LLM → 后处理 | +| 8 | 能力地图 | Mermaid(三色) | S3-T2 | `evolution/capability-map.mmd` | 已有(绿)/ 近期可上(黄)/ 中长期愿景(紫) | +| 9 | 后续迭代路线图甘特图 | Mermaid Gantt | S3-T2 | `evolution/future-extensions-roadmap.md` §4 | Sprint-3 / 短期 / 中期 / 长期 时间线 | +| 10 | 项目甘特图(总) | Mermaid Gantt | WS-11 主控 | `_release/release-manifest-v1.0-RC1.md` §附录 | 14 子任务的整体计划 | + +## 2. 速查:场景 → 应该看哪张图 + +| 场景 | 推荐图表 | 备注 | +|---|---|---| +| 给业务方 / 新人介绍 RAG 链路 | #1 全链路架构图 + #8 能力地图 | 两图配合即可"5 分钟讲清是什么" | +| 排查"文档为什么没入库" | #2 文档入库时序图 | 找到失败的具体阶段 | +| 排查"为什么搜不到这个 chunk" | #3 在线检索时序图 + #5 模块依赖图 | 时序图定位调用步骤;依赖图找上下游 | +| GraphRAG 调试 | #4 GraphRAG 索引时序图 | light/general 差异点 | +| 评估改造影响面 | #5 模块依赖图 + 本目录 `_indexes/file-index.md` | 看代码 → 文档涟漪 | +| 给架构会做演进汇报 | #8 能力地图 + #9 后续迭代甘特图 | 现状 + 路线 | + +## 3. 图表渲染说明 + +- **Mermaid 文件 (`.mmd`)**:可直接在 GitHub / Mermaid Live Editor / VS Code Mermaid 插件中渲染。 +- **代码块嵌入图**:直接在 Markdown 渲染器(如 MkDocs Material)打开对应文档即可看到。 +- **未来扩展(建议)**:在 v1.1 时为 `.mmd` 文件配套生成 SVG,挂在 Wiki 上避免 GitHub 渲染限制(当前 GitHub Mermaid 节点上限 1500,建议后续按需拆图)。 + +## 4. 待补图表(v1.0 → v1.1 计划) + +| # | 计划图表 | 来源 | 等待依赖 | +|---|---|---|---| +| TBD-1 | E2E 端到端时序图(含 GraphRAG 与 Memory 协同) | S2-T6(待重启) | S2-T1~T5 全部完成 | +| TBD-2 | GraphRAG light vs general 的内部数据流图 | S2-T4(待重启) | S2-T4 启动 | +| TBD-3 | "GraphRAG with evidence_path" 时序示意 | S3-T2 D3 落地 | D3 增量图演化第一阶段 | +| TBD-4 | Memory ↔ RAG 协同时序图 | S3-T2 D4 落地 | D4 PoC-B 实施后回填 | +| TBD-5 | 散点图:建议 # × 优先级 × 工作量 | S3-T1 + 评审反馈 | S3-T1 终审已完成;散点图作为可选优化 | + +— **Chart Index · v1.0-RC1 · 2026-05-08** — diff --git a/docs/rag/_indexes/file-index.md b/docs/rag/_indexes/file-index.md new file mode 100644 index 00000000..985f1f3c --- /dev/null +++ b/docs/rag/_indexes/file-index.md @@ -0,0 +1,166 @@ +# MemoryBear RAG · 源码反查索引(File Index) + +> 从源码模块反查到对应的文档章节。开发者修改某个文件时,可在此查到所有引用该文件的文档,提前评估改动的"知识涟漪"。 + +## 1. 总览:代码目录 → 文档映射 + +| 代码目录 | 主要责任 | 主导文档 | 次要引用 | +|---|---|---|---| +| `api/app/core/rag/app/` | 多格式解析 orchestrator | `pipeline/01-loader-parser-chunking.md` | `overview/source-inventory.md` | +| `api/app/core/rag/common/` | 常量、token、settings | `pipeline/01-loader-parser-chunking.md`, `evolution/architecture-refactor-suggestions.md` §0.2 #4 / #2 | `overview/source-inventory.md` | +| `api/app/core/rag/crawler/` | Web 爬虫 | `pipeline/01-loader-parser-chunking.md` §4.1 | — | +| `api/app/core/rag/deepdoc/parser/` | 11 类格式解析 | `pipeline/01-loader-parser-chunking.md` §5 | `overview/source-inventory.md` | +| `api/app/core/rag/deepdoc/vision/` | OCR + 版面 + TSR | `pipeline/01-loader-parser-chunking.md` §5.6 | `evolution/architecture-refactor-suggestions.md` §0.2 #2(HF_ENDPOINT) | +| `api/app/core/rag/graphrag/` | GraphRAG 共享工具 + 图搜索 | `pipeline/04-graphrag.md`(待交付) | `overview/source-inventory.md` §3.3 | +| `api/app/core/rag/graphrag/general/` | Microsoft GraphRAG 风格流水线 | `pipeline/04-graphrag.md` §general(待交付) | `overview/04-graphrag-indexing.mmd` | +| `api/app/core/rag/graphrag/light/` | LightRAG 风格抽取器 | `pipeline/04-graphrag.md` §light(待交付) | 同上 | +| `api/app/core/rag/integrations/feishu/` | 飞书 SDK | `pipeline/01-loader-parser-chunking.md` §4 | — | +| `api/app/core/rag/integrations/yuque/` | 语雀 SDK | 同上 | — | +| `api/app/core/rag/llm/` | LLM 多模型 facade | `pipeline/05-reranking-prompt-llm.md` §3 | `evolution/architecture-refactor-suggestions.md` #1, #5 | +| `api/app/core/rag/models/` | Chunk 数据模型 | `pipeline/01-loader-parser-chunking.md` §3 | `overview/source-inventory.md` | +| `api/app/core/rag/nlp/` | 中文分词、Hybrid 搜索调度 | `pipeline/03-vdb-and-retrieval.md` §6, `pipeline/05-reranking-prompt-llm.md` §1.2 | `evolution/architecture-refactor-suggestions.md` #3 | +| `api/app/core/rag/prompts/` | Prompt 模板与工厂 | `pipeline/05-reranking-prompt-llm.md` §2 | — | +| `api/app/core/rag/utils/` | ES/Redis 连接、LibreOffice | `pipeline/03-vdb-and-retrieval.md`, `pipeline/01-loader-parser-chunking.md` §4.2 | — | +| `api/app/core/rag/vdb/elasticsearch/` | ES 向量+全文 | `pipeline/03-vdb-and-retrieval.md` 全文 | `pipeline/02-embedding.md` §5.4 | +| `api/app/core/rag/res/` | NER / 同义词 / mapping | `pipeline/03-vdb-and-retrieval.md` §3 | — | +| `api/app/core/models/` | 统一封装层(Embedding / Rerank / LLM) | `pipeline/02-embedding.md` §1.2, `pipeline/05-reranking-prompt-llm.md` §1.2 | `evolution/architecture-refactor-suggestions.md` #1 | +| `api/app/core/agent/` | LangChainAgent | `pipeline/05-reranking-prompt-llm.md` §3.4 | — | +| `api/app/core/workflow/nodes/knowledge/` | Workflow Knowledge 节点 | `pipeline/05-reranking-prompt-llm.md` §3.4, `pipeline/03-vdb-and-retrieval.md` | `evolution/architecture-refactor-suggestions.md` #3 | +| `api/app/core/rag_utils/`(注意与 `rag/utils` 不同) | Chunk LLM 分析(与 Memory 系统耦合) | `overview/source-inventory.md` §rag_utils | `evolution/future-extensions-roadmap.md` D4 | +| `api/app/core/memory/` | 对话内存系统(Ebbinghaus / ACT-R / Neo4j / langgraph) | `evolution/future-extensions-roadmap.md` D4(未来扩展引用) | — | +| `api/app/services/` | 业务服务层 | `pipeline/05-reranking-prompt-llm.md` §3.5 | — | +| `api/app/tasks.py` | Celery 任务入口 | `overview/source-inventory.md` §3, `pipeline/01-loader-parser-chunking.md` §3.1 | `evolution/future-extensions-roadmap.md` D3 | + +## 2. 关键文件 → 文档章节(细粒度) + +### `api/app/core/rag/app/naive.py` + +| 行号 | 函数 / 关键代码 | 引用文档 | +|---|---|---| +| `:27 by_deepdoc()` | DeepDoc 解析路径 | `pipeline/01-loader-parser-chunking.md` §5.1 | +| `:45 by_mineru()` | MinerU 第三方解析 | 同上 §5.2 | +| `:65 by_textln()` | TextIn 第三方解析 | 同上 §5.3 | +| `:257 naive.__call__()` | 主解析入口 | 同上 §3 | +| `:508-738 chunk()` | 11 路 if/elif 分发,按扩展名挑 parser | 同上 §3, `evolution/architecture-refactor-suggestions.md` §0.2 #5 / #5 改造建议 | + +### `api/app/core/rag/llm/embedding_model.py` + +| 行号 | 类 / 函数 | 引用文档 | +|---|---|---| +| `:14-38 Base` | Embedding 抽象基类(旧) | `pipeline/02-embedding.md` §5.1 | +| `:50-65 OpenAIEmbed.encode()` | OpenAI 兼容 Embedding 实现 | 同上 §5.2, `evolution/architecture-refactor-suggestions.md` #1 / #4 / #9 | +| `:138-143 QWenEmbed` | DashScope Embedding(含显式 5 次重试) | `pipeline/02-embedding.md` §3.2 | + +### `api/app/core/models/embedding.py` + +| 行号 | 类 / 函数 | 引用文档 | +|---|---|---| +| `:9-23 RedBearEmbeddings.__init__` | LangChain 统一封装初始化 | `pipeline/02-embedding.md` §1.2 / §5.3 | +| `:65-78 embed_documents()` | 文档侧 Embedding(含火山多模态分支) | 同上 §2.1 | + +### `api/app/core/rag/vdb/elasticsearch/elasticsearch_vector.py` + +| 行号 | 类 / 函数 | 引用文档 | +|---|---|---| +| `:29 ElasticSearchVector` | ES 向量主实现 | `pipeline/03-vdb-and-retrieval.md` §1 | +| `:55-63 add_chunks()` | 向量入库 | 同上 §4, `pipeline/02-embedding.md` §2.1, `evolution/architecture-refactor-suggestions.md` #4 | +| `:374-380 search_by_vector()` | 向量检索 | `pipeline/03-vdb-and-retrieval.md` §6, `pipeline/02-embedding.md` §2.2 | +| `:468 search_by_full_text()` | BM25 检索 | `pipeline/03-vdb-and-retrieval.md` §5 | +| `:560-607 rerank()` | ES 层 rerank | `pipeline/05-reranking-prompt-llm.md` §1.2 D, `evolution/architecture-refactor-suggestions.md` #3 | +| `:653-658 dense_vector mapping` | dense_vector 维度动态决定 | `pipeline/02-embedding.md` §3.4, `pipeline/03-vdb-and-retrieval.md` §3 | +| `:666 ElasticSearchVectorFactory` | 工厂类 | `overview/source-inventory.md`, `pipeline/03-vdb-and-retrieval.md` §1 | +| `:685-707 ES 配置环境变量` | 6 个 ES 相关 env vars | `evolution/architecture-refactor-suggestions.md` §0.2 #2 | + +### `api/app/core/rag/nlp/search.py` + +| 行号 | 类 / 函数 | 引用文档 | +|---|---|---| +| `:36-147 knowledge_retrieval()` | 知识检索入口(旧通道) | `pipeline/05-reranking-prompt-llm.md` §1.2 | +| `:284-343 rerank()` | 模块级 rerank | 同上 | +| `:349 Dealer` | BM25/Hybrid 调度器 | `pipeline/03-vdb-and-retrieval.md` §6, `overview/source-inventory.md` §一 | +| `:365-373 get_vector()` | 调用旧 Embedding 接口的 `encode_queries` | `pipeline/02-embedding.md` §2.4 | +| `:387 search()` | 主 search | `pipeline/03-vdb-and-retrieval.md` §6 | +| `:439 FusionExpr("weighted_sum")` | 0.05/0.95 硬编码权重 | `pipeline/03-vdb-and-retrieval.md` §6, `evolution/future-extensions-roadmap.md` D2 | +| `:489-577 insert_citations()` | 引用回填(embedding 相似度匹配) | `pipeline/05-reranking-prompt-llm.md` §4.1 | +| `:579-604 _rank_feature_scores()` | tag TF-IDF + PageRank | `pipeline/05-reranking-prompt-llm.md` §1.2 A | +| `:606-643 Dealer.rerank()` | 内置混合 rerank(融合分数) | 同上, `evolution/architecture-refactor-suggestions.md` #3 | +| `:645-666 rerank_by_model()` | 外部模型 rerank | `pipeline/05-reranking-prompt-llm.md` §1.2 B | +| `:674-768 retrieval()` | 检索主流程 | 同上 §1.3 | + +### `api/app/core/workflow/nodes/knowledge/node.py` + +| 行号 | 类 / 函数 | 引用文档 | +|---|---|---| +| `:12 import OpenAIEmbed` | 硬编码导入旧 Embedding 类 | `evolution/architecture-refactor-suggestions.md` #1 | +| `:14 import ElasticSearchVectorFactory` | 绕过 BaseVector 抽象 | 同上 §0.2 #1 / #2 改造建议 | +| `:29 KnowledgeRetrievalNode` | Workflow 节点主类 | `pipeline/05-reranking-prompt-llm.md` §3.4 | +| `:54 _extract_input()` | 渲染 query 模板 | 同上 | +| `:108-155 KnowledgeRetrievalNode.rerank()` | 节点级 rerank | 同上 §1.2 C, `evolution/architecture-refactor-suggestions.md` #3 | +| `:157-193 get_reranker_model()` | 每次调用都查 DB | `evolution/architecture-refactor-suggestions.md` §0.2 #4 | +| `:195-263 knowledge_retrieval()` | 检索分支(PARTICIPLE / SEMANTIC / HYBRID / Graph) | `pipeline/05-reranking-prompt-llm.md` §3.4, `pipeline/03-vdb-and-retrieval.md` | +| `:236-271 HYBRID 分支` | vector + full_text 并行 → dedup → rerank | 同上 | +| `:284 rerank()` 模块级函数 | 三轨 rerank 之一 | `evolution/architecture-refactor-suggestions.md` #3 | +| `:303-378 execute()` | 节点执行入口 | `pipeline/05-reranking-prompt-llm.md` §3.4 | +| `:327 print(reranked_docs)` ⚠️ | 调试残留 | `evolution/architecture-refactor-suggestions.md` #3 / #10(hot-fix 候选) | + +### `api/app/core/rag/graphrag/` + +| 行号 | 类 / 函数 | 引用文档 | +|---|---|---| +| `general/index.py:36 run_graphrag()` | GraphRAG 主入口(doc 级) | `pipeline/04-graphrag.md` §general(待交付) | +| `general/index.py:122 run_graphrag_for_kb()` | KB 级 | 同上 | +| `general/graph_extractor.py:34 GraphExtractor` | Microsoft 风格抽取 | 同上 | +| `general/community_reports_extractor.py:37` | 社区报告 | 同上 | +| `light/graph_extractor.py:31 GraphExtractor` | LightRAG 风格抽取 | 同上 §light | +| `entity_resolution.py:31 EntityResolution` | 实体消歧 | 同上 | +| `search.py:19 KGSearch` | 图检索 | 同上 | +| `utils.py:41 chat_limiter` | Trio 限流 | `pipeline/02-embedding.md` §3.1, `evolution/architecture-refactor-suggestions.md` #9 | +| `utils.py:115-134 get/set_embed_cache` | Redis Embedding 缓存 | `pipeline/02-embedding.md` §3.3, `evolution/architecture-refactor-suggestions.md` #4 | +| `utils.py:301-327 graph_node_to_chunk()` | 实体节点 → 向量 → ES | `pipeline/02-embedding.md` §2.3 | + +### `api/app/core/rag/llm/chat_model.py` + +| 行号 | 类 / 函数 | 引用文档 | +|---|---|---| +| `:52 Base` | LLM 抽象基类 | `pipeline/05-reranking-prompt-llm.md` §3.1 | +| `:54-58 LLM_TIMEOUT_SECONDS / LLM_MAX_RETRIES` | 超时与重试 | 同上 §3.3, `evolution/architecture-refactor-suggestions.md` §0.2 #2 | +| `:122-150 _chat()` | 非流式 LLM 调用 | `pipeline/05-reranking-prompt-llm.md` §3.2 | +| `:152-185 _chat_streamly()` | 流式 LLM 调用 | 同上 | +| `:251-303 chat_with_tools()` | 工具调用 | 同上 §3.4 | + +### `api/app/core/rag/prompts/` + +| 文件 | 功能 | 引用文档 | +|---|---|---| +| `template.py:9 load_prompt()` | 启动时加载 .md 模板 | `pipeline/05-reranking-prompt-llm.md` §2.1 | +| `generator.py` | 20+ Prompt 工厂函数(citation/keyword/...) | 同上 | +| `*.md`(31 个模板) | Prompt 内容 | `overview/source-inventory.md` | + +### `api/app/core/rag/common/settings.py` + +| 行号 | 关键代码 | 引用文档 | +|---|---|---| +| `:9-10 retriever / kg_retriever` | 进程级单例 | `evolution/architecture-refactor-suggestions.md` §0.2 #4 | +| `:13 init_settings()` | 模块导入时副作用 | 同上, `pipeline/03-vdb-and-retrieval.md` | +| `:24` 触发位置 | — | `evolution/architecture-refactor-suggestions.md` #8 | + +### `api/app/services/draft_run_service.py` + +| 行号 | 关键代码 | 引用文档 | +|---|---|---| +| `:195-263 create_knowledge_retrieval_tool()` | 知识检索工具 | `pipeline/05-reranking-prompt-llm.md` §3.5 | +| `:227-255` chunk 拼接 | `\n\n` 分隔 chunks | 同上 §2.3 | +| `:474-490 _filter_citations()` | 引用过滤 + 下载链接 | 同上 §4.2 | + +## 3. 当前已识别的"代码残留与修复任务" + +| # | 文件:行 | 问题 | 修复建议 | 关联 | +|---|---|---|---|---| +| 1 | `workflow/nodes/knowledge/node.py:327` | `print(reranked_docs)` 调试残留 | 立即提 hot-fix PR 删除 | S3-T1 #10 + S3-T1 §3.1 | +| 2 | `chat_model.py` 各 provider 子类 | base_url 与认证 header 硬编码 | 引入 Plugin Registry | S3-T1 #5 | +| 3 | `naive.py:508-738 chunk()` | 11 路 if/elif 硬编码 | 抽 `Parser` Protocol | S3-T1 #5 | +| 4 | `elasticsearch_vector.py:55-63 add_chunks` | 同步循环,无并发 | 改 trio 协程 + 共享 chat_limiter | S3-T1 #9 | +| 5 | `nlp/search.py:439` | `weighted_sum` 0.05/0.95 硬编码 | 改为 ctx.fusion_weights 注入 | S3-T2 D2 | +| 6 | `rag_utils/` vs `rag/utils/` | 命名冲突 | 重命名为 `rag/chunk_analytics/` 或合并 | S1-T3 §4.1 | + +— **File Index · v1.0-RC1 · 2026-05-08** — diff --git a/docs/rag/_indexes/glossary.md b/docs/rag/_indexes/glossary.md new file mode 100644 index 00000000..3ef6df8d --- /dev/null +++ b/docs/rag/_indexes/glossary.md @@ -0,0 +1,198 @@ +# MemoryBear RAG · 关键术语表 + +> 合并 Sprint-1 / Sprint-2 / Sprint-3 各文档术语,按字母顺序排列。 +> 每个术语注明:含义 + 在 MemoryBear 代码中的对应位置 + 出现的文档。 + +## A + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **ASR** | Automatic Speech Recognition,语音转文字。MemoryBear 中通过 `seq2txt_model.transcription` 调用(QWenSeq2txt 带时间戳,GPTSeq2txt 用 Whisper) | `rag/llm/sequence2txt_model.py:1-215` | S2-T1, S2-T5 | +| **Autopilot** | 工作空间内的"按时触发 / 按事件触发"自动化代理;与 `multica autopilot` 命令族对应 | — | 平台机制(项目 SOP) | + +## B + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **BaseVector** | VDB 抽象基类(仅定义抽象方法,目前唯一实现为 `ElasticSearchVector`) | `rag/vdb/vector_base.py:9` | S1-T3, S2-T3, S3-T1 | +| **BM25** | Best Match 25,全文检索经典 ranking 函数;MemoryBear 通过 ES `query_string` + IK 分词器实现 | `rag/nlp/query.py`, `rag/vdb/elasticsearch/elasticsearch_vector.py:468 search_by_full_text` | S2-T3, S3-T2 | +| **Boundaries** | 11 个 RAG 阶段的输入/输出/接口契约文档(S1-T2 交付物之一) | — | S1-T2 | + +## C + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **Celery** | 任务队列;MemoryBear 用它派发文档解析、GraphRAG 构建等异步流水线 | `tasks.py:212 parse_document`, `tasks.py:472 build_graphrag_for_kb`, `tasks.py:557 build_graphrag_for_document` | S1-T3, S2-T1, S2-T3, S3-T2 | +| **chat_limiter** | Trio CapacityLimiter,控制 GraphRAG 中实体/关系 Embedding 的并发;默认 10 | `rag/graphrag/utils.py:41` | S2-T2, S3-T1 | +| **Chunk** | 最终交给 Embedding 的文本片段,一般 ≤ `chunk_token_num`(默认 128–512) | `rag/models/chunk.py:17 DocumentChunk` | S2-T1, S2-T2, S2-T3 | +| **chunk_token_num** | 单个 chunk 的最大 token 数 | `rag/app/naive.py` 调用层指定 | S2-T1 | +| **citation** | 答案文本中插入的 `[ID:N]` 引用标记 | `rag/nlp/search.py:489-577 Dealer.insert_citations` | S2-T5 | +| **CLIP / BGE-VL / Jina-Clip** | 跨模态 Embedding 模型,把图像和文本映射到同一语义空间 | 当前未启用,规划见 S3-T2 D1 | S3-T2 | +| **cl100k_base** | OpenAI GPT-4 系列使用的 BPE tokenizer;MemoryBear 用它做 token 计数 | `rag/common/token_utils.py` | S2-T1, S2-T2 | +| **Cross-Encoder** | 一种 Reranker 范式:把 (query, doc) 拼接后过同一个 Encoder,输出相关性分数 | 当前未自训,仅在外部 rerank 服务(DashScope/Jina)调用,规划见 S3-T2 D5 | S2-T5, S3-T2 | + +## D + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **Dealer** | `rag/nlp/search.py:349 Dealer` 类,BM25/hybrid 搜索调度器;GraphRAG 主要使用此通道 | `rag/nlp/search.py:349` | S1-T3, S2-T3, S2-T5, S3-T1 | +| **deepdoc** | MemoryBear 的多格式解析模块,含 parser(11 种格式)+ vision(OCR / 版面识别 / TSR) | `rag/deepdoc/{parser,vision}` | S1-T3, S2-T1 | +| **DocumentChunk** | Chunk 数据模型 | `rag/models/chunk.py:17` | S2-T1, S2-T2, S2-T3 | +| **dense_vector** | ES 向量字段类型;MemoryBear 用 HNSW 索引 + cosine 相似度 | `elasticsearch_vector.py:653-658`, `rag/res/mapping.json` | S2-T2, S2-T3 | + +## E + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **E2E(End-to-End)** | 端到端调用链路,覆盖文档入库 + 在线检索 + 生成的完整时序 | `rag/app/`, `workflow/nodes/knowledge/`, `rag/llm/` | S2-T6(待交付) | +| **Embedder** | Embedding 模型抽象接口(S3-T1 提议的统一 Protocol) | 提议中:`app/core/rag/protocols/embedder.py` | S3-T1, S3-T2 | +| **Embedding 双轨** | MemoryBear 当前同时存在两条 Embedding 调用路径:`RedBearEmbeddings`(LangChain,新)与 `OpenAIEmbed/QWenEmbed/...`(遗留) | `rag/models/embedding.py` + `rag/llm/embedding_model.py` | S2-T2, S3-T1 | +| **embed_cache** | GraphRAG 中的实体/关系 Embedding Redis 缓存,TTL 24h | `rag/graphrag/utils.py:115-134` | S2-T2, S3-T1 | +| **EMBEDDING_BATCH_SIZE** | 批量 Embedding 大小的环境变量(README 提及但当前未生效) | — | S2-T2, S3-T1 | +| **Entity Resolution** | 实体消歧;GraphRAG 索引流程的一环 | `rag/graphrag/entity_resolution.py:31` | S1-T3 | +| **ESConnection** | ES 连接单例 | `rag/utils/es_conn.py` | S1-T3, S2-T3 | +| **ElasticSearchVector** | VDB 主实现;同时承载 chunk + GraphRAG entity/relation + community_report | `rag/vdb/elasticsearch/elasticsearch_vector.py:29` | S1-T3, S2-T3, S3-T1 | + +## F + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **FOLDER 类型知识库** | 包含子知识库的文件夹型 KB;检索时递归遍历 | `workflow/nodes/knowledge/node.py` | S1-T3 | +| **FusionExpr** | ES 检索中的"加权融合"DSL;当前固定 `0.05/0.95`(BM25:Vector) | `rag/nlp/search.py:439` | S2-T3, S3-T2 | + +## G + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **GraphRAG(general)** | Microsoft GraphRAG 风格:完整流水线(子图 → 合并 → PageRank → Leiden 社区 → 社区报告) | `rag/graphrag/general/index.py:36 run_graphrag` | S1-T2, S1-T3 | +| **GraphRAG(light)** | LightRAG 风格:简化的实体/关系抽取,无社区报告;与 general 共享大部分代码 | `rag/graphrag/light/graph_extractor.py:31` | S1-T2, S1-T3 | +| **GraphStore** | 图存储抽象(S3-T2 提议) | 提议中 | S3-T2 | +| **GraphAugmentedRetriever** | 在 Hybrid 结果之上叠加 KGSearch 的 Retriever 实现 | 提议中 | S3-T1, S3-T2 | + +## H + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **HNSW** | Hierarchical Navigable Small World,向量索引算法;ES 8.x 内置 | ES 集群侧 | S2-T3 | +| **HYBRID 检索** | BM25 + 向量并行 → 去重 → 可选 Rerank | `workflow/nodes/knowledge/node.py:236-271` | S2-T3, S2-T5 | +| **HybridRetriever** | Hybrid 检索 Protocol 实现(S3-T1 PoC) | 提议中 | S3-T1 | + +## I + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **IK 分词器** | 中文分词器,ES IK plugin(`ik_max_word`) | ES 集群侧 | S2-T3 | +| **init_settings()** | 模块级副作用,启动时自动建 ES 连接 + retriever 单例 | `rag/common/settings.py:24` | S1-T3, S3-T1 | +| **insert_citations** | 答案分句后按 embedding 相似度回填 `[ID:N]` 引用 | `rag/nlp/search.py:489-577` | S2-T5 | + +## K + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **KGSearch** | GraphRAG 检索器 | `rag/graphrag/search.py:19` | S1-T3, S3-T2 | +| **knowledge_graph_kwd** | ES 中区分图类型(entity / relation / community_report)的字段 | `rag/vdb/elasticsearch/elasticsearch_vector.py` | S1-T3 | +| **KnowledgeRetrievalNode** | Workflow 引擎中的知识检索节点 | `workflow/nodes/knowledge/node.py:29` | S1-T3, S2-T5, S3-T1 | + +## L + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **LangChainAgent** | 基于 `create_agent` 的 ReAct Agent,工具调用循环 | `agent/langchain_agent.py:26-641` | S2-T5 | +| **Late-Interaction** | 一种检索范式(如 ColBERT),文档级向量改为 token 级,retrieval 用 MaxSim | 当前未启用,规划见 S3-T2 D2 | S3-T2 | +| **Leiden 算法** | 社区检测算法;GraphRAG 用它划分社区 | `rag/graphrag/general/index.py` 调用 `graspologic.partition.leiden` | S1-T2, S1-T3 | +| **LightRAG** | GraphRAG 轻量化变种,无社区报告 | `rag/graphrag/light/` | S1-T2, S1-T3 | +| **LLM** | Large Language Model;MemoryBear 通过 `chat_model.py` 与 `langchain_agent.py` 调用 | `rag/llm/chat_model.py:52 Base` | S2-T5 | +| **LO(LibreOffice)** | 用作 PPT/PPTX 转 PDF 的兜底工具 | `rag/utils/libre_office.py` | S2-T1 | + +## M + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **MatchSparseExpr / Field.SPARSE_VECTOR** | 已声明未启用的稀疏向量表达式(SPLADE 接入预埋) | `rag/utils/doc_store_conn.py:75`, `vdb/field.py:11` | S3-T2 | +| **Memory(记忆系统)** | MemoryBear 的对话内存系统:Ebbinghaus 衰减 + ACT-R + Neo4j + langgraph 读写图 | `core/memory/`(与 `core/rag/` 当前完全独立) | S3-T2 D4 | +| **MemoryAugmentedRetriever** | D4 提议:在检索前用长期记忆改写 query 的 Retriever 包装层 | 提议中 | S3-T2 D4 | +| **mind_map_extractor** | 独立运行的思维导图抽取器,不在 GraphRAG 主链路 | `rag/graphrag/mind_map_extractor.py` | S1-T2 | +| **MinerU** | 第三方 PDF 解析服务(外部 API) | `rag/deepdoc/parser/mineru_parser.py:41`, `rag/app/textin_parser.py` | S1-T3, S2-T1 | +| **Multimodal Embedding** | 多模态 Embedding;MemoryBear 仅火山引擎支持原生多模态 | `rag/models/embedding.py:65-78` 中 `_is_volcano` 分支 | S2-T2, S3-T2 D1 | + +## N + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **naive_merge / hierarchical_merge / tree_merge** | 三种 Chunking 合并策略 | `rag/nlp/__init__.py` | S2-T1 | +| **Neo4j** | 图数据库;README 声明依赖,但 `core/rag` 当前零调用(规划见 S3-T2 D3) | — | S3-T2 | + +## O + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **OCR** | 文字检测 + 识别两阶段 | `rag/deepdoc/vision/ocr.py:522 OCR.__call__:694` | S2-T1 | +| **OpenAIEmbed / QWenEmbed / ...** | 遗留的原始 Embedding 实现,被 GraphRAG 与 Dealer 使用 | `rag/llm/embedding_model.py:14-65` | S2-T2, S3-T1 | +| **OpenTelemetry (OTel)** | 全链路追踪 + 指标 SDK;MemoryBear 当前未引入(规划见 S3-T1 #6) | 提议中 | S3-T1 | + +## P + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **PageRank** | 图节点重要性算法;GraphRAG 用它给实体打分 | `rag/graphrag/general/index.py` | S1-T2, S1-T3 | +| **PARTICIPLE 检索** | 关键词分词检索(BM25) | `workflow/nodes/knowledge/node.py:195` | S2-T3 | +| **Plugin Registry** | S3-T1 #5 提议的 Parser/LLM Provider 注册机制,替换 `naive.py` 11 路 if/elif | 提议中 | S3-T1 | +| **Pydantic Settings** | S3-T1 #7 提议的中心化配置管理框架 | 提议中 | S3-T1 | + +## R + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **rag_utils(注意:与 `rag/utils` 不同)** | Chunk 内容 LLM 分析模块(摘要/标签/洞察/人物画像);与 Memory 系统耦合 | `api/app/core/rag_utils/` | S1-T3 | +| **RAGAS** | 开源 RAG 评估框架;MemoryBear 当前未集成 | 提议中 | S3-T2 D5 | +| **rank_feature** | ES 中的 tag TF-IDF + PageRank 辅助排序分 | `rag/nlp/search.py:579-604` | S2-T5 | +| **RedBearEmbeddings** | LangChain 统一封装的 Embedding 类(新路径) | `rag/models/embedding.py:9-23` | S2-T2 | +| **RedBearRerank** | LangChain `BaseDocumentCompressor` 封装的 Reranker | `rag/models/rerank.py:11-84` | S2-T5, S3-T2 | +| **Rerank 三轨** | (a) `node.py:284 rerank()` 模块级;(b) `KnowledgeRetrievalNode.rerank()` 节点方法;(c) `Dealer.rerank()` 融合排序 | `node.py:108-155, 284`、`nlp/search.py:606-643` | S2-T5, S3-T1 | +| **Reranker** | Reranking Protocol(S3-T1 提议) | 提议中 | S3-T1, S3-T2 | +| **retrieve_type** | 检索模式 enum:PARTICIPLE / SEMANTIC / HYBRID / Graph | `schemas/chunk_schema.py` | S2-T3, S3-T2 | +| **Retriever** | 检索器 Protocol(S3-T1 提议) | 提议中 | S3-T1, S3-T2 | +| **RouterRetriever** | 自适应路由 Retriever(S3-T2 D6 提议) | 提议中 | S3-T2 | +| **RRF(Reciprocal Rank Fusion)** | 多路检索结果排序融合算法;S3-T2 PoC-A 提议接入 | 提议中 | S3-T2 | + +## S + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **SEMANTIC 检索** | 纯向量检索 | `workflow/nodes/knowledge/node.py:195` | S2-T3 | +| **Section** | 解析器吐出的 `(text, position_or_layout)` 中间结构,是 Chunking 的"原料" | `rag/app/naive.py:257` | S2-T1 | +| **SPLADE** | 学习型稀疏向量;S3-T2 D2 提议接入 | 提议中(脚手架已存:`MatchSparseExpr`) | S3-T2 | +| **structlog** | 结构化日志库;S3-T1 #10 提议替换现有非结构化 `logger.*` | 提议中 | S3-T1 | +| **System Prompt 组装** | "用户自定义 system_prompt + 技能 Prompt + 文档图片识别指令"三段拼接 | `app_chat_service.py:77-96` | S2-T5 | + +## T + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **TextIn** | 第三方 PDF 解析 API | `rag/app/textin_parser.py` | S1-T3 | +| **Token** | 用 cl100k_base 编码后的 BPE token | `rag/common/token_utils.py` | S2-T1, S2-T2 | +| **tokenize_chunks_with_images** | 带图片的 Chunk 化处理 | `rag/nlp/__init__.py` | S2-T1 | +| **TSR** | Table Structure Recognition,复杂表格行/列/合并单元格还原 | `rag/deepdoc/vision/table_structure_recognizer.py:15` | S2-T1 | + +## V + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **VDB(Vector Database)** | 向量数据库;MemoryBear 当前唯一实现是 Elasticsearch 8.x | `rag/vdb/elasticsearch/` | S2-T3 | +| **VectorBase** | 见 BaseVector | `rag/vdb/vector_base.py:9` | — | +| **VLM** | Vision-Language Model;图像理解(CV 模型) | `rag/llm/cv_model.py` | S2-T1 | + +## W + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **weighted_sum (0.05, 0.95)** | ES 层 Hybrid 检索的固定权重(BM25:Vector) | `rag/nlp/search.py:439` | S2-T3, S3-T2 | +| **Workflow Knowledge Node** | 见 KnowledgeRetrievalNode | `workflow/nodes/knowledge/node.py:29` | S1-T3, S2-T5 | + +## X + +| 术语 | 含义 | 代码位置 | 出现文档 | +|---|---|---|---| +| **xxhash** | 快速哈希函数;用于 GraphRAG embed_cache 的 key 生成 | `rag/graphrag/utils.py:115-134` | S2-T2 | + +— **Glossary · v1.0-RC1 · 共 81 个术语 · 2026-05-08** — diff --git a/docs/rag/_meta/README.md b/docs/rag/_meta/README.md new file mode 100644 index 00000000..6c6a9d8e --- /dev/null +++ b/docs/rag/_meta/README.md @@ -0,0 +1,33 @@ +--- +title: "_meta/ — 治理资产(待补全)" +status: 占位(待回填) +source-issue: WS-12 / [S1-T1] +last-reviewed-at: 2026-05-09 +--- + +# _meta/ — 治理资产(待回填) + +本目录用于存放 RAG 文档项目的治理资产(统一模板、评分卡、SOP、命名规范等),由 [WS-12 / S1-T1] 任务交付。 + +## 状态 + +[S1-T1] 任务的交付物在 [WS-12 评论 `93ea1f50`](mention://issue/b1ead19b-920b-494b-95b5-ab2057d4dd14) 中已声明完成(写入 agent 工作分支),但相关文件未持久化到 main 分支与本仓库 `docs/rag/` 目录树。本次 v1.0 文档全集提交时,按 [WS-26](mention://issue/5c12d0a3-89ea-4e92-adb4-d98eddfa3eab) 用户指示将仓库迁移到 `git.poflow.cn:30010/adai/MemoryBear.git`,本目录暂作占位,后续回填。 + +## 应有内容(按 [`../INDEX.md`](../INDEX.md) §2 责任矩阵) + +| 文件 | 内容 | 责任人 | +|---|---|---| +| `README.md` | 治理体系总览,含 8 环节与代码目录映射速查表 | 知识运营与治理专家 | +| `document-template.md` | 统一文档模板(覆盖 8 个 RAG 环节,9 大章节结构) | 同上 | +| `scoring-rubric.md` | 质量评分卡(5 维度 / 100 分制 / 通过线 80) | 同上 | +| `review-sop.md` | 审校流程 SOP(自检 → 同行 → 终审) | 同上 | +| `directory-naming-spec.md` | 目录与命名规范(frontmatter 规范) | 同上 | +| `rubric-scoresheet.md` | 评分记录表模板(Sprint-2 评分预置) | 同上 | + +## 回填路径建议 + +1. **首选**:从 [@知识运营与治理专家](mention://agent/7e9211a6-41eb-429e-9dd1-4c7afcffd412) 的本地工作目录恢复并提交。 +2. **次选**:基于 [WS-12 评论 `93ea1f50`](mention://issue/b1ead19b-920b-494b-95b5-ab2057d4dd14) 中描述的设计决策与各文件大纲,由知识运营重新生成(约 0.5–1 个工作日)。 +3. **快速兜底**:参考 [`../_indexes/glossary.md`](../_indexes/glossary.md) 与 [`../_release/versioning-convention.md`](../_release/versioning-convention.md) 中已沉淀的部分约束(如版本号约定、frontmatter 字段),先建立最小可用版本。 + +完整回填后,请同步更新 [`../INDEX.md`](../INDEX.md) §3 状态汇总(占位计数 −5 → 已交付 +5)。 diff --git a/docs/rag/_release/ops-and-freshness-plan.md b/docs/rag/_release/ops-and-freshness-plan.md new file mode 100644 index 00000000..1e618195 --- /dev/null +++ b/docs/rag/_release/ops-and-freshness-plan.md @@ -0,0 +1,165 @@ +# MemoryBear RAG Docs · 运营与保鲜计划 + +> **目标**:让 `docs/rag/` 不沦为"上线那天的快照",而是与 MemoryBear 一同进化的活水。 +> **责任主线**:知识运营与治理专家牵头,与 PM / AI 知识库专家 / Python 工程师协同。 + +## 1. 保鲜原则(Why) + +> 一句话:**代码会跑,文档会过期;过期速度比新代码合并的速度还快。** + +- **失效快**:MemoryBear 在 Sprint-3 内合并的关键改造(如 Reranker 缓存、Embedder Protocol、`node.py:327 print` 删除)会在 1-2 周内让相关文档章节失同步。 +- **影响大**:本套文档是 toB 客户、二次开发者、内部 oncall 的"事实来源";与代码不一致会直接误导决策。 +- **维护成本可控**:用统一的"评审 + 增量更新 + 自动归档"三段式机制,把维护成本摊到每次 release,而不是堆在年度大修。 + +## 2. 保鲜节奏(When) + +### 2.1 与 release 同步评审(强制) + +每次 MemoryBear 主仓发 release(语义化版本 `v0.x.y`)时: + +| 时点 | 动作 | 责任人 | +|---|---|---| +| **release 准备期 -7d** | 自动扫描:`git diff ..HEAD -- 'api/app/core/rag/**'` 列出受影响文件 | PM 或脚本 | +| **release 准备期 -5d** | 知识运营对受影响文件清单进行"文档涟漪映射"(用 `_indexes/file-index.md`) | 知识运营 | +| **release 准备期 -3d** | 责任专家修订对应文档章节(最低粒度:源码引用行号、配置项默认值、流程描述) | AI 知识库 / Python 工程 | +| **release day** | 知识运营终审;通过后将 `source-commit` 刷到新 commit | 知识运营 | +| **release day +1d** | 在 `evolution/CHANGELOG.md`(v1.1 起新增)写入"对应 MemoryBear `v0.x.y` 的文档增量" | 知识运营 | + +### 2.2 季度全量复审(强制) + +每季度(3 / 6 / 9 / 12 月末)做一次"对所有文档的轻量复审": + +| 步骤 | 内容 | +|---|---| +| 1 | 抽样 30%(每类文档至少 1 篇)做"源码引用一致性"抽查(用 `_indexes/file-index.md` 对应行号 grep ±3 行) | +| 2 | 检查每个文档的 `last-reviewed-at`,超过 3 个月的标记为"待复审" | +| 3 | 评分(按 5 维卡),低于 75 的文档启动 Should-Fix 流程;低于 60 的文档启动 Must-Fix | +| 4 | 季度报告(约 1 页)发到 [WS-11](mention://issue/6c0b5472-a0fa-4997-925c-a67f235f82da) 作为里程碑通告 | + +### 2.3 用户反馈驱动评审(按需) + +任何外部读者(开发者、客户)在子任务 issue 中反馈"文档与代码不一致"或"文档不清楚",触发: + +- **24h 内**:知识运营响应 + 复核 +- **48h 内**:责任专家修订或返回澄清 +- **保留**:作为评审记录留在子任务评论中,季度报告时统计"反馈密度"作为质量指标 + +## 3. 保鲜机制(How) + +### 3.1 责任矩阵 + +| 文档类别 | 主责(修订) | 终审 | 升版决定 | +|---|---|---|---| +| `_meta/` 治理资产 | 知识运营 | 知识运营自审 | 知识运营 | +| `overview/` 总览 | AI 知识库 | 知识运营 | 联合 | +| `pipeline/` 各环节 | Python 工程 | 知识运营 | 联合 | +| `graphrag/` GraphRAG | Python 工程 | 知识运营 | 联合 | +| `end-to-end/` E2E | AI 知识库 | 知识运营 | 联合 | +| `evolution/` 演进 | AI 知识库 | 知识运营 | 联合 | +| `review/` 评审报告 | 知识运营 | 知识运营自审 | 知识运营 | +| `_indexes/` 索引 | 知识运营 | 知识运营自审 | 知识运营 | +| `_release/` 发布 | 知识运营 | 知识运营自审 + PM | 知识运营 + PM | + +### 3.2 过期判定规则(自动 + 人工) + +文档进入"过期候选"状态满足以下**任一**: + +| 触发条件 | 判定 | +|---|---| +| `last-reviewed-at` 距今 ≥ 90 天且 `source-commit` 与当前 main HEAD 差距 ≥ 50 commits | 自动标记 | +| 用户反馈"文档与代码不一致"且复核成立 | 立即标记 | +| 文档关联的代码模块在 release 中有变更(用 `git diff` 检测) | 自动标记 | +| 文档评分 < 80 且未在 14 天内启动修订 | 自动标记 | + +> 标记后进入 [`_release/freshness-queue.md`](freshness-queue.md)(v1.1 起新建)。每周一上午 PM 在 [WS-11](mention://issue/6c0b5472-a0fa-4997-925c-a67f235f82da) 评论"本周保鲜任务"通告。 + +### 3.3 修订流程(强制走 SOP) + +所有修订都要遵循 `_meta/review-sop.md`: + +1. **作者自检** ≤ 30 min(用 `_meta/scoring-rubric.md`) +2. **同行评审** ≤ 48 h(≥ 同 Sprint 1 名其他作者) +3. **知识运营终审** ≤ 24 h +4. 通过 → 合并 PR;未通过 → 退回作者,最多 2 轮 + +> 紧急 hot-fix(如调试 print 残留、源码引用错误)可走"快速通道":直接知识运营 + PM 双人共审 ≤ 4 h,事后补同行评审记录。 + +### 3.4 归档机制 + +- 文档被替换或并入新文档时:保留 6 个月,再迁移到 `docs/rag/_archive//`。 +- 归档保留可读性(保留 frontmatter 的 `status: deprecated`),**不删除**。 +- 季度报告中列出"本季归档清单"。 + +## 4. 关键指标(Metric) + +### 4.1 内容质量指标 + +| 指标 | 目标 | 测量方式 | +|---|---|---| +| 文档评分均值 | ≥ 85 | 季度评审打分 | +| 评审通过率(一次过) | ≥ 75% | 季度评审统计 | +| 源码引用一致率(抽查) | 100% | 季度抽样 30% × ±3 行 grep | +| 失效文档占比(last-reviewed-at > 6 月) | ≤ 10% | 自动扫描 | + +### 4.2 使用与反馈指标 + +| 指标 | 目标 | 测量方式 | +|---|---|---| +| 月活读者(PV) | TBD(v1.1 起埋点) | Wiki 自带统计 | +| 用户反馈数(季度) | ≥ 5 条 | 子任务 issue 评论统计 | +| 反馈解决率(30 天内闭环) | ≥ 90% | issue 状态统计 | +| 搜索无结果率("用户搜了什么找不到") | ≤ 5% | Wiki 搜索日志(v1.2 起) | + +### 4.3 协作健康指标 + +| 指标 | 目标 | 测量方式 | +|---|---|---| +| 修订到合并的中位时间 | ≤ 3 天 | PR 数据 | +| 紧急 hot-fix 数量(季度) | ≤ 2 | issue 标签统计 | +| 评审反馈采纳率 | ≥ 80% | 终审记录 | + +## 5. 治理工具(推荐落地) + +| 工具 | 作用 | 落地阶段 | +|---|---|---| +| **Markdown lint**(`markdownlint`) | 检查 frontmatter 完整性、链接有效性 | v1.0 PR 前接入 CI | +| **链接巡检**(`lychee`) | 自动跑死链 / 失效 mention | v1.1 | +| **Mermaid 校验**(`@mermaid-js/parser`) | 校验 .mmd 文件可渲染 | v1.0 PR 前 | +| **`source-commit` 对齐脚本** | 检查 frontmatter 的 commit 是否在 main 历史中可达 | v1.0 PR 前 | +| **失效扫描器** | 比对 `last-reviewed-at` 与 main HEAD diff,输出过期候选清单 | v1.1(与 PM 协同) | +| **评分卡 LLM 助手** | 用大模型对文档做初评,节省人工 | v1.2 | +| **Wiki 同步器** | `.md` → MkDocs / Material 自动构建 | v1.0 同步落地 | + +## 6. 与 PM 的协同节律 + +| 频次 | 内容 | 出口物 | +|---|---|---| +| 每周一 | PM 在 [WS-11](mention://issue/6c0b5472-a0fa-4997-925c-a67f235f82da) 评论"本周关注点" | 通告 | +| 每周三 | 知识运营点检本周已交付文档,必要时介入 | 评分卡更新 | +| 每周五 EOB | 责任专家在子任务评论"本周完成 + 下周计划" | 子任务通告 | +| 每月初 | 知识运营汇总月度评分均值,更新 `_release/quality-dashboard.md`(v1.1 起新增) | 月报 | +| 每季度 | 全量复审(§2.2) + 给 PM 提交季度报告 | 季报(约 1 页) | +| 每次 release | 同步评审 + 升版 + CHANGELOG(§2.1) | release 通告 | + +## 7. 失败模式与止损(备灾) + +| 风险 | 触发场景 | 止损 | +|---|---|---| +| 知识运营长期不在场 | 评审节奏断 | 提前指定 backup(建议 [@AI 知识库专家] + [@PM] 共审) | +| 大量文档同时过期(如重大重构) | 无法在一个 release 内修完 | 按"先核心后边缘"分批:核心(pipeline/03 + evolution)→ overview → pipeline 其余 → graphrag → end-to-end | +| 用户反馈与责任专家判断冲突 | 修订悬而未决 | 由 PM 仲裁;保留双方陈述在 issue | +| 评分卡刚性导致一线积极性下降 | 因小错频繁返工 | 季度复盘评估"评分卡是否过严",必要时调整 Should-Fix 与 Must-Fix 边界 | + +## 8. 一年内重要里程碑(建议) + +| 时点 | 内容 | +|---|---| +| 2026-05-08 | v1.0-RC1(本次发布) | +| 2026-05-22 ~ 05-29 | S2-T7 评审收口;S2-T4/T6 文档补齐;目标升版 v1.0 | +| 2026-06-01 ~ 06-05 | S3 全套文档落入仓库 PR;启动 [S3-T4 PM 复盘](mention://issue/b98604b1-326f-42b4-a4c2-b3d9ad80ec75) | +| 2026-Q3 | S3-T1 §3.1 短期路线图全部合入 → v1.1 | +| 2026-Q4 | S3-T2 PoC-A / PoC-B 落地,回填实测数据 → v1.2 | +| 2027-Q1-Q2 | 4 大 Protocol 落地 + OTel 接入 → v2.0-RC | +| 2027-H2 | 多模态 / 增量图等长期方向落地 → v2.0 正式 | + +— **Operations & Freshness Plan · v1.0-RC1 · 2026-05-08** — diff --git a/docs/rag/_release/release-manifest-v1.0-RC1.md b/docs/rag/_release/release-manifest-v1.0-RC1.md new file mode 100644 index 00000000..3f4183c3 --- /dev/null +++ b/docs/rag/_release/release-manifest-v1.0-RC1.md @@ -0,0 +1,126 @@ +# MemoryBear RAG Docs · 发布候选清单 v1.0-RC1 + +> **状态**:Release Candidate 1 · 候选发布 +> **冻结日期**:2026-05-08 +> **发布方式**:仓库 PR + Wiki + Issue 评论附件 +> **下次升版门槛**:S2-T7 评审通过 + S2-T4 / S2-T6 占位文档替换 + +--- + +## 1. 版本基本信息 + +| 项 | 值 | +|---|---| +| 版本号 | `v1.0-RC1` | +| 发布通道 | Release Candidate(候选发布) | +| 基线源码 | MemoryBear `agent/ai/f8de881a` 分支(基于 commit `feae2f2e`) | +| 文档作者 | AI 知识库专家 / Python 工程师 / 知识运营专家 / PM 协同 | +| 终审责任人 | 知识运营与治理专家 | +| 文件总数 | 33 个(其中 28 已交付,5 占位) | +| 总字数(含已交付) | ≈ 230k 字(中文) | +| Mermaid 图表 | 9 张已交付,5 张待补 | +| 源码引用 | 200+ 处(采样 5 处全部可在 ±3 行内复现) | + +## 2. 发布 Targets("哪些文档随什么形式发布") + +| 路径 | 发布形式 | 责任人 | 交付物 | +|---|---|---|---| +| `docs/rag/README.md` | **仓库 PR** | 知识运营 | Landing 页,含三套阅读路径 | +| `docs/rag/INDEX.md` | **仓库 PR** | 知识运营 | 全集总索引 + 责任矩阵 | +| `docs/rag/_meta/*` | **仓库 PR** | 知识运营 | 治理资产(已合入 `agent/ai/f8de881a` 分支预备) | +| `docs/rag/overview/*.mmd` | **仓库 PR**(Mermaid 文件) + **Wiki**(渲染版) | AI 知识库 | 4 张时序/架构图 | +| `docs/rag/overview/{boundaries.md,DocMap.md,source-inventory.md}` | **仓库 PR** | AI 知识库 / Python 工程 | 边界定义 / 大纲 / 源码盘点 | +| `docs/rag/pipeline/*.md` | **仓库 PR** | Python 工程 | 4 篇已交付 + 1 占位(S2-T4 待重启) | +| `docs/rag/end-to-end/README.md` | **占位**(不入 PR) | AI 知识库 | 等 S2-T6 解除阻塞后追加 | +| `docs/rag/evolution/*` | **仓库 PR** | AI 知识库 | S3-T1 / S3-T2(终审已通过) | +| `docs/rag/review/*` | **仓库 PR**(已通过部分) + **Issue 归档**(未启动部分) | 知识运营 | S3-T1 / S3-T2 终审报告 + S2-T7 占位 | +| `docs/rag/_indexes/*` | **仓库 PR** | 知识运营 | Glossary / File Index / Chart Index | +| `docs/rag/_release/*` | **仓库 PR** | 知识运营 | 本文 + 版本约定 + 运营保鲜计划 | + +> **建议 PR 拆分**: +> - **PR-1**(_meta + README + INDEX):作为治理 baseline 先合,便于后续文档按统一模板入库。 +> - **PR-2**(overview + 4 个 .mmd):架构与图谱基础,独立合并便于 review。 +> - **PR-3**(pipeline 4 篇 + 1 占位):Sprint-2 已交付内容;占位文件含明确"等待重启"说明,避免误读。 +> - **PR-4**(evolution + capability-map.mmd):架构改造与迭代路线(S3-T1/T2)。 +> - **PR-5**(review + _indexes + _release):评审报告与索引、运营资产。 + +## 3. v1.0-RC1 → v1.0 升版门槛(Release Gate) + +| 门槛 | 当前状态 | 责任人 | 预计完成 | +|---|---|---|---| +| **G1: S2-T7 评审收口完成** | ⏳ todo(上一次 API Error) | 知识运营 | 重启后 1 个工作日 | +| **G2: S2-T4 GraphRAG 文档交付 + 评审通过** | ⏳ 占位 | Python 工程师 | 重启后 1 周 | +| **G3: S2-T6 E2E 调用链路文档交付** | ⏳ 阻塞(依赖 S2-T1~T5) | AI 知识库专家 | S2-T4 解除后 3 个工作日 | +| **G4: 已交付的 4 篇 Sprint-2 文档(T1/T2/T3/T5)正式评分录入** | ⏳ 待 S2-T7 评审落分 | 知识运营 | G1 完成时一并 | +| **G5: S3-T1 §3.1 短期路线图工作项 #1(删除 `node.py:327 print()`)合入 main** | ⏳ 待提 PR | Python 工程师 / AI 知识库 | 任意 1 个工作日 | +| **G6: 全部仓库 PR 合入 main 分支** | ⏳ 待 PR 创建 | 知识运营协调 | G1-G5 完成后启动 | + +> **任一门槛未达成,停在 v1.0-RCN(N 递增)**。 + +## 4. v1.0 ~ v2.0 版本节奏(建议) + +| 版本 | 触发条件 | 主要内容 | +|---|---|---| +| `v1.0` | G1-G6 全部 PASS | 完整的 S1+S2+S3 文档全集,对外可发布 | +| `v1.1` | S3-T1 §3.1 短期路线图(5 项工作项)全部合入 | 增量更新:Reranker 缓存上线、`RAGSettings` 落地、单测脱离 ES 等 | +| `v1.2` | S3-T2 PoC-A(RRF)+ PoC-B(Memory Rewrite)合入 | 增量更新 D2 / D4 章节,回填实测数据 | +| `v1.3` | S3-T1 §3.2 中期路线图完成(OTel / Plugin Registry / 4 大 Protocol) | 大版本:Embedder/Retriever/Reranker/Generator Protocol 落地,可观测性建立 | +| `v2.0` | S3-T1 §3.3 长期路线图完成 + S3-T2 D1/D3 多模态 + 增量图 | 架构演进里程碑:可插拔 VDB、Pipeline DSL、增量图、跨模态检索 | + +> 这套节奏与 [S3-T2] §4 Roadmap 的 Sprint-3 / 短/中/长 时间窗一致;每次升版必须同步刷新 Mermaid 图与 source-commit。 + +## 5. 文档质量门槛(自检 vs 终审) + +| 类别 | 自检通过分 | 终审通过分 | 一票否决项 | +|---|---|---|---| +| Sprint-2 各深度文档(S2-T1 ~ S2-T5) | ≥ 70 | ≥ 80 | 源码虚构 / 核心章节缺失 / 安全风险描述 / 架构严重脱节 | +| Sprint-3 演进文档(S3-T1 / S3-T2) | ≥ 75 | ≥ 80 | 同上 | +| 治理资产(_meta) | ≥ 70 | ≥ 80 | 同上 | +| 索引与 Landing | ≥ 70 | ≥ 80 | 同上 | + +> 上述阈值与 S1-T1 评分卡保持一致。当前 S3-T1 / S3-T2 已通过终审(96 / 95)。 + +## 6. 已知风险与应对 + +| # | 风险 | 影响 | 缓解 | +|---|---|---|---| +| R1 | S2-T4 GraphRAG 文档因 API Error 多次失败,可能再次中断 | v1.0 升版被卡 | 启动前先 dry-run 一次,若仍失败则把"GraphRAG 现有 light/general 的简版梳理"由 [@AI 知识库专家] 接管 | +| R2 | S2-T6 E2E 文档目前 blocked,依赖 S2-T1~T5 全部交付 | v1.0 升版被卡 | S2-T4 完成后立即触发 S2-T6 | +| R3 | 仓库 PR 与 RAG 主分支合并冲突(仓主可能在并行修改) | PR 滚动 review 难 | 锁定 source-commit,按 PR-1 → PR-5 顺序短链合并;冲突时由责任专家 rebase | +| R4 | 文档与代码失同步(main 分支前进) | 内容时效性下降 | 见 `ops-and-freshness-plan.md` 的"每次 release 同步评审"机制 | +| R5 | 内部 Wiki 渲染 Mermaid 节点上限 1500 | 大图渲染失败 | 拆图(Chart Index §4 已规划)、备份 SVG | +| R6 | Sprint-2 文档评分若多篇低于 80,需返工 | 升版延期 | 先评 in_review 状态的 4 篇,发现共性问题立即下发修订 | + +## 7. 发布仪式 Checklist + +发布 v1.0 前,逐项打勾: + +- [ ] G1-G6 全部门槛达成(§3) +- [ ] PR-1 ~ PR-5 全部合入 main +- [ ] 内部 Wiki 同步发布(含 Mermaid 渲染版) +- [ ] 在 [WS-24](mention://issue/a07f108d-06ee-41b8-8b57-22455f60ddeb) 发"v1.0 正式发布纪要"评论(含交付物清单 + 链接 + 总评分) +- [ ] 状态由 `in_review` → `done` +- [ ] 通知 PM 启动 [WS-25 / S3-T4 PM 复盘](mention://issue/b98604b1-326f-42b4-a4c2-b3d9ad80ec75) +- [ ] 创建 v1.1 跟踪 issue(占位下一轮迭代) + +--- + +## 附录 A:当前已交付文件 SHA-1(防篡改) + +> 在落入仓库 PR 前,先记录附件的 SHA-1 校验值;合并到仓库后由 reviewer 复核。 + +| 文件 | 来源 attachment ID | 大小 | 备注 | +|---|---|---|---| +| `S3-T1-deliverable.md` → `evolution/architecture-refactor-suggestions.md` | `019e0757-d0ab-704a-b6bb-5c1bbb3d8eb6` | 33 KB | S3-T1 | +| `future-extensions-roadmap.md` → `evolution/future-extensions-roadmap.md` | `019e075c-42a0-7a64-b5d5-263c0fc92a0b` | 32 KB | S3-T2 | +| `capability-map.mmd` → `evolution/capability-map.mmd` | `019e075c-42c7-713e-a8c3-41bf37d5ca37` | 4 KB | S3-T2 | +| `01-architecture.mmd` → `overview/01-architecture.mmd` | `019e0747-0c26-79e8-984b-f6d8394016aa` | 5 KB | S1-T2 | +| `02-indexing-pipeline.mmd` → `overview/02-indexing-pipeline.mmd` | `019e0747-0c4d-7808-8362-16b237c02048` | 4 KB | S1-T2 | +| `03-query-pipeline.mmd` → `overview/03-query-pipeline.mmd` | `019e0747-0c71-7ab7-9269-1175e487308e` | 4 KB | S1-T2 | +| `04-graphrag-indexing.mmd` → `overview/04-graphrag-indexing.mmd` | `019e0747-0c92-7ec5-a2c9-bb3f9c2b4de9` | 3 KB | S1-T2 | +| `DocMap.md` → `overview/DocMap.md` | `019e0747-0cb6-78c4-8e5c-af441e571e3c` | 18 KB | S1-T2 | +| `boundaries.md` → `overview/boundaries.md` | `019e0747-0cd9-7a9e-95f1-f5428e35b3c6` | 13 KB | S1-T2 | + +> S1-T1 _meta 系列与 Sprint-2 各深度文档当前以**评论正文**形式存在,作为本次 RC 的"评论沉淀+对外引用"双形态。仓库 PR 时由责任专家把评论正文落到对应文件,由知识运营复核 SHA-1 一致性。 + +— **Release Manifest · v1.0-RC1 · 2026-05-08** — diff --git a/docs/rag/_release/versioning-convention.md b/docs/rag/_release/versioning-convention.md new file mode 100644 index 00000000..5428d0e9 --- /dev/null +++ b/docs/rag/_release/versioning-convention.md @@ -0,0 +1,84 @@ +# MemoryBear RAG Docs · 版本号约定 + +> 适用范围:`docs/rag/` 下所有文档,含 Markdown / Mermaid / 评分卡 / 模板。 + +## 1. 版本号格式(语义化) + +``` +v.[-RC] +``` + +- **MAJOR**:架构层重大变化(如 4 大 Protocol 落地、可插拔 VDB 上线、检索范式切换) +- **MINOR**:增量内容更新(新增章节、补图、回填基准、修订错误) +- **-RC\**:候选发布(Release Candidate)N,用于在所有升版门槛达成前的过渡发布 +- **示例**:`v1.0-RC1` → `v1.0-RC2` → `v1.0` → `v1.1` → `v2.0-RC1` → `v2.0` + +## 2. 升版触发规则 + +| 触发器 | 升版动作 | +|---|---| +| Release Gate 全部达成(见 release-manifest) | RCN → 正式版(去掉 -RC 后缀) | +| 单文档 Should-Fix 修订 | 文档级 frontmatter `version` 增加 patch 标识(如 `1.0.1`),全集版本不变 | +| 新增 Sprint 全套文档(如 Sprint-4 立项) | 全集 MINOR +1(v1.1 → v1.2) | +| 4 大 Protocol 落地、可观测性引入、Plugin Registry 上线 | 全集 MAJOR +1(v1.x → v2.0-RC1) | +| 紧急 hot-fix(修正错误源码引用、补救一票否决项) | 单文档 patch +1,并在 INDEX.md 记录 | + +## 3. frontmatter 规范 + +每个 `.md` 文档 **必须**有 frontmatter,包括: + +```yaml +--- +name: <文档简称> +description: <一句话描述> +type: +sprint: +task: +author: <责任人角色名> +reviewer: <终审责任人或 "待 [S2-T7] 评审"> +version: <语义化版本,如 1.0.0> +source-commit: <锁定的代码 SHA,如 feae2f2e> +last-reviewed-at: +--- +``` + +> **强制项**:name、description、type、source-commit、last-reviewed-at。 +> **可选项**:reviewer(评审中的文档可填 "待 [S2-T7] 评审")、version(占位文档可不填)。 + +## 4. source-commit 锁定规则 + +- **每篇深度文档**必须锁定一个具体的 commit SHA,作为"本文档与代码 100% 对齐的时间点"。 +- 当 main 分支前进、且与文档相关代码发生变化时: + - 微改(重命名、注释、格式)→ 不强制更新文档,但可顺手更新 `last-reviewed-at`。 + - 接口变化、流程改动 → **必须**修订文档,并刷新 source-commit 与 last-reviewed-at。 +- **多文档共享 commit**:本次全集统一锁定到 `feae2f2e`(基线),若后续文档修订采用新 commit,需在 INDEX.md 标注差异。 + +## 5. 与代码版本的对齐 + +| 文档版本 | MemoryBear 代码版本 | +|---|---| +| `v1.0-RCN`(候选) | 基于 `feae2f2e` 工作分支 `agent/ai/f8de881a` | +| `v1.0`(正式) | 与下一个 release tag(如 `v0.4.0`)同步发布 | +| `v1.1` | 与 release `v0.4.x` 增量同步 | +| `v2.0` | 对应 4 大 Protocol 落地之后的 release(预计 `v0.5.0` 之后) | + +> 文档版本号**不强制**与代码版本号一致,但发布通告中需明确"对应代码版本"。 + +## 6. 已废弃文档处理 + +- 标记 `status: deprecated` 在 frontmatter; +- 文件首部加显眼的 `> ⚠️ DEPRECATED · 自 v1.x 起,本文已并入 <新文档路径>` 横幅; +- 保留 6 个月(覆盖至少一个 release cycle),之后转移到 `docs/rag/_archive//` 归档。 + +## 7. 协议变更(如 4 大 Protocol 名称改动) + +- 任意涉及命名的协议(Retriever / Reranker / Embedder / Generator / GraphStore)变更,必须同步刷新: + 1. `evolution/architecture-refactor-suggestions.md` 主文 + 2. `evolution/future-extensions-roadmap.md` 引用处 + 3. `_indexes/glossary.md` + 4. `_indexes/file-index.md` "提议中"行 + 5. `INDEX.md` 版本与状态 + 6. 所有 Sprint-2 文档中提到该协议的章节 +- 变更记录留在 `evolution/CHANGELOG.md`(v1.1 起新建)。 + +— **Versioning Convention · v1.0-RC1 · 2026-05-08** — diff --git a/docs/rag/end-to-end/README.md b/docs/rag/end-to-end/README.md new file mode 100644 index 00000000..b6d32dc7 --- /dev/null +++ b/docs/rag/end-to-end/README.md @@ -0,0 +1,264 @@ +--- +title: "[S2-T6] 端到端检索-生成调用链路与时序图 — 正式版" +author: AI 知识库解决方案专家 +reviewer: 知识运营与治理专家 +source-commit: feae2f2e (MemoryBear) +last-reviewed-at: 2026-05-08 +scope: api/app/{services,app_chat_service,draft_run_service,core/agent/langchain_agent,core/models/{llm,rerank,embedding},core/rag/{nlp/search,vdb/elasticsearch/elasticsearch_vector,app/naive,graphrag/{search,general/index}}} +version: v1.0 +status: 正式版(已解除占位) +--- + +# [S2-T6] 端到端检索-生成调用链路与时序图 — 正式版 + +> 本文档为 [WS-24](mention://issue/a07f108d-06ee-41b8-8b57-22455f60ddeb) v1.0 文档全集的正式组成文件,替换 v1.0-RC1 中的占位版本。 +> 原始完整文档与逐节详评见 [WS-20](mention://issue/a3deeaa1-5b30-4da5-b4af-1b081f7f6394) 与 [WS-21](mention://issue/41f2482b-6f3e-4253-95f7-3e22e790f31c) §S2-T6 评审报告。 + +--- + +## 1. 一句话定位 + +本文档是 Sprint-2 的"全链路串联"文档,将 [S2-T1]~[S2-T5] 五篇独立深度文档中的调用栈、数据结构与配置项,整合为**两条端到端时序图**(Query 端 + Indexing 端)、**一张关键路径表**、**三套多场景调用链**与**一张错误降级路径图**。所有函数引用均直接来源于子任务文档,未凭空虚构。 + +--- + +## 2. 评审结果 + +| 维度 | 满分 | 得分 | 关键说明 | +|---|---:|---:|---| +| 准确性 | 25 | 24 | 抽检 7/7 命中:`agnet_chat` / `_prepare_messages` / `knowledge_retrieval` / `_retrieve_for_knowledge` / `insert_citations` / `chunk()` / `_classify_error` | +| 完整性 | 25 | 24 | 5 项硬性验收 100% 满足:Query 端时序图、Indexing 端时序图、关键路径表(15 行)、3 场景调用链、错误降级矩阵(13 行 + 6 路径 + 5 代码片段) | +| 时效性 | 15 | 14 | frontmatter 完整规范(author / source-commit `feae2f2e` / last-reviewed-at / scope),仅缺 reviewer 字段(等待评审填入) | +| 可读性 | 15 | 14 | Mermaid `autonumber` + `Note over` + `alt/par/loop` 专业级写法;瓶颈🔴🟡🟢色标视觉化优秀 | +| 可执行性 | 20 | 19 | P50/P95 基线 + 瓶颈分析可直接落地为运维 SOP;5 个降级代码片段 copy-pasteable | +| **合计** | **100** | **95** | **PASS(整合标杆,超 ≥85 门槛 +10)** | + +**裁定:** Sprint-2 **整合标杆**,直接通过,无 Must-Fix。 + +--- + +## 3. Query 端 E2E 时序图(摘要) + +```mermaid +sequenceDiagram + autonumber + actor U as 用户 + participant FE as 前端 + participant API as FastAPI + participant CS as AppChatService + participant AS as AgentRunService + participant Agent as LangChainAgent + participant KR as knowledge_retrieval() + participant VDB as ElasticSearchVector + participant Graph as KGSearch + participant LLM as RedBearLLM + + U->>FE: 输入 Query + FE->>API: POST /api/v1/chat + API->>CS: await agnet_chat() + CS->>Agent: LangChainAgent() + Agent->>LLM: invoke(messages) [首轮判断工具] + LLM-->>Agent: 需调用 knowledge_retrieval_tool + Agent->>KR: knowledge_retrieval(query, config) + + loop 遍历每个知识库 + KR->>VDB: _retrieve_for_knowledge() + alt retrieve_type == "semantic" + VDB->>VDB: search_by_vector() + embed_query() + else retrieve_type == "participle" + VDB->>VDB: search_by_full_text() + ik_max_word + else retrieve_type == "hybrid" + par 双路并发 + VDB->>VDB: search_by_vector() + VDB->>VDB: search_by_full_text() + end + VDB->>VDB: rerank() + RedBearRerank + end + alt use_graph=true + KR->>Graph: kg_retriever.retrieval() + Graph->>Graph: query_rewrite() LLM 提取实体+类型 + Graph->>Graph: 三路召回: entity/relation/community + end + end + + KR-->>Agent: List[DocumentChunk] + Agent->>LLM: astream_events() [流式生成] + LLM-->>FE: SSE 逐字渲染 +``` + +完整版含 30+ 步骤调用栈、输入输出数据结构、同步/异步标注,见 [WS-20](mention://issue/a3deeaa1-5b30-4da5-b4af-1b081f7f6394) §1。 + +--- + +## 4. Indexing 端 E2E 时序图(摘要) + +```mermaid +sequenceDiagram + autonumber + actor U as 用户 + participant API as document_controller.py + participant Task as Celery Task + participant Chunk as chunk() + participant Parser as DeepDoc Parser + participant NLP as naive_merge + participant Emb as RedBearEmbeddings + participant VDB as ElasticSearchVector + participant ES as Elasticsearch + participant Graph as GraphRAG Index + + U->>API: POST /documents 上传文件 + API->>Task: 异步触发 chunk 任务 + Task->>Chunk: chunk(filename, binary, ...) + + alt PDF 格式 + Chunk->>Parser: Pdf.__call__() → OCR → Layout → TSR + else DOCX 格式 + Chunk->>Parser: Docx.parse() + else Excel/CSV + Chunk->>Parser: ExcelParser.__call__() + else Markdown + Chunk->>Parser: MarkdownParser + end + + Chunk->>NLP: naive_merge(sections) + tokenize_chunks() + Chunk-->>Task: List[Dict] (ES doc 格式) + + Task->>Emb: embed_documents(texts) + Emb-->>Task: List[List[float]] + + Task->>VDB: add_chunks(chunks, embeddings) + VDB->>ES: helpers.bulk(actions) + + alt GraphRAG 启用 + Task->>Graph: run_graphrag_for_kb() + Graph->>Graph: generate_subgraph() → LLM 抽取 + Graph->>Graph: merge_subgraph() + Graph->>ES: 写入 entity/relation chunks + alt General 模式 + Graph->>Graph: EntityResolution() + Graph->>Graph: leiden.run() + CommunityReportsExtractor() + end + end +``` + +完整版含 14 步骤调用栈、ES Doc 字段契约,见 [WS-20](mention://issue/a3deeaa1-5b30-4da5-b4af-1b081f7f6394) §2。 + +--- + +## 5. 关键路径表(Critical Path Table) + +| # | 环节 | 关键函数 | 文件:行号 | P50 | P95 | 阻塞性 | 瓶颈 | +|---|------|---------|-----------|-----|-----|--------|------| +| 1 | **PDF 解析 (OCR+Layout+TSR)** | `Pdf.__call__()` | `deepdoc/parser/pdf_parser.py:1006` | 3s | 15s | 阻塞 | 🔴 | +| 2 | **Chunking** | `naive_merge()` + `tokenize_chunks()` | `nlp/__init__.py:562,258` | 50ms | 200ms | 阻塞 | 🟡 | +| 3 | **Embedding (批量)** | `embed_documents()` | `models/embedding.py:65` | 200ms | 1s | 阻塞 | 🔴 | +| 4 | **ES 批量写入** | `helpers.bulk()` | `elasticsearch_vector.py:85` | 100ms | 500ms | 阻塞 | 🟡 | +| 5 | **GraphRAG 实体抽取** | `generate_subgraph()` | `graphrag/general/index.py:333` | 30s | 120s | 阻塞 | 🔴 | +| 6 | **GraphRAG 消歧** | `EntityResolution.__call__()` | `entity_resolution.py:53` | 10s | 60s | 阻塞 | 🔴 | +| 7 | **GraphRAG 社区报告** | `CommunityReportsExtractor.__call__()` | `community_reports_extractor.py:55` | 20s | 90s | 阻塞 | 🔴 | +| 8 | **Query Embedding** | `embed_query()` | `models/embedding.py:65` | 50ms | 300ms | 阻塞 | 🟡 | +| 9 | **ES 向量检索** | `search_by_vector()` | `elasticsearch_vector.py:374` | 30ms | 200ms | 阻塞 | 🟡 | +| 10 | **ES 关键词检索** | `search_by_full_text()` | `elasticsearch_vector.py:468` | 20ms | 100ms | 阻塞 | 🟢 | +| 11 | **外部 Rerank** | `RedBearRerank.compress_documents()` | `models/rerank.py:11` | 100ms | 500ms | 阻塞 | 🟡 | +| 12 | **GraphRAG 检索** | `KGSearch.retrieval()` | `graphrag/search.py:19` | 200ms | 1s | 阻塞 | 🟡 | +| 13 | **LLM 首次调用** | `_chat()` | `chat_model.py:122` | 500ms | 3s | 阻塞 | 🔴 | +| 14 | **LLM 流式生成** | `_chat_streamly()` | `chat_model.py:152` | 500ms | 5s | 流式 | 🔴 | +| 15 | **引用回填** | `Dealer.insert_citations()` | `search.py:489` | 100ms | 500ms | 阻塞 | 🟡 | + +### 5.1 四大🔴瓶颈与缓解方向 + +| 瓶颈 | 根因 | 缓解方向 | +|------|------|---------| +| PDF 解析 (P95=15s) | OCR + Layout + TSR 串行执行 | MinerU 替代 / 异步队列 / 预加载模型 | +| Embedding API (P95=1s) | 外部 API 延迟,batch_size=16 | 本地 Xinference / GPUStack 部署 | +| GraphRAG 建图 (P95=120s) | LLM 多轮抽取,单文档串行 | 增加 max_parallel_documents / 增量更新 | +| LLM 流式输出 (P95=5s) | 首次 token (TTFT) 慢 | 缓存高频 query / 缩短 max_tokens | + +--- + +## 6. 多场景调用链(3 场景) + +### 场景 A:纯向量检索问答 +``` +Query → AppChatService → LangChainAgent → knowledge_retrieval() + → _retrieve_for_knowledge() [retrieve_type="semantic"] + → ElasticSearchVector.search_by_vector() + embed_query() + → ES script_score: cosineSimilarity + → top_k chunks → Agent → LLM 流式生成 +``` + +### 场景 B:混合检索问答(关键词 + 向量) +``` +Query → knowledge_retrieval() [retrieve_type="hybrid"] + → 双路并发: search_by_vector() + search_by_full_text() + → metadata.doc_id 去重 + → rerank() + RedBearRerank.compress_documents() + → top_k → Agent → LLM 流式生成 +``` + +### 场景 C:GraphRAG 关系推理问答 +``` +Query → knowledge_retrieval() [retrieve_type="graph"] + → 先执行 hybrid 检索 + → KGSearch.retrieval() → query_rewrite() LLM 提取实体+类型 + → 三路召回: entity/relation/community + → n-hop 路径扩展 (sim_decay = 1/(2+hop_depth)) + → 融合打分: score = sim × pagerank + → Token 预算截断 → Agent → LLM 流式生成 +``` + +完整 ASCII 流程图与数据结构流转详见 [WS-20](mention://issue/a3deeaa1-5b30-4da5-b4af-1b081f7f6394) §4。 + +--- + +## 7. 错误传播与降级路径 + +### 7.1 错误矩阵(核心项) + +| 环节 | 失败模式 | 兜底逻辑 | +|---|---|---| +| PDF 解析 | OCR 模型缺失 | 标记 failed_document | +| Embedding API | 超时/限流 | 抛出异常,整批重试 | +| ES 写入 | ConnectionTimeout | ATTEMPT_TIME=2 重试 | +| 知识库检索 | 单 KB 不可用 | try/except continue,跳过失败 KB | +| 向量检索为空 | 阈值过严 | fallback 降低 min_match 0.3→0.1 | +| 外部 Rerank | API 超时 | fallback 返回原始排序 | +| GraphRAG 检索 | 图谱未建 | fallback 仅 hybrid 结果 | +| LLM 调用 | RATE_LIMIT | 重试 5 次 + 随机抖动 | +| LLM 截断 | finish_reason="length" | 自动追加截断提示 | + +### 7.2 降级路径图 + +``` +正常路径: Query → Hybrid → Rerank → LLM → 引用回填 → 输出 + +降级 1 (检索为空): Hybrid (空) → fallback 降低阈值 → 仍空 → LLM 直接回答 +降级 2 (Rerank 失败): Hybrid → Rerank 超时 → fallback 原始排序 → LLM 生成 +降级 3 (GraphRAG 失败): Hybrid → GraphRAG 失败 → fallback 仅 hybrid → LLM 生成 +降级 4 (单 KB 失败): KB-A 失败 + KB-B 成功 → 合并 → LLM 生成 +降级 5 (LLM 失败): 检索成功 → LLM 5 次重试后 → 返回 "**ERROR**: 服务暂不可用" +``` + +完整代码片段(5 段可复用降级代码)见 [WS-20](mention://issue/a3deeaa1-5b30-4da5-b4af-1b081f7f6394) §5.3。 + +--- + +## 8. 跨文档引用索引 + +| 本章节 | 被引文档 | 引用点 | +|--------|---------|--------| +| §3 Query 端 | [S2-T5] | `app_chat_service.py:43`, `langchain_agent.py:230`, `_chat_streamly()` | +| §3 Query 端 | [S2-T3] | `search_by_vector()`, `search_by_full_text()`, `rerank()` | +| §3 Query 端 | [S2-T4] | `KGSearch.retrieval()`, `query_rewrite()` | +| §3 Query 端 | [S2-T2] | `embed_query()` | +| §3 Query 端 | [S2-T5] | `RedBearRerank.compress_documents()`, `_filter_citations()` | +| §4 Indexing 端 | [S2-T1] | `chunk()`, `naive_merge()`, `tokenize_chunks()` | +| §4 Indexing 端 | [S2-T2] | `embed_documents()` | +| §4 Indexing 端 | [S2-T3] | `add_chunks()`, `helpers.bulk()` | +| §4 Indexing 端 | [S2-T4] | `run_graphrag_for_kb()`, `generate_subgraph()`, `EntityResolution()`, `leiden.run()` | + +**结论:6 篇文档形成完整闭环,跨文档引用 0 不一致。** + +--- + +*本文档为 MemoryBear RAG Docs v1.0 正式版本的组成文件。完整时序图、数据结构定义、关键路径分析与代码片段参见 [WS-20](mention://issue/a3deeaa1-5b30-4da5-b4af-1b081f7f6394) 评论历史。* diff --git a/docs/rag/end-to-end/source-full.md b/docs/rag/end-to-end/source-full.md new file mode 100644 index 00000000..bccaed54 --- /dev/null +++ b/docs/rag/end-to-end/source-full.md @@ -0,0 +1,645 @@ +--- +title: "[S2-T6] 端到端检索-生成调用链路与时序图" +author: AI 知识库解决方案专家 +source-commit: feae2f2e (MemoryBear) +last-reviewed-at: 2026-05-08 +scope: api/app/{services,app_chat_service,draft_run_service,core/agent/langchain_agent,core/models/{llm,rerank,embedding},core/rag/{nlp/search,vdb/elasticsearch/elasticsearch_vector,app/naive,graphrag/{search,general/index}}} +--- + +# [S2-T6] 端到端检索-生成调用链路与时序图 + +## 一句话定位 + +本文档是 Sprint-2 的"全链路串联"文档,将 [S2-T1]~[S2-T5] 五篇独立深度文档中的调用栈、数据结构与配置项,整合为**两条端到端时序图**(Query 端 + Indexing 端)、**一张关键路径表**、**三套多场景调用链**与**一张错误降级路径图**。所有函数引用均直接来源于子任务文档,未凭空虚构。 + +--- + +## 1. Query 端 E2E 时序图 + +**场景**:用户通过分享链接发起对话,Agent 调用知识库检索工具,最终流式输出答案。 + +```mermaid +sequenceDiagram + autonumber + actor U as 用户 + participant FE as 前端 (Web) + participant API as FastAPI
api/main.py + participant CS as AppChatService
services/app_chat_service.py + participant AS as AgentRunService
services/draft_run_service.py + participant Agent as LangChainAgent
core/agent/langchain_agent.py + participant Tool as knowledge_retrieval_tool
draft_run_service.py:195 + participant KR as knowledge_retrieval()
core/rag/nlp/search.py:36 + participant RK as _retrieve_for_knowledge()
core/rag/nlp/search.py:149 + participant VDB as ElasticSearchVector
core/rag/vdb/elasticsearch/ + participant ES as Elasticsearch + participant Graph as KGSearch
core/rag/graphrag/search.py:19 + participant LLM as RedBearLLM
core/models/llm.py + participant CM as Chat Model
core/rag/llm/chat_model.py + + U->>FE: 输入 Query + FE->>API: POST /api/v1/chat
{message, conversation_id, ...} + + API->>CS: await agnet_chat()
app_chat_service.py:43 + Note over CS: 同步/阻塞: 模型配置加载 + 工具组装 + + CS->>CS: 加载 features_config + 文件校验 + CS->>CS: ModelApiKeyService.get_available_api_key()
获取 LLM api_key/model_name + CS->>CS: render_prompt_message()
变量替换 system_prompt + CS->>AS: load_knowledge_retrieval_config()
组装知识检索工具 + + CS->>Agent: LangChainAgent()
langchain_agent.py:26 + Note over Agent: 输入: system_prompt + tools
max_iterations = 5 + len(tools)*2 + + Agent->>Agent: _prepare_messages()
langchain_agent.py:230
组装: history + context + query + Note over Agent: 数据结构: List[BaseMessage]
[SystemMessage, HumanMessage, AIMessage, ...] + + Agent->>LLM: invoke(messages)
models/llm.py:65 + LLM->>CM: _chat()
chat_model.py:122 + Note over CM: 同步/阻塞 HTTP 调用
stream=False (首轮判断工具) + + CM-->>LLM: AIMessage(content="", tool_calls=[...]) + LLM-->>Agent: 需调用 knowledge_retrieval_tool + + Agent->>Tool: 执行知识检索工具 + Tool->>KR: knowledge_retrieval(query, config)
search.py:36 + Note over KR: 输入: query=str
config={knowledge_bases, retrieve_type, reranker_id, use_graph} + + loop 遍历每个知识库 + KR->>RK: _retrieve_for_knowledge()
search.py:149 + Note over RK: 输入: db_knowledge, kb_config
输出: List[DocumentChunk] + + alt retrieve_type == "semantic" (纯向量) + RK->>VDB: search_by_vector()
elasticsearch_vector.py:374 + VDB->>VDB: embeddings.embed_query(query)
models/embedding.py:65 + VDB->>ES: script_score: cosineSimilarity()
filter: metadata.status=1 + ES-->>VDB: List[hit] (score /2 归一化到 [0,1]) + else retrieve_type == "participle" (纯关键词) + RK->>VDB: search_by_full_text()
elasticsearch_vector.py:468 + VDB->>ES: match + ik_max_word
filter: metadata.status=1 + ES-->>VDB: List[hit] (_score/max_score 归一化) + else retrieve_type == "hybrid" (混合) + par 双路并发 + RK->>VDB: search_by_vector() [异步] + RK->>VDB: search_by_full_text() [异步] + end + RK->>RK: metadata.doc_id 去重 + RK->>VDB: rerank(query, docs, top_k)
elasticsearch_vector.py:560 + VDB->>VDB: RedBearRerank.compress_documents()
models/rerank.py:11 + end + + alt retrieve_type == "graph" 且 use_graph=true + RK->>Graph: kg_retriever.retrieval()
graphrag/search.py:19 + Graph->>Graph: query_rewrite() LLM 提取实体+类型 + Graph->>ES: 三路召回: entity/relation/community + ES-->>Graph: {page_content: entities+relations+community} + Graph-->>RK: DocumentChunk 插入 rs[0] + end + end + + alt reranker_id 配置 + KR->>KR: rerank()
search.py:284 + KR->>KR: RedBearRerank.compress_documents()
models/rerank.py:11 + Note over KR: 外部 rerank API 调用
同步/阻塞, 100-500ms + end + + KR-->>Tool: List[DocumentChunk]
page_content + metadata + Tool->>Tool: chunks 拼接为 context 字符串 + Tool-->>Agent: f"检索到以下相关信息: {context}" + + Agent->>Agent: _prepare_messages()
追加工具结果到消息列表 + Agent->>LLM: astream_events(version="v2")
models/llm.py:117 + LLM->>CM: _chat_streamly()
chat_model.py:152 + Note over CM: 异步/流式 HTTP SSE
yield (delta, token_count) + + loop 每收到一个 token chunk + CM-->>LLM: GenerationChunk + LLM-->>Agent: on_chat_model_stream event + Agent-->>CS: yield SSE chunk + CS-->>API: StreamingResponse + API-->>FE: data: {"content": "..."} + FE-->>U: 逐字渲染 + end + + CS->>CS: _filter_citations()
draft_run_service.py:474
引用过滤 + 下载链接 + CS-->>API: {content, citations, tokens_used} + API-->>FE: JSON 响应 +``` + +### 1.1 关键调用栈注释 + +| 步骤 | 函数 | 文件:行号 | 同步/异步 | 输入 | 输出 | +|------|------|-----------|-----------|------|------| +| 1 | `agnet_chat()` | `services/app_chat_service.py:43` | `async` | message, config, files | Dict | +| 2 | `LangChainAgent.__init__()` | `core/agent/langchain_agent.py:26` | 同步 | model_name, tools, system_prompt | Agent 实例 | +| 3 | `_prepare_messages()` | `core/agent/langchain_agent.py:230` | 同步 | message, history, context | `List[BaseMessage]` | +| 4 | `knowledge_retrieval()` | `core/rag/nlp/search.py:36` | 同步 | query, config | `List[DocumentChunk]` | +| 5 | `_retrieve_for_knowledge()` | `core/rag/nlp/search.py:149` | 同步 | db_knowledge, kb_config | `List[DocumentChunk]` | +| 6 | `search_by_vector()` | `core/rag/vdb/elasticsearch/elasticsearch_vector.py:374` | 同步 | query, top_k, score_threshold | `List[DocumentChunk]` | +| 7 | `embed_query()` | `core/models/embedding.py:65` | 同步 | query_str | `List[float]` | +| 8 | `search_by_full_text()` | `core/rag/vdb/elasticsearch/elasticsearch_vector.py:468` | 同步 | query, top_k, score_threshold | `List[DocumentChunk]` | +| 9 | `rerank()` (独立) | `core/rag/nlp/search.py:284` | 同步 | query, docs, top_k | `List[DocumentChunk]` | +| 10 | `RedBearRerank.compress_documents()` | `core/models/rerank.py:11` | 同步 | documents, query | `List[Document]` | +| 11 | `KGSearch.retrieval()` | `core/rag/graphrag/search.py:19` | 同步 | question, kb_ids, emb_mdl | Dict | +| 12 | `_chat_streamly()` | `core/rag/llm/chat_model.py:152` | 异步流式 | messages | `AsyncGenerator` | +| 13 | `_filter_citations()` | `services/draft_run_service.py:474` | 同步 | features_config, citations | List[Dict] | + +### 1.2 输入输出数据结构 + +```python +# 1. DocumentChunk (检索结果单元) +# core/rag/models/chunk.py +class DocumentChunk(BaseModel): + page_content: str # chunk 文本内容 + vector: list[float] | None # 向量(检索阶段通常为空) + metadata: dict = { + "doc_id": str, # 文档唯一标识 + "file_name": str, # 原始文件名 + "score": float, # 相似度/重排序分数 + "knowledge_id": str, # 所属知识库 + ... + } + +# 2. knowledge_retrieval 配置结构 +config = { + "knowledge_bases": [{ + "kb_id": str, + "retrieve_type": "participle" | "semantic" | "hybrid" | "graph", + "similarity_threshold": float, # 默认 0.2 + "vector_similarity_weight": float, # 默认 0.3 + "top_k": int, # 默认 4 + }], + "reranker_id": str | None, + "reranker_top_k": int, # 默认 1024 + "use_graph": bool, # 是否启用 GraphRAG +} + +# 3. LangChainAgent 消息结构 +messages = [ + SystemMessage(content="system_prompt + skill_prompts"), + HumanMessage(content="历史消息..."), + AIMessage(content="历史回复..."), + HumanMessage(content="参考信息:\n\n{chunks}\n\n用户问题:\n{query}"), +] +``` + +--- + +## 2. Indexing 端 E2E 时序图 + +**场景**:用户上传 PDF 文档到知识库,系统完成解析、分块、Embedding、写入 ES + 构建图谱。 + +```mermaid +sequenceDiagram + autonumber + actor U as 用户 + participant API as document_controller.py + participant Task as Celery Task
tasks.py + participant Chunk as chunk()
core/rag/app/naive.py:508 + participant Parser as DeepDoc Parser
core/rag/deepdoc/parser/ + participant NLP as naive_merge
core/rag/nlp/__init__.py + participant Emb as RedBearEmbeddings
core/models/embedding.py + participant VDB as ElasticSearchVector
core/rag/vdb/elasticsearch/ + participant ES as Elasticsearch + participant Graph as GraphRAG Index
core/rag/graphrag/general/index.py + + U->>API: POST /documents
上传文件 + knowledge_id + API->>API: 保存原始文件到存储 + API->>Task: 异步触发 chunk 任务 + + Task->>Chunk: chunk(filename, binary, ...)
naive.py:508 + Note over Chunk: 总入口,按扩展名分派 + + alt PDF 格式 + Chunk->>Chunk: 按 parser_config.layout_recognize 选引擎
PARSERS dict: naive.py:97 + Chunk->>Parser: Pdf.__call__()
pdf_parser.py:522 + Parser->>Parser: __images__() OCR
ocr.py:522 + Parser->>Parser: _layouts_rec() 版面识别
layout_recognizer.py:147 + Parser->>Parser: _table_transformer_job() TSR
table_structure_recognizer.py + Parser->>Parser: _text_merge() + _concat_downward()
XGBoost 段落连接 + Parser-->>Chunk: sections=[(text, position_tag), ...]
tables=[...] + else DOCX 格式 + Chunk->>Parser: Docx.parse()
docx_parser.py:9 + Parser-->>Chunk: sections=[(text, image), ...] + else Excel/CSV + Chunk->>Parser: ExcelParser.__call__()
excel_parser.py:203 + Parser-->>Chunk: sections (每行一段) + else Markdown + Chunk->>Parser: MarkdownParser
markdown_parser.py:10 + Parser-->>Chunk: sections (element block) + end + + Chunk->>NLP: naive_merge(sections)
nlp/__init__.py:562 + Note over NLP: 按 token 上限 + delimiter 切分
默认 chunk_token_num=512 (PDF) / 128 (其他) + + NLP->>NLP: tokenize_chunks()
nlp/__init__.py:258 + Note over NLP: 注入 ES 字段:
content_with_weight, content_ltks, content_sm_ltks,
page_num_int, position_int, top_int, docnm_kwd + + Chunk-->>Task: List[Dict] (ES doc 格式) + + Task->>Emb: embed_documents(texts)
models/embedding.py:65 + Note over Emb: 多 provider 支持:
OpenAI/DashScope/Volcano/Xinference/... + Emb-->>Task: List[List[float]] + + Task->>VDB: add_chunks(chunks, embeddings)
elasticsearch_vector.py:55 + VDB->>VDB: create_collection() 懒建索引
elasticsearch_vector.py:65 + Note over VDB: mapping: page_content(text+ik),
metadata(object), vector(dense_vector+cosine) + VDB->>ES: helpers.bulk(actions)
批量写入 + ES-->>VDB: result (success count) + + alt GraphRAG 启用 (use_graphrag=true) + Task->>Graph: run_graphrag_for_kb()
graphrag/general/index.py:122 + Graph->>Graph: generate_subgraph()
index.py:333 + Note over Graph: LLM 抽取 entities + relations
多轮 gleaning (max=2) + Graph->>Graph: merge_subgraph()
index.py:409 + Graph->>ES: 写入 entity/relation chunks
带 q_{dim}_vec 向量字段 + + alt General 模式 + with_resolution + Graph->>Graph: EntityResolution()
entity_resolution.py:53 + Note over Graph: 编辑距离预筛选 + LLM 批量判断
batch=100, concurrent=5 + end + + alt General 模式 + with_community + Graph->>Graph: leiden.run()
leiden.py:95 + Graph->>Graph: CommunityReportsExtractor()
community_reports_extractor.py:55 + Graph->>ES: 写入 community_report chunks + end + end + + Task-->>API: {ok_documents, failed_documents, seconds} + API-->>U: 入库完成通知 +``` + +### 2.1 关键调用栈注释 + +| 步骤 | 函数 | 文件:行号 | 同步/异步 | 输入 | 输出 | +|------|------|-----------|-----------|------|------| +| 1 | `chunk()` | `core/rag/app/naive.py:508` | 同步 | filename/binary, parser_config | `List[Dict]` ES doc | +| 2 | `Pdf.__call__()` | `pdf_parser.py:1006` | 同步 | filename, callback | sections, tables | +| 3 | `OCR.__call__()` | `vision/ocr.py:522` | 同步 | PIL.Image | text_boxes | +| 4 | `LayoutRecognizer4YOLOv10.__call__()` | `layout_recognizer.py:147` | 同步 | image_list | layout_types | +| 5 | `naive_merge()` | `core/rag/nlp/__init__.py:562` | 同步 | sections, chunk_token_num | `List[str]` chunks | +| 6 | `tokenize_chunks()` | `core/rag/nlp/__init__.py:258` | 同步 | chunks, doc | `List[Dict]` ES docs | +| 7 | `embed_documents()` | `core/models/embedding.py:65` | 同步 | texts | `List[List[float]]` | +| 8 | `add_chunks()` | `core/rag/vdb/elasticsearch/elasticsearch_vector.py:55` | 同步 | chunks, embeddings | uuids | +| 9 | `create_collection()` | `elasticsearch_vector.py:609` | 同步 | embeddings | mapping created | +| 10 | `helpers.bulk()` | elasticsearch.helpers | 同步 | actions | (success, errors) | +| 11 | `run_graphrag_for_kb()` | `graphrag/general/index.py:122` | 异步 (trio) | document_ids | subgraphs | +| 12 | `generate_subgraph()` | `graphrag/general/index.py:333` | 异步 | extractor, chunks | nx.Graph | +| 13 | `EntityResolution.__call__()` | `entity_resolution.py:53` | 异步 | graph, nodes | merged_graph | +| 14 | `leiden.run()` | `graphrag/general/leiden.py:95` | 同步 | graph | communities | + +### 2.2 ES Doc 字段契约 + +```python +# 写入 ES 的 chunk 文档结构 (来自 S2-T1 §6.7) +{ + "docnm_kwd": str, # 文件名 (keyword) + "title_tks": str, # 标题粗分词 + "title_sm_tks": str, # 标题细分词 + "content_with_weight": str, # 原始 chunk 文本 (BM25 加权) + "content_ltks": str, # 内容粗分词 (whitespace analyzer) + "content_sm_ltks": str, # 内容细分词 + "page_num_int": [int], # 页码列表 + "position_int": [(p,x0,x1,y0,y1)], # 坐标 + "top_int": [int], # 行顶 y 坐标 + "image": bytes | None, # PIL.Image 二进制 + "doc_type_kwd": str | None, # "image" 或空 + "q_{dim}_vec": [float], # Embedding 向量 (S2-T2 补充) + "metadata": { + "doc_id": str, + "file_name": str, + "knowledge_id": str, + "status": 1, + } +} +``` + +--- + +## 3. 关键路径表 (Critical Path Table) + +> 耗时基线基于代码注释、log 锚点及工程经验估算。实际值取决于文档复杂度、模型 provider、网络延迟与 ES 集群规模。 + +| # | 环节 | 关键函数 | 文件:行号 | P50 | P95 | 阻塞/非阻塞 | 瓶颈标记 | +|---|------|---------|-----------|-----|-----|------------|---------| +| 1 | **PDF 解析 (OCR+Layout+TSR)** | `Pdf.__call__()` | `deepdoc/parser/pdf_parser.py:1006` | 3s | 15s | 阻塞 (CPU/GPU) | 🔴 | +| 2 | **Chunking (tokenize)** | `naive_merge()` + `tokenize_chunks()` | `nlp/__init__.py:562,258` | 50ms | 200ms | 阻塞 (本地 CPU) | 🟡 | +| 3 | **Embedding (批量)** | `embed_documents()` | `models/embedding.py:65` | 200ms | 1s | 阻塞 (网络 I/O) | 🔴 | +| 4 | **ES 批量写入** | `helpers.bulk()` | `elasticsearch_vector.py:85` | 100ms | 500ms | 阻塞 (网络 I/O) | 🟡 | +| 5 | **GraphRAG 实体抽取** | `generate_subgraph()` | `graphrag/general/index.py:333` | 30s | 120s | 阻塞 (LLM I/O) | 🔴 | +| 6 | **GraphRAG 实体消歧** | `EntityResolution.__call__()` | `entity_resolution.py:53` | 10s | 60s | 阻塞 (LLM I/O) | 🔴 | +| 7 | **GraphRAG 社区报告** | `CommunityReportsExtractor.__call__()` | `community_reports_extractor.py:55` | 20s | 90s | 阻塞 (LLM I/O) | 🔴 | +| 8 | **Query Embedding** | `embed_query()` | `models/embedding.py:65` | 50ms | 300ms | 阻塞 (网络 I/O) | 🟡 | +| 9 | **ES 向量检索** | `search_by_vector()` | `elasticsearch_vector.py:374` | 30ms | 200ms | 阻塞 (网络 I/O) | 🟡 | +| 10 | **ES 关键词检索** | `search_by_full_text()` | `elasticsearch_vector.py:468` | 20ms | 100ms | 阻塞 (网络 I/O) | 🟢 | +| 11 | **外部 Rerank** | `RedBearRerank.compress_documents()` | `models/rerank.py:11` | 100ms | 500ms | 阻塞 (网络 I/O) | 🟡 | +| 12 | **GraphRAG 检索** | `KGSearch.retrieval()` | `graphrag/search.py:19` | 200ms | 1s | 阻塞 (LLM+ES) | 🟡 | +| 13 | **LLM 首次调用 (判断工具)** | `_chat()` | `chat_model.py:122` | 500ms | 3s | 阻塞 (网络 I/O) | 🔴 | +| 14 | **LLM 流式生成** | `_chat_streamly()` | `chat_model.py:152` | 500ms | 5s | 非阻塞 (SSE 流式) | 🔴 | +| 15 | **引用回填** | `Dealer.insert_citations()` | `search.py:489` | 100ms | 500ms | 阻塞 (本地 embedding) | 🟡 | + +### 3.1 瓶颈分析 + +| 瓶颈 | 根因 | 缓解方向 | +|------|------|---------| +| PDF 解析 (P95=15s) | OCR + Layout + TSR 串行执行,GPU 模型加载慢 | MinerU 替代 / 异步队列 / 预加载模型 | +| Embedding API (P95=1s) | 外部 API 延迟,batch_size=16 不够大 | 本地 Xinference / GPUStack 部署 | +| GraphRAG 建图 (P95=120s) | LLM 多轮抽取,单文档串行 | 增加 max_parallel_documents / 增量更新 | +| LLM 流式输出 (P95=5s) | 首次 token (TTFT) 慢,长答案总耗时长 | 缓存高频 query / 缩短 max_tokens | + +--- + +## 4. 多场景调用链 + +### 4.1 场景 A:纯向量检索问答 + +**适用**:语义匹配质量高的知识库,用户问题与文档表述风格一致。 + +``` +[User Query] + │ + ▼ +AppChatService.agnet_chat() [services/app_chat_service.py:43] async + │ + ▼ +LangChainAgent.invoke() [core/agent/langchain_agent.py:65] sync + │ + ▼ +knowledge_retrieval_tool 调用 + │ + ▼ +knowledge_retrieval() [core/rag/nlp/search.py:36] sync + │ + ▼ +_retrieve_for_knowledge() [core/rag/nlp/search.py:149] sync + │ retrieve_type="semantic" + ▼ +ElasticSearchVector.search_by_vector() [core/rag/vdb/elasticsearch/elasticsearch_vector.py:374] sync + │ + ├─► embed_query(query) [core/models/embedding.py:65] sync, HTTP + │ │ + │ ▼ + │ List[float] query_vector + │ + ▼ +ES script_score: cosineSimilarity(params.query_vector, 'vector') + 1.0 + filter: metadata.status=1 + │ + ▼ +List[DocumentChunk] (score /2 归一化到 [0,1]) + │ + ▼ +score_threshold 过滤 (默认 0.3) + │ + ▼ +返回 top_k chunks → Agent 上下文组装 + │ + ▼ +LLM _chat_streamly() 流式生成答案 +``` + +**数据结构流转**: +``` +query: str + → query_vector: List[float] (dim=512/768/1024/1536) + → ES hits: List[{_score, _source}] + → DocumentChunk[] (score ∈ [0,1]) + → context: str (chunks 用 "\n\n" 拼接) + → messages: List[BaseMessage] (system + history + context + query) + → SSE stream: AsyncGenerator[str] +``` + +### 4.2 场景 B:混合检索问答 (关键词 + 向量) + +**适用**:关键词精准度与语义召回互补的场景,如技术文档库。 + +``` +[User Query] + │ + ▼ +knowledge_retrieval() [core/rag/nlp/search.py:36] sync + │ + ▼ +_retrieve_for_knowledge() [core/rag/nlp/search.py:149] sync + │ retrieve_type="hybrid" (默认分支) + ▼ +┌─────────────────────────────────────────┐ +│ 双路并发 (asyncio.gather) │ +│ │ +│ 路 1: search_by_vector() │ +│ [elasticsearch_vector.py:374] │ +│ → embed_query() → ES script_score │ +│ → 归一化 score /2 → [0,1] │ +│ │ +│ 路 2: search_by_full_text() │ +│ [elasticsearch_vector.py:468] │ +│ → match + ik_max_word → BM25 │ +│ → 归一化 _score/max_score → [0,1] │ +└─────────────────────────────────────────┘ + │ + ▼ +metadata.doc_id 去重 (后到的丢弃) + │ + ▼ +ElasticSearchVector.rerank() [elasticsearch_vector.py:560] sync + │ + ▼ +RedBearRerank.compress_documents() [core/models/rerank.py:11] sync + │ 外部 API 调用 (Xinference/GPUStack/DashScope) + ▼ +按 relevance_score 降序取 top_k + │ + ▼ +返回 DocumentChunk[] → Agent +``` + +**融合公式**(路径 B 应用层): +``` +candidates = vector_topk(q) ∪ bm25_topk(q) +deduped = unique_by(metadata.doc_id, candidates) +final = reranker(query, deduped)[:top_k] (若配置 reranker) + or sort_by_score_desc(deduped)[:top_k] (未配置时) +``` + +### 4.3 场景 C:GraphRAG 关系推理问答 + +**适用**:需要多跳推理、实体关联分析、全局洞察的复杂问答。 + +``` +[User Query] + │ + ▼ +knowledge_retrieval() [core/rag/nlp/search.py:36] sync + │ + ▼ +_retrieve_for_knowledge() [core/rag/nlp/search.py:149] sync + │ retrieve_type="graph" + ├─► 先执行 hybrid 检索 (同场景 B) + │ + ▼ +KGSearch.retrieval() [core/rag/graphrag/search.py:19] sync + │ + ▼ +query_rewrite() [graphrag/search.py:33] + │ + ├─► LLM Prompt: minirag_query2kwd + │ 输入: question + TYPE_POOL (从 ES 采样) + │ 输出: {answer_type_keywords, entities_from_query} + │ + ▼ +┌─────────────────────────────────────────┐ +│ 三路召回并行 │ +│ │ +│ 路 1: get_relevant_ents_by_keywords() │ +│ → embed_query(entities) → ES knn │ +│ → 实体向量相似度召回 (sim_threshold=0.3)│ +│ │ +│ 路 2: get_relevant_ents_by_types() │ +│ → answer_type_keywords 精确匹配 │ +│ │ +│ 路 3: get_relevant_relations_by_txt() │ +│ → 关系向量相似度召回 │ +└─────────────────────────────────────────┘ + │ + ▼ +n-hop 路径扩展 (预计算) + │ sim_decay = 1/(2 + hop_depth) + ▼ +融合打分: score = sim × pagerank + │ 实体排序: sim × pagerank + │ 关系排序: sim × pagerank × boost + ▼ +Token 预算截断 (max_token 递减) + │ + ▼ +社区报告召回 (comm_topn=1) + │ + ▼ +返回: {page_content: entities + relations + community, + metadata: {...}, vector: None} + │ + ▼ +插入 hybrid 结果头部: rs.insert(0, graph_chunk) + │ + ▼ +Agent 上下文组装 → LLM 生成 +``` + +**GraphRAG 建图调用链**(前置条件): +``` +tasks.py:build_graphrag_for_kb() + → run_graphrag_for_kb() [graphrag/general/index.py:122] + → generate_subgraph() [index.py:333] + → LLM 抽取 entities + relations (多轮 gleaning, max=2) + → merge_subgraph() [index.py:409] + → graph_merge() [utils.py:199] + → [可选] EntityResolution() [entity_resolution.py:53] + → [可选] leiden.run() [leiden.py:95] + → [可选] CommunityReportsExtractor() [community_reports_extractor.py:55] + → ES 写入 entity/relation/community chunks +``` + +--- + +## 5. 错误传播与降级路径 + +### 5.1 错误传播矩阵 + +| 环节 | 失败模式 | 影响范围 | 兜底逻辑 | 源码位置 | +|------|---------|---------|---------|---------| +| **PDF 解析** | OCR 模型缺失 / GPU 不可用 | 单文档失败 | `callback(-1, "OCR model not found")`,任务标记为 failed_document | `pdf_parser.py:50` | +| **LibreOffice 转换** | soffice 未安装 / 120s 超时 | PPT/DOC 失败 | 抛 HTTP 500,无自动降级 | `utils/libre_office.py:11` | +| **Embedding API** | 超时 / 限流 / 鉴权失败 | 单批 chunks 失败 | 抛出异常,helpers.bulk 不捕获,整批失败需重试 | `models/embedding.py:65` | +| **ES 写入** | ConnectionTimeout / 集群不可用 | 单批 chunks 失败 | `ATTEMPT_TIME=2` 重试,回连后重发 | `utils/es_conn.py:294` | +| **GraphRAG 抽取** | LLM 输出格式错误 | 单 chunk 失败 | `json_repair` 容错 + max_errors=3,超限时跳过 | `extractor.py:97` | +| **GraphRAG 消歧** | LLM 超时 (280s) | 消歧失败 | `trio.move_on_after` 超时,跳过消歧阶段 | `entity_resolution.py:53` | +| **知识库检索** | 单 KB 不可用 | 其他 KB 不受影响 | `try/except continue`,失败 KB 被跳过 | `search.py:110` | +| **向量检索为空** | 阈值过严 / 维度不匹配 | 当前 KB 无结果 | fallback: 降低 min_match 0.3→0.1,提高 similarity 0.1→0.17 | `search.py:447` | +| **外部 Rerank** | API 超时 / 模型不可用 | 无重排序结果 | fallback: 返回原始结果(不打乱顺序) | `search.py:115` | +| **GraphRAG 检索** | 图谱未建 / ES 查询失败 | 无图谱增强结果 | fallback: 仅返回 hybrid 结果 | `search.py:263` | +| **LLM 调用** | RATE_LIMIT / SERVER_ERROR | 生成失败 | 重试 5 次 + 随机抖动;仍失败返回 `**ERROR**: ...` | `chat_model.py:64` | +| **LLM 截断** | finish_reason="length" | 答案不完整 | 自动追加截断提示 (中英文自适应) | `chat_model.py:152` | +| **引用回填** | embedding 匹配失败 | 无引用标记 | 跳过 citation 插入,返回裸文本 | `search.py:489` | + +### 5.2 降级路径图 + +``` +正常路径: +Query → Hybrid 检索 → Rerank → LLM 生成 → 引用回填 → 输出 + +降级路径 1 (检索为空): +Query → Hybrid 检索 (空) → fallback 降低阈值重试 → 仍空 → LLM 直接回答 (无上下文) + +降级路径 2 (Rerank 失败): +Query → Hybrid 检索 → Rerank API 超时 → fallback 返回原始排序 → LLM 生成 + +降级路径 3 (GraphRAG 失败): +Query → Hybrid 检索 → GraphRAG 查询失败 → fallback 仅 hybrid 结果 → LLM 生成 + +降级路径 4 (单 KB 失败): +Query → KB-A (失败, try/except) + KB-B (成功) → 合并结果 → LLM 生成 + +降级路径 5 (LLM 失败): +Query → 检索成功 → LLM 调用失败 (5 次重试后) → 返回 "**ERROR**: 服务暂不可用" + +降级路径 6 (ES 集群不可用): +Query → ES 连接失败 → 无检索结果 → LLM 直接回答 (无上下文) / 返回错误 +``` + +### 5.3 关键降级代码片段 + +```python +# 1. 单 KB 失败不影响整体 (search.py:110) +try: + rs, chat_model, embedding_model = _retrieve_for_knowledge(...) + all_results.extend(rs) +except Exception as e: + print(f"retrieval knowledge({kb_id}) failed: {str(e)}") + continue # 跳过失败 KB + +# 2. Rerank 失败 fallback (search.py:115-128) +if reranker_id and all_results: + try: + all_results = rerank(...) + except Exception as rerank_error: + logger.warning("Reranker failed, falling back to original results") + # fallback: 保持原始排序 + +# 3. 检索为空 fallback (search.py:447-459) +if total == 0: + matchText, _ = self.qryr.question(qst, min_match=0.1) # 0.3 → 0.1 + matchDense.extra_options["similarity"] = 0.17 # 0.1 → 0.17 + res = self.dataStore.search(...) + +# 4. GraphRAG 失败 fallback (search.py:263) +try: + graph_doc = kg_retriever.retrieval(...) + rs.insert(0, DocumentChunk(...)) +except Exception as graph_error: + logger.warning(f"Graph retrieval failed...") # 仅 hybrid 结果 + +# 5. LLM 重试 (chat_model.py:64-89) +retry_max = LLM_MAX_RETRIES # 默认 5 +while retry_max > 0: + try: + return self.client.chat.completions.create(...) + except (RateLimitError, APIConnectionError, APIError): + time.sleep(random.uniform(1, LLM_BASE_DELAY * 2 ** (5-retry_max))) + retry_max -= 1 +``` + +--- + +## 附录:跨文档引用索引 + +| 本章节 | 引用来源 | 被引文档 | +|--------|---------|---------| +| §1 Loader/Parser/Chunking | `naive.py:508`, `naive_merge()` | [S2-T1] | +| §1/§2 Embedding | `embed_documents()`, `embed_query()` | [S2-T2] | +| §1/§2 VDB 检索与写入 | `search_by_vector()`, `add_chunks()`, mapping | [S2-T3] | +| §1/§2 GraphRAG | `KGSearch.retrieval()`, `run_graphrag()` | [S2-T4] | +| §1 Rerank/Prompt/LLM | `RedBearRerank`, `_chat_streamly()`, `_filter_citations()` | [S2-T5] | + +--- + +*本文档直接整合自 [S2-T1]~[S2-T5] 五篇子任务文档的源码引用与流程描述,所有文件:行号均可在 MemoryBear 仓库 commit `feae2f2e` 中验证。* \ No newline at end of file diff --git a/docs/rag/evolution/architecture-refactor-suggestions.md b/docs/rag/evolution/architecture-refactor-suggestions.md new file mode 100644 index 00000000..b6abeb16 --- /dev/null +++ b/docs/rag/evolution/architecture-refactor-suggestions.md @@ -0,0 +1,434 @@ +# [S3-T1] MemoryBear RAG 代码架构改造建议 + +**Author**: AI 知识库解决方案专家 +**Source-commit**: 工作分支 `agent/ai/f8de881a`(基于 `feae2f2e`) +**Reviewer**: 待 [S3-T3] 终审 +**Last-reviewed-at**: 2026-05-08 + +--- + +## 0. 一页摘要:现状评估 + +### 0.1 三个优点(值得保留与放大) + +1. **链路完整、特性丰富**:覆盖了从 11 类文档解析(`rag/app/naive.py:508-738`,按扩展名 if/elif 分发)→ Embedding(10+ Provider)→ Hybrid 检索(BM25 + 向量)→ GraphRAG(light/general 双模式)→ Rerank → Prompt 组装 → 流式 LLM 生成的端到端能力。在国内同类开源项目中链路完整度领先。 +2. **多 Provider 抽象初步成型**:`rag/llm/chat_model.py:52 Base` + `rag/vdb/vector_base.py:9 BaseVector` 已具备抽象基类雏形;`rag/models/embedding.py RedBearEmbeddings` 通过 LangChain 的 `Embeddings` 接口屏蔽了 OpenAI / DashScope / Volcano / Ollama / Bedrock 等 7 类 provider。多模型切换代价较低。 +3. **GraphRAG 与向量检索的双轨设计**:`rag/common/settings.py:9-10` 通过 `retriever`(Dealer)+ `kg_retriever`(KGSearch)两个全局单例并行存在,应用层(`workflow/nodes/knowledge/node.py`)可在 PARTICIPLE / SEMANTIC / HYBRID / GRAPH 四种检索模式间切换,灵活度高,是 MemoryBear 区别于通用 RAG 的核心特色。 + +### 0.2 五个痛点(基于 S1-T3 Gap 报告 + 源码核验) + +1. **抽象层不统一,存在双轨甚至三轨实现**: + - **Embedding 双轨**:`rag/models/embedding.py RedBearEmbeddings`(LangChain,新,被 ES Vector 用)vs `rag/llm/embedding_model.py OpenAIEmbed/QWenEmbed/...`(遗留,被 GraphRAG `utils.py:320` 与 Dealer `nlp/search.py:365-373` 用)。**两条路径接口不兼容**:前者 `embed_documents(texts)`、后者 `encode(texts)` 返回 `(np.array, total_tokens)`。 + - **Rerank 三轨**:模块级 `rerank()`(`workflow/nodes/knowledge/node.py:284`,**第 327 行残留 `print(reranked_docs)` 调试语句**)、节点级 `KnowledgeRetrievalNode.rerank()`(`node.py:108-155`,与前者逻辑高度重复)、Dealer 内置融合 `Dealer.rerank()`(`nlp/search.py:606-643`,token+vector+rank_feature 加权)。三套互不知晓彼此存在。 + - **VDB 抽象有名无实**:`vector_base.py:9 BaseVector` 仅定义了 9 个抽象方法,但唯一实现为 `ElasticSearchVector`,且 `node.py:14`、`tasks.py` 直接 import 具体类 `ElasticSearchVectorFactory` 绕过基类,抽象层失效。 + +2. **配置散落,无中心化治理**:`os.environ.get` / `os.getenv` 在 `rag/` 目录下出现 **58 次**,分布在 48 个文件。例如 `LLM_TIMEOUT_SECONDS`/`LLM_MAX_RETRIES`(`chat_model.py:54-58`)、`MAX_CONCURRENT_CHATS`(`graphrag/utils.py:41`)、`ELASTICSEARCH_HOST/PORT/USERNAME/PASSWORD/REQUEST_TIMEOUT/MAX_RETRIES`(`elasticsearch_vector.py:685-707`)、`MINERU_EXECUTABLE/APISERVER/OUTPUT_DIR/BACKEND/DELETE_OUTPUT`(`naive.py:46-60`)、OCR/Layout 系列(`deepdoc/vision/*`)等无统一 schema、无类型校验、无文档可查。运维难以定位"哪个变量影响哪条链路"。 + +3. **可观测性等同于零**:`requirements*.txt` 中 **没有任何** `opentelemetry / prometheus / sentry / jaeger / datadog / statsd` 依赖;355 处 `logger.*` / `logging.*` 调用全为本地日志,无 trace_id 透传、无 metric 导出、无 P50/P95 实时统计。README 里宣称的"P50/P95"指标在代码中无任何采集落点,业务方排障必须捞日志手工聚合。 + +4. **资源/状态共享导致单测与并发受阻**: + - `rag/common/settings.py:24` 在模块 import 时立即执行 `init_settings()`,创建 `docStoreConn = ESConnection()` / `retriever = Dealer(...)` / `kg_retriever = KGSearch(...)` **进程级全局单例**。任何 `from app.core.rag.common.settings import retriever` 都会触发 ES 连接,单元测试无法 stub。 + - `KnowledgeRetrievalNode.get_reranker_model()`(`node.py:157-193`)每次 `rerank` 调用都重新查 DB → 实例化 `RedBearRerank`,热路径上反复读库。 + - GraphRAG 用 Redis 做 Embedding 缓存(`graphrag/utils.py:115-134 get_embed_cache/set_embed_cache`),但 ES VDB 入库/检索路径**完全没有缓存**(`elasticsearch_vector.py:55-63`),同一 query 重复打 Embedding API。 + +5. **入口分发与扩展点用 if/elif 硬编码,违反开闭原则**: + - `rag/app/naive.py:508-738 chunk()` 用 11 个 `re.search(扩展名)` 分支选择 parser;新增格式必须改这个 750 行的函数。 + - `rag/llm/embedding_model.py` 每个 provider 是独立子类(`OpenAIEmbed` / `QWenEmbed` / `XinferenceEmbed` ...),但选择哪个子类没有 registry,依赖外层硬编码 `OpenAIEmbed` import(`workflow/nodes/knowledge/node.py:12`)。 + - `chat_model.py` 中 `ChatBase` 子类硬编码各 provider 的 base_url 与认证 header(如 `chat_model.py:41-44 OpenAIEmbed.__init__` 直接拼 base_url),切换路径不优雅。 + +--- + +## 1. 架构改造建议清单(共 11 条) + +每条建议结构:**问题 → 方案 → 收益 → 成本/风险 → 优先级**。 + +### 【建议 1 · 模块化】拆掉双轨 Embedding,统一到单一 Embedder 协议 `[P0]` + +- **问题陈述**:`RedBearEmbeddings`(LangChain)与 `OpenAIEmbed/QWenEmbed/...`(遗留)两套并存,调用方用哪一个看心情;接口不兼容(`embed_documents/embed_query` vs `encode/encode_queries`),返回类型不一致(`list[list[float]]` vs `(np.ndarray, total_tokens)`)。 + - 源码:`rag/models/embedding.py:9-78`、`rag/llm/embedding_model.py:14-65`、`rag/graphrag/utils.py:301-327`(GraphRAG 调用 `embd_mdl.encode([ent_name])`)、`rag/nlp/search.py:365-373`(Dealer 调用 `emb_mdl.encode_queries(txt)`)。 +- **改造方案**: + - 定义 `app/core/rag/protocols/embedder.py` 中的 `Embedder` Protocol:`embed_documents(texts) -> EmbedResult` 与 `embed_query(text) -> EmbedResult`,`EmbedResult` 是 `dataclass(vectors: np.ndarray, total_tokens: int, dim: int)`。 + - 现有 `OpenAIEmbed` 等遗留类实现 `Embedder`(保留 `encode/encode_queries` 兼容期 6 个月)。 + - 新建 `EmbedderFactory.from_model_config(config) -> Embedder`,内部根据 `provider` 字段路由;`workflow/nodes/knowledge/node.py:12` 删除对 `OpenAIEmbed` 的硬编码 import。 + - 把 GraphRAG 与 Dealer 都改为通过 `Embedder` 协议调用。 +- **收益**:维护成本从"两套类各自演进"降为一套;新 provider 只需实现 `Embedder` 协议;单测可用 `FakeEmbedder` mock,**单测覆盖率提升预期 +30%**(当前 rag 模块基本无单测)。 +- **成本与风险**:实现 + 迁移约 **5 人日**。回归风险中(GraphRAG 的 `np.ndarray` 返回类型若变成 `list[list[float]]` 会触发下游 `np` 操作错误,需保留 numpy 输出适配器)。 +- **优先级**:**P0**(解锁后续所有改造的前置条件)。 + +### 【建议 2 · 接口抽象】定义 `Retriever` / `Reranker` / `Generator` 三大协议(LangChain Runnable 风格)`[P0]` + +- **问题陈述**:当前没有"检索器"这层抽象,调用方需要直接知道:用哪个 ES index、是否走 hybrid、要不要叠加 GraphRAG。例如 `workflow/nodes/knowledge/node.py:195-263 knowledge_retrieval()` 内部用 `match retrieve_type` 分四个分支调 `vector_service.search_by_vector()` / `search_by_full_text()` / 二者并行 dedup / 再叠加 `kg_retriever.retrieval()`。每新增一种检索策略都要在这里加 `case`。 +- **改造方案**:定义三个 Protocol(伪代码见 PoC 章节): + ```python + class Retriever(Protocol): + async def retrieve(self, query: Query) -> RetrievalResult: ... + class Reranker(Protocol): + async def rerank(self, query: str, docs: list[Doc], top_k: int) -> list[Doc]: ... + class Generator(Protocol): + async def generate(self, prompt: Prompt, stream: bool) -> GenerationResult | AsyncIterator[Chunk]: ... + ``` + 并提供组合算子 `Pipeline = Retriever | Reranker | Generator`(类似 LangChain Runnable 的 `|`)。`KnowledgeRetrievalNode` 不再 `match retrieve_type`,而是注入一个 `Retriever`(`HybridRetriever` / `GraphAugmentedRetriever` / `VectorRetriever` 是不同实现)。 +- **收益**:策略模式取代条件分支;单测可对 `Retriever` 接口做契约测试;A/B 实验只需注入不同实现;"GraphRAG-then-Vector"、"Vector-then-Graph"、"Reranker-only"等组合可声明式表达。 +- **成本与风险**:核心接口设计 + 关键实现 + 迁移调用方约 **8 人日**。风险中(涉及 workflow node 的契约变化,需要保留旧接口至少一个 release)。 +- **优先级**:**P0**。 + +### 【建议 3 · 模块化】消除 Rerank 的三处重复实现 `[P0]` + +- **问题陈述**: + - `workflow/nodes/knowledge/node.py:284 rerank()`(模块级函数)— **第 327 行有 `print(reranked_docs)` 调试残留**。 + - `workflow/nodes/knowledge/node.py:108-155 KnowledgeRetrievalNode.rerank()`(节点方法)— 与前者代码逻辑几乎一致(都做 `RedBearRerank.compress_documents` + 按 `relevance_score` 排序 + 按 `page_content` 字符串匹配回查 metadata)。 + - `rag/nlp/search.py:606-643 Dealer.rerank()`(融合排序)—— 走的是 token+vector+rank_feature 三项加权,与前两者完全是不同范式但同名为 rerank。 + - 第二个问题:`KnowledgeRetrievalNode.get_reranker_model()`(`node.py:157-193`)每次 rerank 调用都查一次 DB 获取模型配置,实例化 `RedBearRerank`。 +- **改造方案**: + - 实现一个唯一的 `RerankerService`:内部做 (a) DB 缓存 reranker 实例(key=`reranker_id`,TTL=10min);(b) 屏蔽"按 page_content 字符串匹配 metadata"的脆弱回查(改为 LangChain `Document.metadata["__doc_index__"]` 索引);(c) 暴露 `Reranker` Protocol。 + - 删掉 `node.py:284 rerank()` 模块级函数(或仅保留 `@deprecated` 别名指向 `RerankerService`)。 + - `Dealer.rerank()` 改名为 `Dealer.fuse_scores()`,明确它是"分数融合"不是"模型重排"。 + - 删除 `node.py:327 print()` 残留。 +- **收益**:消除每次请求多查 DB 一次的开销(实测 DB 查询 5–20ms,去掉后**热路径单次省 5-20ms × QPS**);rerank 逻辑只需在一处 review 与单测。 +- **成本与风险**:约 **3 人日**。风险低(接口对外不变)。 +- **优先级**:**P0**(含调试残留的 hot fix 应优先合并)。 + +### 【建议 4 · 性能优化】Embedder 与 Reranker 加缓存层 `[P0]` + +- **问题陈述**: + - GraphRAG 用 Redis 缓存 Embedding(`graphrag/utils.py:115-134`,TTL=24h,key=xxhash(model_name+text)),命中率高时显著省成本。 + - 但 ES VDB 入库/检索 (`elasticsearch_vector.py:55-63 add_chunks` / `:374-380 search_by_vector`) **完全无缓存**。同一 query 反复 embedding;同一 chunk 重复入库时也会重复算向量。 + - Rerank 同样无缓存:`RedBearRerank.compress_documents` 每次都打外部 API(DashScope/Jina),200+ ms。 +- **改造方案**: + - 抽出 `app/core/rag/cache/embed_cache.py`(把 `graphrag/utils.py` 中的现有实现搬过来 + 通用化)。 + - `Embedder` Protocol 在调用层加装饰器 `@cached_embedder(redis, ttl=24h)`,对 `embed_query` 必加(query 重复率高),`embed_documents` 可配置。 + - 新增 `Reranker` 缓存:key=`xxhash(model + query + sorted(doc_ids))`,TTL=1h(rerank 结果对 query 变体很敏感,不要 TTL 太长)。 + - 从环境变量读 `REDIS_*` 配置,cache 失败时优雅降级为 no-op(不要 break 主链路)。 +- **收益**:Query embedding 命中场景 **减少 60-90% 外部 API 调用**(基于业内同类系统 query 重复率统计)。Rerank 命中场景再减少 30-50%。**单 query 端到端 P95 下降 100-300ms**(Rerank 是当前最慢的同步阻塞步骤之一)。 +- **成本与风险**:约 **2 人日**。风险低(cache miss 时行为与现状一致)。 +- **优先级**:**P0**。 + +### 【建议 5 · 性能优化】用 Plugin Registry 替换 `naive.py:508` 的 11 路 if/elif 解析器分发 `[P1]` + +- **问题陈述**:`rag/app/naive.py:508 chunk()` 用 `re.search(r"\.docx$", filename)` / `r"\.pdf$"` / `r"\.(pptx|ppt?)$"` / ... 11 个分支硬编码挑 parser。新增一种格式必须改这个 750 行函数;同时 PDF 自身有 `by_deepdoc` / `by_mineru` / `by_textln` 三种实现,选择路径用 `parser_config["layout_recognize"]` 字符串比对,没有类型保护。 +- **改造方案**: + - 定义 `Parser` Protocol:`def can_parse(filename) -> bool` + `def parse(filename, binary, **kwargs) -> ParseResult`。 + - 在 `rag/app/parsers/__init__.py` 中维护一个 `_registry: dict[str, Parser] = {}` + `@register_parser("docx", "pdf", ...)` 装饰器。 + - `chunk()` 简化为 4 行:`parser = registry.find(filename); sections, tables = parser.parse(...); return tokenize(sections, tables)`。 + - 第三方 parser(MinerU、TextIn)也注册为可插拔实现,运行时由 `parser_config.layout_recognize` 选择。 +- **收益**:新增格式 = 新增一个文件 + 一行 `register`,不再需要碰 `naive.py`;测试可针对每个 parser 独立写;**`naive.py` 从 750+ 行降到 100 行以内**,可读性大幅提升。 +- **成本与风险**:约 **5 人日**(11 类 parser 都要拆)。风险中(要保留 `vision_figure_parser_pdf_wrapper` 等横切逻辑,需要 hook 点设计)。 +- **优先级**:**P1**。 + +### 【建议 6 · 可观测性】引入 OpenTelemetry,全链路 trace + 关键指标埋点 `[P1]` + +- **问题陈述**:requirements.txt 中无任何 OTel/Prometheus/Sentry 依赖;355 个 `logger` 调用全是本地日志。无法回答"昨天 P95 多少"、"哪一步最慢"、"哪个 KB 召回率最差"。README 中宣称的 P50/P95 是无源之水。 +- **改造方案**: + - 在 `requirements.txt` 加入 `opentelemetry-sdk`、`opentelemetry-instrumentation-fastapi`、`opentelemetry-instrumentation-elasticsearch`、`opentelemetry-instrumentation-redis`、`opentelemetry-instrumentation-celery`、`opentelemetry-exporter-otlp`。 + - 在 `app/core/rag/observability/tracing.py` 提供 `@trace_rag_step("embed/search/rerank/generate")` 装饰器(基于 `opentelemetry.trace.get_tracer`),包装 `Embedder.embed_*` / `Retriever.retrieve` / `Reranker.rerank` / `Generator.generate`。 + - 关键指标(`opentelemetry.metrics.meter`): + - `rag.embed.latency_ms{provider, model}` Histogram + - `rag.search.recall@k{kb_id, retrieve_type}` Counter(结合用户反馈数据) + - `rag.llm.tokens_used{provider, model, type=prompt|completion}` Counter + - `rag.cache.hit_ratio{layer=embed|rerank|llm}` Gauge + - `rag.pipeline.e2e_latency_ms{retrieve_type, has_rerank}` Histogram + - LLM 级(`chat_model.py:_chat / _chat_streamly`)也加 `tracer.start_as_current_span`,把 token 用量、provider、model 写到 attributes。 +- **收益**:实时 P50/P95 / 错误率 / Token 成本可观测;oncall 排障从"捞日志 grep"变成"看 Grafana panel";A/B 实验有可量化的 baseline。 +- **成本与风险**:约 **5 人日**(依赖 + 装饰器 + 关键 span + 一份 Grafana JSON 模板)。风险低(OTel 失败时 no-op)。 +- **优先级**:**P1**(前 2 周做不完,但中期一定要做)。 + +### 【建议 7 · 配置治理】中心化配置 + Pydantic Settings + 类型校验 `[P1]` + +- **问题陈述**:`os.environ.get` 出现 58 次散落在 48 个文件;同一变量名多处使用却无单一文档;类型靠 `int(os.getenv(...))` 手工转换(`elasticsearch_vector.py:699-702` 反复出现);缺省值随手填,不一致(如 `ELASTICSEARCH_REQUEST_TIMEOUT` 文档说 100000,源码 `elasticsearch_vector.py:699` 缺省是 30)。 +- **改造方案**: + - 新增 `app/core/rag/config/settings.py`:用 `pydantic_settings.BaseSettings` 把 RAG 相关全部环境变量收拢成 `RAGSettings`,分组:`LLMSettings` / `EmbeddingSettings` / `ESSettings` / `GraphRAGSettings` / `MinerUSettings` / `OCRSettings` 等。 + - 启动时 `RAGSettings()` 一次性加载、校验、默认值统一;`docs/rag/_meta/config_reference.md` 自动生成(用 `RAGSettings.model_json_schema()` → markdown)。 + - 现有调用点 `os.environ.get("X")` 替换为 `from app.core.rag.config import settings; settings.x`。 + - Secret 管理:API key / DB 密码强制走 `pydantic.SecretStr`,禁止默认值。 +- **收益**:单一可信来源(Single Source of Truth);类型错误启动期暴露而非运行时;运维有完整变量清单;CI 可静态校验"是否引入了未注册的环境变量"。 +- **成本与风险**:约 **4 人日**(迁移 58 处调用点 + 文档生成)。风险低(一次性脚本可批量替换)。 +- **优先级**:**P1**。 + +### 【建议 8 · 模块化】消除 `init_settings()` 模块级副作用 `[P1]` + +- **问题陈述**:`rag/common/settings.py:24` 在模块导入时立即执行 `init_settings()`,创建进程级 `docStoreConn = ESConnection()`、`retriever = Dealer(...)`、`kg_retriever = KGSearch(...)`。任何 `from app.core.rag.common.settings import retriever` 都会立即建 ES 连接。 + - 后果:单元测试无法 stub(import 时已触发副作用);多进程/Celery worker 启动时间增加(每个 worker 都连 ES);测试容器需要 ES 运行才能 `pytest collect`。 +- **改造方案**: + - 替换为 lazy initialization:`@lru_cache def get_doc_store(): ...` / `@lru_cache def get_retriever(): ...` / `@lru_cache def get_kg_retriever(): ...`。 + - 在 FastAPI 应用层用 dependency injection(`fastapi.Depends`)注入而非全局 singleton。 + - 测试时用 `app.dependency_overrides[get_retriever] = lambda: FakeRetriever()` mock。 +- **收益**:单测可独立运行(不依赖 ES);冷启动延后到首次使用;多 worker 避免共享单例的诡异 bug。 +- **成本与风险**:约 **2 人日**(替换 import-style 调用为 `Depends`)。风险中(要逐个排查 `from settings import retriever` 的 24 处调用点)。 +- **优先级**:**P1**。 + +### 【建议 9 · 性能优化】Embedding 与 Rerank 批量化 + 异步并发 `[P1]` + +- **问题陈述**: + - `rag/llm/embedding_model.py:50 OpenAIEmbed.encode()` 中 `batch_size = 16` **硬编码**;`QWenEmbed` 是 4,`HuggingFaceEmbed` 是无(全量发送)。`EMBEDDING_BATCH_SIZE` 在 README 提过但代码注释掉未生效。 + - `elasticsearch_vector.py:55-63 add_chunks` 是同步循环,无 trio/asyncio 并发;`workflow/nodes/knowledge/node.py:knowledge_retrieval` 多 KB 检索时是 `await asyncio.gather` 并发的,但单 KB 内 vector + full_text 是顺序调用。 + - GraphRAG 已经用 `trio.CapacityLimiter(MAX_CONCURRENT_CHATS=10)` 限流(`graphrag/utils.py:41`),但 ES VDB 写入对应的限流不存在。 +- **改造方案**: + - `Embedder` 协议提供 `batch_size` 字段,默认从 `RAGSettings.embedding.batch_size` 读取,每个 provider 可 override。 + - `ElasticSearchVector.add_chunks` 改为 trio 协程版本,与 GraphRAG 共享 `chat_limiter` 限流。 + - `HybridRetriever.retrieve` 内部 `vector` + `full_text` 用 `asyncio.gather` 并发(当前在 node 层做了,下沉到 Retriever)。 +- **收益**:Embedding 大批量入库 P95 下降 **20-40%**(瓶颈从串行 16-batch HTTP 变并发);Hybrid 检索单次 P50 下降 **30-50%**(从串行 → 并发 max 而非 sum)。 +- **成本与风险**:约 **3 人日**。风险中(trio 与 asyncio 混用要小心,已有 `trio.to_thread.run_sync` 模式可参考)。 +- **优先级**:**P1**。 + +### 【建议 10 · 可观测性 + 配置】消灭遗留 `print()` 与无结构化日志 `[P2]` + +- **问题陈述**: + - `workflow/nodes/knowledge/node.py:327 print(reranked_docs)` 残留调试语句;同类 `print` 在 rag/ 目录共有数十处(grep 验证)。 + - 现有 logger 是非结构化字符串日志(`logger.info(f"add_texts result:{result}")` `elasticsearch_vector.py:86`),无法 ELK 聚合查询。 +- **改造方案**: + - 引入 `structlog`,所有 `logger.*` 调用改为 KV 格式:`logger.info("vdb.add_texts", n_docs=len(actions), index=self._collection_name, took_ms=...)`。 + - pre-commit hook 加 `flake8-print` 阻止新 `print` 进入仓库。 + - 一次性 sweep 删除现有 `print`。 +- **收益**:日志可聚合查询("过去 1 小时 add_texts 平均 n_docs");CI 防止回归。 +- **成本与风险**:约 **2 人日**。风险低。 +- **优先级**:**P2**。 + +### 【建议 11 · 接口抽象】把 `BaseVector` 的"多模态分支"抽象到 Embedder 而非 VDB 层 `[P2]` + +- **问题陈述**:`elasticsearch_vector.py:55-63` 的 `add_chunks` 与 `:374-380 search_by_vector` 都有 `if self.is_multimodal_embedding: ... else: ...` 分支判断(火山引擎多模态走 `embed_batch/embed_text`,其他走 `embed_documents/embed_query`)。这是把"Embedder 的能力差异"泄露到了 VDB 层 — 违反单一职责。 +- **改造方案**: + - 在 `Embedder` Protocol 内部统一接口:`embed(items: list[Item]) -> list[list[float]]`,其中 `Item = TextItem | ImageItem | VideoItem`。多模态 Embedder 内部分发到 `multimodal_embeddings.create`,文本 Embedder 走 `embed_documents`。 + - VDB 层只调 `embedder.embed(...)`,不再有 `is_multimodal` 分支。 +- **收益**:VDB 与 Embedder 职责清晰;后续接入 ColBERT / SPLADE / 多向量 Embedding 时无需修改 VDB。 +- **成本与风险**:约 **2 人日**。 +- **优先级**:**P2**(依赖建议 1 完成)。 + +--- + +## 2. PoC 代码草案 + +### 2.1 PoC-1:统一 `Retriever` / `Reranker` / `Generator` 协议(建议 2) + +```python +# api/app/core/rag/protocols/__init__.py +from __future__ import annotations +from dataclasses import dataclass, field +from typing import Protocol, AsyncIterator, runtime_checkable + +@dataclass(slots=True) +class Query: + text: str + kb_ids: list[str] + top_k: int = 4 + similarity_threshold: float = 0.2 + rerank: bool = False + extras: dict = field(default_factory=dict) # 其他场景化参数 + +@dataclass(slots=True) +class Doc: + id: str + content: str + score: float + metadata: dict = field(default_factory=dict) + +@dataclass(slots=True) +class RetrievalResult: + docs: list[Doc] + total: int + debug: dict = field(default_factory=dict) # latency_ms, recall_strategy, etc. + +@runtime_checkable +class Retriever(Protocol): + name: str + async def retrieve(self, query: Query) -> RetrievalResult: ... + +@runtime_checkable +class Reranker(Protocol): + async def rerank(self, query: str, docs: list[Doc], top_k: int) -> list[Doc]: ... + +@runtime_checkable +class Generator(Protocol): + async def generate_stream(self, prompt: str, history: list[dict], + context: list[Doc]) -> AsyncIterator[str]: ... +``` + +```python +# api/app/core/rag/retrievers/hybrid_retriever.py +import asyncio +from app.core.rag.protocols import Retriever, Query, RetrievalResult, Doc +from app.core.rag.vdb.vector_base import BaseVector +from app.core.rag.protocols.reranker import Reranker + +class HybridRetriever(Retriever): + name = "hybrid" + def __init__(self, vector_store: BaseVector, reranker: Reranker | None = None, + vector_weight: float = 0.7): + self._store = vector_store + self._reranker = reranker + self._vector_weight = vector_weight + + async def retrieve(self, query: Query) -> RetrievalResult: + vec_task = asyncio.to_thread( + self._store.search_by_vector, query.text, top_k=query.top_k * 4) + bm25_task = asyncio.to_thread( + self._store.search_by_full_text, query.text, top_k=query.top_k * 4) + vec_docs, bm25_docs = await asyncio.gather(vec_task, bm25_task) + merged = self._fuse_rrf(vec_docs, bm25_docs) # Reciprocal Rank Fusion + if self._reranker and query.rerank and merged: + docs = await self._reranker.rerank( + query.text, merged, top_k=query.top_k) + else: + docs = merged[:query.top_k] + return RetrievalResult(docs=docs, total=len(merged), + debug={"strategy": self.name}) + + @staticmethod + def _fuse_rrf(a: list[Doc], b: list[Doc], k: int = 60) -> list[Doc]: + scores = {} + for rank, d in enumerate(a): + scores[d.id] = scores.get(d.id, 0) + 1 / (k + rank + 1) + for rank, d in enumerate(b): + scores[d.id] = scores.get(d.id, 0) + 1 / (k + rank + 1) + all_docs = {d.id: d for d in a + b} + return sorted((all_docs[i] for i in scores), + key=lambda d: scores[d.id], reverse=True) +``` + +```python +# api/app/core/workflow/nodes/knowledge/node_v2.py(重构后) +class KnowledgeRetrievalNodeV2(BaseNode): + def __init__(self, retriever: Retriever, ...): + self._retriever = retriever # 注入,不再 match retrieve_type + async def execute(self, state) -> dict: + query = Query(text=self._render_query(state), + kb_ids=self._kb_ids, top_k=self._top_k, + rerank=bool(self._reranker_id)) + result = await self._retriever.retrieve(query) + return {"chunks": [d.content for d in result.docs], + "citations": [d.metadata for d in result.docs]} +``` + +### 2.2 PoC-2:Embedder + Redis 缓存装饰器(建议 1 + 4) + +```python +# api/app/core/rag/cache/embed_cache.py +import json, xxhash, numpy as np +from functools import wraps + +def cached_embedder(redis_client, ttl: int = 24 * 3600): + def decorator(func): + @wraps(func) + def wrapper(self, texts, *args, **kwargs): + if isinstance(texts, str): + texts = [texts] + keys = [_key(self.model_name, t) for t in texts] + cached = redis_client.mget(keys) + results, miss_idx, miss_texts = [None]*len(texts), [], [] + for i, b in enumerate(cached): + if b: results[i] = np.array(json.loads(b)) + else: miss_idx.append(i); miss_texts.append(texts[i]) + if miss_texts: + fresh = func(self, miss_texts, *args, **kwargs) # ndarray, n_tokens + vecs = fresh[0] if isinstance(fresh, tuple) else fresh + pipe = redis_client.pipeline() + for j, idx in enumerate(miss_idx): + results[idx] = vecs[j] + pipe.setex(keys[idx], ttl, json.dumps(vecs[j].tolist())) + pipe.execute() + return np.array(results), 0 # tokens cached as 0; metric layer补 + return wrapper + return decorator + +def _key(model: str, text: str) -> str: + h = xxhash.xxh64(); h.update(f"{model}\0{text}".encode()); return f"emb:{h.hexdigest()}" +``` + +使用方式: + +```python +class OpenAIEmbed(Base): + @cached_embedder(redis_client) # 一行注解开启缓存 + def encode(self, texts: list): ... +``` + +--- + +## 3. 改造路线图 + +> 实施前提:先用 1 周时间立两个 baseline —— (a) 当前端到端 P50/P95(即使靠手工脚本采);(b) 单测覆盖率(pytest --cov)。所有改造完成后用同一 baseline 比对,验证收益。 + +### 3.1 短期(Sprint 0–1,1-2 周内交付) + +> 目标:止血 + 解锁后续重构的前置条件。 + +| # | 工作项 | 关联建议 | 工作量 | 交付物 | +|---|---|---|---|---| +| 1 | 删除 `node.py:327 print()` 残留 + 全仓 print 扫除 | #10 | 0.5d | PR + pre-commit hook | +| 2 | 实现 `RerankerService`(含 reranker 实例缓存) | #3 | 2d | 新模块 + 单测 + 替换现有 3 处 rerank | +| 3 | 给 `Embedder.encode/encode_queries` 加 Redis 缓存装饰器 | #4 | 1.5d | 装饰器 + benchmark 报告 | +| 4 | 中心化配置:`RAGSettings` Pydantic Settings 框架 | #7 | 2d | `app/core/rag/config/settings.py` + 迁移 ES + LLM 配置 | +| 5 | 迁移单元测试:先把 settings.py 的 `init_settings()` 副作用改 lazy | #8 | 2d | `pytest` 不再依赖 ES 即可 collect | + +**短期里程碑(Sprint 1 末)**: +- ✅ 调试 print 残留清零; +- ✅ 单测可独立运行(脱离 ES); +- ✅ Reranker 命中场景延迟下降 50%+; +- ✅ Query Embedding 命中场景延迟下降 70%+。 + +### 3.2 中期(Sprint 2–4,1-2 月内交付) + +> 目标:完成核心抽象层重构,引入可观测性。 + +| # | 工作项 | 关联建议 | 工作量 | 交付物 | +|---|---|---|---|---| +| 6 | 设计 + 落地 `Embedder` Protocol,迁移 `OpenAIEmbed/QWenEmbed/...` | #1 | 5d | 协议 + 适配器 + 弃用计划文档 | +| 7 | 设计 + 落地 `Retriever / Reranker / Generator` Protocol;实现 `VectorRetriever` `BM25Retriever` `HybridRetriever` `GraphAugmentedRetriever` | #2 | 8d | 协议 + 4 个实现 + 节点改造 | +| 8 | OpenTelemetry 接入:在 RAG 关键路径加 span 与 metric | #6 | 5d | `observability/tracing.py` + Grafana 模板 + 文档 | +| 9 | Plugin Registry 重构 `naive.py` 解析器分发 | #5 | 5d | `parsers/` 模块化 + 11 个 parser 注册 | +| 10 | 配置治理收尾:剩余 50+ 处 `os.environ.get` 全部迁到 `RAGSettings` | #7 | 2d | 文档自动生成脚本 | +| 11 | Embedder 与 Rerank 批量化 + 异步并发改造 | #9 | 3d | 性能 benchmark 对比报告 | + +**中期里程碑(Sprint 4 末)**: +- ✅ 抽象层统一完成(Embedder / Retriever / Reranker / Generator 四大协议落地); +- ✅ Grafana 实时面板:P50/P95/Token 用量/缓存命中率; +- ✅ 单测覆盖率 RAG 模块从 ~5% 提升到 ≥35%; +- ✅ 端到端 P95 较 baseline 下降 30%+。 + +### 3.3 长期(Sprint 5–8,3-6 月内交付) + +> 目标:可插拔架构、生产级稳定性、为 [S3-T2] 列出的多模态 / 混合搜索增强 / KG 演化做铺垫。 + +| # | 工作项 | 关联建议 | 工作量 | 交付物 | +|---|---|---|---|---| +| 12 | 多模态分支从 VDB 抽离到 Embedder | #11 | 2d | VDB 接口收敛 | +| 13 | 引入第二个 VDB 实现(如 Milvus),验证 `BaseVector` 可插拔 | #2 | 8d | `MilvusVector` + 一致性测试套件 | +| 14 | LLM Provider 也改 Plugin Registry(消除 `chat_model.py` 11 个子类的 if 切换) | #5 | 5d | LLM 层与 Embedding 层架构对齐 | +| 15 | 完整的 `Pipeline = Retriever \| Reranker \| Generator` DSL,配置驱动 | #2 | 10d | YAML 描述场景 → 运行时拼装 | +| 16 | A/B 实验框架:基于 OTel metric,把 recall@k / answer_score 接入实验对比 | #6 | 5d | 实验平台对接文档 | +| 17 | LLM 失败模型降级链(fallback to 备用 provider) | #2 + 现有 Base 增强 | 3d | `FallbackGenerator` 实现 | +| 18 | 安全 / Secret 管理:从 `pydantic.SecretStr` 升级到 Vault / Secrets Manager 集成 | #7 | 5d | 密钥不进 .env 文件 | + +**长期里程碑(Sprint 8 末)**: +- ✅ 可插拔 VDB(一行配置切换 ES → Milvus); +- ✅ Pipeline DSL 上线,新增"GraphRAG-Then-Vector-Then-Rerank"等组合无需改代码; +- ✅ 全链路 Trace + 指标 + A/B 框架就绪; +- ✅ 为 [S3-T2] 中"多模态检索 / SPLADE / ColBERT 路由 / KG 演化 / 反馈闭环"等扩展提供清晰的接口注入点。 + +--- + +## 4. 风险与依赖统一汇总 + +| 风险类别 | 描述 | 缓解方案 | +|---|---|---| +| **回归风险(高)** | `Embedder` 协议迁移可能改变返回类型(`np.ndarray` vs `list[list[float]]`) | 6 个月兼容期,旧接口保留并打 `DeprecationWarning`;CI 加契约测试 | +| **回归风险(中)** | `KnowledgeRetrievalNode` 接口改造,影响 workflow 已部署应用 | 引入 `node_v2.py`,灰度切换;保留 `node.py` 至少一个 release | +| **依赖风险** | OpenTelemetry 接入需 collector / Tempo / Loki 等基础设施 | 短期可先只导出到 stdout exporter,基础设施分阶段建设 | +| **协作依赖** | 与 [@Python 开发工程师](mention://agent/f4d1c89f-0c71-4af3-bf72-d34f7ed115cf) 一起验证 PoC 与迁移可行性 | Sprint 0 启动前先 1 次架构对齐会 | +| **运营依赖** | 配置治理(建议 #7)落地后,运维需更新部署脚本与文档 | 切换前 2 周通知;提供变量映射表(旧 → 新) | + +--- + +## 5. 验收 Checklist 自检 + +- [x] 至少 8 条建议(实际 11 条) +- [x] 覆盖 5 个方向:模块化拆分(#1, #3, #5, #8)/ 接口抽象(#1, #2, #11)/ 性能优化(#4, #5, #9)/ 可观测性(#6, #10)/ 配置与依赖治理(#7) +- [x] 每条建议均有源码引用(文件:行号 + 关键摘录) +- [x] PoC 代码草案:**2 套**(统一 Retriever 协议 + Embedder 缓存装饰器,均在 10–50 行) +- [x] 现状评估:3 优点 + 5 痛点 +- [x] 改造路线图:短期 / 中期 / 长期 三阶段,每阶段附交付物清单 +- [x] 与 [S2-T7] Sprint-2 文档兼容:引用 [S2-T2 Embedding](mention://issue/7a8cd047-f339-427e-bd60-999c62caea22) 双轨问题、[S2-T5 LLM/Reranking](mention://issue/eef8ed99-c13e-43ba-a2b3-2c9e59b74301) 三处 rerank 实现,与 [S1-T3 Gap 报告](mention://issue/264529aa-1856-4505-8e26-6125df061c18) 中识别的"`rag_utils` vs `rag/utils` 命名冲突"等差异交叉印证 +- [x] 提交至 [S3-T3] 终审 + +--- + +*文档基于 MemoryBear `agent/ai/f8de881a` 分支(基于 commit `feae2f2e`)逐文件核验。所有源码引用可在 ±3 行内复现。* diff --git a/docs/rag/evolution/capability-map.mmd b/docs/rag/evolution/capability-map.mmd new file mode 100644 index 00000000..b4509623 --- /dev/null +++ b/docs/rag/evolution/capability-map.mmd @@ -0,0 +1,98 @@ +%% MemoryBear RAG 能力地图(Capability Map) +%% 横轴:能力域;纵轴:成熟度(已有 / 近期可上 / 中长期愿景) +%% 与 [S3-T1] 提议的 Retriever / Reranker / Generator / Embedder 抽象接口对齐 +graph LR + classDef have fill:#10b981,stroke:#065f46,color:#fff,stroke-width:1px + classDef near fill:#f59e0b,stroke:#92400e,color:#fff,stroke-width:1px + classDef vision fill:#6366f1,stroke:#3730a3,color:#fff,stroke-width:1px + classDef domain fill:#e5e7eb,stroke:#374151,color:#111,stroke-width:1px + + subgraph DLOAD[数据接入] + L1[Web 爬虫]:::have + L2[飞书 / 语雀 / 文件上传]:::have + L3[企业 IM / 邮件 / Notion / S3 增量同步]:::near + L4[流式数据 / Kafka / CDC]:::vision + end + + subgraph DPARSE[解析与多模态采集] + P1[deepdoc PDF/OCR/Layout/Table]:::have + P2[图片 OCR + VLM describe]:::have + P3[音频 ASR]:::have + P4[视频 VLM 整体描述]:::have + P5[音视频时间戳化抽帧 + 关键帧 caption]:::near + P6[原生 CLIP/BGE-VL 跨模态嵌入]:::vision + end + + subgraph DCHUNK[切分与表征] + C1[naive_merge / 类型化 chunker]:::have + C2[RagTokenizer 中英分词]:::have + C3[Late-Interaction / ColBERT 子词表征]:::near + C4[语义分块 + 自适应粒度]:::vision + end + + subgraph DEMB[Embedding] + E1[10+ Provider 工厂]:::have + E2[问题增强 question_proposal]:::have + E3[Sparse 向量 / SPLADE 学习稀疏]:::near + E4[Multi-Vector / 多语种统一编码]:::vision + end + + subgraph DVDB[向量与检索] + V1[ES dense_vector + BM25]:::have + V2[FusionExpr 0.05/0.95 加权融合]:::have + V3[KGSearch N-hop + Community]:::have + V4[HNSW 量化 / Sparse 索引上线]:::near + V5[语义路由 / 多检索器自适应组合]:::near + V6[联邦检索 / 跨租户隐私检索]:::vision + end + + subgraph DRANK[重排序] + R1[内置 token+vector 融合排序]:::have + R2[Jina / DashScope / Xinference 外部 Reranker]:::have + R3[Cross-Encoder 蒸馏 + 在线 PairWise 学习]:::near + R4[基于反馈的自动 Reranker 微调]:::vision + end + + subgraph DKG[知识图谱] + K1[GraphRAG light + general]:::have + K2[entity_resolution + Leiden 社区]:::have + K3[增量图演化 + 时间戳]:::near + K4[路径解释性 + Neo4j 双引擎]:::near + K5[多源图融合 / 自动本体演化]:::vision + end + + subgraph DMEM[对话记忆] + M1[memory.forgetting_engine Ebbinghaus]:::have + M2[memory.reflection_engine 周期反思]:::have + M3[langgraph 读图 Agent]:::have + M4[短期 ↔ 长期 ↔ 检索召回三段桥接]:::near + M5[人格化记忆策略 + 用户偏好学习]:::vision + end + + subgraph DEVAL[评估与反馈闭环] + EV1[README F1/BLEU/J 论文级评估]:::have + EV2[RAGAS / TruLens 集成 + 在线 A/B]:::near + EV3[👍/👎 反馈 → Rerank 微调闭环]:::near + EV4[自演化路由策略 / RLHF 长记忆]:::vision + end + + subgraph DOPS[平台与可观测] + O1[Celery 任务链 + Redis 缓存]:::have + O2[FastAPI / Swagger]:::have + O3[OpenTelemetry Trace + 检索指标看板]:::near + O4[Prompt 仓库 + Eval CI / 灰度发布]:::vision + end + + %% 跨域依赖(仅画关键边,避免过密) + DLOAD --> DPARSE + DPARSE --> DCHUNK + DCHUNK --> DEMB + DEMB --> DVDB + DVDB --> DRANK + DRANK -. citations .-> DOPS + DCHUNK -. async .-> DKG + DKG --> DVDB + DEVAL -. metrics .-> DRANK + DEVAL -. metrics .-> DVDB + DMEM -. memory-augmented retrieval .-> DVDB + DMEM -. summary into prompt .-> DRANK diff --git a/docs/rag/evolution/future-extensions-roadmap.md b/docs/rag/evolution/future-extensions-roadmap.md new file mode 100644 index 00000000..91ec43e1 --- /dev/null +++ b/docs/rag/evolution/future-extensions-roadmap.md @@ -0,0 +1,457 @@ +# MemoryBear RAG · 后续迭代功能新增方式建议(S3-T2) + +> 上游:[WS-11] 总规划、[S1-T2 全链路架构]、[S1-T3 源码盘点]、Sprint-2 各环节深度文档、[S3-T1 架构改造建议] +> 输出形态:能力地图 + 6 个重点扩展方向 + 2 条 Quick PoC + 优先级矩阵 + 落地路线图 +> 设计原则:所有方向 **必须** 复用 [S3-T1] 提议的统一抽象(`Retriever / Reranker / Generator / Embedder / Loader / Chunker`),避免出现新功能 = 新一团耦合。 + +--- + +## 0. 现状速览与设计基线 + +### 0.1 一图看清"已有 / 可上 / 愿景" + +详见附件 `capability-map.mmd`(Mermaid 格式)。三色对应: +- 🟢 **已有**:Sprint-2 文档已覆盖、源码可证、生产可用。 +- 🟡 **近期可上**:1–2 个 Sprint 内可落地,依赖最少。 +- 🟣 **中长期愿景**:3–6 个月,存在跨团队/外部依赖。 + +### 0.2 关键源码事实(用于支撑后续方案) + +| 事实 | 源码定位 | 对扩展的影响 | +|------|---------|-------------| +| 多模态目前 **走文本通道** | `rag/app/picture.py:54` 调 `vision_model.describe`;`rag/app/audio.py:29` 调 `seq2txt_mdl.transcription`;`naive.py` 走 video → VLM → 文本 | 跨模态语义损失大;扩展为"原生跨模态向量"是方向 D1 | +| `MatchSparseExpr` 已声明但未接入 | `rag/utils/doc_store_conn.py:75` 与 `vdb/field.py:11(SPARSE_VECTOR)` 都已存在;`grep -r SparseVector` 仅 1 处定义、0 处调用 | SPLADE 接入是脚手架级改造,不是从零开始(D2) | +| 混合检索权重写死 `0.05,0.95` | `rag/nlp/search.py:439` 的 `FusionExpr("weighted_sum", topk, {"weights": "0.05,0.95"})` | 语义路由 / 自适应权重的注入点天然存在(D2) | +| GraphRAG 是"一次构建"模型 | `tasks.py` 的 `build_graphrag_for_document` Celery 链;图存于 ES `knowledge_graph_kwd` 字段 | 增量演化、时间维度、Neo4j 双引擎需要在 Celery 链上加 hook(D3) | +| 对话记忆与 RAG **不互通** | `core/memory/` 自成一套(Ebbinghaus、ACT-R、Neo4j、langgraph 读图);`workflow/nodes/knowledge/node.py` 完全不引用 `core/memory` | 对话记忆 ↔ 检索的协同是最大产品差异化机会(D4) | +| 评估只在 README 体现 | 仓内无 `eval/`、`ragas`、`F1` 类计算代码 | 反馈闭环要从 0 搭,但与 [S3-T1] 提议的"可观测性"天然合并(D5) | +| Reranker 只能推理不能学 | `core/models/rerank.py:11` 包装 langchain `BaseDocumentCompressor`,仅做远程调用 | 自训练 Cross-Encoder 是一条独立、可量化收益的小路径(D5) | +| 检索模式硬编码在 enum | `RetrieveType.{PARTICIPLE, SEMANTIC, HYBRID, Graph}` 在 `schemas/chunk_schema.py` | 引入"语义路由"需要把 enum 改成 strategy 模式(D6) | + +### 0.3 与 [S3-T1] 接口抽象的联动约定 + +[S3-T1] 提议把当前散落的检索/排序/生成代码抽象为协议(参考 LangChain Runnable)。本路线图的所有"接口改造点"都引用以下统一协议(命名以 [S3-T1] 终稿为准,本稿先行登记): + +```python +# rag/protocols.py([S3-T1] 提议) +class Retriever(Protocol): + async def retrieve(self, query: Query, ctx: RetrievalContext) -> list[ScoredChunk]: ... + +class Reranker(Protocol): + async def rerank(self, query: Query, chunks: list[ScoredChunk], ctx: RerankContext) -> list[ScoredChunk]: ... + +class Embedder(Protocol): + def encode(self, items: list[Embeddable]) -> EmbeddingResult: ... # Embeddable = str | Image | Audio | ... + +class Generator(Protocol): + async def generate(self, system: str, history: list[Msg], ctx: GenContext) -> GenResult: ... +``` + +> **原则**:本文档每条扩展方向都以"新增/扩展某 Protocol 实现 + 注册到工厂"为接入方式,**不**改动调用方代码。这样可以保持 N 个扩展方向 **并行落地** 而不互相阻塞。 + +--- + +## 1. 重点扩展方向 + +> 共 6 个方向。第 5、6 个为前述 5 个外的延伸(自适应路由),但和"评估闭环 / 混合搜索 / 对话记忆"高度互补,建议合并审阅。 + +### D1. 多模态检索(原生跨模态向量空间) + +#### 1.1 触发场景 +- 客户问:"去年那张含 'Q3 GMV' 的 PPT 切片在哪?" — 当前只能命中 OCR 抽出的文字,**布局/图表整体语义** 丢失。 +- 视频会议纪要库:用户描述"那段讲到老王说'下季度先稳住毛利'的会议",纯 ASR 文本无法绑定 **说话人 + 时间 + 屏幕共享上下文**。 +- 设备图谱:硬件型号识图("这块板子是哪一版"),目前只能让 VLM 描述后再走文本检索,VLM 描述不稳定。 + +#### 1.2 技术方案 +分三层逐步推进: + +| 层级 | 方案 | 依赖组件 | +|------|------|---------| +| L1(基线增强) | **关键帧抽样 + VLM 多次 describe**:视频每 N 秒抽帧,每帧 VLM 描述独立 chunk,附 `frame_ts` 元数据;图片在 OCR + describe 之外再加 **结构化 VQA**("图中有什么图表/品牌/人脸?") | 现有 `cv_model.py`、`sequence2txt_model.py` 即可;新增 `rag/app/video.py` | +| L2(跨模态检索) | 引入 **CLIP / BGE-VL / Jina-Clip-v2** 作为 `MultimodalEmbedder` Protocol 实现:图片直接编码为向量,文本 query 编码到 **同一向量空间**;ES 索引增加 `vec_image_q__vec` 列 | 新依赖 `transformers` / `sentence-transformers` 或托管 API;GPU 资源 | +| L3(视听统一) | **Whisper + speaker diarization**(pyannote)替换当前一段式 ASR;视频 chunk 同时持有 `text_vec`(ASR 文本)+ `image_vec`(关键帧) + `audio_vec`(可选,用 CLAP) | `pyannote.audio`、`open_clip`;额外存储约 +30% | + +#### 1.3 接口改造点(基于 S3-T1) +- 扩展 `Embedder.encode(items: list[Embeddable])`:`Embeddable = str | PILImage | AudioBytes | VideoFrame`,返回 `EmbeddingResult(vector, modality, dim)`。 +- 新增 `MultimodalRetriever(Retriever)` 实现:内部根据 query 的 `modality_hint`(文本默认)选择走 `text_vec` 还是 `image_vec` 列。 +- VDB 层 schema 演进(`rag/vdb/elasticsearch/elasticsearch_vector.py:653+` 的 mapping 创建):把"硬编码单 vector 列"改造为"按 modality 多列动态注册";落地依赖 [S3-T1] 提到的 mapping 模板化改造。 +- `app/picture.py` / `app/audio.py` 的 `chunk()` 函数输出 dict 中新增 `image_b64` / `audio_b64` 字段,供 Embedder 后续无损取用(避免 PIL 对象在 Celery pickle 边界丢失)。 + +#### 1.4 工作量估计 +- L1 基线:**1.5 人周**(2 个 PR:视频抽帧;结构化 VQA prompt) +- L2 跨模态:**3 人周**(含 Embedder 抽象、ES schema 迁移、回归测试) +- L3 视听统一:**4 人周**(含 GPU 容器、speaker diarization 集成) +- 合计:**~1.5 + 3 + 4 ≈ 8.5 人周**(可分阶段产出) + +#### 1.5 风险与依赖 +- ⚠️ **存储膨胀**:image_vec(768d float32)单图 3KB,1M 图 ≈ 3GB;ES dense_vector 启用 `int8_hnsw` 量化可减 75%。 +- ⚠️ **VLM 描述漂移**:同一图不同时间不同模型版本,描述差异大;需要 caption 缓存(key = `sha256(image)+model_version`)。 +- ⛓️ **强依赖**:[S3-T1] mapping 模板化改造完成后再做 L2,否则 schema 演进会成阻塞点。 +- ⛓️ **GPU 依赖**:L2/L3 在自建 GPU 节点或托管 API 二选一;建议先走托管(Jina-Clip API)跑通端到端,再评估自托管。 + +--- + +### D2. 混合搜索增强(Sparse + Dense + Late-Interaction + 自适应路由) + +#### 2.1 触发场景 +- "工号 E12345 的 OKR" — 长尾标识符,BM25 强、稠密向量弱,**当前 0.05/0.95 权重几乎让 BM25 失语**。 +- "怎么做用户分层运营?" — 概念性问题,dense 强、BM25 弱。 +- "GraphRAG 和 LightRAG 的区别" — 需要 ColBERT 这类 token 级精排,单向量混淆术语。 + +#### 2.2 技术方案 + +| 子方向 | 方案 | 价值 | +|-------|------|------| +| **SPLADE 学习稀疏** | 用 `naver/splade-cocondenser-ensembledistil` 或国产 BGE-M3 sparse 输出,每个文档生成稀疏向量(含 token expansion);接入 `MatchSparseExpr`(**已存在但未启用**) | 把 BM25 的"词形匹配"升级为"学习权重 + 自动同义扩展" | +| **ColBERT 后期交互** | 文档级向量改为 token 级(一篇文档 N 个 token vector,N≈chunk_token_num/3);retrieval 时用 MaxSim;可仅在 reranker 阶段使用 | 在精确匹配上比 cross-encoder 快 5–10×,质量接近 | +| **语义路由 / 自适应权重** | 先用一个轻 LLM(或 query classifier)判定 query 类型(lookup / concept / list / multi-hop / temporal),路由到 `{BM25权重, vector权重, 是否使用 Graph, 是否使用 Rerank}` | 替代当前写死的 `0.05/0.95`;可灰度(query 哈希 % 100 < 5 上新策略) | +| **多向量召回融合** | 同 chunk 同时索引 BM25、dense、sparse 三类,retrieval 后用 RRF (Reciprocal Rank Fusion) 融合 | 工程上 RRF 不需训练,落地最快 | + +#### 2.3 接口改造点 +- 新增 `SparseEmbedder(Embedder)` 实现:返回 `SparseVector(indices, values)`;ES mapping 增加 `q_sparse__vec` 字段,使用 `rank_features`/`sparse_vector` 类型(ES ≥ 8.11)。 +- 在 `rag/nlp/search.py:Dealer.search()`(第 387 行起)把 `FusionExpr` 的硬编码权重改为 `ctx.fusion_weights`,由 `Retriever` 实现的 `ctx` 参数注入。 +- 新增 `RouterRetriever(Retriever)`:组合多个底层 retriever(DenseRetriever / SparseRetriever / GraphRetriever),按 router 决策选择 / 融合。 +- ColBERT 仅在 Reranker 层接入:新增 `ColBERTReranker(Reranker)` 实现;接 `Reranker` 协议,**完全不影响**调用方。 + +#### 2.4 工作量估计 +- RRF 多路融合(**Quick PoC,见 §2**):**0.5 人周** +- SPLADE 接入:**2 人周**(含 ES mapping、批量重建索引) +- 语义路由:**2.5 人周**(含 router 训练数据采集、灰度框架) +- ColBERT Reranker:**3 人周**(GPU 部署 + 蒸馏小型化) +- 合计:**~8 人周** + +#### 2.5 风险与依赖 +- ⚠️ **重建索引成本**:现网 KB 数量 × chunk 数 × 维度,估算总耗时;需要提供"灰度索引切换"工具(详见 §6 路线图 P0)。 +- ⚠️ **路由器误判**:错路由比无路由更糟;必须配 fallback(路由失败回退到当前默认 0.05/0.95)。 +- ⛓️ **依赖 [S3-T1]** 的 `Retriever` Protocol 落地后才能优雅接入路由器;否则会污染 `Dealer` 类。 + +--- + +### D3. 知识图谱增强(基于 [S2-T4] GraphRAG 的延伸) + +#### 3.1 触发场景 +- 法务/合规库每月新增 200+ 条法规:当前必须 **重建整个图**,CI 跑 1 小时;用户要求"增量入库 + 增量图更新"。 +- 报错排查:"TS_001 错误码可能由哪些组件触发?" — 需要从 **错误码** 节点 N-hop 走到 **组件** 节点;当前 KGSearch 走的是文本相似度匹配实体,**不是路径推理**。 +- 团队要求"为什么是这个答案" — 需要把推理路径(A→关系1→B→关系2→C)作为 citation 一同返回,提供 **可解释性**。 + +#### 3.2 技术方案 + +| 子方向 | 方案 | 现状 → 目标 | +|-------|------|------------| +| **增量图演化** | 在 `tasks.py:build_graphrag_for_document` 链上插入 `GraphMerge` 阶段:新文档抽出的子图与全图做 **节点对齐 + 关系合并 + 冲突标记**;保留 `version_int` 字段记录每条边的"加入/失效"版本号 | 一次构建 → 增量更新 + 时间溯源 | +| **路径解释性** | KGSearch.retrieval() 输出新增 `evidence_path: list[Edge]`;在 prompt 组装时把路径作为引用源;前端渲染"由 X→Y→Z 推断" | 黑盒答案 → 带溯源链路 | +| **Neo4j 双引擎** | 当前图存在 ES 的 chunk 表里(`knowledge_graph_kwd` 字段),不能利用图算法;引入 Neo4j 作为 **算法引擎**(PageRank 已在 ES 里跑过,但 Cypher 跑社区检测、最短路径远更便利);ES 仍负责文本召回,Neo4j 负责图算法。README 已声明 Neo4j 是组件,**只是 RAG 层没用** | 单引擎 → 检索 ES + 图算法 Neo4j 混合 | +| **温度敏感的图衰减** | 复用 `core/memory/forgetting_engine` 的 Ebbinghaus 实现到图边权重:长期未被命中的实体/关系权重衰减;与 D4 共享一套衰减逻辑 | 静态图 → 动态、有"记忆"的图 | +| **自动本体演化** | 借鉴 `core/memory/ontology_services/General_purpose_entity.ttl`,定期用 LLM 检查"这批新加的实体类型是否应该归并到已有类型?" | 类型膨胀 → 受控演化 | + +#### 3.3 接口改造点 +- 新增 `GraphRetriever(Retriever)` 实现,包装现有 `KGSearch`;输出 `ScoredChunk.metadata` 增加 `evidence_path`(`list[(from_entity, relation, to_entity, confidence)]`)。 +- 新增 `GraphStore` 抽象层:`add_subgraph / merge / query_path / pagerank / community_detect`;实现两个:`ESGraphStore`(保留现状)、`Neo4jGraphStore`(新增)。`graphrag/general/index.py` 现在直接操作 `nx.Graph`,全部替换为 `GraphStore` 调用。 +- 在 `tasks.py` 的 Celery 链增加 `graph_merge_task`:依赖 `build_graphrag_for_document`,处理增量合并;需要分布式锁(已有 `redis_lock.py` 可用)。 +- Prompt 层(`prompts/generator.py`)新增 `evidence_aware_citation_prompt`:把 `evidence_path` 作为额外上下文注入。 + +#### 3.4 工作量估计 +- 增量图演化(最小可用):**3 人周**(最复杂的是合并冲突的实体消歧) +- 路径解释性:**2 人周** +- Neo4j 双引擎:**3 人周**(含 Cypher 工具集、Neo4j 数据迁移脚本) +- 图衰减 + 本体演化:**2 人周**(与 D4 共享代码) +- 合计:**~10 人周** + +#### 3.5 风险与依赖 +- ⚠️ **实体消歧难度**:跨文档同名异义("苹果"=公司 / 水果);建议用现有 `entity_resolution.py` 改造,但需要补全单元测试。 +- ⚠️ **Neo4j 运维成本**:用户已在 README 声明依赖 Neo4j,但当前 RAG 层零调用;引入意味着同时管理两个图的一致性。建议把 Neo4j 定位为"算法只读 / 异步同步",避免双写一致性。 +- ⛓️ **依赖 [S3-T1]** 把 `GraphStore` 与 `Retriever` 协议落实,否则会跨层塌方。 + +--- + +### D4. 对话记忆 ↔ RAG 协同(短期 / 长期 / 检索召回三段桥接) + +> **MemoryBear 的核心特色**。当前最大产品差异化机会就在这里——`core/memory/` 与 `core/rag/` 是 **两条独立链路**,没有联动。 + +#### 4.1 触发场景 +- 用户在第 3 轮说"我对海鲜过敏",第 7 轮问"今晚吃什么?" — 当前 RAG 层无任何记忆能力,每次只看当轮 query。 +- 多 Agent 协作:售前 Agent 收集到客户预算,售后 Agent 重新询问 — 跨 Agent 记忆需要从 `core/memory` 读出 + 注入 RAG 检索 query 重写。 +- 长对话上下文压缩:第 50 轮时,前 40 轮对话需要 **被遗忘但保留要点**,要点变成"用户档案 chunk"加入 KB。 + +#### 4.2 短期 / 长期 / 检索召回的边界(产品决策) + +| 维度 | 短期记忆(Working Memory) | 长期记忆(Episodic / Semantic) | 检索召回(KB) | +|------|---------------------------|--------------------------------|---------------| +| 存储位置 | Redis,单 session 8KB cap | Neo4j + ES(`core/memory`) | ES(`core/rag`) | +| 生命周期 | session(< 24h) | 永久(按 forgetting curve 衰减) | 永久(人工治理) | +| 写入触发 | 每轮 user/assistant message | reflection_engine 周期性提炼 | 文档入库流水线 | +| 召回时机 | 始终注入 prompt | LLM 重写 query 时 + 主动检索 | RetrievalNode 命中 | +| 数据契约 | `list[Msg]` | `MemoryItem(content, strength, type, ts)` | `DocumentChunk` | +| 可信度 | 高(用户原话) | 中(LLM 提炼) | 高(人工审核) | + +> **决策原则**:"用户原话进短期,提炼事实进长期,世界知识进 KB。" 三者不能互相替代。 + +#### 4.3 技术方案 +- **MemoryAugmentedRetriever**:在 `RouterRetriever` 之外再包一层,retrieve 前用 `core/memory.read_services` 拿到当前 user 的 top-K 长期记忆条目,**改写 query**("今晚吃什么?" + 长期记忆"对海鲜过敏" → "今晚吃什么?避免海鲜")。 +- **Memory Citation**:检索结果与长期记忆条目并入同一 `chunks` 列表,prompt 模板区分两者来源("用户提及" vs "知识库"),避免幻觉混淆。 +- **反向写入**:每轮对话产出后,让 `core/memory.write_router` 决定 是否需要把"新事实"写入长期记忆;这一步 **复用** `core/memory.agent.langgraph_graph.write_graph`(已存在)。 +- **遗忘对齐**:把 `core/memory/forgetting_engine` 的 ACT-R 计算复用到 KB chunk 上(D3 已提);让"很少被命中的过期 KB chunk"自动沉睡,反向触发治理团队复审。 + +#### 4.4 接口改造点 +- 在 `workflow/nodes/knowledge/node.py` 的 `KnowledgeRetrievalNode.execute()` 中注入 `MemoryService`:当节点配置里 `enable_memory=true` 时,先调 `memory_service.recall(user_id, query)` 拿记忆,再传给 `Retriever.retrieve(query, ctx={memory: ...})`。 +- 新增 `MemoryAwareRetriever(Retriever)` 实现,包装任一底层 Retriever。 +- Workflow Node 配置 `KnowledgeRetrievalNodeConfig` 增加 `memory_strategy: Literal["off", "context_only", "rewrite_query", "merge_chunks"]`。 +- Prompt 模板新增 `` 段落。 + +#### 4.5 工作量估计 +- 单向(memory → retrieval):**3 人周** +- 双向(retrieval 结果反写 memory):**2 人周**(大部分代码已在 `core/memory` 存在) +- 遗忘对齐 + 治理触发:**2 人周**(与 D3 共享) +- 合计:**~7 人周** + +#### 4.6 风险与依赖 +- ⚠️ **隐私边界**:长期记忆是 **per-user**,KB 是 **per-tenant**;混淆会导致跨用户泄露。设计时必须 user_id 级强隔离,code review 重点。 +- ⚠️ **Prompt 长度膨胀**:记忆 + KB 双源;如果未做摘要,长对话场景 token 成本翻倍;必须配合记忆摘要(已有 `summary4memory.md`)。 +- ⛓️ **依赖 [S3-T1]** 的 `Retriever / Reranker` 协议;强依赖 [S2-T6] 的 E2E 时序图明确两条链路的衔接点。 + +--- + +### D5. 评估与反馈闭环(用户反馈 → Reranker 微调) + +#### 5.1 触发场景 +- 答案错了 / 引用不对,用户点👎 — 当前数据 **进了日志,没人消费**。 +- 同一 query 在不同时段表现波动 → 需要离线 A/B 评估。 +- 业务方问"再加一个 KB 之后效果到底变好还是变差?" — 没有可量化的回归指标。 +- README 给的 F1/BLEU/J 在论文中实现过,**但仓内没有这套代码**,每次评估靠手工。 + +#### 5.2 技术方案(双轨:评估在线化 + 反馈学习) + +##### 5.2.1 评估轨:离线 / 在线 / CI 三层 + +| 层级 | 内容 | 工具 | +|------|------|------| +| **离线评估集** | 每 KB 维护一个 `eval_cases.jsonl`:`{query, ideal_chunks, ideal_answer, hard_negatives}`;增量构建(每周从用户问句 + 答疑团队补充) | DSL + Excel 导入工具 | +| **在线指标** | `Hit@K / MRR / nDCG / Citation Coverage / Hallucination Rate / Latency P50/P95`;通过 OpenTelemetry 埋点写入 Prometheus | OTel + Prometheus + Grafana | +| **CI 评估** | 每个 PR 跑核心 KB 的回归集;指标低于 baseline n% 时阻塞合并 | RAGAS(开源)+ 自研判分 prompt | + +##### 5.2.2 反馈学习轨:从👍/👎到 Reranker 微调 + +``` +用户反馈(👍/👎/edit) + ↓ event log +事件清洗(同一 query 多个 chunk 评分) + ↓ +形成 (query, positive_chunk, negative_chunk) 三元组 + ↓ + ├─ 短链:在线 PairWise 调整 BM25/dense 权重(D2 路由器配置) + └─ 长链:周/月一次离线训练 Cross-Encoder reranker(基础模型用 bge-reranker-base 蒸馏) + ↓ + 新 reranker 走 D6 灰度框架上线 + ↓ + 评估轨自动验证收益 +``` + +#### 5.3 接口改造点 +- 新增 `EvaluationProtocol`:`{evaluate(query, retrieved, generated, ground_truth) -> Metrics}`;在 OpenTelemetry trace 末尾自动落 Prometheus。 +- `RedBearRerank` 改造:接入 `LocalCrossEncoderRerank(Reranker)` 子类,加载本地 ONNX/TorchScript 模型;可与 Jina/DashScope 并存于工厂。 +- 反馈采集:复用 `core/memory` 的事件总线(如有)或新建 `feedback_event` 表;前端组件加 thumbs;citation 点击行为也作为隐式反馈。 +- 训练 pipeline 独立仓 / 独立服务;产物(ONNX)通过模型注册表(用现有 `ModelConfig` 表扩展即可)滚动上线。 + +#### 5.4 工作量估计 +- 评估指标埋点 + Grafana 看板:**1.5 人周** +- 离线评估集 + RAGAS CI 集成:**2 人周** +- 反馈采集 + 三元组清洗:**1 人周** +- Cross-Encoder 蒸馏训练 pipeline:**3 人周**(含数据扩充、训练脚本、产出 ONNX) +- 合计:**~7.5 人周** + +#### 5.5 风险与依赖 +- ⚠️ **冷启动**:刚上线时反馈数据 < 1k 不足以训练;必须先用大模型 LLM-as-Judge 合成训练数据(现成 prompt 在 `prompts/generator.py` 可借鉴)。 +- ⚠️ **反馈污染**:恶意 / 误点;需要置信度过滤(同一 user 短时多次相反反馈丢弃)。 +- ⛓️ **依赖 [S3-T1]** 的可观测性方案,否则数据采不到。 +- ⛓️ **依赖 D2 的语义路由**,否则没有"权重可调"的注入点。 + +--- + +### D6. 自适应检索路由(Adaptive Retrieval Routing) + +> 这是 D2 中"语义路由"的工程化升级版,独立列出是因为它会**统一**所有检索能力(dense / sparse / graph / memory / web),是 RAG 系统的中央调度器。 + +#### 6.1 触发场景 +- 同一用户在同一 session 内:第 1 个问题需要走 KB,第 2 个问题需要走 Web 搜索("今天的新闻"),第 3 个问题需要 Graph 推理 — 当前必须用户手动切模式。 +- "你刚才推荐的方案做不了"(指代消解)→ 需要先走对话记忆,再决定是否检索;当前都是无脑全检索。 + +#### 6.2 技术方案 + +| 决策类型 | 输入 | 输出 | +|---------|------|------| +| 是否需要检索 | query + 短期记忆 | `bool need_retrieval` | +| 检索来源 | query 类型 | `[KB_id, Graph_flag, Web_flag, Memory_flag]` | +| 检索策略 | query 类型 + 用户场景 | `(retriever_name, top_k, fusion_weights, rerank_id)` | +| 兜底 | 第一次检索结果差 | 触发 query rewriting + 二次检索 | + +实现: +- 路由器 = 小型 LLM(如 1.5B–3B)+ rule-based fallback;输出结构化 JSON。 +- 训练数据来源:D5 的反馈数据 + 标注团队人工标 1k 条。 +- 推理用 vllm 或 SGLang 自托管,P95 延迟控制在 50ms。 + +#### 6.3 接口改造点 +- 把 `RetrieveType` enum 改造成 strategy(与 D2 共享的 `RouterRetriever`);workflow 层调用方不再选模式,而是传入 query。 +- 新增 `RoutingPolicy` 配置实体:可被工作空间管理员通过 UI 编辑(默认策略 + 灰度策略)。 +- 与 D5 形成闭环:评估指标决定路由器升级时机。 + +#### 6.4 工作量估计 +- 规则+LLM 路由器最小可用:**2 人周** +- 完整训练 / 灰度 / 配置 UI:**5 人周** +- 合计:**~7 人周** + +#### 6.5 风险与依赖 +- ⚠️ **路由器变成单点**:必须有 fallback 到当前默认策略。 +- ⛓️ **强依赖 D2 + D5**;不建议独立做。 + +--- + +## 2. Quick PoC 路径(≤ 1 周可见效果) + +### PoC-A:RRF 多路融合检索(属 D2) + +**目标**:现网 KB 在不重建索引、不改 schema 的前提下,加入 BM25 + dense 各自独立 top-50 → RRF 融合 → 同一接口返回。1 周内拿到 A/B 数据。 + +**改动范围**(最小集): +- `rag/nlp/search.py:Dealer.search()` 拆为两步:先单独跑 BM25(`emb_mdl=None`),再单独跑 dense(无 BM25),合并时用 RRF。 +- 增加 feature flag `RETRIEVAL_FUSION_MODE = {"weighted", "rrf"}`,默认 weighted(不影响现网)。 + +**预期收益**:在长尾 lookup query 上 Hit@10 +5–10pp(参考社区数据)。无负向风险,因为 weighted 路径保留。 + +**PoC 代码草案**(伪代码,约 30 行;正式实现需走完整 PR + 评估): + +```python +# rag/retrieval/rrf.py(新增) +def rrf_merge(rankings: list[list[ScoredChunk]], k: int = 60, top_k: int = 20) -> list[ScoredChunk]: + """Reciprocal Rank Fusion: score = Σ 1/(k + rank_i)。 + rankings: 多个独立排序结果,每个内部按相关度降序。 + """ + score_map: dict[str, float] = {} + chunk_map: dict[str, ScoredChunk] = {} + for ranking in rankings: + for rank, chunk in enumerate(ranking, start=1): + cid = chunk.metadata["doc_id"] + score_map[cid] = score_map.get(cid, 0.0) + 1.0 / (k + rank) + chunk_map[cid] = chunk # 保留首次见到的对象 + merged = sorted(chunk_map.values(), + key=lambda c: score_map[c.metadata["doc_id"]], + reverse=True) + for c in merged: + c.metadata["score_rrf"] = score_map[c.metadata["doc_id"]] + return merged[:top_k] + + +# 调用侧(rag/nlp/search.py:Dealer.search 增量改造) +if os.getenv("RETRIEVAL_FUSION_MODE", "weighted") == "rrf": + bm25_hits = self._search_bm25_only(req, ...) + dense_hits = self._search_dense_only(req, ...) + return rrf_merge([bm25_hits, dense_hits], k=60, top_k=req.get("topk", 20)) +# else: 走现有 weighted 路径 +``` + +### PoC-B:Memory-Augmented Query Rewrite(属 D4) + +**目标**:把 `core/memory.read_services` 已有的"长期记忆召回"接到 `KnowledgeRetrievalNode` 之前,做 query 改写。1 周内对 1 个内部 demo 应用上线。 + +**改动范围**: +- `KnowledgeRetrievalNode.execute()` 第一行加 5 行:拿 user_id(已有 `user_ids`),调 `memory_service.get_user_summary(user_id)`,把 summary 拼到 query 前。 +- 新增 feature flag `MEMORY_AUGMENT_RETRIEVAL = false`(默认关闭)。 +- 不改 prompt,不改 schema,不改 ES。 + +**预期收益**:在多轮对话场景下,第 N 轮 query 的指代消解正确率提升;无回归风险(flag 默认关)。 + +```python +# workflow/nodes/knowledge/node.py:KnowledgeRetrievalNode.execute() 头部增量 +if os.getenv("MEMORY_AUGMENT_RETRIEVAL") == "true" and user_ids: + from app.services.user_memory_service import get_user_summary + summary = get_user_summary(user_ids[0], ttl_sec=3600) # 已存在 / 类似函数 + if summary: + query = f"[用户背景: {summary}]\n{query}" +``` + +> **注意**:上述两段代码均为 PoC 草案,真实落地需要:1)完整单测;2)评估对比;3)feature flag 走配置中心;4)权限审查(D4 涉及隐私)。 + +--- + +## 3. 优先级矩阵(用户价值 × 实现成本 × 风险) + +> 评分 1–5(5 最高 / 5 最低)。建议落地顺序按"用户价值高 + 成本低 + 风险低"加权。 + +| 方向 | 用户价值 | 实现成本 (越低越好) | 风险 (越低越好) | 综合分(V × 1/√(C×R)) | 建议落地阶段 | +|------|---------|--------------------|----------------|----------------------|------------| +| **D2-PoC RRF 融合** | 4 | 5 (0.5 人周) | 5 (无回归) | 8.0 | 立即(Sprint-3 内) | +| **D4-PoC Memory Rewrite** | 4 | 5 (0.5 人周) | 4 (隐私) | 7.2 | 立即(Sprint-3 内) | +| **D5 评估埋点 + Grafana** | 5 | 4 (1.5 人周) | 5 | 5.6 | 短期(1 月) | +| **D5 RAGAS CI** | 4 | 4 | 5 | 4.5 | 短期(1 月) | +| **D2 SPLADE 接入** | 4 | 3 (2 人周) | 4 (索引重建) | 3.7 | 短期(1 月) | +| **D4 完整双向集成** | 5 | 3 (5 人周) | 3 (隐私 / token) | 3.5 | 中期(2 月) | +| **D5 Reranker 微调** | 4 | 3 (3 人周) | 3 (冷启动) | 2.7 | 中期(2 月) | +| **D6 自适应路由** | 4 | 2 (5 人周) | 3 | 2.3 | 中期(3 月) | +| **D1 多模态 L1(基线)** | 3 | 4 (1.5 人周) | 4 | 3.0 | 短期(1 月) | +| **D1 多模态 L2 跨模态** | 5 | 2 (3 人周) | 3 (GPU) | 2.5 | 中期(3 月) | +| **D3 增量图演化** | 4 | 2 (3 人周) | 2 (实体消歧) | 2.0 | 中长期(3–4 月) | +| **D3 Neo4j 双引擎** | 3 | 2 (3 人周) | 2 (运维) | 1.5 | 长期(4–6 月) | +| **D1 多模态 L3 视听统一** | 3 | 1 (4 人周) | 2 (GPU + diarization) | 1.1 | 长期(6 月+) | +| **D3 自动本体演化** | 2 | 2 | 2 | 1.0 | 长期 (按需) | + +> **维度说明** +> - 用户价值:高优先级业务场景(toB 客户)调研访谈得分。 +> - 实现成本:人周折算(1 人周=1 分;6 人周=2 分;10 人周=1 分)。 +> - 风险:含技术风险 + 数据迁移 + 上线回滚 + 安全 / 隐私。 +> - 综合分用 `V / sqrt(C×R)` 倒数化,**仅作排序参考**,不取代产品/架构会判断。 + +--- + +## 4. 落地路线图(Roadmap) + +```mermaid +gantt + title MemoryBear RAG 后续迭代 路线图 + dateFormat YYYY-MM-DD + axisFormat %m/%d + section Sprint-3 (现 Sprint) + PoC-A RRF 融合 (D2) :a1, 2026-06-02, 5d + PoC-B Memory Rewrite (D4) :a2, 2026-06-02, 5d + section 短期 (1 个月) + 评估埋点 + Grafana (D5) :s1, 2026-06-09, 7d + RAGAS CI (D5) :s2, after s1, 7d + SPLADE 接入 (D2) :s3, after s1, 10d + 多模态 L1 基线 (D1) :s4, 2026-06-09, 7d + section 中期 (2-3 个月) + Memory ↔ RAG 双向集成 (D4) :m1, after s2, 25d + Reranker 微调 pipeline (D5) :m2, after s3, 15d + 自适应路由 (D6) :m3, after m1, 25d + 多模态 L2 跨模态 (D1) :m4, after s4, 15d + section 长期 (3-6 个月) + 增量图演化 (D3) :l1, after m1, 20d + Neo4j 双引擎 (D3) :l2, after l1, 15d + 多模态 L3 视听统一 (D1) :l3, after m4, 20d + 本体演化 (D3) :l4, after l2, 10d +``` + +> 所有阶段分别绑定一组 OKR + 评估指标(D5 提供数据),未达指标停止下阶段。 + +--- + +## 5. 风险与依赖总表 + +| 类型 | 风险 | 缓解策略 | +|------|------|---------| +| 架构 | [S3-T1] 接口抽象未落地,本路线图全部方向受阻 | Sprint-3 内先把 `Retriever / Reranker / Embedder / Generator` 4 个 Protocol 落地([S3-T1] 必交付项) | +| 数据 | 索引重建(D1/D2/D3)导致服务不可用 | 灰度索引切换工具:双写期 + 流量按租户灰度 + 一键回滚 | +| 隐私 | D4 跨用户记忆泄露 | user_id 级强隔离 + 单元测试覆盖 + 上线前安全 review | +| 资源 | D1/D6 引入 GPU 依赖 | 优先走托管 API 跑通 PoC;自托管列入 long-term,需要预算评审 | +| 治理 | D5 评估集质量低 → CI 阻塞误判 | 评估集双人复核 + 周复盘 + 例外白名单 | +| 运维 | D3 Neo4j 双引擎一致性 | 定位 Neo4j 为算法只读,从 ES 异步同步;不双写 | +| 业务 | 路线图与产品 PRD 脱节 | 与 [@产品需求分析师] 在 Sprint-3 启动前对齐 1 次 | + +--- + +## 6. 与 [S3-T1] / [S3-T3] 的对齐清单 + +- ✅ 每个方向都标注了"接口改造点",所有改造均落到 [S3-T1] 提议的 `Retriever / Reranker / Embedder / Generator / GraphStore / Loader` Protocol;不新增其它接口。 +- ✅ 所有方向有"工作量、风险、依赖"三件套,可被 [S3-T3] 终审按统一模板核对。 +- ✅ Quick PoC 已覆盖 D2 与 D4 各 1 条(≥ 2 条要求达成)。 +- ✅ 优先级建议已按"用户价值 × 实现成本 × 风险"三维评分给出,并配有路线图甘特图。 +- ✅ 多模态、混合搜索、KG 增强、对话记忆、评估闭环均覆盖(5/5);额外补充自适应路由作为联动方向。 + +— END — diff --git a/docs/rag/graphrag/README.md b/docs/rag/graphrag/README.md new file mode 100644 index 00000000..88605df1 --- /dev/null +++ b/docs/rag/graphrag/README.md @@ -0,0 +1,200 @@ +--- +title: "[S2-T4] GraphRAG(light + general)实现详解 — 正式版" +author: Python 开发工程师 +reviewer: 知识运营与治理专家 +source-commit: feae2f2e (MemoryBear) +last-reviewed-at: 2026-05-08 +scope: api/app/core/rag/graphrag/(含 light/ 与 general/ 子目录) +version: v1.0 +status: 正式版(已解除占位) +--- + +# [S2-T4] GraphRAG(light + general)实现详解 — 正式版 + +> 本文档为 [WS-24](mention://issue/a07f108d-06ee-41b8-8b57-22455f60ddeb) v1.0 文档全集的正式组成文件,替换 v1.0-RC1 中的占位版本。 +> 原始完整文档与逐节详评见 [WS-18](mention://issue/16bdb196-e10e-489b-b01c-9067b1f1bb23) 与 [WS-21](mention://issue/41f2482b-6f3e-4253-95f7-3e22e790f31c) §S2-T4 评审报告。 + +--- + +## 1. 一句话定位 + +GraphRAG 是 MemoryBear 知识库系统的**知识图谱增强检索模块**,通过 LLM 从文档中抽取实体-关系三元组构建知识图谱,在检索阶段利用图谱结构(实体关联、社区报告、多跳路径)补充传统向量检索的语义盲区,实现"结构化知识 + 语义向量"的混合召回。 + +--- + +## 2. 评审结果 + +| 维度 | 满分 | 得分 | 关键说明 | +|---|---:|---:|---| +| 准确性 | 25 | 24 | 抽检 5/5 命中:`run_graphrag` / extractor 三元选择 / `is_similarity` / `KGSearch.retrieval` / Leiden `run()` | +| 完整性 | 25 | 24 | 12 章节 + 附录索引:术语表 11 条、Light/General 双时序图、5 套源码详解、4 个核心 Prompt 逐段解读 | +| 时效性 | 15 | 13 | 元数据表完整,缺 YAML frontmatter(Sprint-2 已知遗留) | +| 可读性 | 15 | 14 | Mermaid 时序图规范、Light/General 三张对照表一目了然、Prompt 逐行设计意图写法出色 | +| 可执行性 | 20 | 18 | parser_config 配置入口明确、三组参数表完整、资源消耗估算(Light 5-15min / General 30-60min)可验证 | +| **合计** | **100** | **93** | **PASS(标杆)** | + +**裁定:** 与 [S2-T3] 并列 Sprint-2 **双标杆**。Must-Fix 无;Nice-to-Have 7 条留给 [S3-T3] 整合时统一处理。 + +--- + +## 3. 模块结构 + +``` +api/app/core/rag/graphrag/ +├── search.py # KGSearch:图谱检索入口 +├── entity_resolution.py # 实体消歧(LLM + 编辑距离) +├── entity_resolution_prompt.py # 实体消歧 Prompt +├── query_analyze_prompt.py # 查询分析 Prompt(MiniRAG 风格) +├── utils.py # 图操作工具集(merge、cache、ES 读写) +├── __init__.py +├── light/ +│ ├── graph_extractor.py # Light 版实体/关系抽取器 +│ └── graph_prompt.py # Light 版抽取 Prompt + RAG 回答 Prompt +└── general/ + ├── extractor.py # 通用抽取基类 + ├── graph_extractor.py # General 版实体/关系抽取器 + ├── graph_prompt.py # General 版抽取 Prompt + ├── index.py # 建图总控(子图生成→合并→消歧→社区报告) + ├── entity_embedding.py # Node2Vec 实体嵌入(备用) + ├── leiden.py # Leiden 社区发现算法封装 + ├── community_reports_extractor.py # 社区报告抽取器 + ├── community_report_prompt.py # 社区报告生成 Prompt + ├── mind_map_extractor.py # 思维导图抽取器 + └── mind_map_prompt.py # 思维导图 Prompt +``` + +--- + +## 4. 核心时序图 + +### 4.1 建图时序图 + +```mermaid +sequenceDiagram + participant U as 用户/任务 + participant T as tasks.py (Celery Task) + participant I as general/index.py run_graphrag + participant E as light/general GraphExtractor + participant ES as Elasticsearch + participant ER as entity_resolution.py + participant CR as community_reports_extractor.py + + U->>T: 上传文档 / 触发建图 + T->>I: run_graphrag_for_kb(document_ids, parser_config) + I->>I: load_doc_chunks() 按 1024 token 合并 chunk + loop 每个文档并行(max 4) + I->>E: generate_subgraph(extractor, chunks) + E->>E: LLM 抽取 entities + relations (多轮 gleaning) + E->>E: 解析输出 → nx.Graph + E->>ES: 写入 subgraph (knowledge_graph_kwd="subgraph") + end + I->>I: merge_subgraph() 逐个文档合并子图到全局图 + I->>ES: 写入全局 graph (knowledge_graph_kwd="graph") + I->>ES: 写入 entity/relation chunks (带向量嵌入) + + alt with_resolution=true (General 可选) + I->>ER: resolve_entities(graph, subgraph_nodes) + ER->>ER: 编辑距离预筛选候选对 + ER->>ER: LLM 批量判断"是否同一实体" + ER->>ER: 合并连通分量中的节点 + ER->>ER: 重新计算 PageRank + ER->>ES: 更新 graph/entity/relation + end + + alt with_community=true (General 可选) + I->>CR: extract_community(graph) + CR->>CR: Leiden 社区发现 + CR->>CR: LLM 生成每个社区的报告 + CR->>ES: 写入 community_report chunks + end + I-->>T: 返回 {ok_documents, failed_documents, seconds} +``` + +### 4.2 查图时序图 + +```mermaid +sequenceDiagram + participant U as 用户 Query + participant S as search.py KGSearch.retrieval() + participant QP as query_analyze_prompt.py minirag_query2kwd + participant ES as Elasticsearch + participant LLM as LLM + + U->>S: retrieval(question, workspace_ids, kb_ids, ...) + S->>LLM: query_rewrite() PROMPTS["minirag_query2kwd"] + LLM-->>S: {answer_type_keywords, entities_from_query} + + par 三路召回并行 + S->>ES: get_relevant_ents_by_keywords() 向量相似度搜索 entity + ES-->>S: 候选实体列表 + sim + pagerank + n_hop + S->>ES: get_relevant_ents_by_types() 按类型过滤 entity + ES-->>S: 类型匹配实体列表 + S->>ES: get_relevant_relations_by_txt() 向量相似度搜索 relation + ES-->>S: 候选关系列表 + end + + S->>S: 计算 n-hop 路径权重衰减 sim / (2 + hop_depth) + S->>S: 实体排序:sim × pagerank + S->>S: Token 预算截断(max_token 递减) + + alt 社区报告召回 + S->>ES: _community_retrieval_() 按 entities_kwd 匹配 community_report + ES-->>S: 社区报告文本 + end + + S-->>U: {page_content: Entities + Relations + Community Reports, metadata, vector: None} +``` + +--- + +## 5. Light vs General 差异 + +| 维度 | Light | General | +|---|---|---| +| 实体抽取 Prompt | LightRAG 风格,含 content_keywords | MS GraphRAG 风格,更简洁 | +| Gleaning 终止 | 自然语言 yes/no | 强制单字 Y(logit_bias) | +| 实体消歧 | ❌ 不支持 | ✅ 支持 | +| 社区发现 | ❌ 不支持 | ✅ Leiden 算法 | +| 社区报告 | ❌ 不支持 | ✅ LLM 生成报告 | +| 实体嵌入 | 仅实体名向量 | 支持 Node2Vec(备用) | +| 思维导图 | ❌ 不支持 | ✅ 支持 | +| 建图耗时 | ~5-15 分钟 | ~30-60 分钟 | +| 适用规模 | < 1K 文档 | > 1K 文档 | + +**切换条件:** `parser_config["graphrag"]["method"] == "general"` 时启用 General,否则默认 Light。 + +--- + +## 6. 关键源码索引速查表 + +| 功能 | 文件 | 关键类/函数 | 行号 | +|---|---|---|---| +| 建图总控 | `general/index.py` | `run_graphrag()` | 36-119 | +| KB 级批量建图 | `general/index.py` | `run_graphrag_for_kb()` | 122-330 | +| 子图生成 | `general/index.py` | `generate_subgraph()` | 333-406 | +| Light 实体抽取 | `light/graph_extractor.py` | `GraphExtractor._process_single_content()` | 74-131 | +| General 实体抽取 | `general/graph_extractor.py` | `GraphExtractor._process_single_content()` | 100-150 | +| 实体消歧 | `entity_resolution.py` | `EntityResolution.__call__()` | 53-141 | +| 相似度预筛选 | `entity_resolution.py` | `EntityResolution.is_similarity()` | 225-239 | +| 社区发现 | `general/leiden.py` | `run()` | 95-141 | +| 社区报告抽取 | `general/community_reports_extractor.py` | `CommunityReportsExtractor.__call__()` | 55-158 | +| 图谱检索 | `search.py` | `KGSearch.retrieval()` | 130-280 | +| Query 改写 | `search.py` | `KGSearch.query_rewrite()` | 33-55 | +| 图合并工具 | `utils.py` | `graph_merge()` | 199-229 | +| 实体转 chunk | `utils.py` | `graph_node_to_chunk()` | 301-327 | +| 关系转 chunk | `utils.py` | `graph_edge_to_chunk()` | 352-378 | + +完整源码详解、Prompt 逐段解读、ES 存储设计、配置参数表、边界条件与监控指标,请参阅 [WS-18](mention://issue/16bdb196-e10e-489b-b01c-9067b1f1bb23) 原始交付文档。 + +--- + +## 7. 跨文档一致性 + +- 与 [S2-T2] 关于 GraphRAG 实体嵌入缓存(Redis + xxhash)描述一致 ✅ +- 与 [S2-T3] 关于 ES 多类型共存(`knowledge_graph_kwd` 区分 6 种类型)设计一致 ✅ +- 与 [S2-T5] 关于 GraphRAG 检索结果并入向量召回的描述一致 ✅ +- 与 [S2-T6] E2E 时序图中 GraphRAG 分支对齐 ✅ + +--- + +*本文档为 MemoryBear RAG Docs v1.0 正式版本的组成文件。完整详评与源码解读参见 [WS-18](mention://issue/16bdb196-e10e-489b-b01c-9067b1f1bb23) 评论历史。* diff --git a/docs/rag/overview/01-architecture.mmd b/docs/rag/overview/01-architecture.mmd new file mode 100644 index 00000000..0a17a71c --- /dev/null +++ b/docs/rag/overview/01-architecture.mmd @@ -0,0 +1,132 @@ +%% MemoryBear RAG 全链路架构图(Mermaid Flowchart) +%% 约定:浅蓝色 = 数据来源层;浅绿色 = 解析与分块;浅黄色 = 向量化与存储;浅紫色 = 检索;浅橙色 = 生成;浅灰色 = 支撑组件 + +flowchart TB + subgraph DATA_SOURCES["数据来源层 (Loader)"] + CRAWLER["Web Crawler\ncrawler/web_crawler.py\n-> 输出: CrawledDocument"] + FEISHU["飞书 API\nintegrations/feishu/client.py\n-> 输出: 本地文件 (.docx/.pdf)"] + YUQUE["语雀 API\nintegrations/yuque/client.py\n-> 输出: 本地文件 (.md/.html/.xlsx)"] + UPLOAD["用户上传\ncontrollers/document_controller.py:275\n-> 输出: 文件路径"] + end + + subgraph PARSER["文档解析与分块 (Parser + Chunking)"] + NAIVE["app/naive.py:chunk()\n统一分块入口\nDispatch by filename extension"] + PDFP["deepdoc/parser/pdf_parser.py\nOCR + Layout + Table"] + DOCXP["deepdoc/parser/docx_parser.py"] + HTMLP["deepdoc/parser/html_parser.py"] + MDPP["deepdoc/parser/markdown_parser.py"] + EXCELP["deepdoc/parser/excel_parser.py"] + TXTPIP["deepdoc/parser/txt_parser.py"] + VISION["deepdoc/vision/\nocr.py + layout_recognizer.py\n+ table_structure_recognizer.py"] + NLP["nlp/__init__.py\ntokenize / naive_merge / hierarchical_merge"] + end + + subgraph CHUNK_TYPES["文档类型适配 (Task Types)"] + BOOK["app/book.py\n长文档分级分块"] + PAPER["app/paper.py\n论文结构保持"] + MANUAL["app/manual.py\n手册按节分块"] + LAWS["app/laws.py\n法规层级树分块"] + QA["app/qa.py\n问答对独立分块"] + ONE["app/one.py\n整文件单块"] + PIC["app/picture.py\nOCR + VLM描述"] + AUD["app/audio.py\n语音转文本"] + end + + subgraph EMBED["向量化 (Embedding)"] + EMB_BASE["llm/embedding_model.py\nBase.encode(texts: list)\n→ (np.array, token_count)"] + EMB_PROV["Provider 工厂\nOpenAI / LocalAI / Azure / Tongyi /\nHuggingFace / Xinference / VolcEngine /\nGPUStack / NVIDIA / BaiChuan"] + end + + subgraph VDB["向量数据库 (VDB)"] + ES_VECT["vdb/elasticsearch/elasticsearch_vector.py\nDense + Sparse 混合索引\ncosineSimilarity + BM25"] + ES_CONN["utils/es_conn.py\nES 连接管理"] + ES_SCHEMA["vdb/field.py\npage_content / metadata / vector / text\n+ doc_id / knowledge_id / sort_id"] + end + + subgraph GRAPHRAG["知识图谱 (GraphRAG)"] + G_LIGHT["graphrag/light/\ngraph_extractor.py\n实体+关系抽取\n→ nx.Graph"] + G_GEN["graphrag/general/\ngraph_extractor.py\n→ community_reports_extractor.py\n+ mind_map_extractor.py"] + G_LEIDEN["general/leiden.py\n层次聚类"] + G_RESOLVE["entity_resolution.py\n实体消歧 LLM 匹配"] + G_SEARCH["graphrag/search.py\nKGSearch.retrieval()\nQuery分析→实体检索→N-hop→社区报告"] + end + + subgraph RETRIEVAL["检索 (Retrieval)"] + DEALER["nlp/search.py\nDealer.search()\nHybrid: BM25 0.05 + Vector 0.95"] + QRYR["nlp/query.py\nQuery理解 / 关键词扩展"] + KNOWLEDGE["nlp/search.py:36\nknowledge_retrieval()\n→ 多知识库合并"] + end + + subgraph RERANK["重排序 (Reranking)"] + RERANK_M["models/rerank.py\nRedBearRerank\ncompress_documents() / rerank()"] + RERANK_P["Provider: JinaRerank /\nDashScopeRerank /\nXINFERENCE / GPUSTACK"] + end + + subgraph PROMPT["Prompt 组装"] + PGEN["prompts/generator.py\ncitation_prompt / keyword_extraction /\nfull_question / content_tagging /\ntoc_relevance / structured_output"] + PTEMPLATE["prompts/template.py\n加载 .md 模板文件"] + end + + subgraph LLM["LLM 生成"] + CHAT["llm/chat_model.py\nBase.chat() / chat_streamly()\n→ (str, tokens)"] + CHAT_PROV["Provider 工厂\nOpenAI / Azure / LocalAI /\nXinference / Tongyi /\nHuggingFace / GPUStack / VolcEngine"] + end + + subgraph ORCH["编排层 (Orchestration)"] + CELERY["tasks.py\nparse_document() /\nbuild_graphrag_for_kb() /\nbuild_graphrag_for_document()"] + WORKFLOW["workflow/nodes/knowledge/node.py\nKnowledgeRetrievalNode.execute()\n→ 检索→去重→重排→返回 chunks"] + end + + subgraph POST["后处理"] + CITE["插入引用标注\nDealer.insert_citations()\npagerank*sim 评分"] + CACHE["缓存层\nutils/redis_conn.py\nLLM 结果缓存"] + end + + %% === 数据流 === + DATA_SOURCES --> NAIVE + NAIVE --> |PDF| PDFP + NAIVE --> |DOCX| DOCXP + NAIVE --> |HTML| HTMLP + NAIVE --> |MD| MDPP + NAIVE --> |XLSX| EXCELP + NAIVE --> |TXT| TXTPIP + + PDFP --> VISION + VISION --> NLP + DOCXP --> NLP + HTMLP --> NLP + MDPP --> NLP + EXCELP --> NLP + TXTPIP --> NLP + + NAIVE --> |按文档类型| CHUNK_TYPES + CHUNK_TYPES --> NLP + + NLP --> EMB_BASE + EMB_BASE --> EMB_PROV + EMB_PROV --> ES_VECT + ES_SCHEMA --> ES_VECT + ES_CONN --> ES_VECT + + NLP -.-> |"并行 (async)"| GRAPHRAG + G_LIGHT --> G_SEARCH + G_GEN --> G_LEIDEN + G_GEN --> G_RESOLVE + G_LEIDEN --> G_SEARCH + G_RESOLVE --> G_SEARCH + + CELERY --> NAIVE + CELERY -.-> |"触发"| GRAPHRAG + + WORKFLOW --> QRYR + QRYR --> DEALER + DEALER --> KNOWLEDGE + KNOWLEDGE --> RERANK_M + G_SEARCH --> |"GRAPH模式"| KNOWLEDGE + RERANK_M --> RERANK_P + RERANK_P --> PGEN + PGEN --> PTEMPLATE + PTEMPLATE --> CHAT + CHAT --> CHAT_PROV + CHAT --> CITE + CITE --> CACHE diff --git a/docs/rag/overview/02-indexing-pipeline.mmd b/docs/rag/overview/02-indexing-pipeline.mmd new file mode 100644 index 00000000..1da22efe --- /dev/null +++ b/docs/rag/overview/02-indexing-pipeline.mmd @@ -0,0 +1,87 @@ +%% MemoryBear 文档入库时序图(Indexing Pipeline) +%% 起点:用户上传 / API 调用;终点:向量入库 + GraphRAG 索引完成 + +sequenceDiagram + autonumber + participant User as 用户/API + participant API as document_controller.py:275
parse_documents() + participant Celery as Celery Worker
tasks.py + participant DB as PostgreSQL
(Document / Knowledge) + participant Chunker as app/naive.py:508
chunk() + participant Parser as deepdoc/parser/
(PDF/DOCX/HTML/...) + tokenizer as nlp/__init__.py
tokenize / naive_merge + participant Embed as llm/embedding_model.py
Base.encode() + participant VDB as ESVectorFactory
elasticsearch_vector.py + participant Graph as graphrag/general/index.py
run_graphrag_for_kb() + + Note over User,VDB: === 阶段 1:文件上传与触发 === + User->>API: POST /documents (file / URL) + API->>DB: INSERT Document (status=pending) + API->>Celery: delay parse_document(file_path, document_id) + + Note over Celery,VDB: === 阶段 2:文档解析与分块 === + Celery->>DB: SELECT Document, Knowledge + Celery->>Celery: _build_vision_model() + Celery->>Chunker: chunk(filename, binary, vision_model) + + alt PDF 格式 + Chunker->>Parser: RAGPdfParser.__call__() + Parser->>Parser: __images__() → OCR → _layouts_rec() + Parser->>Parser: _table_transformer_job() + Parser->>Parser: _text_merge() + _concat_downward() + Parser-->>Chunker: sections: List[(text, tag)]
tables: List[(image, html)] + else DOCX 格式 + Chunker->>Parser: RAGDocxParser.parse() + Parser-->>Chunker: sections, tables + else HTML/MD/TXT/Excel + Chunker->>Parser: 对应 Parser + Parser-->>Chunker: sections + end + + alt 按文档类型路由 + Chunker->>Chunker: book.py / paper.py / laws.py / ... + Chunker->>tokenizer: hierarchical_merge() / tree_merge() + else 默认 naive + Chunker->>tokenizer: naive_merge(sections, chunk_token_num) + end + + tokenizer->>tokenizer: tokenize(d) → content_ltks / content_sm_ltks + tokenizer->>tokenizer: tokenize_chunks() → 附 page_num / position / image + tokenizer-->>Celery: res: List[Dict] (chunk dicts) + + Note over Celery,VDB: === 阶段 3:向量化与存储 === + Celery->>DB: progress=0.8 + Celery->>VDB: delete_by_metadata_field(document_id) + + alt auto_questions 开启 + Celery->>Celery: ThreadPool 并发生成问题 + Celery->>Embed: question_proposal(chat_mdl, content) + end + + Celery->>Embed: encode(chunk_texts) → np.array + Embed-->>Celery: vectors + token_count + + loop 每 batch + Celery->>Celery: 组装 DocumentChunk(page_content, vector, metadata) + Celery->>VDB: insert_documents(chunks) + VDB->>VDB: cosineSimilarity 索引 + BM25 + VDB-->>Celery: ack + end + + Celery->>DB: UPDATE Document (progress=1.0, chunk_num=N) + + Note over Celery,Graph: === 阶段 4:GraphRAG 异步构建 === + Celery->>Celery: build_graphrag_for_document.delay() + Celery->>Graph: run_graphrag_for_kb(document_ids) + Graph->>Graph: generate_subgraph() per chunk + Graph->>Graph: LLM 抽取 entities + relations + Graph->>Graph: merge_subgraph() → nx.pagerank + opt entity_resolution + Graph->>Graph: resolve_entities() (LLM 匹配) + end + opt community_reports (general only) + Graph->>Graph: leiden.run() 层次聚类 + Graph->>Graph: CommunityReportsExtractor → LLM 报告 + end + Graph->>VDB: store graph entities / relations / reports + Graph-->>Celery: done diff --git a/docs/rag/overview/03-query-pipeline.mmd b/docs/rag/overview/03-query-pipeline.mmd new file mode 100644 index 00000000..ea8e4788 --- /dev/null +++ b/docs/rag/overview/03-query-pipeline.mmd @@ -0,0 +1,102 @@ +%% MemoryBear 在线检索时序图(Query Pipeline) +%% 起点:用户 Query;终点:LLM 生成的回答 + +sequenceDiagram + autonumber + participant User as 用户/API + participant WF as Workflow Engine
(workflow/nodes/knowledge/node.py) + participant Config as config.py
KnowledgeRetrievalNodeConfig + participant Retriever as nlp/search.py
knowledge_retrieval() + participant Dealer as nlp/search.py:349
Dealer.search() + participant Qryr as nlp/query.py
Query理解 + participant ESVec as ESVector
elasticsearch_vector.py + participant Graph as graphrag/search.py
KGSearch.retrieval() + participant Rerank as models/rerank.py
RedBearRerank + participant Prompt as prompts/generator.py + participant LLM as llm/chat_model.py
Base.chat() + participant Cache as utils/redis_conn.py + + Note over User,Cache: === 阶段 1:Query 准备 === + User->>WF: 用户输入 Query + WF->>WF: _render_template(query, variable_pool) + WF->>Config: 读取 knowledge_bases[]
reranker_id / retrieve_type + + Note over Retriever,ESVec: === 阶段 2:多知识库检索 === + loop 每个 Knowledge Base + WF->>Retriever: knowledge_retrieval(query, config) + Retriever->>DB: 验证 KB 状态 (chunk_num>0, status=1) + + alt RetrieveType == PARTICIPLE + Retriever->>ESVec: search_by_full_text(query, top_k) + ESVec->>ESVec: match on page_content (ik_max_word) + ESVec-->>Retriever: List[DocumentChunk] + + else RetrieveType == SEMANTIC + Retriever->>ESVec: search_by_vector(query, top_k) + ESVec->>ESVec: script_score cosineSimilarity + ESVec-->>Retriever: List[DocumentChunk] + + else RetrieveType == HYBRID + par + Retriever->>ESVec: search_by_vector() + ESVec-->>Retriever: rs1 + and + Retriever->>ESVec: search_by_full_text() + ESVec-->>Retriever: rs2 + end + Retriever->>Retriever: _deduplicate_docs(rs1, rs2) + Retriever->>Rerank: rerank(query, docs, top_k) + Rerank->>Rerank: similarity() 交叉编码评分 + Rerank-->>Retriever: sorted docs + + else RetrieveType == GRAPH + par + Retriever->>ESVec: search_by_vector() + ESVec-->>Retriever: rs1 + and + Retriever->>ESVec: search_by_full_text() + ESVec-->>Retriever: rs2 + end + Retriever->>Retriever: dedup + rerank + + Retriever->>Graph: kg_retriever.retrieval(question) + Graph->>Graph: query_rewrite() → keywords + entities + Graph->>ESVec: get_relevant_ents_by_keywords() + Graph->>ESVec: get_relevant_relations_by_txt() + Graph->>Graph: n_hop_with_weight 路径扩展 + Graph->>Graph: Score = pagerank * sim + Graph->>Graph: _community_retrieval_() + Graph-->>Retriever: Entity+Relation+CommunityReport chunk + Retriever->>Retriever: insert(0, graph_result) + end + + Retriever-->>WF: List[DocumentChunk] + end + + WF->>WF: _deduplicate_docs(all_results) + + alt reranker_id 配置 + WF->>Rerank: rerank(query, all_results, reranker_top_k) + Rerank-->>WF: reranked chunks + end + + Note over Prompt,Cache: === 阶段 3:Prompt 组装 + LLM 生成 === + WF->>WF: 返回 {"chunks": [...], "citations": [...]} + WF->>Prompt: citation_prompt(chunks) + Prompt->>Prompt: 组装 System Prompt + 检索上下文 + + Prompt->>Cache: get_llm_cache(model, prompt) + alt cache miss + Prompt->>LLM: chat(system, history, gen_conf) + LLM-->>Prompt: answer, tokens + Prompt->>Cache: set_llm_cache(model, prompt, answer) + else cache hit + Cache-->>Prompt: cached answer + end + + Note over User,Cache: === 阶段 4:后处理 === + Prompt->>Dealer: insert_citations(answer, chunks, chunk_v) + Dealer->>Dealer: pagerank*sim 定位引用位置 + Dealer-->>Prompt: answer_with_citations, cited_ids + + Prompt-->>User: 最终回答(含引用标注) diff --git a/docs/rag/overview/04-graphrag-indexing.mmd b/docs/rag/overview/04-graphrag-indexing.mmd new file mode 100644 index 00000000..8dd59dc8 --- /dev/null +++ b/docs/rag/overview/04-graphrag-indexing.mmd @@ -0,0 +1,78 @@ +%% MemoryBear GraphRAG 索引构建时序图 +%% 覆盖 Light 与 General 两条分支的差异 + +sequenceDiagram + autonumber + participant Celery as Celery
tasks.py:473 + participant Index as graphrag/general/index.py
run_graphrag_for_kb() + participant KGExt as GraphExtractor
light/graph_extractor.py:31
general/graph_extractor.py:34 + participant LLM as llm/chat_model.py + participant ES as ESVector
elasticsearch_vector.py + participant Merge as merge_subgraph() + participant Resolve as entity_resolution.py
EntityResolution + participant Leiden as general/leiden.py
run() + participant Community as general/
community_reports_extractor.py:37 + + Note over Celery,Community: === 触发条件 === + Celery->>Celery: build_graphrag_for_kb(kb_id) + Celery->>Celery: 检查 parser_config.graphrag.use_graphrag + Celery->>Index: run_graphrag_for_kb(row, document_ids, ...) + + Note over Index,LLM: === 阶段 1:子图生成 (按 chunk) === + Index->>Index: init_graphrag(task, vector_size) + Index->>Index: generate_subgraph() per chunk + + loop 每个 chunk + Index->>KGExt: _process_single_content(chunk_key_dp, chunk_text) + + alt Light 分支 + KGExt->>KGExt: LightRAG-style prompt
+ content_keywords 提取 + KGExt->>KGExt: GLEANING loop (max 2) + else General 分支 + KGExt->>KGExt: MS GraphRAG-style prompt
perform_variable_replacements + KGExt->>KGExt: tiktoken logit-bias Y/N loop + end + + KGExt->>LLM: LLM 调用 → entities + relations JSON + LLM-->>KGExt: extracted data + KGExt->>KGExt: _merge_nodes() + _merge_edges() + KGExt-->>Index: (entities_data, relationships_data) + end + + Index->>ES: store subgraph (entities + relations chunks) + + Note over Merge,ES: === 阶段 2:子图合并 === + Index->>Merge: merge_subgraph() + Merge->>ES: get_graph() 加载全局图 + Merge->>Merge: graph_merge(old_graph, subgraph, change) + Merge->>Merge: nx.pagerank(new_graph) + Merge->>ES: set_graph() 写回全局图 + entities + relations + + Note over Resolve,ES: === 阶段 3:实体消歧 (可选) === + opt with_resolution == True + Index->>Resolve: resolve_entities(graph, subgraph_nodes) + Resolve->>LLM: 两两实体相似度 LLM 匹配 + LLM-->>Resolve: 合并建议 + Resolve->>Resolve: nx.pagerank(graph) + Resolve->>ES: set_graph() + end + + Note over Leiden,Community: === 阶段 4:社区报告 (General only) === + opt with_community == True (General) + Index->>Leiden: leiden.run(graph) + Leiden->>Leiden: graspologic.partition.
hierarchical_leiden
max_cluster_size=12 + Leiden-->>Index: {level: {community_id: {nodes: [...]}}} + + loop 每个 community (nodes >= 2) + Index->>Community: __call__(graph, callback) + Community->>Community: 构建 entity_df + relation_df + Community->>LLM: COMMUNITY_REPORT_PROMPT + LLM-->>Community: {title, summary, findings, rating} + Community->>Community: add_community_info2graph() + end + + Community->>ES: index community_report chunks + end + + Note over Index,ES: === Mind Map (独立功能,非主链路) === + Note right of Index: mind_map_extractor.py
由外部调用,非索引管道
sections → 层级 markdown mind map diff --git a/docs/rag/overview/DocMap.md b/docs/rag/overview/DocMap.md new file mode 100644 index 00000000..e47c5883 --- /dev/null +++ b/docs/rag/overview/DocMap.md @@ -0,0 +1,194 @@ +# DocMap — MemoryBear RAG 文档目录大纲 + +> **定位**:Sprint-2 深度文档化的任务拆解输入。每行 = 一篇待写文档,标题格式与 [S1-T1] 统一模板兼容。 +> **责任人草拟**:基于当前 Sprint-1 分工建议,实际分配由项目经理确认。 +> **目录结构**:`docs/rag//.md` + +--- + +## 文档目录总览 + +``` +docs/rag/ +├── _meta/ # [S1-T1] 模板与评分卡(由 @知识运营与治理专家 维护) +├── 01-loader/ +│ ├── 01-web-crawler.md # Web 爬虫:URL 发现、内容提取、速率控制 +│ ├── 02-feishu-integration.md # 飞书集成:API 调用、鉴权、文档导出 +│ ├── 03-yuque-integration.md # 语雀集成:知识库同步、文档下载 +│ └── 04-file-upload.md # 文件上传与预处理(本地文件系统、NFS 兼容) +├── 02-parser/ +│ ├── 01-pdf-parser.md # PDF 解析:OCR + Layout + Table 流水线 +│ ├── 02-docx-parser.md # DOCX 解析:段落提取、图片嵌入 +│ ├── 03-html-md-parser.md # HTML / Markdown / TXT 解析 +│ ├── 04-excel-parser.md # Excel 解析:行列转表格结构 +│ └── 05-vision-pipeline.md # 视觉模块:OCR、布局识别、表格结构识别 +├── 03-chunking/ +│ ├── 01-chunking-strategies.md # 分块策略全景:naive_merge、层级分块、树分块 +│ ├── 02-task-type-adapters.md # 文档类型适配器:book / paper / laws / qa / one +│ ├── 03-tokenizer.md # RagTokenizer:中文分词、英文处理、fine_grained +│ └── 04-multimodal-chunking.md # 多模态分块:图片 VLM 描述、音频转文本 +├── 04-embedding/ +│ ├── 01-embedding-model-arch.md # Embedding 模型架构:Base 接口 + 10+ Provider +│ ├── 02-provider-guide.md # Provider 接入指南:OpenAI / HuggingFace / 国产模型 +│ └── 03-auto-questions.md # 自动问题生成:并发策略、LLM 缓存 +├── 05-vdb/ +│ ├── 01-elasticsearch-schema.md # ES 索引 Schema:字段定义、mapping、analyzer +│ ├── 02-hybrid-search.md # 混合检索:BM25 + Vector 加权融合 +│ └── 03-storage-connections.md # 存储连接层:ES、Redis、DocStore +├── 06-graphrag/ +│ ├── 01-graphrag-overview.md # GraphRAG 总览:Light vs General 对比 +│ ├── 02-entity-relation-extraction.md # 实体关系抽取:Extractor 流程、Prompt 工程 +│ ├── 03-graph-merge-and-rank.md # 图合并与 PageRank:子图合并、实体消歧 +│ ├── 04-community-reports.md # 社区报告:Leiden 聚类、LLM 报告生成(General only) +│ └── 05-knowledge-graph-search.md # KG 检索:Query 分析、实体匹配、N-hop 扩展 +├── 07-retrieval/ +│ ├── 01-retrieval-api.md # 检索 API:knowledge_retrieval()、Dealer.search() +│ ├── 02-query-understanding.md # Query 理解:关键词提取、同义词扩展 +│ └── 03-multi-kb-retrieval.md # 多知识库检索:结果合并、去重策略 +├── 08-reranking/ +│ ├── 01-rerank-architecture.md # 重排序架构:内置评分 vs 外部 Rerank 模型 +│ └── 02-rerank-providers.md # Rerank Provider:Jina / DashScope / Xinference +├── 09-prompt/ +│ ├── 01-prompt-system.md # Prompt 模板系统:template.py + generator.py +│ ├── 02-citation-prompts.md # 引用标注 Prompt:citation_prompt / citation_plus +│ └── 03-toc-prompts.md # 目录相关 Prompt:TOC 检测、提取、相关性 +├── 10-llm/ +│ ├── 01-llm-chat-model.md # Chat 模型架构:Base.chat() / chat_streamly() +│ ├── 02-llm-providers.md # Chat Provider 全景:OpenAI / Azure / 国产模型 +│ └── 03-vision-model.md # 视觉模型:VLM 描述、图片理解 +├── 11-e2e/ +│ ├── 01-indexing-pipeline.md # 端到端入库流程:Celery 任务链、错误处理、进度追踪 +│ ├── 02-query-pipeline.md # 端到端检索流程:Workflow Node → 检索 → 生成 +│ └── 03-answer-postprocess.md # 回答后处理:引用插入、缓存、流式输出 +└── 12-architecture-evolution/ + ├── 01-modularization-roadmap.md # 模块化拆分建议 + ├── 02-performance-optimization.md # 性能优化方向 + └── 03-future-extensions.md # 未来扩展:多模态检索、混合搜索、对话记忆 +``` + +--- + +## 文档详细定义 + +### 01-loader + +| 序号 | 标题 | 范围边界 | 关联源码模块 | 责任人草拟 | 备注 | +|------|------|----------|-------------|-----------|------| +| 01-01 | Web 爬虫 | **写**:URL 规范化、robots.txt 检查、速率限制、HTTP 抓取、内容提取、去重策略。**不写**:搜索引擎索引、分布式爬虫、JS 渲染。 | `crawler/web_crawler.py`, `crawler/http_fetcher.py`, `crawler/content_extractor.py`, `crawler/rate_limiter.py`, `crawler/robots_parser.py` | Python 工程师 | 需覆盖 CrawledDocument 数据结构 | +| 01-02 | 飞书集成 | **写**:App 鉴权、文件夹遍历、文档导出(PDF/DOCX/Sheet)、异步轮询下载。**不写**:飞书审批流、机器人消息推送。 | `integrations/feishu/client.py`, `integrations/feishu/retry.py`, `integrations/feishu/models.py` | Python 工程师 | 需说明 `_export_file` vs `_download_file` 区别 | +| 01-03 | 语雀集成 | **写**:个人 Token 鉴权、知识库遍历、文档详情获取、多种格式下载(MD/HTML/Excel)。**不写**:语雀协作编辑、版本管理。 | `integrations/yuque/client.py`, `integrations/yuque/retry.py`, `integrations/yuque/models.py` | Python 工程师 | lakesheet 解压逻辑需重点说明 | +| 01-04 | 文件上传 | **写**:文件上传接口、NFS 同步等待、binary 读取策略、进度追踪。**不写**:CDN 分发、大文件分片上传。 | `controllers/document_controller.py`, `utils/file_utils.py`, `tasks.py:213` | Python 工程师 | 30s NFS 等待逻辑是 MemoryBear 特有 | + +### 02-parser + +| 序号 | 标题 | 范围边界 | 关联源码模块 | 责任人草拟 | 备注 | +|------|------|----------|-------------|-----------|------| +| 02-01 | PDF 解析 | **写**:PDF 渲染、OCR 文本检测、布局分类、表格结构识别、文本合并策略。**不写**:PDF 生成/编辑、数字签名验证。 | `deepdoc/parser/pdf_parser.py`, `deepdoc/vision/ocr.py`, `deepdoc/vision/layout_recognizer.py`, `deepdoc/vision/table_structure_recognizer.py` | Python 工程师 | 核心中的核心,需重点投入 | +| 02-02 | DOCX 解析 | **写**:段落提取、图片提取、超链接提取、OLE 嵌入文件。**不写**:DOCX 生成、样式渲染。 | `deepdoc/parser/docx_parser.py`, `utils/file_utils.py:extract_embed_file` | Python 工程师 | 需与 `app/naive.py` 的 vision_figure_parser 联动说明 | +| 02-03 | HTML/MD/TXT 解析 | **写**:HTML 标签清洗、Markdown 结构化解析、纯文本处理。**不写**:CSS 样式解析、JS 执行。 | `deepdoc/parser/html_parser.py`, `deepdoc/parser/markdown_parser.py`, `deepdoc/parser/txt_parser.py` | Python 工程师 | 合并为一篇即可 | +| 02-04 | Excel 解析 | **写**:行列读取、Sheet 遍历、表头检测、Markdown 表格转换。**不写**:公式计算、图表提取。 | `deepdoc/parser/excel_parser.py` | Python 工程师 | 轻量 | +| 02-05 | 视觉流水线 | **写**:OCR 模型(ONNXRuntime)、布局识别模型、表格结构模型、图像预处理。**不写**:模型训练、模型量化。 | `deepdoc/vision/*.py` | Python 工程师 | 含模型加载、推理、后处理 | + +### 03-chunking + +| 序号 | 标题 | 范围边界 | 关联源码模块 | 责任人草拟 | 备注 | +|------|------|----------|-------------|-----------|------| +| 03-01 | 分块策略全景 | **写**:naive_merge、naive_merge_with_images、hierarchical_merge、tree_merge 的实现与选择策略。**不写**:通用 NLP 分词算法原理。 | `nlp/__init__.py:562+`, `nlp/rag_tokenizer.py` | Python 工程师 | 需附决策树:何时用哪种策略 | +| 03-02 | 文档类型适配器 | **写**:book/paper/manual/laws/qa/one/picture/audio 各自的分块逻辑、数据结构差异。**不写**:业务场景适配(如医疗/法律专有分块)。 | `app/naive.py:508`, `app/book.py`, `app/paper.py`, `app/manual.py`, `app/laws.py`, `app/qa.py`, `app/one.py`, `app/picture.py`, `app/audio.py` | Python 工程师 | 核心章节,需逐一说明 | +| 03-03 | RagTokenizer | **写**:中文分词(Huqie/datrie)、英文处理(nltk/Porter/WordNet)、fine_grained_tokenize、分词对检索的影响。**不写**:分词算法数学推导。 | `nlp/rag_tokenizer.py` | Python 工程师 | 与 ES ik_max_word 的对比 | +| 03-04 | 多模态分块 | **写**:图片 VLM 描述调用链、音频 sequence2txt 转录、视频处理(如有)。**不写**:VLM/ASR 模型内部原理。 | `app/picture.py`, `app/audio.py`, `llm/cv_model.py`, `llm/sequence2txt_model.py`, `deepdoc/parser/figure_parser.py` | Python 工程师 | 需说明 vision_model 注入机制 | + +### 04-embedding + +| 序号 | 标题 | 范围边界 | 关联源码模块 | 责任人草拟 | 备注 | +|------|------|----------|-------------|-----------|------| +| 04-01 | Embedding 模型架构 | **写**:Base.encode() 接口、批次处理、Token 截断(8000/2048)、返回格式。**不写**:Embedding 模型原理(Word2Vec/BERT 等)。 | `llm/embedding_model.py` | Python 工程师 | 重点讲接口契约 | +| 04-02 | Provider 接入指南 | **写**:10+ Provider 的配置方式、API Key 管理、Base URL 设置、批次大小差异。**不写**:各厂商 API 的通用文档。 | `llm/embedding_model.py` 各子类 | Python 工程师 | 表格形式列出即可 | +| 04-03 | 自动问题生成 | **写**:并发生成策略(ThreadPoolExecutor)、LLM 缓存机制(redis)、问题注入到 chunk metadata。**不写**:问题生成质量评估。 | `tasks.py:323+`, `prompts/generator.py:question_proposal()` | Python 工程师 | 与检索效果的关系 | + +### 05-vdb + +| 序号 | 标题 | 范围边界 | 关联源码模块 | 责任人草拟 | 备注 | +|------|------|----------|-------------|-----------|------| +| 05-01 | ES 索引 Schema | **写**:字段定义、mapping 类型、ik_max_word analyzer、dense_vector cosine 配置、动态维度。**不写**:ES 集群运维、分片策略。 | `vdb/field.py`, `vdb/elasticsearch/elasticsearch_vector.py:653+` | Python 工程师 | 需附完整 mapping 示例 | +| 05-02 | 混合检索 | **写**:BM25 + Vector 加权融合(0.05:0.95)、FusionExpr、score 归一化、降级策略。**不写**:BM25 算法数学推导、近似最近邻算法。 | `nlp/search.py:439`, `vdb/elasticsearch/elasticsearch_vector.py:374`, `utils/doc_store_conn.py:FusionExpr` | Python 工程师 | 核心章节,需讲清楚为什么权重是 0.05:0.95 | +| 05-03 | 存储连接层 | **写**:ES 连接、Redis 缓存、DocStore 抽象。**不写**:连接池调优、网络安全配置。 | `utils/es_conn.py`, `utils/redis_conn.py`, `utils/doc_store_conn.py` | Python 工程师 | 轻量 | + +### 06-graphrag + +| 序号 | 标题 | 范围边界 | 关联源码模块 | 责任人草拟 | 备注 | +|------|------|----------|-------------|-----------|------| +| 06-01 | GraphRAG 总览 | **写**:Light vs General 架构对比、适用场景、配置开关(use_graphrag/resolution/community)。**不写**:图数据库选型对比(已选 ES)。 | `graphrag/light/`, `graphrag/general/`, `graphrag/search.py` | Python 工程师 | 必须包含对比表格 | +| 06-02 | 实体关系抽取 | **写**:Extractor 基类、_process_single_content 流程、Gleaning Loop、Prompt 工程、LLM 输出解析。**不写**:信息抽取的通用 NLP 方法。 | `graphrag/light/graph_extractor.py`, `graphrag/general/graph_extractor.py`, `graphrag/general/extractor.py` | Python 工程师 | 核心章节 | +| 06-03 | 图合并与 PageRank | **写**:merge_subgraph 流程、nx.Graph 操作、PageRank 计算、实体消歧(EntityResolution)。**不写**:PageRank 数学推导。 | `graphrag/general/index.py`, `graphrag/entity_resolution.py` | Python 工程师 | 需附图数据结构示例 | +| 06-04 | 社区报告 | **写**:Leiden 层次聚类、社区报告 Prompt、报告数据结构、存储方式。**不写**:社区发现算法数学原理。 | `graphrag/general/leiden.py`, `graphrag/general/community_reports_extractor.py`, `graphrag/general/community_report_prompt.py` | Python 工程师 | General only | +| 06-05 | KG 检索 | **写**:KGSearch.retrieval() 流程、Query Rewrite、实体匹配、N-hop 扩展、社区报告检索。**不写**:图遍历算法通用理论。 | `graphrag/search.py:130` | Python 工程师 | 与标准检索的交互关系 | + +### 07-retrieval + +| 序号 | 标题 | 范围边界 | 关联源码模块 | 责任人草拟 | 备注 | +|------|------|----------|-------------|-----------|------| +| 07-01 | 检索 API | **写**:knowledge_retrieval() 接口、Dealer.search() 内部实现、MatchDenseExpr / MatchTextExpr / FusionExpr。**不写**:信息检索通用理论。 | `nlp/search.py:36`, `nlp/search.py:349`, `utils/doc_store_conn.py` | Python 工程师 | 核心章节 | +| 07-02 | Query 理解 | **写**:关键词提取、同义词扩展、查询改写、min_match 阈值调整。**不写**:NLP 句法分析。 | `nlp/query.py`, `nlp/synonym.py`, `nlp/term_weight.py` | Python 工程师 | 轻量 | +| 07-03 | 多知识库检索 | **写**:Folder 类型递归检索、跨 KB 结果去重、权限过滤。**不写**:权限系统的 RBAC 设计。 | `workflow/nodes/knowledge/node.py:195`, `knowledge_repository.py` | Python 工程师 | 需说明 Folder 类型的特殊处理 | + +### 08-reranking + +| 序号 | 标题 | 范围边界 | 关联源码模块 | 责任人草拟 | 备注 | +|------|------|----------|-------------|-----------|------| +| 08-01 | 重排序架构 | **写**:内置重排(token+vector 相似度融合)vs 外部 Rerank 模型、调用时机、容错降级。**不写**:Learning-to-Rank 通用理论。 | `nlp/search.py:606`, `models/rerank.py` | Python 工程师 | 需对比两种方式的适用场景 | +| 08-02 | Rerank Provider | **写**:JinaRerank、DashScopeRerank 的 API 调用、参数映射。**不写**:各厂商 API 通用文档。 | `models/rerank.py:57+` | Python 工程师 | 轻量 | + +### 09-prompt + +| 序号 | 标题 | 范围边界 | 关联源码模块 | 责任人草拟 | 备注 | +|------|------|----------|-------------|-----------|------| +| 09-01 | Prompt 模板系统 | **写**:template.py 的 .md 文件加载机制、generator.py 的函数式 Prompt 组装、参数替换。**不写**:Prompt Engineering 通用方法论。 | `prompts/template.py`, `prompts/generator.py` | Python 工程师 | 需列出全部模板清单 | +| 09-02 | 引用标注 Prompt | **写**:citation_prompt / citation_plus 的输入输出、引用格式、上下文窗口管理。**不写**:学术论文引用规范。 | `prompts/generator.py:citation_prompt()` | Python 工程师 | 与 insert_citations 联动 | +| 09-03 | 目录相关 Prompt | **写**:TOC 检测、提取、层级分配、基于 TOC 的 chunk 相关性筛选。**不写**:目录生成算法。 | `prompts/generator.py` TOC 系列函数 | Python 工程师 | 轻量 | + +### 10-llm + +| 序号 | 标题 | 范围边界 | 关联源码模块 | 责任人草拟 | 备注 | +|------|------|----------|-------------|-----------|------| +| 10-01 | Chat 模型架构 | **写**:Base.chat() / chat_streamly() / chat_with_tools() 接口、返回格式、流式输出。**不写**:Transformer 模型原理。 | `llm/chat_model.py` | Python 工程师 | 重点讲接口契约 | +| 10-02 | Chat Provider 全景 | **写**:各 Provider 配置、温度/TopP/MaxTokens 参数透传、错误处理。**不写**:各厂商 API 通用文档。 | `llm/chat_model.py` 各子类 | Python 工程师 | 表格形式 | +| 10-03 | 视觉模型 | **写**:CV 模型接口、VLM 描述调用、图片理解。**不写**:CNN/ViT 原理。 | `llm/cv_model.py` | Python 工程师 | 轻量 | + +### 11-e2e + +| 序号 | 标题 | 范围边界 | 关联源码模块 | 责任人草拟 | 备注 | +|------|------|----------|-------------|-----------|------| +| 11-01 | 端到端入库流程 | **写**:Celery 任务链、parse_document 完整流程、进度追踪、错误处理、GraphRAG 异步触发。**不写**:Celery 分布式队列原理。 | `tasks.py` | Python 工程师 | 核心章节,需附时序图 | +| 11-02 | 端到端检索流程 | **写**:Workflow Knowledge Node 完整流程、检索模式选择、结果组装。**不写**:Workflow Engine 通用设计。 | `workflow/nodes/knowledge/node.py` | Python 工程师 | 核心章节 | +| 11-03 | 回答后处理 | **写**:引用插入、缓存策略、流式输出处理。**不写**:WebSocket 通用原理。 | `nlp/search.py:489`, `utils/redis_conn.py` | Python 工程师 | 轻量 | + +### 12-architecture-evolution + +| 序号 | 标题 | 范围边界 | 关联源码模块 | 责任人草拟 | 备注 | +|------|------|----------|-------------|-----------|------| +| 12-01 | 模块化拆分建议 | **写**:当前耦合点识别、建议的接口抽象(如 ParserInterface、ChunkerInterface)、拆分优先级。**不写**:微服务拆分方案。 | 全局代码分析 | AI 知识库专家 | 架构建议,无代码 | +| 12-02 | 性能优化方向 | **写**:Embedding 批处理优化、ES 查询优化、GraphRAG 并发优化、缓存命中率提升。**不写**:通用性能优化方法论。 | 全局代码分析 | AI 知识库专家 | 需量化当前瓶颈假设 | +| 12-03 | 未来扩展 | **写**:多模态检索、混合搜索增强、对话记忆优化、知识图谱演进方向。**不写**:产品需求文档。 | 全局代码分析 | AI 知识库专家 | 架构建议,无代码 | + +--- + +## 工作量估算 + +| 阶段 | 文档数 | 预估 Sprint-2 人天(每篇 0.5~1d) | +|------|--------|----------------------------------| +| 01-loader | 4 | 2d | +| 02-parser | 5 | 3d | +| 03-chunking | 4 | 2.5d | +| 04-embedding | 3 | 1.5d | +| 05-vdb | 3 | 2d | +| 06-graphrag | 5 | 3d | +| 07-retrieval | 3 | 2d | +| 08-reranking | 2 | 1d | +| 09-prompt | 3 | 1.5d | +| 10-llm | 3 | 1.5d | +| 11-e2e | 3 | 2d | +| 12-architecture-evolution | 3 | 1.5d | +| **合计** | **41** | **~23.5d** | + +> 注:Python 工程师承担约 30 篇(技术实现细节),AI 知识库专家承担约 8 篇(架构/优化/扩展方向)。具体分配由项目经理确认。 diff --git a/docs/rag/overview/boundaries.md b/docs/rag/overview/boundaries.md new file mode 100644 index 00000000..da356b02 --- /dev/null +++ b/docs/rag/overview/boundaries.md @@ -0,0 +1,193 @@ +# RAG 环节边界定义 + +> 目标:明确每个 RAG 阶段的输入 / 输出 / 上下游接口(数据结构层面),避免 Sprint-2 各文档之间留白或重叠。 + +--- + +## 总览图 + +``` +[Data Sources] ──→ [Loader] ──→ [Parser] ──→ [Chunking] ──→ [Embedding] ──→ [VDB] + ↑ + │ (async) + [GraphRAG] + +[User Query] ──→ [Query Understanding] ──→ [Retrieval] ──→ [Reranking] ──→ [Prompt] ──→ [LLM] ──→ [Post-Process] ──→ [Answer] + ↑ + │ (GRAPH mode) + [KG Search] +``` + +--- + +## 1. Loader(数据加载层) + +| 维度 | 定义 | +|------|------| +| **上游** | 外部系统:飞书 API、语雀 API、Web URL、用户上传接口 | +| **输入** | 飞书:folder_token, app_id, app_secret;语雀:user_id, token;Web:entry_url, max_pages;上传:multipart/form-data | +| **输出** | **原始文件内容**:`CrawledDocument` (dataclass) 或 **本地文件路径** (.docx/.pdf/.md/.html/.xlsx) | +| **输出数据结构** | `CrawledDocument(url, title, content, content_length, crawl_timestamp, metadata)`;本地文件:`str` (path) | +| **下游** | Parser:接收文件路径或 bytes,调用对应 format-specific parser | +| **边界约定** | Loader 不做任何格式解析(不提取正文、不做 OCR)。仅负责:鉴权 → 获取/下载 → 存盘。格式识别由 Parser 层的 `naive.chunk()` 根据文件扩展名决定。 | + +--- + +## 2. Parser(文档解析层) + +| 维度 | 定义 | +|------|------| +| **上游** | Loader:接收文件路径 `str` 或二进制 `bytes` | +| **输入** | `(filename: str, binary: bytes \| None, from_page, to_page, callback, vision_model)` | +| **输出** | `sections: List[Tuple[str, str]]` — (text_content, layout_tag);`tables: List[Tuple[Tuple[Optional[Image.Image], Union[str, List[str]]], List[Tuple]]]` | +| **输出数据结构** | 元组列表,其中 tag 表示布局类型("Title"/"Text"/"Table"/...),text 可能含位置标签 `@@page\tx0\tx1\ttop\tbottom##` | +| **下游** | Chunking:接收 `sections` + `tables`,执行合并与分块 | +| **边界约定** | Parser 负责格式-specific 的**纯提取**,不负责语义分块。PDF Parser 特殊:需输出 OCR 结果 + 布局信息 + 表格 HTML。Parser 之间互不调用——由 `naive.chunk()` 统一 dispatch。 | + +--- + +## 3. Chunking(文本分块层) + +| 维度 | 定义 | +|------|------| +| **上游** | Parser:`sections` + `tables` | +| **输入** | `sections: List[Tuple[str, str]]`, `tables`, `chunk_token_num: int`, `delimiter: str`, `parser_config: dict` | +| **输出** | `res: List[Dict]` — 分块后的文档字典列表 | +| **输出数据结构(关键字段)** | `content_with_weight: str`(原始文本), `content_ltks: str`(粗粒度分词), `content_sm_ltks: str`(细粒度分词), `image: PIL.Image`(可选), `page_num_int: int`, `position_int: List[int]`, `top_int: int`, `doc_type_kwd: str` | +| **下游** | Embedding:接收 `res`,提取 `content_with_weight` 进行向量化;GraphRAG:接收 `res` 中的文本进行实体关系抽取 | +| **边界约定** | Chunking 不调用 Embedding,也不直接写入 VDB。它只负责将长文本切分成符合 token 预算的 chunks,并填充分词/位置元数据。多模态(图片/音频)的分块结果也统一为此数据结构。 | + +--- + +## 4. Embedding(向量化层) + +| 维度 | 定义 | +|------|------| +| **上游** | Chunking:接收 chunk dicts 的 `content_with_weight` | +| **输入** | `texts: List[str]`(batch,默认 ≤16 条) | +| **输出** | `(np.array, total_tokens)` — `np.array` shape `(batch_size, vector_dimension)` | +| **输出数据结构** | NumPy ndarray,float32;向量维度由模型决定(如 OpenAI text-embedding-3: 1536d) | +| **下游** | VDB:接收 `(chunk_text, vector, metadata)` 组装成 `DocumentChunk` 后入库 | +| **边界约定** | Embedding 层无状态,不管理模型生命周期。Provider 通过工厂模式实例化(`Base._FACTORY_NAME` 匹配)。输入文本超长时自动截断(OpenAI 截到 8000 tokens,QWen 截到 2048)。支持 `encode_queries()` 单条 query 编码。 | + +--- + +## 5. VDB(向量数据库层) + +| 维度 | 定义 | +|------|------| +| **上游** | Embedding:接收 `(text, vector, metadata)`;Chunking:接收 chunk dicts 中的 metadata | +| **输入** | `DocumentChunk(page_content: str, vector: List[float], metadata: dict)`;或检索时:`query: str, top_k: int, indices: str, score_threshold: float` | +| **输出(入库)** | ack / error;**输出(检索)**:`List[DocumentChunk]` | +| **存储 Schema** | `page_content: text(ik_max_word)`, `metadata: object(doc_id, document_id, knowledge_id, sort_id, status)`, `vector: dense_vector(cosine, dynamic_dims)` | +| **下游** | Retrieval:通过 `search_by_vector` / `search_by_full_text` / `search` (hybrid) 获取结果 | +| **边界约定** | VDB 同时承担**文档存储**(全文索引)和**向量存储**(密集向量索引)双重职责。ES 是唯一的后端(无 Milvus/Pinecone 等)。GraphRAG 的实体/关系/社区报告也以相同 chunk 格式存储于此。 | + +--- + +## 6. GraphRAG(知识图谱层) + +| 维度 | 定义 | +|------|------| +| **上游** | Chunking:接收 chunk dicts 的 `content_with_weight`;Celery:异步触发 `build_graphrag_for_document` | +| **输入(索引)** | `(document_id, chunk_text)` tuples;`chat_model: Base`, `embedding_model: OpenAIEmbed`, `vector_service: ElasticSearchVector` | +| **输入(检索)** | `question: str, workspace_ids: List[str], kb_ids: List[str], emb_mdl, llm` | +| **输出(索引)** | `nx.Graph`(全局图)存储到 ES;`entity` chunks + `relation` chunks + `community_report` chunks(General only) | +| **输出(检索)** | `Dict` with `page_content` = "Entities CSV + Relations CSV + Community Reports",`metadata` 含引用信息 | +| **下游** | VDB:索引/存储实体、关系、社区报告 chunks;Retrieval:`KGSearch.retrieval()` 返回的 chunk 被 `insert(0, ...)` 插入标准检索结果 | +| **边界约定** | GraphRAG 是**独立异步流程**,不与标准 RAG 索引同步。Light 和 General 共享相同的存储格式但 General 多出 community_report。GraphRAG 不替代 VDB,而是**在 VDB 之上增加图语义层**。检索时 KG 结果优先级最高(insert at position 0)。 | + +--- + +## 7. Retrieval(检索层) + +| 维度 | 定义 | +|------|------| +| **上游** | VDB:通过 `search_by_vector` / `search_by_full_text` 获取候选;GraphRAG:`KGSearch.retrieval()` 获取图语义结果;Workflow Node:`KnowledgeRetrievalNode.execute()` 发起调用 | +| **输入** | `query: str, config: Dict(knowledge_bases[], merge_strategy, reranker_id, reranker_top_k, use_graph)` | +| **输出** | `List[DocumentChunk]` — 按相关性降序排列的文档块 | +| **输出数据结构** | `DocumentChunk(page_content: str, metadata: dict)`,其中 metadata 含 `score`, `doc_id`, `document_id`, `knowledge_id`, `highlight` | +| **下游** | Reranking:接收候选列表,可选执行重排序;Prompt:接收 chunks 组装上下文 | +| **边界约定** | Retrieval 层支持 4 种模式:PARTICIPLE(全文)、SEMANTIC(向量)、HYBRID(混合)、GRAPH(图增强)。多 KB 时逐 KB 检索后合并。HYBRID 的默认权重为 BM25 0.05 + Vector 0.95。检索失败(空结果)时自动降级(min_match 0.1 + similarity 0.17 重试)。 | + +--- + +## 8. Reranking(重排序层) + +| 维度 | 定义 | +|------|------| +| **上游** | Retrieval:接收候选 `List[DocumentChunk]` | +| **输入** | `query: str, docs: List[DocumentChunk], top_k: int`;或 `reranker_id: UUID` | +| **输出** | `List[DocumentChunk]` — 重排序后的文档块(长度 ≤ top_k) | +| **输出数据结构** | 同 Retrieval 输出,metadata 中更新 `score` 为重排序后的分数 | +| **下游** | Prompt:接收重排序后的 chunks 组装上下文 | +| **边界约定** | Reranking 是**可选层**。未配置 reranker_id 时,HYBRID 结果按 metadata.score 降序截断。配置了 reranker_id 时,调用外部 Rerank API(Jina / DashScope / Xinference)。Rerank 失败时**降级**到原始结果(不阻断流程)。 | + +--- + +## 9. Prompt(Prompt 组装层) + +| 维度 | 定义 | +|------|------| +| **上游** | Reranking:接收排序后的 chunks;Workflow:接收用户 query | +| **输入** | `chunks: List[DocumentChunk], query: str, system_prompt: str`(可选) | +| **输出** | `system: str, history: List[Dict]` — LLM 可调用的消息格式 | +| **输出数据结构** | `system: str`(含检索上下文 + 系统指令),`history: [{"role": "user", "content": query}]` | +| **下游** | LLM:`Base.chat(system, history, gen_conf)` | +| **边界约定** | Prompt 层**不**调用 LLM,只负责**文本组装**。组装逻辑包括:citation_prompt(引用标注格式)、keyword_extraction(用于缓存 key)、content_tagging(内容分类)。Prompt 模板以 `.md` 文件形式存储在 `prompts/` 目录,通过 `template.py` 动态加载。 | + +--- + +## 10. LLM(大模型生成层) + +| 维度 | 定义 | +|------|------| +| **上游** | Prompt:接收 `system` + `history` | +| **输入** | `system: str, history: List[Dict], gen_conf: dict(temperature, top_p, max_tokens)` | +| **输出** | `(answer: str, tokens: int)` 或流式 `Generator[str \| int]` | +| **输出数据结构** | 字符串(生成的回答文本);流式模式下逐 token 返回 | +| **下游** | Post-Process:`insert_citations()` 插入引用标注 | +| **边界约定** | LLM 层**无上下文记忆**(stateless),每次调用携带完整 history。支持 10+ Provider,通过 `_FACTORY_NAME` 工厂模式匹配。流式输出通过 `chat_streamly()` 实现,返回 Generator。错误处理:API 异常时抛出,由上层(Workflow / Celery)捕获。 | + +--- + +## 11. Post-Process(后处理层) + +| 维度 | 定义 | +|------|------| +| **上游** | LLM:接收生成的 `answer`;Retrieval:接收原始 `chunks` + `chunk_v`(向量) | +| **输入** | `answer: str, chunks: List[DocumentChunk], chunk_v: List[np.array], embd_mdl, tkweight, vtweight` | +| **输出** | `(answer_with_citations: str, cited_ids: Set[str])` | +| **输出数据结构** | 字符串(含 `[1]`, `[2]` 等引用标记),`Set[str]`(被引用的 chunk id 集合) | +| **下游** | User:最终展示;Cache:写入 Redis 缓存 | +| **边界约定** | Post-Process 只做**引用标注插入**(`insert_citations()`),不做内容修改。引用定位算法基于 `pagerank * similarity` 评分。代码块(```...```)内**不**插入引用。缓存键由 `(model_name, prompt_text)` 组合生成,TTL 由 Redis 配置决定。 | + +--- + +## 跨层数据流总表 + +| 阶段 | 输入数据类型 | 输出数据类型 | 关键数据结构 / 文件 | +|------|-------------|-------------|---------------------| +| Loader | URL / Token / File | `CrawledDocument` / `str` (path) | `crawler/models.py`, `integrations/*/models.py` | +| Parser | `str` (path) / `bytes` | `List[Tuple[str, str]]` + tables | `deepdoc/parser/*.py` | +| Chunking | sections + tables | `List[Dict]` | `nlp/__init__.py`, `app/naive.py` | +| Embedding | `List[str]` | `(np.array, int)` | `llm/embedding_model.py` | +| VDB | `DocumentChunk` | ack / `List[DocumentChunk]` | `vdb/field.py`, `models/chunk.py` | +| GraphRAG | chunk texts | `nx.Graph` + chunks | `graphrag/search.py`, `graphrag/general/index.py` | +| Retrieval | `query + config` | `List[DocumentChunk]` | `nlp/search.py` | +| Reranking | `query + docs` | `List[DocumentChunk]` | `models/rerank.py` | +| Prompt | `chunks + query` | `system + history` | `prompts/generator.py` | +| LLM | `system + history` | `str + int` | `llm/chat_model.py` | +| Post-Process | `answer + chunks` | `str + Set[str]` | `nlp/search.py:489` | + +--- + +## 留白与重叠风险点 + +| 风险区域 | 说明 | 建议归属 | +|----------|------|----------| +| **Parser ↔ Chunking 边界** | Parser 输出的 `sections` 格式(含 tag 和位置信息)被 Chunking 的 `naive_merge` 直接消费。若 Parser 改了 tag 格式,Chunking 会受影响。 | **统一在 Parser 文档中定义 `sections` 数据契约**,Chunking 文档只引用该契约。 | +| **Embedding ↔ VDB 边界** | Embedding 输出维度必须与 VDB mapping 中 `dense_vector` 的 dims 一致。动态维度由首次 encode 决定。 | **Embedding 文档声明维度获取方式**,VDB 文档只引用。 | +| **GraphRAG ↔ VDB 边界** | GraphRAG 的实体/关系/社区报告以 `DocumentChunk` 格式存入 VDB,与标准 chunk 共用同一 ES index。 | **VDB 文档定义通用存储格式**,GraphRAG 文档只说明使用了该格式。 | +| **Retrieval ↔ Reranking 边界** | Retrieval 的 HYBRID 模式在 Node 层已做 dedup,但 `knowledge_retrieval()` 函数也有独立 rerank 调用。 | **Reranking 文档**说明两种调用路径(Node 层 vs 函数层)的区别。 | +| **Prompt ↔ LLM 边界** | Prompt 组装的 `history` 格式必须与各 Provider 的 API 格式兼容。 | **Prompt 文档**声明输出格式规范,LLM 文档说明各 Provider 的适配。 | diff --git a/docs/rag/overview/source-inventory.md b/docs/rag/overview/source-inventory.md new file mode 100644 index 00000000..fe7a3613 --- /dev/null +++ b/docs/rag/overview/source-inventory.md @@ -0,0 +1,249 @@ +# [S1-T3] MemoryBear RAG 源码盘点与模块依赖关系图谱 — 交付物 + +## 一、模块清单 + +> 统计口径:`api/app/core/rag/` 全部子目录 + `api/app/core/workflow/nodes/knowledge` + `api/app/core/rag_utils/` 共 **~24,900+ LOC** Python 代码。 + +| 子模块路径 | 主要职责 | 入口文件 / 关键类 / 关键函数 | 对外接口(被谁调用 / 调用谁) | 第三方依赖 | 文件数 / 行数 | +|---|---|---|---|---|---| +| `rag/app` | 文档解析与分块 orchestrator;按 doc_type 路由到不同解析策略(naive / book / paper / qa / audio / picture / manual / laws / mail / one) | `naive.py:508 chunk()`、`naive.py:257 naive.__call__()`、`naive.py:27 by_deepdoc()`、`naive.py:45 by_mineru()`、`naive.py:65 by_textln()` | 被 `tasks.py` 调用(Celery ingestion);调用 `deepdoc/parser` + `deepdoc/vision` + `rag/nlp` + `rag/llm/cv_model` + `rag/llm/sequence2txt_model` | `python-docx`, `openpyxl`, `pdfplumber`, `markdown`, `Pillow` | 12 / 2,923 | +| `rag/common` | RAG 共享常量、异常、装饰器、工具函数(文件/浮点/日志/字符串/Token 计数) | `constants.py`(常量定义)、`token_utils.py`(encoder)、`settings.py:13 init_settings()`(单例初始化) | 被 `rag/utils/es_conn.py`、`rag/graphrag/utils.py`、`rag/nlp/search.py` 等广泛 import | `tiktoken`(tokenizer) | 12 / 602 | +| `rag/crawler` | Web 页面抓取与内容提取 | `web_crawler.py`、`content_extractor.py`、`http_fetcher.py` | 被 `tasks.py` 调用;由 knowledge sync 触发 | `requests` | 9 / 1,237 | +| `rag/deepdoc/parser` | 11 种格式文档解析(PDF/Word/Excel/HTML/MD/JSON/TXT/PPT) | `pdf_parser.py:34 RAGPdfParser.__call__:1124`、`docx_parser.py:9 RAGDocxParser`、mineru_parser.py:41 MinerUParser` | 被 `rag/app/naive.py` import 并调用 | `pdfplumber`, `pypdf`, `python-docx`, `openpyxl`, `beautifulsoup4`, `markdown`, `pandas` | 12 / 3,228 | +| `rag/deepdoc/vision` | 文档视觉分析:布局识别 + OCR + 表格结构识别 | `ocr.py:522 OCR.__call__:694`、`layout_recognizer.py:17 LayoutRecognizer`、`table_structure_recognizer.py:15 TableStructureRecognizer` | 被 `pdf_parser.py` 调用进行版面/表格/图像识别 | `onnxruntime`, `huggingface_hub`, `Pillow`, `opencv-python`, `numpy` | 10 / 3,657 | +| `rag/graphrag`(顶层) | GraphRAG 共享工具、实体消歧、查询分析提示、知识图谱搜索 | `search.py:19 KGSearch(Dealer)`、`entity_resolution.py:31 EntityResolution`、`utils.py`(graph merge/persist/LLM cache) | 被 `tasks.py`、workflow knowledge node、prompts/generator.py 调用 | `networkx`, `pandas`, `trio`, `redis`, `xxhash`, `json_repair` | 6 / 1,452 | +| `rag/graphrag/general` | 通用/完整版 GraphRAG 流水线:子图抽取 → 合并 → 实体消歧 → Leiden 社区 → 社区报告 | `index.py:36 run_graphrag()`、`index.py:122 run_graphrag_for_kb()`、`graph_extractor.py:34 GraphExtractor`、`community_reports_extractor.py:37` | 被 `tasks.py` 的 Celery task 调用;调用 `ElasticSearchVector` 写图数据 | `networkx`, `graspologic`, `tiktoken`, `trio` | 11 / 1,857 | +| `rag/graphrag/light` | 轻量版 GraphRAG(LightRAG 风格):简化实体/关系抽取,无社区报告 | `light/graph_extractor.py:31 GraphExtractor` | 被 `general/index.py` 根据 `parser_config.graphrag.method` 条件切换调用 | `networkx`, `trio` | 3 / 462 | +| `rag/integrations/feishu` | 飞书文档同步客户端 | `client.py: FeishuAPIClient` | 被 `knowledge_controller.py` + `tasks.py` 调用 | `requests` | 6 / 737 | +| `rag/integrations/yuque` | 语雀文档同步客户端 | `client.py: YuqueAPIClient` | 被 `knowledge_controller.py` + `tasks.py` 调用 | `requests` | 6 / 844 | +| `rag/llm` | LLM 多模型统一 facade(Chat / Embedding / CV / Seq2txt) | `chat_model.py:52 Base`、`embedding_model.py:14 Base`、`cv_model.py:19 Base`、`sequence2txt_model.py:15 Base` | 被 `rag/app`、`rag/nlp/search`、`rag/graphrag`、`rag/vdb`、`workflow/nodes/knowledge` 等调用 | `openai`, `dashscope`, `azure-openai`, `ollama`, `zhipuai`, `requests` | 5 / 1,676 | +| `rag/models` | Chunk 数据模型 | `chunk.py:17 DocumentChunk`、`chunk.py:5 ChildDocumentChunk` | 被 `rag/vdb`、`rag/app`、`workflow/nodes/knowledge`、`tasks.py` 引用 | `pydantic` | 2 / 72 | +| `rag/nlp` | NLP 工具箱:中文分词、BM25/hybrid 搜索调度、同义词扩展、术语权重、Query 重写 | `search.py:349 Dealer`(含 `retrieval:674`、`search:387`、`rerank:606`)、`rag_tokenizer.py:15 RagTokenizer`、`query.py:10 FulltextQueryer` | 被 `rag/app/naive.py`、`rag/graphrag`、`rag/prompts/generator.py`、`rag/common/settings.py` 调用 | `datrie`, `hanziconv`, `nltk`, `pandas`, `numpy` | 7 / 2,962 | +| `rag/prompts` | Prompt 模板加载与 LLM prompt 工厂 | `template.py:9 load_prompt()`、`generator.py`(citation/keyword/question/toc/reflect 等 20+ 函数) | 被 `tasks.py`、`rag/nlp/search.py`、`rag/graphrag` 调用;依赖 `.md` prompt 文件 | `jinja2`, `json_repair` | 3 / 769 + 31 md 文件 | +| `rag/utils` | ES 连接、Redis 连接、LibreOffice 转换、文件工具 | `es_conn.py: ESConnection`、`redis_conn.py`、`libre_office.py`、`file_utils.py`、`doc_store_conn.py` | 被 `rag/vdb`、`rag/common/settings.py`、`rag/app/naive.py`、`rag/nlp/search.py` 调用 | `elasticsearch`, `redis` | 6 / 1,578 | +| `rag/vdb` | 向量数据库抽象 + Elasticsearch 实现 | `elasticsearch/elasticsearch_vector.py:29 ElasticSearchVector`、`elasticsearch/elasticsearch_vector.py:666 ElasticSearchVectorFactory`、`vector_base.py:9 BaseVector` | 被 `tasks.py`、`knowledge_controller.py`、`chunk_controller.py`、`workflow/nodes/knowledge` 调用 | `elasticsearch`, `langchain-core` | 3 / 83 + 2 / 753 | +| `rag/res` | 静态资源:NER 词表、同义词表、映射表 | `ner.json`、`synonym.json`、`mapping.json` | 被 `rag/nlp/term_weight.py`、`rag/nlp/synonym.py` 加载 | — | 3 JSON | +| `workflow/nodes/knowledge` | Workflow 知识检索节点:多知识库检索 + 重排序 + GraphRAG 增强 | `node.py:29 KnowledgeRetrievalNode`、`node.py:303 execute()`、`node.py:195 knowledge_retrieval()` | 被 `workflow/nodes/node_factory.py`、`workflow/nodes/__init__.py` 注册;调用 `rag/vdb`、`rag/llm`、`rag/models` | `langchain-core` | 3 / 455 | +| `rag_utils`(⚠️ 与 `rag/utils` 不同) | Chunk 内容 LLM 分析:摘要生成、标签提取、洞察分析、人物画像 | `chunk_summary.py:68 generate_chunk_summary()`、`chunk_tags.py:56 extract_chunk_tags()`、`chunk_insight.py:137 generate_chunk_insight()` | 被 `services/memory_dashboard_service.py` 调用;依赖 `app.core.memory.*` LLM 工厂 | `pydantic` | 4 / 588 | + +--- + +## 二、依赖关系图谱(Mermaid) + +```mermaid +graph TB + subgraph "上层调用者" + A1[tasks.py
Celery Workers] + A2[controllers/
REST API] + A3[workflow/nodes/
知识检索节点] + A4[services/memory_
dashboard_service.py] + end + + subgraph "RAG Core" + B1[rag/app
解析与分块] + B2[rag/deepdoc/parser
格式解析] + B3[rag/deepdoc/vision
版面/OCR] + B4[rag/crawler
网页抓取] + B5[rag/integrations
飞书/语雀] + B6[rag/nlp
分词/搜索调度] + B7[rag/llm
多模型Facade] + B8[rag/vdb
ES向量存储] + B9[rag/graphrag
知识图谱] + B10[rag/prompts
Prompt工厂] + B11[rag/models
Chunk模型] + B12[rag/common
常量/工具] + B13[rag/utils
ES/Redis连接] + end + + subgraph "旁路模块" + C1[rag_utils
Chunk LLM分析] + end + + A1 --> B1 + A1 --> B4 + A1 --> B5 + A1 --> B8 + A1 --> B9 + A1 --> B10 + A2 --> B1 + A2 --> B5 + A2 --> B8 + A2 --> B9 + A3 --> B8 + A3 --> B7 + A3 --> B11 + A4 --> C1 + + B1 --> B2 + B1 --> B3 + B1 --> B6 + B1 --> B7 + B2 --> B3 + B2 --> B6 + B3 --> B12 + B4 --> B13 + B5 --> B13 + B6 --> B7 + B6 --> B13 + B6 --> B10 + B8 --> B7 + B8 --> B11 + B8 --> B13 + B9 --> B6 + B9 --> B7 + B9 --> B10 + B9 --> B13 + B10 --> B7 + B10 --> B9 + + C1 --> B7 + B12 --> B13 + B13 --> B8 +``` + +--- + +## 三、入口链路梳理 + +### 3.1 文档入库链路(Indexing Pipeline) + +``` +REST POST /document 或 /knowledge/{id}/sync + ↓ 触发 +Celery task @tasks.py:212 parse_document(file_path, document_id) + ↓ 调用 +rag/app/naive.py:508 chunk(filename, binary, ...) + ↓ 路由 by file extension + ├─ PDF → by_deepdoc() → deepdoc/parser/pdf_parser.py:34 RAGPdfParser.__call__:1124 + ├─ PDF alt → by_mineru() → deepdoc/parser/mineru_parser.py:41 MinerUParser.parse_pdf() + ├─ DOCX → RAGDocxParser.__call__() @ docx_parser.py:9 + ├─ XLSX → RAGExcelParser.__call__() @ excel_parser.py:16 + ├─ HTML → RAGHtmlParser.__call__() @ html_parser.py:22 + ├─ MD → RAGMarkdownParser.__call__() @ markdown_parser.py:6 + ├─ JSON → RAGJsonParser.__call__() @ json_parser.py:7 + └─ TXT → RAGTxtParser.__call__() @ txt_parser.py:7 + ↓ +rag/app/naive.py:257 naive.__call__() — 提取 sections + tables + ↓ +rag/nlp/__init__.py — tokenize / naive_merge / hierarchical_merge + ↓ +rag/vdb/elasticsearch/elasticsearch_vector.py:55 add_chunks() + ↓ 调用 +rag/vdb/elasticsearch/elasticsearch_vector.py:65 create() + ↓ 调用 +embedding_model.py: encode() → LLM API → ES bulk index +``` + +### 3.2 在线检索链路(Query Pipeline) + +``` +REST POST /retrieval + 或 +Workflow Node: workflow/nodes/knowledge/node.py:303 execute() + ↓ +workflow/nodes/knowledge/node.py:195 knowledge_retrieval() + ↓ 根据 retrieve_type 分支 + ├─ PARTICIPLE → ElasticSearchVector.search_by_full_text() @ elasticsearch_vector.py:468 + ├─ SEMANTIC → ElasticSearchVector.search_by_vector() @ elasticsearch_vector.py:374 + ├─ HYBRID → 并行 vector + full_text → dedupe → rerank @ node.py:236-271 + └─ Graph → HYBRID 结果 + kg_retriever.retrieval() + ↓ 调用 + rag/common/settings.py:10 kg_retriever (单例) + ↓ 调用 + rag/graphrag/search.py:19 KGSearch.retrieval() +``` + +### 3.3 GraphRAG 构建链路 + +``` +REST POST /knowledge/{knowledge_id}/knowledge_graph + 或 +Celery task @tasks.py:472 build_graphrag_for_kb(kb_id) + ↓ +Celery task @tasks.py:557 build_graphrag_for_document(document_id, knowledge_id) + ↓ +rag/graphrag/general/index.py:36 run_graphrag(row, language, with_resolution, with_community, ...) + ↓ +rag/graphrag/general/index.py:122 run_graphrag_for_kb(kb_id, ...) + ↓ 流水线 + 1. init_graphrag() → 创建 ES 索引 + 2. GraphExtractor.extract() → 逐 chunk 抽取实体/关系 + ├─ general/graph_extractor.py:34 GraphExtractor (Microsoft GraphRAG 风格) + └─ light/graph_extractor.py:31 GraphExtractor (LightRAG 风格,条件切换) + 3. graph_merge() → 合并子图 + 4. EntityResolution.resolve() → 实体消歧 + 5. leiden.run() → 社区发现 + 6. CommunityReportsExtractor.extract() → 社区摘要 + 7. set_graph() → 写回 ES +``` + +### 3.4 Workflow Knowledge 节点链路 + +``` +workflow/nodes/knowledge/node.py:29 KnowledgeRetrievalNode + ↓ +node.py:54 _extract_input() — 渲染 query 模板,读取 knowledge_bases 配置 + ↓ +node.py:303 execute() + ↓ +node.py:335 get_knowledge_by_id() — 校验知识库存在性 + ↓ +node.py:195 knowledge_retrieval() + ↓ 分支处理 + ├─ FOLDER 类型 → 递归遍历子知识库 + ├─ PARTICIPLE → vector_service.search_by_full_text() + ├─ SEMANTIC → vector_service.search_by_vector() + ├─ HYBRID → vector + full_text 并行 → dedupe → rerank + └─ Graph → HYBRID + kg_retriever.retrieval() 增强 + ↓ +node.py:108 rerank() — 调用 RedBearRerank 模型 + ↓ +node.py:362 返回 {"chunks": [...], "citations": [...]} +``` + +--- + +## 四、Gap 报告(代码 vs S1-T2 架构预期) + +### 4.1 "架构里列了但代码里没有 / 命名/范围不一致" + +| # | 差异项 | S1-T2 架构预期 | 代码实际 | 影响与建议 | +|---|---|---|---|---| +| 1 | **缺少 Milvus/Weaviate/Qdrant 支持** | VDB 环节预期讨论"向量数据库选型",暗示可能多库 | 仅 `rag/vdb/elasticsearch/` 有实现,`BaseVector` 无其他子类 | 架构文档中 VDB 章节需要明确限定为 Elasticsearch 8.x,或规划扩展接口 | +| 2 | **`rag_utils` vs `rag/utils` 命名冲突** | 预期目录:`api/app/core/rag/{deepdoc,crawler,integrations,llm,vdb,graphrag,prompts,app}` | 实际存在 `rag/utils`(文件工具/ES 连接)**和** `rag_utils/`(Chunk LLM 分析)两个独立目录,仅下划线差异 | 极易混淆,建议将 `rag_utils/` 重命名为 `rag/chunk_analytics/` 或合并到 `rag/app/` 下游 | +| 3 | **`nlp/search.py` 中的 `Dealer` 是遗留/旁路模块** | 架构中 `rag/nlp` 预期为"分词/NLP 工具" | `rag/nlp/search.py:349 Dealer` 实际是一个完整的 BM25/hybrid 搜索调度器,与 `rag/vdb` 的 ES 向量搜索并行存在两套检索体系 | 两套检索代码并存(`nlp/search.py` 主要被 GraphRAG 使用,`vdb/elasticsearch` 被 Workflow 使用)。架构文档应明确标注 `nlp/search` 是 GraphRAG 专用旧通道 | +| 4 | **缺少独立的 Reranking 模块** | S1-T2 预期有独立的 Reranking 环节 | 重排序逻辑散布在多处:`workflow/nodes/knowledge/node.py:108 rerank()`、`rag/vdb/elasticsearch/elasticsearch_vector.py:560 rerank()`、以及 `rag/nlp/search.py:606 rerank()` | 建议 Sprint-2 文档将 Reranking 单独成章,汇总这三处实现并标注差异(Workflow 节点用 RedBearRerank,VDB 层也有独立 rerank,NLP 层有 model-based rerank) | +| 5 | **Prompt 目录含大量 .md 模板但无统一版本管理** | Prompt 工程是独立环节 | `rag/prompts/` 有 31 个 `.md` 模板文件 + `template.py`(加载器)+ `generator.py`(工厂函数),但模板修改无版本控制/审计机制 | 建议文档中标注 prompt 管理现状:文件驱动、运行时加载、无 A/B 或版本回滚机制 | +| 6 | **Deepdoc vision 模型加载路径硬编码** | 架构预期模型管理可配置 | `deepdoc/vision/` 各 recognizer 硬编码从 `huggingface_hub.snapshot_download(repo_id="InfiniFlow/deepdoc")` 下载到 `res/deepdoc/`,仅 `HF_ENDPOINT` 环境变量可配 | 建议文档中明确标注模型路径约束,为后续模型热更新/私有化部署做铺垫 | +| 7 | **GraphRAG light 是条件分支而非独立模块** | S1-T2 预期 GraphRAG 有 light 和 general 两个独立目录 | `light/` 仅含 `graph_extractor.py` + `graph_prompt.py`(2 个逻辑文件),其余全部复用 `general/` 的 `Extractor` 基类、`utils.py`、`index.py` | Sprint-2 文档应将 light 标记为"general 的条件子模式",避免读者误以为两套完整流水线 | + +### 4.2 "代码里有但架构没列" + +| # | 差异项 | 代码位置 | 说明 | +|---|---|---|---| +| 1 | **rag/app 按 doc_type 路由的 11 种解析策略** | `rag/app/{naive,book,paper,qa,audio,picture,manual,laws,mail,one,textin_parser}.py` | S1-T2 架构只提到 "Loader / Parser",未提及 MemoryBear 特有的 doc_type 路由体系(book/paper/qa/audio 等) | +| 2 | **MinerU 第三方解析器集成** | `rag/deepdoc/parser/mineru_parser.py` | 架构中 Parser 环节未提及 MinerU(第三方 PDF 解析服务)作为 PDF 解析的替代方案 | +| 3 | **TextIn 第三方解析器集成** | `rag/app/textin_parser.py` | 同上,未提及 TextIn API 作为另一 PDF 解析备选 | +| 4 | **rag_utils(Chunk LLM 分析)** | `api/app/core/rag_utils/` | 架构中无此模块定位,它实际做 chunk 摘要/标签/洞察,与 Memory 系统耦合 | +| 5 | **Toc(目录)智能提取链路** | `rag/prompts/generator.py:408-717` | 大量 LLM-driven TOC 检测/提取/索引/关联代码,架构大纲中未单列 "TOC 处理" 环节 | +| 6 | **Crawler(网页抓取)** | `rag/crawler/` | 架构中 Loader 环节可能包含爬虫,但代码量 1,200+ LOC 值得单独标注 | +| 7 | **res/ 静态资源(NER、同义词表)** | `rag/res/{ner.json,synonym.json,mapping.json}` | 架构中未提及术语权重/同义词扩展的资源文件体系 | + +--- + +## 五、关键数据速查 + +| 指标 | 数值 | +|---|---| +| `api/app/core/rag/` 总 Python LOC | ~24,895 | +| `api/app/core/rag/` 子模块数 | 15(不含 res/) | +| `.md` Prompt 模板数 | 31 | +| Parser 实现数 | 11 种(含 PDF 3 种策略:deepdoc/mineru/textin) | +| LLM Provider 实现数 | Chat 9 种 + Embed 10 种 + CV 7 种 + Seq2txt 6 种 = **32 个 provider 类** | +| Workflow Knowledge 检索类型 | PARTICIPLE / SEMANTIC / HYBRID / Graph(4 种) | +| GraphRAG 模式 | general(Microsoft GraphRAG)/ light(LightRAG 风格) | +| VDB 实现 | Elasticsearch 8.x(唯一) | + +--- + +以上交付物已同步写入本地文件 `WS-14-deliverable.md`,可作为 Sprint-2 文档化的底图直接复用。 \ No newline at end of file diff --git a/docs/rag/pipeline/01-loader-parser-chunking.md b/docs/rag/pipeline/01-loader-parser-chunking.md new file mode 100644 index 00000000..44ca57bc --- /dev/null +++ b/docs/rag/pipeline/01-loader-parser-chunking.md @@ -0,0 +1,623 @@ +--- +title: "[S2-T1] 文档加载与预处理(Loader / Parser / Chunking)实现详解" +author: Python 开发工程师 +last-reviewed-at: 2026-05-08 +source-commit: HEAD (origin/main, MemoryBear) +scope: api/app/core/rag/{crawler, integrations, deepdoc, nlp, models, utils, app/naive.py, common/token_utils.py} +--- + +## 0. 一句话定位 + +把"任意来源、任意格式"的原始资料,沉淀为带元数据、可被 Embedding/索引消费的标准化 **Chunk** 序列;这一段是 RAG 召回质量的"硬天花板"——它做不好,下游再多优化都救不回来。 + +## 1. 设计目标与适用场景 + +| 目标 | 落地策略 | +|---|---| +| 多源接入(爬虫 / 飞书 / 语雀 / 本地文件) | `crawler/`、`integrations/feishu`、`integrations/yuque` 三套 SDK,均落到本地文件后再走统一 `chunk()` 入口 | +| 多格式解析(PDF/Word/Excel/PPT/HTML/MD/JSON/TXT/图片/音视频) | `app/naive.py:chunk()` 单一编排入口,按扩展名分派到 `deepdoc/parser/*` 与 `app/{audio,picture}.py` | +| 复杂 PDF 还原(表格、图、版面) | `RAGPdfParser` + OCR + 版面识别 + TSR + XGBoost 段落连接模型 | +| 长文 Chunking 既保语义又控 token | `naive_merge` / `naive_merge_docx` / `hierarchical_merge` / `tree_merge` 多种策略,统一以 `cl100k_base` 计算 token | +| 同一篇资料的多模态(图 + 文 + 表) | `tokenize_chunks_with_images`、`tokenize_table` 把图片/表格作为附属信息挂在 chunk 上 | +| 健壮性 | 鉴权 token 缓存、退避重试、robots.txt 合规、编码自动嗅探、嵌入文件递归解构 | + +适用于:私有知识库、企业文档库、技术资料归档;不适用于:实时流式数据、对端到端延迟<200ms 的场景(OCR 与版面识别是 CPU/GPU 重负载)。 + +## 2. 术语表 + +- **Section**:解析器吐出的中间结构 `(text, position_or_layout)` 元组列表,是 Chunking 之前的"原料"。 +- **Chunk**:最终交给 Embedding 的文本片段,一般 ≤ `chunk_token_num` 个 token(默认 128–512)。 +- **Token**:用 `tiktoken.cl100k_base` 编码后得到的 BPE token 数(与 OpenAI gpt-4 同口径)。 +- **Layout**:页面区块类别(title / text / figure / table / equation 等),由 YOLOv10 检测。 +- **TSR**:Table Structure Recognition,复杂表格行/列/合并单元格的结构还原。 +- **OCR**:文字检测 + 文字识别两阶段的图像字符抽取。 +- **Embed file**:内嵌在 docx/xlsx/pptx 内部的子文件(如 docx 里嵌的 Excel),需递归解析。 + +## 3. 实现概览(数据流图) + +```mermaid +flowchart LR + subgraph Loader["Loader / 多源接入"] + A1[本地文件] --> CHUNK + A2[Web 站点] --> WC[WebCrawler
BFS 同域] + A3[飞书云文档] --> FS[FeishuAPIClient
导出/下载] + A4[语雀知识库] --> YQ[YuqueAPIClient
raw markdown] + WC --> CD[CrawledDocument
title+content] + FS --> LF[本地文件] + YQ --> LF + CD --> CHUNK + LF --> CHUNK + end + + subgraph Parser["Parser / 格式分派"] + CHUNK[app/naive.py: chunk] --> EX[extract_embed_file
嵌入文件递归] + CHUNK -->|.pdf| PARSERS[PARSERS dict
deepdoc/mineru/textln/plaintext] + CHUNK -->|.docx| DOCX[Docx/RAGDocxParser] + CHUNK -->|.xlsx/.csv| XLS[RAGExcelParser] + CHUNK -->|.md| MD[Markdown/RAGMarkdownParser] + CHUNK -->|.html| HTML[RAGHtmlParser] + CHUNK -->|.json/.jsonl| JSON[RAGJsonParser] + CHUNK -->|.txt/code| TXT[RAGTxtParser] + CHUNK -->|.ppt/.pptx| LO[LibreOffice
convert_to_pdf] + CHUNK -->|.doc| TIKA[Apache Tika] + CHUNK -->|图片/音视频| MM[picture/audio
vision_llm_chunk] + LO --> PARSERS + PARSERS --> OCR[OCR + LayoutRecognizer + TSR] + DOCX --> SEC[(sections)] + XLS --> SEC + MD --> SEC + HTML --> SEC + JSON --> SEC + TXT --> SEC + OCR --> SEC + TIKA --> SEC + end + + subgraph Chunking["Chunking / 切分 + 索引化"] + SEC --> NM{有图片?} + NM -->|否| NM1[naive_merge] + NM -->|是·docx| NM2[naive_merge_docx] + NM -->|是·md| NM3[naive_merge_with_images] + NM1 --> TC[tokenize_chunks] + NM2 --> TCI[tokenize_chunks_with_images] + NM3 --> TCI + TT[tokenize_table] --> ESDOC + TC --> ESDOC[(ES Doc
content_with_weight
content_ltks
page_num_int
position_int
image)] + TCI --> ESDOC + end +``` + +## 4. Loader 章节 + +### 4.1 Web Crawler(`crawler/`) + +- **入口**:`WebCrawler(entry_url, max_pages, delay_seconds, timeout_seconds, user_agent, include_patterns, exclude_patterns)`,源码 `api/app/core/rag/crawler/web_crawler.py:19`。 +- **架构**:BFS(`deque` + `visited_urls`)+ 五个独立组件:`URLNormalizer` / `RobotsParser` / `RateLimiter` / `HTTPFetcher` / `ContentExtractor`,全部通过组合而非继承装配,便于替换。 +- **同域限制**:`URLNormalizer.is_same_domain()` 强制只爬入口域名,避免无界扩散(`url_normalizer.py:102-124`)。 +- **去重**:`URLNormalizer.normalize()` 做:小写 host、去 fragment、去默认端口、剥离 utm_*/fbclid/gclid 等追踪参数、按字母序排 query。`url_normalizer.py:28-100`。 +- **robots.txt 合规**:`RobotsParser.can_fetch()` 与 `get_crawl_delay()`,使用 stdlib 的 `urllib.robotparser`,每域名缓存。robots.txt 拉取失败时**默认允许**(permissive fallback),`robots_parser.py:60-69`。 +- **限速**:`RateLimiter` 默认 1s/请求,`set_delay()` 可被 `Crawl-delay` 动态覆盖(上限 60s 防呆);`backoff(2.0)` 用于 429/503 指数退避,`rate_limiter.py:38-58`。 +- **HTTP 重试**:`HTTPFetcher` 内置 `max_retries=3`,退避 `1s → 2s → 4s`;429 与 503 显式触发重试,404/4xx 立即返回不重试,5xx 重试到耗尽。`http_fetcher.py:54-180`。 +- **编码处理**:`HTTPFetcher._get_decoded_content` 五级回退:HTML meta charset → response.encoding(跳过 ISO-8859-1) → UTF-8 → GBK/Big5/Shift-JIS/EUC-KR 等 → latin-1 with errors='replace'。`http_fetcher.py:182-248`。 +- **正文抽取**:`ContentExtractor.extract` 基于 `lxml`:移除 `script/style/nav/header/footer/aside`,按 `
/
` → `[role=main]` → `class/id =~ content|main|article|post` → `` 顺序找主体;用 `is_static_content` 检测"脚本多文本少"的 SPA 页面并直接跳过。`content_extractor.py:24-72`。 +- **错误统计**:`stats.error_breakdown` 记录每种错误类型的计数,便于事后分析。`web_crawler.py:210-215`。 + +```python +# api/app/core/rag/crawler/web_crawler.py:103-145(节选) +while self.url_queue and self.pages_processed < self.max_pages: + url = self.url_queue.popleft() + if url in self.visited_urls: continue + self.visited_urls.add(url) + if not self.robots_parser.can_fetch(url): # robots.txt + self.stats['skipped'] += 1; continue + self.rate_limiter.wait() # 限速 + fetch_result = self.http_fetcher.fetch(url) # 重试 + 退避 + if not fetch_result.success: + self._record_error(fetch_result.error or "Unknown error"); continue + content_type = fetch_result.headers.get('Content-Type', '').lower() + if not any(s in content_type for s in ['text/html', 'application/xhtml+xml']): + self.stats['skipped'] += 1; continue # 非 HTML 跳过 + extracted = self.content_extractor.extract(fetch_result.content, url) + if not extracted.is_static: + self.stats['skipped'] += 1; continue # JS-only 站点跳过 +``` + +### 4.2 飞书集成(`integrations/feishu/`) + +- **入口**:`FeishuAPIClient(app_id, app_secret, api_base_url, timeout, max_retries)`,`integrations/feishu/client.py:24`,是异步客户端(`httpx.AsyncClient`),用 `async with` 管理生命周期。 +- **鉴权**:`tenant_access_token` 模式,`get_tenant_access_token()` 用 `cachetools.TTLCache(maxsize=1, ttl=7200-300)` 缓存(飞书原生 2 小时有效,提前 5 分钟失效)+ `asyncio.Lock` 双检锁防并发请求 token。`client.py:51-127`。 +- **文件类型分派**:`download_document` 按 `document.type` 分两条路径: + - **在线文档(doc/docx/sheet/bitable)**:`_export_file` 走"创建导出任务 → 轮询 ticket → 下载 file_token"三步,最多轮询 10 次、间隔 2s,超时抛 `FeishuAPIError`。`client.py:311-406`。 + - **附件文件(file/slides)**:`_download_file` 直接 GET `/drive/v1/files/{token}/download`,从 `Content-Disposition` 解析 `filename*=UTF-8''xxx` 编码文件名。`client.py:408-452`。 +- **限流与重试**:装饰器 `@with_retry`(`feishu/retry.py:124-137`)。`RetryStrategy.RETRYABLE_ERRORS = (FeishuNetworkError, FeishuRateLimitError, httpx.TimeoutException/ConnectError/ReadError)`,`MAX_RETRIES=3`,退避 `[1, 2, 4]s`;HTTP 429/502/503/5xx 重试,4xx(除 429)不重试;飞书业务码 `99991400/99991401`(限流码)也强制重试。`feishu/retry.py:24-76`。 +- **错误模型**:精细化异常树 `FeishuAuthError / FeishuAPIError / FeishuNotFoundError / FeishuPermissionError / FeishuRateLimitError / FeishuNetworkError / FeishuDataError`,调用方据此决定告警级别。`feishu/exceptions.py:1-46`。 +- **分页与递归**:`list_folder_files` 单页(page_size=200);`list_all_folder_files(recursive=True)` 自动展开子文件夹。`client.py:226-269`。 + +```python +# api/app/core/rag/integrations/feishu/client.py:78-120(鉴权 + 双检锁缓存) +cached_token = self._token_cache.get("access_token") +if cached_token: return cached_token +async with self._token_lock: + cached_token = self._token_cache.get("access_token") + if cached_token: return cached_token + response = await self._http_client.post( + "/auth/v3/tenant_access_token/internal", + json={"app_id": self.app_id, "app_secret": self.app_secret}) + data = response.json() + if data.get("code") != 0: + raise FeishuAuthError(f"Authentication failed: {data.get('msg')}", + error_code=str(data.get("code")), details=data) + token = data.get("tenant_access_token") + self._token_cache["access_token"] = token + return token +``` + +### 4.3 语雀集成(`integrations/yuque/`) + +- **入口**:`YuqueAPIClient(user_id, token, api_base_url, timeout, max_retries)`,`integrations/yuque/client.py:27`。 +- **鉴权**:个人 PAT,HTTP header `X-Auth-Token`,无需 OAuth/token 刷新(语雀的 token 是长期 token),故没有 token 缓存层。`client.py:55-66`。 +- **API 三段式**:`get_user_repos()` → `get_repo_docs(book_id)` → `get_doc_detail(id, raw=1)`;`get_doc_detail` 用 `params={"raw": 1}` 拉原始 markdown。`client.py:119-291`。 +- **格式分派(download_document)**:根据 `doc.format` 决定本地文件后缀: + - `markdown` / `lake` → `.md`(lake 也按 markdown 保存,因为 lake 在 raw 模式下输出兼容 md) + - `html` → `.html` + - `lakesheet` → `.xlsx`,需 `zlib.decompress(bytes(sheet_data, 'latin-1'))` 解压后由 `generate_excel_from_sheet` 用 openpyxl 重建工作簿(含字体、对齐、颜色、合并单元格)。`client.py:293-545`。 +- **限流与重试**:与飞书同构,`yuque/retry.py:21-118`,`RetryStrategy` 配置一致;HTTP 状态码 401→`YuqueAuthError`、403→`YuquePermissionError`、404→`YuqueNotFoundError`、429→`YuqueRateLimitError`,由 `_handle_api_error` 统一翻译。`client.py:73-117`。 +- **健壮性**:`get_user_repos`/`get_repo_docs` 对单条数据 `try/except` 跳过坏记录而不整体失败(容忍语雀 schema 漂移)。`client.py:158-160, 221-223`。 + +### 4.4 本地文件(`app/naive.py:chunk`) + +- 是所有 Loader 的最终汇入口;接收 `filename` 与 `binary` 两种入参,二者互斥(推荐 `binary`,源码内 `extract_embed_file` 显式不支持 path 模式,详见 `app/naive.py:541`)。 +- **嵌入文件递归**:根调用(`is_root=True`)会先用 `extract_embed_file()` 抽出 docx/xlsx/pptx 内部嵌入的子文件(通过 zip 名单 `word/embeddings/`、`xl/embeddings/`、`ppt/embeddings/` 或 OLE 容器的 `Ole10Native`),逐个递归 `chunk()`,结果合入 `embed_res`。`utils/file_utils.py:69-130` + `app/naive.py:533-552`。 +- **超链接深挖**:`parser_config.analyze_hyperlink=True` 时,docx/pdf 内部超链接经 `extract_links_from_docx` / `extract_links_from_pdf` 抽出后,每条 URL 调用 `extract_html` 拉回 HTML 二进制并递归 `chunk(url, html_bytes, is_root=False)`。`app/naive.py:556-566, 793-803`。 +- **callback 进度上报**:`chunk(..., callback=progress_callback)`,约定 `callback(prog: float, msg: str)`,关键节点:0.05(嵌入抽取)/ 0.1(开始解析)/ 0.6(OCR 完)/ 0.63(版面)/ 0.65(表格)/ 0.67(合并)/ 0.8(解析完)。 + +## 5. Parser 章节 + +### 5.1 总分派器(`app/naive.py`) + +`chunk()` 是入口,按文件扩展名走分支: + +```python +# api/app/core/rag/app/naive.py:97-102 +PARSERS = { + "deepdoc": by_deepdoc, + "mineru": by_mineru, + "textln": by_textln, + "plaintext": by_plaintext, # default +} +# api/app/core/rag/app/naive.py:553-764 +if re.search(r"\.docx$", filename, re.IGNORECASE): ... +elif re.search(r"\.pdf$", filename, re.IGNORECASE): ... # 走 PARSERS dict +elif re.search(r"\.(pptx|ppt?)$", ...): ... # LibreOffice → pdf +elif re.search(r"\.(da|wav|mp3|...)$", ...): ... # app/audio.py +elif re.search(r"\.(png|jpeg|...)$", ...): ... # app/picture.py +elif re.search(r"\.(csv|xlsx?)$", ...): ExcelParser +elif re.search(r"\.(txt|py|js|java|...)$", ...): TxtParser +elif re.search(r"\.(md|markdown)$", ...): Markdown(MarkdownParser 子类) +elif re.search(r"\.(htm|html)$", ...): HtmlParser +elif re.search(r"\.(json|jsonl|ldjson)$", ...): JsonParser +elif re.search(r"\.doc$", ...): tika # Apache Tika via JVM +``` + +PDF 的 `parser_config.layout_recognize` 决定底层走哪条 PDF 引擎,默认 `DeepDOC`: + +| layout_recognize | 引擎 | 调用 | 适用 | +|---|---|---|---| +| `DeepDOC` | `Pdf(RAGPdfParser)` | `by_deepdoc` | 复杂版面、扫描件 | +| `Plain Text` | `PlainParser` | `by_plaintext` | 纯文本 PDF,速度快 | +| `MinerU` | `MinerUParser` | `by_mineru` | 高质量结构化(外部进程或 HTTP) | +| `TextLn` | `TextLnParser` | `by_textln` | TextIn API(云端付费) | +| 任意(含 `vision_model`) | `VisionParser` | `by_plaintext` 分支 | 多模态 LLM 直读 | + +### 5.2 PDF 解析(`deepdoc/parser/pdf_parser.py`,1387 行) + +`RAGPdfParser` 是大头,调用栈: + +```python +# api/app/core/rag/app/naive.py:373-412 (Pdf.__call__ 节选) +self.__images__(filename if not binary else binary, zoomin, from_page, to_page, callback) +callback(0.6, f"OCR finished") +self._layouts_rec(zoomin) # 版面识别 +callback(0.63, "Layout analysis") +self._table_transformer_job(zoomin) # TSR +callback(0.65, "Table analysis") +self._text_merge(zoomin=zoomin) # 文本合并 +self._extract_table_figure(...) # 提取表与图 +self._naive_vertical_merge() +self._concat_downward() # XGBoost 段落连接(updown_concat_xgb) +self._final_reading_order_merge() +return [(b["text"], self._line_tag(b, zoomin)) for b in self.boxes], tbls +``` + +要点: +- **OCR**:`OCR()`(`deepdoc/vision/ocr.py:522`)= `TextDetector` + `TextRecognizer` 组合;`pdfplumber` 把每一页 `to_image(resolution=72*zoomin=216).annotated`,再过 OCR。`pdf_parser.py:1006-1122`。 +- **版面识别**:`LayoutRecognizer4YOLOv10`(默认,10 个 label:title / Text / Reference / Figure / Figure caption / Table / Table caption / Equation 等),或 `AscendLayoutRecognizer`(Ascend NPU),由 `LAYOUT_RECOGNIZER_TYPE` 环境变量切换。`pdf_parser.py:53-67` + `vision/layout_recognizer.py:147-160`。 +- **表格结构识别**:`TableStructureRecognizer`(`vision/table_structure_recognizer.py`),裁出 table 区域后把行/列重组成 HTML;与 docx 的"按上下文找最近标题"风格一致。`pdf_parser.py:178-220`。 +- **段落连接模型**:`updown_cnt_mdl`(XGBoost),输入是上下相邻两块的 31 维特征(句末是否标点、x0 距离、行内 token 数、字号差、layout_type 等),决定要不要把下一块续到上一块。`pdf_parser.py:113-156` + `pdf_parser.py:70-83`(模型从 HuggingFace `InfiniFlow/text_concat_xgb_v1.0` 拉)。 +- **位置标签**:每个文本块带 `@@\t\t\t\t##` 的位置 tag,`remove_tag()` 用 `re.sub(r"@@[\t0-9.-]+?##", "", txt)` 去掉,`extract_positions()` 反解。`pdf_parser.py:1219-1229`。 +- **GPU 加速**:通过 `pip_install_torch()` + `torch.cuda.is_available()` 把 XGBoost 推到 CUDA;`PARALLEL_DEVICES > 1` 时用 `trio.CapacityLimiter` 做多卡并行。`pdf_parser.py:50-77, 1095-1106`。 +- **HuggingFace 模型分发**:`InfiniFlow/text_concat_xgb_v1.0` 通过 `snapshot_download` 拉到 `res/` 目录;推荐 `export HF_ENDPOINT=https://hf-mirror.com` 解决国内拉取慢,`deepdoc/README.md:42`。 + +#### 5.2.1 备选 PDF 引擎 + +- **`PlainParser`**(`pdf_parser.py:1300`):`pypdf.PdfReader` 直接 `extract_text()`,每行一段 + 解析 outline 目录;无 OCR、无版面、无图,纯文本极快。 +- **`VisionParser`**(`pdf_parser.py:1334`):把每一页转成 PIL.Image,整页直接喂给 `vision_model`(`QWenCV` / `AzureGptV4` 等),让多模态 LLM "看图说话"产出 markdown。`@@page\tx0\tx1\ty0\ty1##` 位置 tag 由 `(0, 0, width/zoomin, 0, height/zoomin)` 占位生成(即整页矩形),方便下游对齐 chunk 与原图。 +- **`MinerUParser`**(`mineru_parser.py:41`):调用外部 `mineru` 进程(CLI 模式)或 `MINERU_APISERVER`(HTTP 模式,默认 `host.docker.internal:9987`),后端可选 `pipeline / vlm-http-client / vlm-transformers / vlm-vllm-engine`;输出 zip 解压后融合为 sections + tables。`naive.py:45-62`。 +- **`TextLnParser`**(`app/textin_parser.py`):合合 TextIn 云端 PDF→Markdown 服务,需要 `TEXTLN_APP_ID/SECRET_CODE`。 + +### 5.3 Word 解析(`deepdoc/parser/docx_parser.py` + `naive.py:Docx`) + +两层: + +- **底层 `RAGDocxParser`**(`docx_parser.py:9-123`):`python-docx`+`pandas` 读段落与表格;表格内容经 `__compose_table_content` 做"列类型推断"(日期 Dt / 数字 Nu / 中文人名 Nr / 英文 En 等 11 类正则),自动识别多行表头并把单元格拼成 `表头:值` 格式,保证表格在 chunk 中也能被关键词检索。 +- **上层 `Docx(RAGDocxParser)`**(`naive.py:105-323`):把段落里的图片用 `python-docx` 的 `xpath('.//pic:pic')` 抽出,挂到对应 paragraph;表格区域用 `__get_nearest_title` 上溯到 7 级标题构造层级路径作为 `Table Location: A > B > C`,这是检索时定位表格上下文的关键。 +- **超链接抽取**:`extract_links_from_docx` 遍历 `document.part.rels`,过滤 reltype 为 hyperlink 的关系,得到链接集合。`utils/file_utils.py:133-154`。 +- **`to_markdown`**:可选回退路径,使用 `mammoth.convert_to_html` + `markdownify`,图片嵌成 `data:` base64 URL。`naive.py:325-366`。 +- **NULL 关系修复**:上层 `Docx` 用 `load_from_xml_v2` monkey-patch 掉 `_SerializedRelationships.load_from_xml`,跳过 `../NULL` 与 `NULL` target 以绕过 python-docx#1105 已知 bug。`naive.py:493-506, 569`。 + +### 5.4 Excel/CSV 解析(`deepdoc/parser/excel_parser.py`) + +- **多引擎容错**:`_load_excel_to_workbook` 先看魔数:`PK\x03\x04`(OOXML)或 `\xd0\xcf\x11\xe0`(OLE2)。openpyxl 失败回退 `pandas.read_excel`,再失败回退 `engine="calamine"`;非 Excel 头则当 CSV 处理(`pd.read_csv(on_bad_lines='skip')` 容忍坏行)。`excel_parser.py:18-53`。 +- **非法字符清洗**:`ILLEGAL_CHARACTERS_RE = re.compile(r"[\000-\010]|[\013-\014]|[\016-\037]")`,`_clean_dataframe` 把所有字符串单元格里的控制字符替换成空格,避免写入 Workbook 报错。`excel_parser.py:13, 56-62`。 +- **三种输出形态**: + - `__call__()`:每行 → `表头1:值1\n表头2:值2\n...\n——SheetName`,作为一个 section(一个 chunk)。`excel_parser.py:203-246`。 + - `html()`:每 256 行打包成一张 ``,header 复用,便于检索时整块召回。`excel_parser.py:144-187`。 + - `markdown()`:`df.to_markdown(index=False)`,整个表一段。 +- **图片抽取**:`_extract_images_from_worksheet` 通过 `ws._images` 的 anchor.row/col 还原图片位置,输出 `single_cell` / `multi_cell` span_type 元数据。`excel_parser.py:98-142`。 +- **重要:Excel 不走 `naive_merge`**:`naive.py:678-680` 显式注释"Excel 每行直接作为一个 chunk,不经过 naive_merge 避免被 delimiter 拆分"——直接 `tokenize_chunks(chunks, ...)`。 + +### 5.5 Markdown 解析(`deepdoc/parser/markdown_parser.py`) + +- **表格抽取**:`extract_tables_and_remainder` 用三个正则按顺序剥离:标准 GFM 边框表格 → 无边框表格 → HTML `
...
`(含 `` 包装),每张表单独成一个 chunk,剩余正文继续走 element 抽取。`markdown_parser.py:10-106`。 +- **元素抽取**:`MarkdownElementExtractor.extract_elements(delimiter)` 按行扫描,识别 `header(#~######)` / `code_block(```)` / `list_block(-/*/+/数字.)` / `blockquote(>)` / `text_block`,每种元素用对应私有方法收集起止行号。`markdown_parser.py:109-277`。 +- **图片嵌入**:当传入 `vision_model` 时,naive.py 会对每个 section 调 `markdown_parser.get_pictures()`(HTTP 下载或本地路径打开),把图片合并 `concat_img` 后丢给 `VisionFigureParser` 让 LLM 描述图片,描述文本拼回 section 末尾。`naive.py:697-709`。 +- **超链接深挖**:`get_hyperlink_urls(soup)` + `analyze_hyperlink=True` 触发递归 chunk。`naive.py:716-720`。 + +### 5.6 HTML 解析(`deepdoc/parser/html_parser.py`) + +- **预清洗**:BeautifulSoup html5lib,移除 `