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,以及证据不足时的停止边界。
点击图表可打开原尺寸 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 只能选择 answer、rerank、retrieve_again 或 insufficient,总检索轮数最多为二。最终调用链、引用和 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 的调用链是什么」。
- 当前 System RAG Shadow Grounded Query 把原始问题作为
active_query直接交给 Hybrid Retrieval;旧回答链路中的 Rewrite 属于另一查询入口,不能拼成这条路径的第一步; - Dense Lane 处理语义近似,Keyword Lane 处理 Field、Resolver、Class 等精确符号;
- Weighted RRF 合并 Rank,不直接相加不同尺度的原始分数;
- Child 命中回溯 Parent Context,保留可读上下文;
- 只从最高相关命中的 Canonical Entity 出发,在 Neo4j 查带 Verified Evidence 的路径;
- Controller 判断证据够不够,最多允许一次有目标的补检索;
- Developer Answer 只输出经验证的节点顺序,缺边就把证据缺口记录到
limitations,不补全参数、返回值或部署状态。
如果问题已经给出精确文件和 Symbol,则直接读当前工作区通常更快。RAG 在这里不是必经 Runtime,而是「找入口、补跨来源上下文」的工具;最终代码事实仍应回到当前 Commit 验证。
我坚持的四条不变量
- 来源身份不能在 Embedding 时丢失。 每个 Chunk 都要能回到
source_record_id、Locator、Original Hash 和 CAS URI。 - 相似度不能升级为关系证明。 Vector Hit 是候选;Verified Graph Edge 必须带独立 Evidence。
- 新鲜度不进入内容身份。 时间变化可以影响排序,不能每天制造新的 Content Hash 和 Embedding Identity。
- 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,不暴露来源特定内容。
参考资料
- Lewis 等人的原始论文 Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks;
- Datawhale All-in-RAG:用于补充中文基础概念和章节学习路径,不作为本文项目实现事实的证据;
- Cormack、Clarke、Büttcher 的 Reciprocal Rank Fusion;
- Qdrant Hybrid Queries 官方文档;
- Neo4j Cypher Path Matching 官方文档;
- Qwen3 Embedding 技术报告。
后续十篇不是重复讲一张大图,而是分别解释每个边界为什么存在、当前代码怎样实现、失败时在哪里停,以及我为此接受了什么复杂度。
相关文章
一次代码命中为什么还不算答案
工程检索必须把结果绑到仓库、commit 和 symbol 上;缺失、过期或来自错误仓库的命中,只能当定位线索。
同一个问题,代码、Jira 和 Confluence 各能回答到哪里
跨来源检索真正难的不是找到更多文本,而是判断需求、设计和当前代码分别能证明什么。