想象你是一家国际厨艺大赛的主评委。十位厨师要在三十分钟内,用同样的半成品食材完成一道招牌菜。你会怎么打分?如果只凭一句“我觉得好吃”,不同评委口味各异,结果既无法复现,选手也不服气。聪明的评委团会设计一套科学的评分体系:先对着参考摆盘照片比对成品的视觉还原度,再检查食材层次和摆盘结构是否合理,最后亲口品尝,验证火候和调味是否真的到位。 AI 代码补全的评测,和这场厨艺大赛如出一辙。你在编辑器里写下半个函数,AI 替你补全后半段——就像厨师料理那份半成品。要判断补全得好不好,同样需要一套“评委体系”:字符层面的比对(对照片)、语法结构的匹配(查摆盘)、功能正确性的验证(真实试吃),再加上一套标准化的比赛题库(评测基准)。这篇文章就带你完整走一遍这套评审系统的设计思路,让你从“凭感觉”升级为“看数据”。

岗位关联

掌握科学的代码补全评测方法,你就能用客观数据替代主观直觉,在模型选型、产品迭代和能力量化中做出可复现的决策——这是 AI 工程师与评测工程师的核心竞争力。

目录
  1. 可视化演示
  2. 评测缘起:为什么不能凭感觉打分
  3. 基础指标:精确匹配与字符串相似度
  4. 编辑距离:允许微调的照片比对
  5. AST匹配:检查摆盘结构
  6. 功能正确性:真正的试吃
  7. 主流基准:标准化的比赛规程
  8. 实践要诀:设计你自己的评审体系
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示编辑距离、AST匹配、功能正确性三种评测方法的原理对比,pass@k指标曲线,以及多模型基准雷达图。

本节配有交互动效(进入视口后自动加载,约 3KB)。

三种评测方法 编辑距离衡量字符级文本相似性 AST匹配评估语法结构正确性 功能正确性是金标准 ED + AST + Test = 全方位评测 三种方法互补,覆盖文本/结构/功能三个维度

评测缘起:为什么不能凭感觉打分

第一届厨艺大赛上,评委团靠直觉打分:有人偏爱清淡,有人偏爱浓油赤酱,同一道菜两个评委给出的分数差了四十分,冠军是谁吵了一个礼拜都没定论。第二年主办方换了一批评委,结果排名完全变了。选手们抱怨:“评分标准到底是什么?”缺乏统一尺度的评判,注定是一笔糊涂账。

代码补全产品也面临同样的困境。今天你让两位工程师分别试用 Copilot 和自研补全工具,一个说“补全挺准”,另一个说“老给我瞎编代码”,你该信谁?更麻烦的是,无论说好还是说坏,结论都复现不出来——换个场景、换个提问方式,体验就变了。评测的意义就在于把“我觉得好用”翻译成可重复测量的数字。

科学的评测通常围绕三个问题展开:测什么(数据)、怎么测(指标)、怎么解读(协议)。先定义一道“评测题目” $x_i$,再选择打分函数 $f$,最后把所有题目的得分平均,就得到了模型能力的整体估计。这套框架支撑起三大应用场景:横向对比不同模型、纵向监控同一模型的版本回归,以及把能力量化给管理层和客户看。

就像厨艺大赛第二年发布的《评分手册》把“好吃”拆成色、香、味、形四个可打分维度,AI 评测也要把“好用”拆成可量化的指标。接下来几节,我们依次认识这套手册里的几把尺子。

AbilityM=1N∑i=1Nf(M,xi)\displaystyle \text{Ability}_M = \frac{1}{N} \sum_{i=1}^{N} f(M, x_i)
这个公式说的是:模型 $M$ 的能力,等于它在 $N$ 道评测题上所得分数的平均值。
  1. 评测题目$x_i$ 表示评测集中的第 $i$ 道补全任务,比如“给一个函数签名,补全函数体”。
    xi∈Dataset,i=1,2,…,N\displaystyle x_i \in \text{Dataset},\quad i = 1, 2, \dots, N
  2. 打分函数$f(M, x_i)$ 表示模型 $M$ 在第 $i$ 题上的得分,$f$ 可以是后面要讲的精确匹配、编辑相似度或 Pass@k。
    f(M,xi)∈[0,1]\displaystyle f(M, x_i) \in [0, 1]
  3. 平均聚合$\frac{1}{N}\sum_{i=1}^{N}$ 把 $N$ 道题的得分取平均,得到模型能力的整体估计。
    1N∑i=1N(⋅)\displaystyle \frac{1}{N} \sum_{i=1}^{N} (\cdot)

基础指标:精确匹配与字符串相似度

评审手册里第一条最简单粗暴:把成品菜和参考照片逐像素对比。像素一模一样,满分;任何偏差,零分。这条规则省事,但厨师摆盘时多撒了一粒芝麻就判零分,显然不合理。这就像精确匹配(Exact Match,简称 EM):补全结果与标准答案字符串完全相同才算对。

精确匹配指标的定义非常干脆:给定 $N$ 道题,把“预测文本等于参考答案”的题数占总题数的比例作为得分。它计算快、容易理解,是最早被广泛使用的补全质量指标之一,至今仍是许多论文报告的基础指标。

但它的缺点同样明显:变量名不同、换行符不同、多了一个空格、把 return a + b 写成 return b + a,都会直接得零分。在真实开发中,这些写法完全等价,却被 EM 一票否决。于是评测者开始寻找“允许小差异”的宽松指标,编辑距离由此登场。

用严格的“照片比对”打分,虽然公平,却误伤了许多“其实做得不错”的菜。评委团意识到,需要一把允许微小偏差的尺子。

EM=1N∑i=1N1[M(xi)=yi]\displaystyle \text{EM} = \frac{1}{N} \sum_{i=1}^{N} \mathbb{1}\left[ M(x_i) = y_i \right]
EM 统计的是“与标准答案一字不差”的题目比例,是一个 0 到 1 之间的数值,数值越高说明补全越“照本宣科”。
  1. 单题判定$\mathbb{1}[\cdot]$ 是指示函数,括号内的等式成立取 1,不成立取 0。
    1[M(xi)=yi]={1完全一致0否则\displaystyle \mathbb{1}[M(x_i) = y_i] = \begin{cases} 1 & \text{完全一致} \\ 0 & \text{否则} \end{cases}
  2. 统计总数$\sum_{i=1}^{N}$ 把“与标准答案完全相同”的题数累加起来。
    ∑i=1N1[⋅]\displaystyle \sum_{i=1}^{N} \mathbb{1}[\cdot]
  3. 归一化比例$\frac{1}{N}$ 将累加结果转化为通过率,落在 0 到 1 之间。
    正确题数N\displaystyle \frac{\text{正确题数}}{N}

编辑距离:允许微调的照片比对

评委团改进了规则:比对照片时,允许三种“微调”——把成品里的某种食材换掉(替换)、在成品里多放一种食材(插入)、去掉一种食材(删除),每做一次微调记一次“调整费”。最终,把两张照片调整到一模一样所需的最少调整费,就是两盘菜的“编辑距离”。距离越小,还原度越高。

计算机科学家莱文斯坦早在 1965 年就提出了这套算法,因此它也叫莱文斯坦距离(Levenshtein Distance)。对两段文本 $a$ 和 $b$,编辑距离就是通过插入、删除、替换三种操作,把 $a$ 变成 $b$ 所需的最少操作次数。它可以用动态规划高效求解——开头动效演示里那个 4×4 的数字表格,就是它在逐格填表:从左上角的 0 出发,每个格子取“删除、插入、替换”三者中的最小值。

为了让分数不受代码长度影响,评测时通常把距离归一化:相似度 = 1 − 编辑距离 ÷ 两段文本中较长的长度。两段代码越接近,相似度越接近 1。

逐格计算得出。

编辑距离让“多了一粒芝麻”不再被判零分,但新问题又来了:两盘菜都可能少放盐(同样的功能性错误),照片比对却都给高分。字符层面的对比,终究看不到“这盘菜靠不靠谱”。

Sim(a,b)=1−ED(a,b)max⁡(∣a∣,∣b∣)\displaystyle \text{Sim}(a, b) = 1 - \frac{\text{ED}(a, b)}{\max(|a|, |b|)}
这个公式把“最少编辑次数”换算成 0 到 1 的相似度:编辑距离越小,相似度越高,1 表示完全相同。
  1. 编辑距离$\text{ED}(a,b)$ 表示把 $a$ 变成 $b$ 最少需要的插入、删除、替换次数,由动态规划按递推式
    ED(i,j)=min⁡{ED(i−1,j)+1ED(i,j−1)+1ED(i−1,j−1)+1[ai≠bj]\displaystyle \text{ED}(i,j) = \min \begin{cases} \text{ED}(i-1, j) + 1 \\ \text{ED}(i, j-1) + 1 \\ \text{ED}(i-1, j-1) + \mathbb{1}[a_i \neq b_j] \end{cases}
  2. 最长长度归一$\max(|a|,|b|)$ 取两段文本较长者的长度作为分母,把距离压缩到 0 到 1 的相对尺度。
    max⁡(∣a∣,∣b∣)\displaystyle \max(|a|, |b|)
  3. 补数转换$1 - \dfrac{\text{ED}}{\max}$ 把“距离”翻转为“相似度”,距离为 0 时相似度为 1。
    1−ED(a,b)max⁡(∣a∣,∣b∣)\displaystyle 1 - \frac{\text{ED}(a,b)}{\max(|a|,|b|)}

AST匹配:检查摆盘结构

资深评委不会只看照片。他们会观察这道菜的结构:酱汁是淋在表面还是混进食材里?主菜和配菜的层次是否清晰?这些结构特征,照片上体现不出来,却决定了菜品的专业度。对代码而言,这个“结构”就是抽象语法树(Abstract Syntax Tree,AST)。

编译器在真正理解代码之前,会先把源码解析成一棵树:从根节点出发,函数定义、参数列表、返回语句都是一个个节点,嵌套关系对应树的父子结构。“返回 a 加 b”和“返回 b 加 a”虽然字符不同,AST 结构却一模一样;而“返回 a 减 b”虽然只差一个字符,AST 中却换了一个运算符节点。AST 匹配就是把参考代码与补全代码各自解析成语法树,统计两棵树中“类型与内容都相同”的公共节点数量,再用 Dice 系数归一化,得到 0 到 1 的结构相似度。更严格的做法是计算树编辑距离(Tree Edit Distance),把编辑距离算法搬到树上,允许对节点做插入、删除、替换操作。

检查摆盘结构能识别“形似而神异”的菜,但结构再完美,也代替不了最后一步——亲口尝一尝。如果菜是生的,摆盘再漂亮也是废品。功能正确性,就是这最后一道试吃。

ASTSim(T1,T2)=2×∣Common(T1,T2)∣∣Nodes(T1)∣+∣Nodes(T2)∣\displaystyle \text{ASTSim}(T_1, T_2) = \frac{2 \times |\text{Common}(T_1, T_2)|}{|\text{Nodes}(T_1)| + |\text{Nodes}(T_2)|}
这个公式衡量两棵语法树的“重叠程度”:公共节点越多,结构越相似,1 表示结构完全一致。
  1. 两棵语法树$T_1, T_2$ 分别是补全代码与参考代码解析出的抽象语法树。
    T1=parse(pred),T2=parse(ref)\displaystyle T_1 = \text{parse}(pred),\quad T_2 = \text{parse}(ref)
  2. 公共节点$\text{Common}(T_1, T_2)$ 是两棵树中类型与内容都一致的节点集合,其大小乘以 2。
    2×∣Common(T1,T2)∣\displaystyle 2 \times |\text{Common}(T_1, T_2)|
  3. 节点总数分母$|\text{Nodes}(T_1)| + |\text{Nodes}(T_2)|$ 是两棵树的节点数之和,用于归一化。
    ∣Nodes(T1)∣+∣Nodes(T2)∣\displaystyle |\text{Nodes}(T_1)| + |\text{Nodes}(T_2)|
  4. Dice 归一化比值落在 0 到 1,结构越像得分越高。
    2×∣Common∣∣T1∣+∣T2∣∈[0,1]\displaystyle \frac{2 \times |\text{Common}|}{|T_1| + |T_2|} \in [0, 1]

功能正确性:真正的试吃

厨艺大赛最有说服力的环节永远是试吃:夹起一筷子放进嘴里,咸淡、生熟、口感,一尝便知。代码评测里对应的动作是运行测试用例——像评委一样,给补全出来的代码输入真实数据,检查输出是否符合预期。这叫功能正确性验证。

具体做法是:每道补全题都附带一组单元测试,把补全结果放回原函数体中,编译、运行、断言。全部用例通过,这题才算通过;任何一个用例失败,这就是一道“夹生的菜”。比如要求实现加法,模型补全成 return a - b,代码能编译、能运行,但测试一跑就露馅。

由于大模型生成具有随机性,一次生成可能“手抖”,评测时普遍采用 Pass@k:对每道题生成 $k$ 个候选,只要其中至少一个通过全部测试,就算这题通过,最后统计通过比例。这相当于给厨师 $k$ 次重做机会,只要端出一盘合格的就计分。

试吃是终极标准,但成本不低:写测试用例、搭运行环境、处理超时,都很费功夫。而且一场比赛只能覆盖有限的菜式——于是评测界开始建设“标准化比赛”:把题目、测试和打分规则固化成大众认可的题库。这就是基准。

Pass@k=1N∑i=1N1[⋁j=1kpass(Si,j,Ti)]\displaystyle \text{Pass}@k = \frac{1}{N} \sum_{i=1}^{N} \mathbb{1}\left[ \bigvee_{j=1}^{k} \text{pass}(S_{i,j}, T_i) \right]
Pass@k 回答的问题是:“给模型 $k$ 次机会,它能至少成功一次的概率有多大?”这是目前评价代码生成模型最主流的指标。
  1. 多次采样$S_{i,j}$ 表示对第 $i$ 题生成的第 $j$ 个候选补全,$k$ 是候选总数。
    Si,j,j=1,2,…,k\displaystyle S_{i,j},\quad j = 1, 2, \dots, k
  2. 测试用例$\text{pass}(S_{i,j}, T_i)$ 表示候选补全 $S_{i,j}$ 是否通过了该题附带的测试用例 $T_i$。
    pass(S,T)∈{0,1}\displaystyle \text{pass}(S, T) \in \{0, 1\}
  3. 或运算$\bigvee_{j=1}^{k}$ 表示“只要 $k$ 个候选中有一个通过,就记作通过”。
    ⋁j=1kpass(Si,j,Ti)\displaystyle \bigvee_{j=1}^{k} \text{pass}(S_{i,j}, T_i)
  4. 汇总平均$\frac{1}{N}\sum$ 统计 $N$ 道题中满足条件的比例。
    1N∑i=1N1[⋅]\displaystyle \frac{1}{N} \sum_{i=1}^{N} \mathbb{1}[\cdot]

主流基准:标准化的比赛规程

为了让不同厨师、不同年份的比赛结果可以横向比较,大赛组委会需要一份固定的菜单和评分规程。代码补全领域同样沉淀出了几个公众认可的“比赛”,其中最著名的是 OpenAI 提出的 HumanEval 和 Google 的 MBPP。

HumanEval 包含 164 道函数级编程题(对应思维导图中的“函数级基准”),每道题给出函数签名、文档字符串和几个单元测试,要求模型补全出函数体。MBPP(Mostly Basic Python Problems)则包含约 1000 道面向初学者的基础编程题,覆盖面更广、难度更低。两者的共同点是:题目固定、测试公开、评分协议明确,因此任何团队都可以复现和对比——这就是“数据设计”与“评测协议”的精髓。

HumanEval 在报告分数时,还引入了一个重要的统计技巧——Pass@k 的无偏估计。朴素的“采样 $k$ 次看是否通过”会高估模型能力,因此官方采用组合数公式修正偏差:设每题采样 $n$ 个样本、其中 $c$ 个通过,则随机抽取的 $k$ 个样本里至少有一个通过的概率是下面这个公式。这样只用一次采样就能无偏估计任意 $k$ 的通过率,节省大量算力。

基准比赛保障了“公平对比”,但它终究是标准化的模拟赛。真实厨房里的灶台、食材和顾客口味千差万别——这也解释了为什么 HumanEval 分数高的模型,在真实 IDE 里不一定最好用。聪明的团队会自己设计“内部评测赛”。

Pass@k=E(n,c)[1−(n−ck)(nk)]\displaystyle \text{Pass}@k = \mathbb{E}_{(n, c)} \left[ 1 - \frac{\binom{n-c}{k}}{\binom{n}{k}} \right]
这个公式用“全部候选都失败”的概率取反,得到“至少一个成功”的概率,避免了小样本下的乐观偏差。
  1. 采样总数与通过数$n$ 表示每道题抽样的样本总数,$c$ 是其中通过测试的样本数。
    n≥c≥0\displaystyle n \geq c \geq 0
  2. 全失败组合$\binom{n-c}{k}$ 表示从失败的 $n-c$ 个样本中任选 $k$ 个的组合数。
    (n−ck)=(n−c)!k!(n−c−k)!\displaystyle \binom{n-c}{k} = \frac{(n-c)!}{k!(n-c-k)!}
  3. 全体组合$\binom{n}{k}$ 表示从 $n$ 个样本中任选 $k$ 个的全部组合数。
    (nk)=n!k!(n−k)!\displaystyle \binom{n}{k} = \frac{n!}{k!(n-k)!}
  4. 期望修正$1 - \dfrac{\cdot}{\cdot}$ 得到“随机抽 $k$ 个至少含一个通过样本”的概率,再对 $(n,c)$ 的分布取期望;当 $n-c < k$ 时结果定义为 1。
    1−(n−ck)(nk)\displaystyle 1 - \frac{\binom{n-c}{k}}{\binom{n}{k}}

实践要诀:设计你自己的评审体系

成熟的评委团从不指望一把尺子量遍所有菜品,而是把色、香、味、形按权重综合计分,并在每届比赛后复盘校准。实际评测代码补全,同样建议走“组合指标 + 持续跟踪”的路线,而不是迷信某一个分数。

一个实用的做法是设计复合评分:字符相似度 Sim 占权重 $w_1$,AST 结构相似度占 $w_2$,Pass@k 功能正确率占 $w_3$,三者加权求和。权重根据场景调节——偏重行尾补全的产品可以提高 Sim 权重;偏重函数生成的场景则把 Pass@k 放在首位,因为“能不能跑对”比“长得像不像”更重要。

执行流程上,先明确评测场景(补全粒度、语言、领域),再选择工具:通用场景用 HumanEval/MBPP 做横向对比,业务场景则从真实仓库的提交记录中抽取“补全前一刻的代码”构造内部题库。跑分后要结合人工抽检解读——某些指标异常可能是测试用例质量差,而不是模型退化。最后把评测集和脚本固化到 CI 流程,每次模型更新自动回归。

就像一流餐厅会持续追踪顾客评分、定期重测招牌菜,你的评测体系也应当随代码库演进而更新题库、随业务目标调整权重。真正的“评审大师”,不是会做一道菜,而是能设计一整套让所有人都信服的评分体系。

Score=w1⋅Sim+w2⋅ASTSim+w3⋅Pass@k\displaystyle \text{Score} = w_1 \cdot \text{Sim} + w_2 \cdot \text{ASTSim} + w_3 \cdot \text{Pass}@k
综合得分是三个维度的加权和。权重之和为 1,具体数值由业务场景决定,并用真实用户反馈持续校准。
  1. 权重系数$w_1, w_2, w_3$ 是三个维度的权重,满足 $w_1 + w_2 + w_3 = 1$。
    ∑t=13wt=1\displaystyle \sum_{t=1}^{3} w_t = 1
  2. 字符相似度$\text{Sim}$ 用编辑距离归一化得到,衡量字符层面的贴近程度。
    Sim∈[0,1]\displaystyle \text{Sim} \in [0, 1]
  3. 结构相似度$\text{ASTSim}$ 用 Dice 系数得到,衡量语法结构的一致程度。
    ASTSim∈[0,1]\displaystyle \text{ASTSim} \in [0, 1]
  4. 功能正确率$\text{Pass}@k$ 衡量真实运行的正确比例,三者加权求和即为综合分。
    w1⋅Sim+w2⋅ASTSim+w3⋅Pass@k\displaystyle w_1 \cdot \text{Sim} + w_2 \cdot \text{ASTSim} + w_3 \cdot \text{Pass}@k

AI 应用场景

Copilot 类产品的选型评估

阶段:模型选型与产品评估

解决的问题:传统做法是让几位工程师试用一周再投票,主观且不可复现;不同试用者的代码习惯不同,结论常常互相矛盾。用评测框架后,团队抽取公司仓库的真实补全场景,离线计算编辑相似度、AST 相似度与 Pass@k,再结合小范围灰度确认体验,让选型决策建立在可复现的数据上。

具体案例:某研发团队要在 GitHub Copilot 与自研补全引擎间二选一。他们从 Git 提交历史中提取 800 个“真实补全片段”构造评测集,跑完三个维度指标后,发现自研引擎在新增代码场景的 Pass@1 高 8%,最终据此确定了方案,并把评测集沉淀为后续迭代的回归基线。

补全模型的迭代回归测试

阶段:模型迭代与质量监控

解决的问题:模型微调或更换底座后,“修好了 A 场景却悄悄破坏了 B 场景”是常态。把 HumanEval 加内部题库固化为评测集,在 CI 中自动跑分并对比历史基线,能在发布前拦截质量回退。

具体案例:某 AI 公司每个版本发布前自动运行 164 道 HumanEval 加 2000 道内部题,若 Pass@1 较上个版本下降超过 1 个百分点,流水线直接亮红灯,阻止上线并要求研发调参。

编辑器插件真实效果量化

阶段:产品效果度量与校准

解决的问题:离线指标只是“模拟赛”,用户真实使用中的接受率才是“实战成绩”。通过统计 IDE 插件里补全建议被接受还是被拒绝,得到用户反馈的“试吃”信号,再用它校准离线指标的权重,让评测体系与真实体验对齐。

具体案例:某团队统计一周内 10 万次补全建议的接受率,发现其与编辑相似度的相关性为 0.61,与精确匹配的相关性仅 0.15,因而在综合评分中上调了 Sim 的权重并下调了 EM,评测结果与用户反馈的一致性明显提升。

Python 演示

python79 行
import astimport math  def edit_distance(a, b):    """计算莱文斯坦编辑距离:把 a 变成 b 所需的最少编辑次数"""    rows = len(a) + 1    cols = len(b) + 1    dp = [[0] * cols for _ in range(rows)]    for i in range(rows):        dp[i][0] = i    for j in range(cols):        dp[0][j] = j    for i in range(1, rows):        for j in range(1, cols):            if a[i - 1] == b[j - 1]:                dp[i][j] = dp[i - 1][j - 1]            else:                dp[i][j] = min(                    dp[i - 1][j] + 1,      # 删除 a 中的字符                    dp[i][j - 1] + 1,      # 插入 b 中的字符                    dp[i - 1][j - 1] + 1,  # 替换为 b 中的字符                )    return dp[rows - 1][cols - 1]  def edit_similarity(a, b):    """把编辑距离归一化成 0 到 1 的相似度"""    if not a and not b:        return 1.0    return 1.0 - edit_distance(a, b) / max(len(a), len(b))  def exact_match(a, b):    """精确匹配:两段文本完全相同才得 1 分"""    return 1.0 if a == b else 0.0  def ast_similarity(code_a, code_b):    """AST 匹配:比较两段代码语法树的节点类型集合"""    try:        types_a = set(type(n).__name__ for n in ast.walk(ast.parse(code_a)))        types_b = set(type(n).__name__ for n in ast.walk(ast.parse(code_b)))    except SyntaxError:        return 0.0    if not types_a and not types_b:        return 1.0    common = len(types_a & types_b)    return 2.0 * common / (len(types_a) + len(types_b))  def pass_at_k(total, passed, k):    """Pass@k 无偏估计:k 次采样中至少一次通过的概率"""    if total - passed < k:        return 1.0    return 1.0 - math.comb(total - passed, k) / math.comb(total, k)  def evaluate_completions(reference, candidates):    """对一组候选补全结果做多维打分"""    print("=== 代码补全评测示例 ===")    for idx, cand in enumerate(candidates, 1):        em = exact_match(reference, cand)        es = edit_similarity(reference, cand)        a_s = ast_similarity(reference, cand)        print("候选%d: EM=%.1f 编辑相似度=%.3f AST相似度=%.3f"              % (idx, em, es, a_s))  if __name__ == "__main__":    reference = "def add(a, b):\n    return a + b"    candidates = [        "def add(a, b):\n    return a + b",        "def add(a, b):\n    return b + a",        "def add(x, y):\n    return x - y",    ]    evaluate_completions(reference, candidates)    print("Pass@1(5题过3题):", pass_at_k(5, 3, 1))    print("Pass@5(5题过3题):", pass_at_k(5, 3, 5))
目的:实现编辑距离、编辑相似度、精确匹配、AST 相似度和 Pass@k 无偏估计,对一组候选补全结果做多维打分,让你直观看到“长得像”与“真能用”的区别。
  1. dp = [[0] * cols for _ in range(rows)]初始化 DP 表格,行列分别是两段文本的长度加一,用来存子问题的编辑距离。
  2. if a[i - 1] == b[j - 1]: dp[i][j] = dp[i - 1][j - 1]当前字符相同则无需操作,直接沿用上一格的代价。
  3. dp[i][j] = min(...)从删除、插入、替换三种操作中取最小代价,这是莱文斯坦递推的核心。
  4. 1.0 - edit_distance(a, b) / max(len(a), len(b))把绝对距离除以较长长度,归一化成 0 到 1 的相似度。
  5. ast.parse(code_a)把源码解析成抽象语法树,这是 AST 匹配的第一步。
  6. 2.0 * common / (len(types_a) + len(types_b))用 Dice 系数衡量两棵树节点类型集合的重叠程度。
  7. if total - passed < k: return 1.0当失败样本数不足 k 时,任意抽 k 个必然包含通过样本,概率为 1。
  8. 1.0 - math.comb(total - passed, k) / math.comb(total, k)用“全是失败样本”的组合占比取反,得到至少一个通过的无偏概率。

常见误区

误区我测试补全结果时,只要代码在 IDE 里能运行、没报错,就认为这次补全质量合格。

正解能运行不等于功能正确。比如要求实现加法,模型补全成 return a - b,代码完全能运行,结果却全错。正确的做法是用带断言的单元测试验证输入输出,并统计 Pass@k。这就像评委试吃时,菜能端上桌不代表熟了,必须尝一口才知道。

为何常见:本地运行报不报错是最容易观察到的信号,而且“运行通过”会产生安全错觉,让人忽略逻辑上的错误。

误区补全结果和标准答案的字符越接近,质量一定越高,所以字符串比较就够了。

正解字符相似度高只说明“长得像”。return a + b 和 return b + a 字符不同、相似度低,但功能完全相同;把变量名从 a、b 改成 x、y 也会拉低相似度,却完全等价。这时需要 AST 匹配和功能测试来兜底,才能分辨“形异神同”的合格答案。

为何常见:字符串比较最直观、实现成本最低,人们天然倾向于先用量化最方便的指标,并把它当成全部。

误区给模型打分只需要一个指标,比如 Pass@1 或编辑相似度,选一个就够了。

正解不同补全场景对指标的敏感度差异很大:行尾补全对字符相似度敏感,函数生成必须用 Pass@k 验证功能,重构类补全则要看 AST 结构。组合使用多个指标、按业务调权重,评测结论才稳健。单指标容易被特定分布的测试集“骗到高分”,就像只用“摆盘好看”来评菜,会漏掉味道这个关键维度。

为何常见:多指标意味着更多工程成本和分析负担,追求简单是人的天性,但评测对象的复杂性不允许偷懒。

误区HumanEval 分数越高的模型,接入我们的 IDE 插件后体验一定越好。

正解HumanEval 是通用函数题,和真实代码库的语境差异很大:真实补全包含大量行尾补全、长函数、框架 API 和业务命名,这些正是基准题不覆盖的。真实体验还需要结合内部评测集、用户接受率和人工评审,不能只看公开基准分数。就像拿国宴菜谱衡量家常菜厨师,维度全错。

为何常见:基准分数公开易得、宣传常见,且直觉上“分数高等于厉害”符合常理,容易让人忽略数据分布的巨大差异。

误区评测是一次性的,测完拿到分数,后面就不用再管了。

正解代码库在演进、模型在更新、业务场景在变化,评测集和权重都需要持续维护。成熟团队把评测固化到 CI 流水线,每次模型发布自动跑分,一旦 Pass@1 回退就阻止上线。评测体系是一套需要定期保养的工具,不是一次性的体检报告。

为何常见:搭建评测体系的成本不小,做完后容易产生“任务完成”的心态,忽略了评测本身需要持续投入的事实。

面试题

Q1. 评测代码补全时,以下哪项属于“字符串级”的相似度指标?

单选题 · easy

  • A.编辑距离← 正确答案
  • B.AST匹配
  • C.单元测试
  • D.覆盖率

答案:A。编辑距离直接在字符序列上计算插入、删除、替换次数,属于字符串级指标;AST 匹配针对语法树,单元测试和覆盖率属于功能正确性验证。就像比对菜品照片是“看长相”,编辑距离就是那张照片的差异计数。

Q2. 莱文斯坦编辑距离允许的三种基本操作是什么?

单选题 · easy

  • A.插入、删除、替换← 正确答案
  • B.复制、粘贴、剪切
  • C.编译、运行、调试
  • D.拆分、合并、排序

答案:A。莱文斯坦距离只允许插入、删除、替换三种字符级操作,其余选项都是对代码工程或文本处理的描述,不属于编辑距离的定义。三种操作对应评委对照片的“加料、减料、换料”。

Q3. 两段代码的编辑距离为 3,长度分别是 10 和 12,归一化后的编辑相似度约为多少?

单选题 · mid

  • A.0.25
  • B.0.75← 正确答案
  • C.0.70
  • D.0.85

答案:B。归一化相似度 = 1 − 编辑距离 ÷ 较长文本长度 = 1 − 3/12 = 0.75。分母取较长的 12,因为归一化的目标是让相似度落在 0 到 1 之间,用最长长度作为最大可能距离。

Q4. 与精确匹配(EM)相比,编辑距离的主要优势是什么?

单选题 · easy

  • A.能容忍细微差异← 正确答案
  • B.计算速度更快
  • C.不需要参考答案
  • D.能识别语法错误

答案:A。精确匹配要求字符串完全相同,而编辑距离允许插入、删除、替换造成的细微差异,因此对变量名、空格等变化更宽容。编辑距离并不比 EM 更快、仍需参考答案,且无法识别语法错误。

Q5. AST 匹配评估补全结果时,重点关注的是代码的哪个方面?

单选题 · mid

  • A.代码的语法结构← 正确答案
  • B.代码的注释风格
  • C.代码的缩进格式
  • D.代码的变量命名

答案:A。AST 匹配把代码解析成抽象语法树,比较节点的类型和嵌套关系,因此关注的是语法结构。注释、缩进和变量命名在语法树中都不会体现,属于“照片看长相”之外的“结构检查”。

Q6. 以下哪些方法可以用于评估代码补全的“功能正确性”?

多选题 · mid

  • A.运行单元测试← 正确答案
  • B.检查测试覆盖率← 正确答案
  • C.对比字符串相似度
  • D.检查是否通过编译

答案:AB。功能正确性必须通过运行测试用例验证输入输出,检查覆盖率能说明测试是否充分,两者都服务于“真试吃”;字符串相似度属于字符层面对比,编译通过只说明语法合法,不保证逻辑正确——能编译的 return a - b 仍是错的。

Q7. 关于 Pass@k 指标,下列说法正确的是?

多选题 · hard

  • A.k 表示候选样本数← 正确答案
  • B.有任一通过即算通过← 正确答案
  • C.Pass@5 一定大于 Pass@1
  • D.k 越大计算越简单

答案:AB。Pass@k 对每道题生成 k 个候选,只要有一个通过测试就算通过;Pass@k 随 k 单调不减,但可能相等,不能说“一定大于”;k 越大采样和测试成本越高而不是越简单。这相当于给厨师 k 次重做机会,次数越多成功率只会更高或持平。

Q8. HumanEval 基准的主要特点是什么?

单选题 · mid

  • A.题目为函数级代码生成← 正确答案
  • B.题目为无限长的程序
  • C.只评测注释与补全
  • D.不包含任何测试用例

答案:A。HumanEval 包含 164 道函数级编程题,每道题给出函数签名、文档字符串和单元测试,要求模型补全函数体。它题目有限、测试公开,是标准化的“模拟赛”,而不是无限长程序、注释补全或无测试的题库。

Q9. 使用 Pass@k 无偏估计公式时,关于参数与结果的描述正确的是?

多选题 · extreme

  • A.n-c 小于 k 时结果取 1.0← 正确答案
  • B.结果表示至少一个通过概率← 正确答案
  • C.结果等价于平均通过率
  • D.k 必须严格等于 n

答案:AB。当失败样本数 n−c 小于 k 时,任意抽 k 个必然含通过样本,概率为 1;公式的物理意义是“随机抽 k 个至少有一个通过”的概率估计。它并不等于简单的 c/n(c/n 是单次采样通过率),也不要求 k 等于 n。这个修正避免了小样本下的乐观偏差。

Q10. 某模型每道题生成 10 个样本,其中 2 个通过测试,其 Pass@1 的无偏估计约为多少?

单选题 · hard

  • A.0.20← 正确答案
  • B.0.40
  • C.0.80
  • D.1.00

答案:A。Pass@1 = 1 − C(10−2, 1) / C(10, 1) = 1 − 8/10 = 0.20。从 10 个样本中随机抽 1 个,抽到通过样本的概率就是通过样本占比 2/10;当 k=1 时无偏估计恰好等于通过率 c/n。