CMU按语法树切代码块,RepoEval补全最高加6.7分

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 + GISTRecall@575.070.7
RepoEval + GIST + StarCoder2Pass@157.951.2
RepoEval + CodeSage + StarCoder2Pass@173.267.6
SWE-bench Lite + CodeSage + ClaudePass@116.714.0
CrossCodeEval + CodeSage, 代码 EM29.124.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,结构切块减了语言间波动,但没有抹平语言难度。

术语

原文与代码

社区讨论

全部论文解读