一次代码命中为什么还不算答案
工程检索必须把结果绑到仓库、commit 和 symbol 上;缺失、过期或来自错误仓库的命中,只能当定位线索。
本页目录 · 9 节
语义检索最容易制造的错觉,是让「很像」冒充「就是它」。大型工程中存在同名 Method、复制过的模块、已经删除的旧路径和跨仓库 Contract;一个非常高的相似度只能说明文本接近,不能说明它属于目标仓库、当前 Commit,也不能证明两个 Symbol 之间有调用关系。
Canonical Resolution 的任务,是在检索之前建立稳定实体,在关系上绑定独立 Evidence,并在查询时把版本不确定的结果降级成 Locator。
本篇知识地图
flowchart LR
S["Source-specific Keys"] --> C["Canonical Entity ID"]
R["Revision / Commit / Version"] --> E["Evidence Locator"]
L["File / Line / Section / Page"] --> E
C --> G["Canonical Node"]
E --> X["Evidence-backed Edge"]
G --> X
X --> V{"VERIFIED?"}
V -->|yes| P["Verified Path"]
V -->|inferred / ambiguous / stale| O["Candidate / Locator only"]
核心知识点包括 Stable Entity Identity、Revision Evidence、Confidence 分层、Exact Identifier、Conflict Handling 和 Verified Path。
实体身份与这一版证据必须分开
目标 code_symbol_id() 的 Canonical Key 由 Workspace、Repository、Language、Relative Path、Kind 和 Qualified Name 构成。Commit SHA、Line Range 和 Content Hash 不进入这个 Helper 的 Symbol ID,而进入 Evidence Revision/Locator。
这让系统同时表达两件事:
RebuildProjectionJob是一个跨 Commit 保持稳定的逻辑实体;- 我现在引用的是 Commit
x中path:line的那一次观察。
如果把 Line 放进 Entity ID,代码移动一行就会产生新实体;如果完全不保存 Commit,旧索引又会伪装成当前实现。Canonical Identity 负责合并「同一个对象」,Evidence 负责保留「哪一版、哪里看到」。
当前实现还没有完全达到这个目标:实际 CPG _node_id() 把 Line 编入 Hash,所以 Symbol 移动行号会改变 CPG/Canonical Node Identity。现有 Stable-ID 测试覆盖 Helper,不等于实际 CPG Node 已稳定。这是一个真实 Identity Gap,后续需要让 CPG 生产者和 Canonical Helper 收敛,而不是在文章里把目标合同写成完成事实。
Canonical Graph 不是一张无来源关系网
当前 SystemGraph 把 Node、Edge 和 Evidence 分开:
CanonicalNode: id / kind / name / source_keys / properties
CanonicalEdge: source / target / relation / confidence / evidence_ids
Evidence: source_record_id / locator / excerpt_hash / revision / cas_uri / verification
每条 Edge 必须至少有一条 Evidence。VERIFIED Edge 还必须至少关联一条 verification=verified 的 Evidence;只有 Candidate Evidence 时,代码拒绝创建 Verified Edge。
同一 Canonical Node 从多个 Source Key 出现时会合并 Source Keys 和 Properties;Kind 冲突会报错,不会把一个 Page 和一个 Method 因同名合成同一对象。重复 Evidence ID 如果 Locator、Excerpt Hash、Revision 或 CAS URI 不一致,也会报 Identity Conflict。
flowchart TB
N1["Method: resolver"] -->|CALLS| N2["Method: provider"]
E1["repo + commit + file + line"] -. supports .-> N1
E2["repo + commit + file + line"] -. supports edge .-> N2
J["Jira says expected behavior"] -. context only .-> N1
Jira 可以证明需求背景,不能替代 Code Edge Evidence。Confluence 可以解释业务规则,不能把一个 AMBIGUOUS Call 提升成 VERIFIED。
Exact Match 为什么仍然要检查 Repo 与 Commit
精确命中 RebuildProjectionJob 比语义命中「重建投影任务」更强,却仍有三个锚点:
- Expected Repo:命中是否来自目标仓库;
- Expected Commit:索引是否对应当前要解释的 Revision;
- Exact Symbol:Payload 中是否真的包含目标 Symbol,而不是摘要里提过。
只读定位命令可以显式传入这些约束:
rtk uv run --frozen --offline --no-sync --python 3.11 \
python -B -m mt_rag.cli retrieve-qdrant-policy "RebuildProjectionJob" \
--source-type code --embedding-backend local-qwen \
--local-files-only --no-write \
--expected-repo "<target-repository>" --expected-commit "<current-head>" \
--exact-symbol RebuildProjectionJob
--no-write 不创建 Query Log 或 Result 文件,--local-files-only 禁止临时访问模型仓库,Offline/No-sync 避免安装依赖。诊断为 missing、stale、wrong_repo 或 unknown 时,结果只能用于找入口,不能支撑「当前系统就是这样工作」的结论。
Graph Path 如何保持 Fail Closed
System Graph Query 在查路径前先验证三层版本:Graph Projection Manifest 必须对应当前 Source Manifest;Graph Consumer Cursor 必须等于当前 Generation;Neo4j 必须存在相同 Projection Hash 的 Complete Marker。
Cypher 路径再要求:
- 所有 Node 都属于 Active Projection;
- 所有 Relationship 的
confidence='VERIFIED'; - 每条 Relationship 至少有 Evidence ID;
- 返回后逐条加载
SystemEvidenceV1,确认每个 Required Evidence 都存在且为verified。
任一条件失败就报 Stale 或 Evidence Missing,不返回「大部分正确」的调用链。Path 还从最高排名 Retrieval Entity 的 graph_ids 开始,而不是从次要候选借一条真实但偏题的关系。
一次命中之后,真正的验证才开始
假设检索返回一个 Resolver:
- 先看 Payload 的 Repo、Commit、File、Line 与 Symbol;
- 回当前目标 Commit 打开文件,确认 Signature 与实际 Body;
- 沿 Import、Provider、GraphQL Schema 或 HTTP Contract 继续读;
- 图中有 Verified Edge 时检查其 Evidence Locator;
- 问题涉及生产行为时,静态链路到此仍然不够,需要部署和 Runtime Readback。
RAG 命中像地图;Canonical ID 是坐标;Revision Evidence 是地图版本;当前源码与运行时才是现场。
失败模式与取舍
| 诊断 | 含义 | 正确下一步 |
|---|---|---|
missing |
索引没有预期实体 | 回当前 Repo 搜索,检查 Rename/Extractor Coverage |
stale |
命中不对应 Expected Commit | 仅用旧路径定位,在当前 Commit 重新追踪 |
wrong_repo |
同名内容来自其他仓库 | 切回目标 Repo,不拼接跨仓库结论 |
unknown |
身份或 Currentness 无法确认 | 明确标未知,不提高 Confidence |
AMBIGUOUS Edge |
静态解析只看到了 Call Name | 保留候选,不进入 Verified Chain |
| Graph Cursor Lag | Vector 新、Graph 旧 | 可以展示文本候选,调用链必须报 Stale |
| Canonical Kind Conflict | Source Key 被解析成不同实体类型 | 阻断合并,修 Resolver/Schema,而不是后写覆盖 |
| CPG Node ID 含行号 | Symbol 仅移动行也可能生成新实体 | 目前作为已知 Identity Gap,不能声称跨版本完全稳定 |
Canonical Resolution 增加了 ID 设计、Schema 和 Conflict Handling 成本,却避免最危险的错误:把一个真实存在但属于错误版本的对象,写成当前答案。
可复核的项目证据
src/mt_rag/core/stable_ids.py:Source、Repository、Code File/Symbol 等稳定 ID;src/mt_rag/system_graph/canonical.py:Canonical Node/Edge/Evidence、Conflict 与 Verified Path;src/mt_rag/retrieval/system_index.py:Payload 中的 Source ID、Locator、Graph IDs、Commit Currentness;src/mt_rag/system_graph/query.py:Manifest/Cursor/Projection Gate 与 Verified-only Cypher;docs/02-runbooks/retrieval-usage.md:Expected Repo/Commit/Exact Symbol 与 Locator-only 诊断;docs/03-reference/code-cpg-system-graph-v1.md:Full CPG、Semantic CPG 和 Confidence 合同。
参考资料
- W3C PROV-O;
- Git Revisions 官方文档;
- SCIP Code Intelligence Protocol;
- Neo4j Constraints 官方文档;
- Neo4j
MERGE官方文档; - Qdrant Filtering 官方文档。
工程 RAG 的质量不只取决于有没有找到相似内容,更取决于系统能否证明:这是哪个实体、哪一版观察、哪条关系,以及证据到哪里为止。
相关文章
RAG 不是一条必经流水线,而是一种按需取证的能力
先理解目标、先看当前源码,只有跨来源信息真的能改善判断时,才调用 Local RAG 去找证据候选。
同一个问题,代码、Jira 和 Confluence 各能回答到哪里
跨来源检索真正难的不是找到更多文本,而是判断需求、设计和当前代码分别能证明什么。