代码库知识库系列(04):三种 Chunking 策略对比——AST 精确分割反而输了?

一个几乎所有人都会犯的错

要把代码切成块(chunk)喂给向量模型,最"专业"的做法是什么?

大多数工程师的第一反应是:按 AST(抽象语法树)切,一个函数一个 chunk。理由很硬——函数是最自然的语义单元,AST 精确对齐了语义边界,不会把一个函数拦腰截断。相比之下,按固定行数硬切(比如每 20 行一块)显得又土又蠢,会把函数切得七零八落。

听起来无懈可击。可实验数据把这个直觉打了个响亮的耳光。

在同一份 266 行的 Python 代码上,我跑了三种 Chunking 策略。结果是:AST 函数级分割的得分最低。那个"又土又蠢"的固定行分割,反而拿了满分。

这不是随机噪声,背后有一个非常值得挖的机制。


三种策略

数据集:266 行 Python 代码,涵盖认证、数据库、缓存、支付、通知 5 个模块,共 27 个函数。Embedding 模型统一用bge-large-zh-v1.5,评估用 12 个自然语言查询。

三种切法:

策略 1:固定行分割(每 20 行,overlap=3)

不管代码结构,每 20 行切一刀,相邻 chunk 重叠 3 行防止边界信息丢失。

Chunk 1: 第 1-20 行 Chunk 2: 第 18-37 行 ← 和上一块重叠 3 行 Chunk 3: 第 35-54 行 ...

策略 2:文件级分割(整个文件一个 chunk)

简单粗暴,整份代码就是一个 chunk。

Chunk 1: 第 1-266 行 ← 全塞进去

策略 3:AST 函数级分割(一个函数一个 chunk)

用 Python 的ast模块解析语法树,按函数定义精确切割。

Chunk 1: def validate_jwt_token(...) 第 9-18 行 Chunk 2: def hash_password(...) 第 20-28 行 Chunk 3: def create_payment_intent(...) 第 ... ...

先看一眼固定行分割"土"在哪里。以validate_jwt_token这个函数为例(第 9-18 行,共 10 行):

函数 'validate_jwt_token'(第 9-18 行,10 行) 被切进 2 个 chunk: Chunk 第 1-20 行: 包含函数第 9-18 行(10/10 行) Chunk 第 18-37 行: 包含函数第 18-18 行(1/10 行) 问题:没有任何一个 chunk 包含完整的函数。 一个关于 'validate_jwt_token' 的查询会检索到一段残缺的实现。

看到没?函数的最后一行(第 18 行)被切到了下一个 chunk 里。这正是固定行分割最被诟病的地方——它不认识函数边界,说切就切。

按理说,这种残缺应该会拖累检索效果。AST 分割每个函数都完完整整,怎么看都该赢。


运行结果

Strategy Chunks AvgLen R@3 R@5 vs AST R@5 ──────────────────────────── ─────── ─────── ─────── ─────── ────────── 1_fixed_lines_20 16 755 0.917 1.000 +0.042 2_file_level 1 10270 1.000 1.000 +0.042 3_ast_function 27