2026-09-22
CMU提出cAST,按抽象语法树递归切分再合并兄弟节点。RepoEval上Recall@5最高加4.3,StarCoder2补全Pass@1平均加5.5。
代码 RAG 的第一步是切块。绝大多数流水线还在用按行切、固定窗口的办法,从自然语言 RAG 原样搬过来。函数体、类定义、返回值声明经常被从中间切断,检索器拿到半截函数,生成模型就会猜错接口。
论文给了一个直观例子:computestats 被切成两段后,模型误以为它返回一个字典,写出的补全直接跑不通。对仓库级补全、跨文件引用、GitHub issue 修 bug 这类任务,切块边界决定了模型能看见什么。
cAST 用 tree-sitter 把源码解析成抽象语法树,再走一套递归的「先切后合」流程。
设计目标写得很清楚:语法完整、块内信息密度高、不写语言特判、切完拼回去必须和原文件逐字节一致,方便直接替换现有 RAG 流水线。
评测覆盖 RepoEval 仓库内补全、CrossCodeEval 多语言跨文件、SWE-bench Lite 的 300 道单文件修 issue。检索看 nDCG、Precision、Recall@5,生成看 Pass@1 或精确匹配。
| 设置 | 指标 | cAST | 按行切 |
| RepoEval + GIST | Recall@5 | 75.0 | 70.7 |
| RepoEval + GIST + StarCoder2 | Pass@1 | 57.9 | 51.2 |
| RepoEval + CodeSage + StarCoder2 | Pass@1 | 73.2 | 67.6 |
| SWE-bench Lite + CodeSage + Claude | Pass@1 | 16.7 | 14.0 |
| CrossCodeEval + CodeSage, 代码 EM | 29.1 | 24.8 |
摘要里写的 Recall@5 最高加 4.3,对应 GIST 在 RepoEval 上从 70.7 到 75.0。StarCoder2-7B 在 RepoEval 三条检索器上分别加 4.2、6.7、5.6,平均约 5.5。SWE-bench 上 Claude 的 Pass@1 最多加 2.7。只切不合的消融里,BGE 的 nDCG 从 71.1 掉到 53.5,小节点把索引撑爆了。
块大小在 2000 到 2500 字符时检索和生成最好。上下文从 4000 提到 8000 并不稳定变好,CodeSage 加 StarCoder2 甚至从 73.2 掉到 69.2。下游更跟 Precision 走,跟 Recall、nDCG 相关性弱:必要证据进了 top-k 之后,再往里塞低排名块会引入干扰。
这是 RAG 里最便宜的一环。不换检索器、不换生成模型,只换切块。对写代码 agent 的人,tree-sitter 加递归合并就能落地,仓库已开源。对做代码 RAG 评测的人,固定行切会系统性低估结构敏感方法。
增益是渐进的,不是换架构级别的跳跃。CodeSage、Jina 这类代码检索器吃结构块更明显,通用文本检索器也能涨,但幅度小一截。
作者自己点了三处。公平起见,实验没有把上层 AST 节点当多级上下文塞进去,而文本 RAG 里这种层级信息已经被证明有用。切的是纯代码文件,没把注释、伪代码、输入输出说明当多视图候选。也没有用执行轨迹、类型推断、编译信息做按查询自适应。
SWE-bench 用的是 Lite,且限制单文件修改,Pass@1 绝对值仍在 13% 到 17%。表 7 里 BGE-large 加 Claude 在 cAST 下是 13.3,按行切反而是 14.6,并非所有检索器都涨。跨语言表里 TypeScript 的 EM 仍然明显低于 Python 和 Java,结构切块减了语言间波动,但没有抹平语言难度。