给代码库提问,先看返回的依据
读一个陌生项目时,我经常知道自己在找什么,却不知道实现在哪个文件。例如想看“订单过期以后如何回补库存”,需要先定位负责这件事的函数,再沿着调用关系理解上下文。
CodeRAG 当前做的就是检索这一步:扫描本地代码库,返回相关代码块、路径、起止行号和分数。它没有接入向量检索、rerank 或 LLM 回答。把检索先做成独立服务,能单独检查“找对了没有”,不必用一段看起来流畅的回答掩盖证据缺失。
扫描范围先决定哪些内容有资格进入索引
当前扫描器跳过依赖目录、构建产物、符号链接和 junction,也限制文件数量、文件大小和源码总字节数。根目录的 .gitignore 会参与过滤,但实现采用简化规则,不声称完全复现 Git 的匹配语义。
这里一个容易忽略的区别是:没有找到相关代码,与相关文件根本没有进入索引,不是同一个问题。排查搜索结果之前,先看索引文件数、解析警告和支持的语言。
项目目前支持 Python、Java、JavaScript/JSX 和 TypeScript/TSX。mini-redis 是 C++,不能因为扫描完成且没有报错,就声称已经支持它的语法结构。
为什么按结构分块
按固定行数切代码,容易把函数签名和函数主体分开,也可能截断类的方法上下文。CodeRAG 使用 Tree-sitter 识别声明结构,保留函数、方法、类等块,同时保留模块级块,给不在声明体内的代码一个检索入口。
结构分块也不意味着块之间不会重叠。一个类块可能包含某个方法,模块块又覆盖同一段代码。搜索结果如果连续返回同一文件的多个重叠片段,看起来命中了很多条,实际可用信息可能很少。
所以“返回多少块”与“覆盖多少相关文件”需要分别观察。unique_files 选项会每文件保留最好的块,再取 Top K;它改变的是结果组织方式,不能自动保证回答更正确。
代码分词要照顾标识符
源码里经常出现 OrderService、scan_repository 一类名字。当前分词器既保留整体词项,也拆分常见驼峰和下划线片段。这样查询完整函数名时仍有精确入口,查询 order 或 repository 时也有机会召回对应实现。
BM25 在这里主要利用词项出现情况、文档频率和代码块长度评分。它适合函数名、配置名和错误关键词这一类有明确字面线索的查询;当问题只剩自然语言描述,而源码没有相应词汇时,就可能漏检。
这也是我在面试研习社练习 RAG 问题时会先强调的边界:检索方式、语料和提问语言之间需要匹配,不能只列出“切块、召回、生成”几个步骤。
用真实项目做一轮阅读
从仓库根目录先为自身建索引,再查一个确实存在的函数:
.venv/Scripts/python -m backend.cli index coderag .
.venv/Scripts/python -m backend.cli search coderag scan_repository --top-k 3
拿到结果后,我会先打开返回路径,核对行号是否仍对应当前源码;再看函数上下游,而不是只读截出来的几行。如果源码刚刚修改过,需要重新索引,数据库里保存的快照不会自动变成新版本。
评测要把找得到和读得懂分开
项目的离线评测会对固定问题检查召回与排名。小题集可以发现重构造成的回退,但题目由开发者编写、语料规模也有限,不能据此证明通用代码问答能力。
我更关心逐题失败原因:相关文件没被扫描、分块丢了上下文、词汇对不上,还是排名被其他块挤掉。把错误放回对应阶段,才知道下一步应该改扫描器、切块、分词还是排序。
源码入口是 backend/ingestion/indexer.py、parser.py 和 backend/retrieval/bm25.py。下一篇再记录索引更新与缓存失效,因为搜得快之前,先要保证搜到的是正确版本。