RAG 不是一条必经流水线,而是一种按需取证的能力

先理解目标、先看当前源码,只有跨来源信息真的能改善判断时,才调用 Local RAG 去找证据候选。

本页目录 · 9 节

很多 RAG 教程从一条很短的链路开始:文档切块,计算 Embedding,写入向量库;收到问题后检索 Top-K,再让大模型组织答案。这条链路解释了「外部知识怎样进入生成」,却没有回答工程现场最棘手的问题:命中的材料是否属于正确仓库、正确版本和正确调用关系?

我构建 mt_rag,不是为了让 Codex 每次工作都先经过一个检索流水线,而是为了在需要跨越代码、文档、事项跟踪、附件和经批准的配置时,提供一套可按需调用、能回到原文、证据不足会停下来的本地取证能力。

这决定了它在整个开发系统中的位置:安全规则和用户目标仍然在前;当前源码仍然是实现事实的首选;Local RAG 负责缩小跨来源搜索范围,并把候选材料连回可验证证据。

本系列的知识地图

整套架构可以拆成十个职责明确的步骤。它们是数据系统内部的依赖关系,不是要求每个开发任务都完整运行一遍的 Workflow。

flowchart TB
  S["1 企业来源<br/>代码 / 文档 / 事项跟踪 / 附件"]
  R["2 Raw 数据控制面<br/>发现 / Freshness / Fetch Decision"]
  C["3 本地不可变 CAS<br/>Raw / IR / CPG / Manifest"]
  I["4 Typed IR<br/>DocumentIR / Issue IR / Code CPG"]
  E["5 Canonical Resolution<br/>Entity / Relation / Evidence"]
  D["6 增量投影<br/>Event / Delta / Cursor / Tombstone"]
  Q["7 Query Stores<br/>Vector / Keyword / Neo4j"]
  H["8 混合检索与图扩展<br/>70/30 RRF / Parent / Verified Path"]
  L["9 受控 LLM<br/>Rewrite / Controller / Rerank / Answer"]
  O["10 Developer Output<br/>Summary / Chain / Citation / Limitations"]
  S --> R --> C --> I --> E --> D --> Q --> H --> L --> O

完整架构全景图

简图用于快速建立顺序;下面的全景图展开了三种 Typed Pipeline、不可变证据合同、独立 Consumer Cursor、Version Gate、Parent Hydration、Verified Path,以及证据不足时的停止边界。

Local RAG 证据系统完整架构图;点击打开原尺寸 SVG

点击图表可打开原尺寸 SVG 并继续缩放;也可以下载可编辑的 draw.io 源文件。图中的实线表示确定性数据流,虚线表示补检索或反馈;任何 Store Generation 与 Manifest、Consumer Cursor 不一致时,查询都应报告 stale,而不是继续生成答案。

这十步围绕四类知识组织:

知识域 核心问题 对应章节
来源与身份 材料从哪里来、是什么版本、能证明什么 企业来源、Raw Control Plane、CAS
结构与关系 文档、工单、代码怎样保留结构,跨来源实体怎样对齐 Typed IR、Canonical Resolution
新鲜度与查询 变化怎样安全进入不同存储,怎样同时处理语义、精确词和关系 增量投影、Query Stores、Hybrid Retrieval
生成与交付 模型可以决定什么,怎样把候选变成工程结论 Controlled LLM、Developer Output

不是「一个数据库加一个 Prompt」

当前架构分成三个平面。

证据平面保存可回放事实。Raw Snapshot、DocumentIR 和完整 Code Property Graph 进入本地 SHA-256 CAS;不可变 Manifest Generation 再引用这些对象及其 Hash、Commit、Locator 和 Parser Version。这里回答的是「当时到底读到了什么」,而不是把 Manifest 本身和 CAS Object 混成同一种存储合同。

查询投影平面为不同查询模式服务。Qdrant 保存 Dense Vector;SQLite FTS 与 Exact Alias 处理关键词和 Identifier;Neo4j 保存 Canonical Entity、Evidence-backed Relation 和 Semantic CPG。它们都不是最终事实源,而是可以从证据平面重建的查询视图。

回答平面把检索、图路径和 LLM 约束在同一份证据合同里。第一轮 Dense/Keyword 检索后,只从最高排名命中的 graph_ids 出发寻找 Verified Path;Controller 只能选择 answerrerankretrieve_againinsufficient,总检索轮数最多为二。最终调用链、引用和 Confidence 再由确定性代码校验。

flowchart LR
  A["Evidence Plane<br/>CAS + Manifest"] --> B["Projection Plane<br/>Qdrant + FTS + Neo4j"]
  B --> C["Answer Plane<br/>Controller + Guard"]
  C --> D["Grounded Developer Answer"]
  D -. "citation / locator / commit" .-> A

这种拆分看起来比「把文本写进向量库」复杂,但它解决的是不同问题:CAS 保证可回放,向量和关键词负责发现,图负责证明已抽取的关系,LLM 负责语义判断,确定性 Guard 负责不越过证据边界。

当前真实实现

这里必须区分「已经在本机验证」和「已经切换生产」。当前 Runtime Policy 仍让 System RAG 保持 Shadow,明确要求 Production Cutover 单独批准,并禁止自动删除 Legacy 数据。因此,Shadow Readiness 证明这条新链路内部一致,不等于生产调用方已经切换

当前本地 Shadow 的可验证状态,来自近期某一完整 Generation 的一次快照

投影 当前本机证据
Evidence Corpus 大型本地语料,包含 Parent 与 Child Chunk
Query Projection Vector、Keyword 与 Graph 绑定同一 Generation
Readiness Shadow 校验通过,Vector 与 Graph Cursor 均为当前版本

这是一次本机时间点状态,不是永久 Corpus 规模,更不是准确率结论。重要的是这些投影绑定 Manifest Hash、Collection、Graph Projection 和 Consumer Cursor;任何一项版本不一致,查询代码都会报 stale,而不是继续给出看似正常的答案。

一次工程问题怎样经过系统

假设问题是「某个 GraphQL 字段从前端到 Provider 的调用链是什么」。

  1. 当前 System RAG Shadow Grounded Query 把原始问题作为 active_query 直接交给 Hybrid Retrieval;旧回答链路中的 Rewrite 属于另一查询入口,不能拼成这条路径的第一步;
  2. Dense Lane 处理语义近似,Keyword Lane 处理 Field、Resolver、Class 等精确符号;
  3. Weighted RRF 合并 Rank,不直接相加不同尺度的原始分数;
  4. Child 命中回溯 Parent Context,保留可读上下文;
  5. 只从最高相关命中的 Canonical Entity 出发,在 Neo4j 查带 Verified Evidence 的路径;
  6. Controller 判断证据够不够,最多允许一次有目标的补检索;
  7. Developer Answer 只输出经验证的节点顺序,缺边就把证据缺口记录到 limitations,不补全参数、返回值或部署状态。

如果问题已经给出精确文件和 Symbol,则直接读当前工作区通常更快。RAG 在这里不是必经 Runtime,而是「找入口、补跨来源上下文」的工具;最终代码事实仍应回到当前 Commit 验证。

我坚持的四条不变量

  1. 来源身份不能在 Embedding 时丢失。 每个 Chunk 都要能回到 source_record_id、Locator、Original Hash 和 CAS URI。
  2. 相似度不能升级为关系证明。 Vector Hit 是候选;Verified Graph Edge 必须带独立 Evidence。
  3. 新鲜度不进入内容身份。 时间变化可以影响排序,不能每天制造新的 Content Hash 和 Embedding Identity。
  4. Readiness 不授予部署权限。 Shadow 通过、测试通过、Cursor Lag 为零,都不能代替 Production Cutover 的显式确认。

失败模式与取舍

失败模式 系统行为 代价与取舍
旧 Confluence 排名很高 Query-time Freshness 降权,仍保留其历史证据身份 可能牺牲仍然有效的老文档排名,因此版本变化会覆盖年龄策略
Code 命中来自旧 Commit currentness_valid=false,不能当当前实现 需要维护 Repo Pin 与 Commit Lineage
向量成功、Neo4j 落后 Graph Cursor/Manifest Gate 报 stale,不输出 Verified Chain 可用性让位于调用链正确性
LLM 无法返回合法 JSON 使用 Deterministic Fallback;没有证据则 insufficient 答案可能不够流畅,但不会扩写未知事实
一个 Store 写入失败 失败的 Consumer 保留旧 Cursor;其他 Consumer 独立提交或保持各自已提交状态 多 Cursor 增加运维状态,但实现失败隔离

可复核的项目证据

  • README.md:当前数据范围、目录、命令与 Shadow 状态总览;
  • Runtime Policy:CAS、Embedding、70/30 RRF、两轮 Controller 和 Shadow/Cutover 边界;
  • src/mt_rag/system/pipeline.py:Raw 到 IR/CPG、Chunk、Manifest 的组装入口;
  • src/mt_rag/retrieval/system_index.py:Qdrant、Keyword、Exact Candidate Completion 与 Weighted RRF;
  • src/mt_rag/system_graph/query.py:Projection/Cursor 校验和 Verified Path 查询;
  • src/mt_rag/query/runtime.py:受控 Controller 与 Grounded Answer 运行时;
  • 本地 Readiness 产物:Generation 绑定、Hash、Cursor 与 Blocker,不暴露来源特定内容。

参考资料

后续十篇不是重复讲一张大图,而是分别解释每个边界为什么存在、当前代码怎样实现、失败时在哪里停,以及我为此接受了什么复杂度。

相关文章

在做类似的事情?

很乐意就分布式系统、交付流程和应用 AI 交换意见。

[email protected]