代码库 RAG:跨仓库语义检索


文档摘要

代码库 RAG:跨仓库语义检索 本节摘要:2026 年每家正经的工程组织都在内部跑一套「懂语义而非只懂字符串」的代码搜索。Sourcegraph Amp、Cursor 的 codebase answers、Augment 的企业级图谱、Aider 的 repomap、Pinterest 的内部 MCP——形态一致:摄取多个仓库,用 tree-sitter 解析,按函数/类粒度切块,混合检索,重排序,带引用作答。本节要求你构建一个能处理横跨 10 个仓库、200 万行代码的系统,并在每次 git push 后存活增量重索引。你会学到 AST 感知切块、混合(稠密+BM25)检索、交叉编码器重排序、引用忠实度后过滤,以及把 50 个文件的 push 重索引压到 60 秒内的基础设施功夫。

代码库 RAG:跨仓库语义检索

本节摘要:2026 年每家正经的工程组织都在内部跑一套「懂语义而非只懂字符串」的代码搜索。Sourcegraph Amp、Cursor 的 codebase answers、Augment 的企业级图谱、Aider 的 repomap、Pinterest 的内部 MCP——形态一致:摄取多个仓库,用 tree-sitter 解析,按函数/类粒度切块,混合检索,重排序,带引用作答。本节要求你构建一个能处理横跨 10 个仓库、200 万行代码的系统,并在每次 git push 后存活增量重索引。你会学到 AST 感知切块、混合(稠密+BM25)检索、交叉编码器重排序、引用忠实度后过滤,以及把 50 个文件的 push 重索引压到 60 秒内的基础设施功夫。

对应原课程:Phase 19 · Lesson 02 · rag-over-codebase(原英文 phases/19-capstone-projects/02-rag-over-codebase/docs/en.md)。

学习目标

阅读完本节,你应当能够:

  1. 解释为什么光靠上下文窗口解决不了跨仓库问题,以及朴素余弦搜索为什么会中毒。
  2. 用 tree-sitter 在 AST 节点边界切块,而非固定 token 窗口,并产出三元表示(稠密嵌入/BM25 词项/自然语言摘要)。
  3. 实现混合检索:稠密与 BM25 并发,合并 top-k,交给交叉编码器重排序。
  4. 用长上下文合成器强制每条论断带「文件:行号」引用,并对无引用答案做后过滤。
  5. 实现增量重索引:基于符号级 diff 只重嵌入变更块,50 个文件的 push 在 60 秒内可查。
  6. 在 100 个跨仓库问题上度量 MRR@10、nDCG@10、引用忠实度与 p50/p99 延迟。

一、问题与直觉

到 2026 年,每个前沿编程 Agent 都自带代码库检索层,因为光靠上下文窗口解决不了跨仓库问题。Claude 的 100 万 token 上下文有帮助,但消除不了对排序检索的需求。在原始块上做朴素余弦搜索,会在生成的代码、monorepo 重复、罕见导入符号的长尾上把结果搞砸。生产答案是:在 AST 感知块上做混合(稠密 + BM25)检索加重排序,背后是一张符号引用图。

你要学的,是去索引一支真实的舰队(不是一个教程仓库),并度量 MRR@10、引用忠实度、增量新鲜度。失败模式是基础设施性的:10 万文件的 monorepo、一次触碰半数文件的 push、一个需要跨四个仓库才能答对的问题。

二、从零实现

AST 感知摄取流水线用 tree-sitter 解析每个文件,提取函数和类节点,在节点边界而非固定 token 窗口切块。每个块得到三种表示:一个稠密嵌入(Voyage-code-3 或 nomic-embed-code)、稀疏 BM25 词项、一段简短的自然语言摘要。摘要增加了第三种可检索模态——用户问「X 是怎么被授权的」,摘要里提到「authz」,哪怕代码里只有 check_permission

块摘要器骨架(批量 + 提示缓存):

SUMMARY_PROMPT = ( "用一句话总结这个函数,点明它的公开契约与副作用。\n\n代码:\n{body}" ) # 批量进 Haiku 4.5,系统前导词 prompt-cache,存到块记录里 chunk = {"repo","path","start_line","end_line","symbol","body","summary"}

检索是混合的。一次查询同时触发稠密与 BM25 搜索,合并 top-k,把并集交给交叉编码器重排序(Cohere rerank-3 或 bge-reranker-v2-gemma-2b)。重排序后的列表送给长上下文合成器(Claude Sonnet 4.7 配提示缓存,或自托管的 Llama 3.3 70B),指示它对每条论断按文件与行号范围引用。没有引用的答案被后过滤器拒掉。

BM25 字段加权(让「按名找」与「按义找」并存):

TANTIVU_WEIGHTS = { "symbol_name": 4, # 权重最高:按名找函数 "summary": 2, # 自然语言桥接 "symbol_body": 1, # 正文兜底 }

增量新鲜度是基础设施题。git push 触发 diff:哪些文件变了、哪些符号变了。只对受影响的块重嵌入。受影响的跨文件符号边(导入、方法调用)重算。索引在不每次提交重处理 200 万行的前提下保持一致。

三、架构与技术栈

  • 解析:tree-sitter,17 种语言语法(Python/TS/Rust/Go/Java/C++ 等)。
  • 稠密嵌入:Voyage-code-3(托管)或 nomic-embed-code-v1.5(自托管),bge-code-v1 兜底。
  • 稀疏索引:Tantivy(Rust),BM25F,按符号名 vs 正文做字段加权。
  • 向量库:Qdrant 1.12 混合搜索,或 pgvector + pgvectorscale(向量数 < 5000 万的团队)。
  • 块摘要模型:Claude Haiku 4.5 或 Gemini 2.5 Flash,提示缓存。
  • 重排序:Cohere rerank-3 或自托管 bge-reranker-v2-gemma-2b。
  • 编排:摄取用 LlamaIndex Workflows,查询用 LangGraph。
  • 合成器:Claude Sonnet 4.7(100 万上下文)配提示缓存。
  • 符号图:Neo4j(托管)或 kuzu(嵌入式),存导入与调用边。
  • 可观测性:Langfuse,每次检索 + 合成各一个 span。

四、可复用产物

可复用技能 outputs/skill-codebase-rag.md:给定一组仓库语料,它搭起摄取流水线、混合索引与查询 Agent,对任意跨仓库问题返回带引用的答案。评分量表:

权重 标准 度量方式
25 检索质量 100 题留出集上的 MRR@10nDCG@10
20 引用忠实度 答案论断中带可验证 file:line 锚的比例
20 延迟与规模 索引语料规模下 1 万 QPS 的 p95 查询延迟
20 增量索引正确性 50 文件 commit 从 git push 到可查的耗时
15 UX 与答案格式 引用可点击、片段预览、追问能力
100

一次典型问答:

$ code-rag ask "how is S3 multipart abort wired into our retry budget?" [retrieve] 12 chunks dense + 7 chunks bm25, 16 unique after dedup [rerank] top-5 kept (cohere rerank-3) [synth] claude-sonnet-4.7, cache hit rate 68%, 2.1s answer: Multipart aborts are triggered by `AbortMultipartOnFail` in services/uploader/retry.go:122-148, which decrements the per-bucket retry budget defined in config/budgets.yaml:34-51 ... citations: [services/uploader/retry.go:122-148, config/budgets.yaml:34-51, libs/s3client/multipart.ts:44-61]

五、框架对比

Qdrant 的原生混合搜索开箱即用,适合中等规模;pgvector + pgvectorscale 对已有 Postgres 栈的团队更友好,但 p99 在大批量下需调优;Vespa 在多向量与 MaxSim 上更专业,但运维更重。嵌入侧,Voyage-code-3 托管质量最稳,nomic-embed-code 自托管省成本,差距在开了重排序后通常会收窄。合成器层面,Claude Sonnet 4.7 配提示缓存是性价比之选,1M 上下文能装下大量重排序块。

六、练习

  1. 换嵌入:把 Voyage-code-3 换成自托管 nomic-embed-code,度量 MRR@10 差值,报告开了重排序后差距是否收窄。
  2. 注入生成代码:往语料里注入 20% LLM 生成的样板代码,重评估,观察检索中毒;给 payload 加 generated 标志并对这类命中降权。
  3. 基准对比:在你的语料规模上基准测试 Qdrant 混合搜索 vs pgvector + pgvectorscale,报告批量大小为 1 时的 p99。
  4. 漂移巡检:加一个基于采样的漂移检查——每周重跑 100 题评估,MRR@10 跌幅 > 5% 时告警。
  5. 跨语言符号:扩展到跨语言符号解析——一个 Python 函数通过 gRPC 调 Go 服务,用符号图把它们连起来。

本节要点回顾

  1. 窗口不够:100 万 token 上下文有帮助但不消除排序检索需求;朴素余弦会在生成代码、重复、长尾上中毒。
  2. AST 感知切块:在 tree-sitter 节点边界切,而非固定 token 窗口。
  3. 三元表示:稠密嵌入 + BM25 词项 + 自然语言摘要,三种模态互补。
  4. 混合检索 + 重排序:稠密与 BM25 并发,合并 top-k,交叉编码器重排序。
  5. 引用强制:合成器对每条论断给 file:line 引用,无引用答案被后过滤拒掉。
  6. 增量新鲜度:符号级 diff,只重嵌入变更块,50 文件 push 在 60 秒内可查。
  7. 度量矩阵:MRR@10、nDCG@10、引用忠实度、p50/p99 延迟,在真实舰队而非教程仓库上度量。

下一节,我们把战场从「文本」搬到「语音」——构建一个端到端延迟低于 800ms 的实时语音助手。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U