从混合检索到 Agentic GraphRAG:代码仓库问答的设计笔记
这篇笔记由 2026 年 3 月的项目阶段记录整理成文,项目代码与时间线可在 CodeRag 查看。
做代码仓库问答时,我很快发现:只把代码切块后放进向量库,能够回答“这段代码大概在做什么”,却很难稳定回答“谁调用了它”“一条请求怎样穿过多个模块”“修改这个符号会影响哪里”。原因并不神秘——代码的核心信息不只存在于文本相似度里,还存在于结构关系里。
因此,这个项目的目标不是再做一个聊天壳,而是构建一条能够在文本证据与图关系之间主动切换的检索链路。
1. 先把代码变成两种可检索表示
第一种表示是文本。仓库文件经过读取、清洗和切分后,分别进入 BM25 与向量索引:
- BM25 擅长精确命中函数名、类名、配置键与报错文本;
- 向量检索擅长处理自然语言问题和代码描述之间的语义差异;
- 两路结果通过 RRF(Reciprocal Rank Fusion)合并,减少对某一路分数尺度的依赖。
第二种表示是符号图。Tree-sitter 负责从不同语言的语法树中抽取函数、类、方法与导入关系,再将 CALLS、IMPORTS、OWNS、HERITAGE 等边写入 KuzuDB。这样,系统不需要让大模型“猜”调用关系,而是可以执行邻居扩展和路径搜索。
2. 为什么需要 Agent 路由
并不是每个问题都需要 GraphRAG。比如“README 中如何启动服务”通常用关键词检索就够了;“解释 handle_request 到 save_order 的调用路径”才需要符号消歧和图搜索。
我把流程拆成多个职责清晰的节点:
router判断是直接回答、普通 RAG、GraphRAG 还是调用路径问题;symbol_suggest_tool对短名进行前缀与模糊匹配,避免同名符号误判;retrieve_tool执行语义或混合检索;expand_graph_tool沿调用、导入和归属边扩展上下文;call_path_tool查询两个符号之间的路径;answer只基于整理后的context_pack生成回答;verify检查证据是否足够,必要时改写查询并重试一次。
这里最重要的不是节点数量,而是“决策可以被观察”。前端能够看到 route、attempts、节点调用顺序、引用片段和原始 trace。调试时,我可以分辨到底是路由选错、符号消歧失败、检索召回不足,还是回答阶段没有遵守证据。
3. 上下文不是越多越好
图扩展很容易带来新的问题:一个高连接度符号可能在两跳内扩出大量节点。如果把所有结果交给模型,既浪费上下文,也会稀释真正相关的证据。
我的处理方式是给扩展设置明确边界:限制深度、限制每类边的数量、保留初始命中与扩展命中的来源,并在组装 context_pack 时优先保留能直接回答问题的符号与代码片段。换句话说,图不是为了“查得更多”,而是为了“补齐文本检索缺失的关系”。
4. 自验证应检查证据,而不是检查文风
第一次实现验证节点时,很容易写成“你觉得回答好不好”。这类检查太主观,也不能驱动下一步动作。更有效的方式是检查结构化条件:
- 路径类问题是否真的得到路径结果;
- 回答中的关键结论是否有对应 citation;
- 符号存在歧义时是否已经完成消歧;
- 检索结果是否覆盖问题中的关键实体。
只有检查失败时才触发一次重试,并提高检索范围或改写查询。有限循环比无限“反思”更容易控制延迟与成本。
5. 这次实践留下的结论
代码 RAG 的质量不只取决于模型。解析质量、符号命名、混合检索、图扩展边界、引用格式和运行轨迹共同决定了系统是否可信。Agent 的价值也不是让每个问题都经过复杂流程,而是让系统能够根据问题类型选择最小充分路径,并在证据不足时知道下一步该做什么。
参考资料
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!












