想象你是高三班主任,要给班里的“AI 插班生”摸底。平时作业它全对——但那些题它可能早背下了。为了公平,你请来 OpenAI 命题组,手工新编一套 164 道题的“高考编程卷”,取名 HumanEval。每道题只给“函数签名 + 功能说明”,让模型写出函数体,再用配套的单元测试“黑盒判卷”——不管过程多花哨,答案跑不对就零分。评分还引入“重考机制”:允许模型答 k 次,只要一次全对就算通过,这个概率就是 pass@k。这套“AI 高考”至今仍是大模型代码能力的标尺。这篇文章就带你还原这套卷子的命题思路、评分规则、阅卷流程,以及它考不出什么。
学会用 HumanEval 这把“AI 高考尺”衡量模型代码能力,你就能在编程助手选型、评测与迭代时读懂分数背后的含义,不被漂亮数字误导。
可视化演示
展示编程题目经LLM生成代码到测试验证的完整评测链路,含pass@k柱状图和模型能力演进曲线,粒子流动模拟评测过程,点击按钮切换不同视角
本节配有交互动效(进入视口后自动加载,约 3KB)。
HumanEval评测全链路 164道手写编程题覆盖核心算法 LLM生成代码经测试用例验证 pass@k量化功能正确性 多语言扩展评估跨语言能力 端到端评测管道量化代码生成能力
基准概述
班主任想搞一场公平的摸底考,最怕学生做过原题。市面上的题库早就被 AI 学生背得滚瓜烂熟,于是只能请一批“从不出现在市面习题册里”的命题老师,现场手写试卷。2021 年,OpenAI 的“命题组”发布了这样一套卷子——HumanEval,专门用来检验大模型的代码编写能力。
在论文《Evaluating Large Language Models Trained on Code》中,OpenAI 提出:要测“代码听写”能力,就得让模型从一句自然语言需求出发,写出一个能真正运行的函数。于是他们手工编写了 164 道 Python 编程题,覆盖字符串处理、算法、数据结构等基础场景。每道题只给出函数签名和 docstring,要求模型补全函数体。
这 164 道题构成一个题库集合,每道题配 3-8 个隐藏单元测试。模型生成的函数必须通过全部测试才算正确。它的特别之处在于:不比较“生成文本和参考答案像不像”,而比较“跑出来的结果对不对”,这更接近真实编程中“能不能用”的评判标准。
就像高考卷的命题要“原创、不超纲、能区分水平”,HumanEval 的 164 道题同样经过人工筛选与质检。它从诞生起,就成为衡量各家代码大模型的“统一考场”,让所有 AI 考生在同一张卷子上公平竞争。
- P 表示整个题库集合HumanEval 的全部题目构成一个集合,记为 P,这是所有评测流程的数据源。
- p_i 表示其中一道题每个 p_i 是一道独立的编程题,结构相同但功能各异,彼此之间没有依赖。
- 164 为题目总数集合的元素个数 \(|P| = 164\),这个数字决定了后续所有平均指标的分母。
题目设计
数学大题会先给“已知条件”,再写“作答要求”。HumanEval 的题目也严格遵循这个版式:函数签名就是已知条件——函数叫什么、接收什么参数、返回什么类型;docstring 就是作答要求——请实现什么功能、输入输出长什么样。
每道题本质上是一个“待补全的函数”。比如题目给出 def has_close_elements(numbers: List[float], threshold: float) -> bool:,docstring 描述:“判断列表中是否存在两个元素之差小于阈值”,并附上示例输入输出。模型的任务就是补全函数体,让行为与描述完全一致。
题目由三部分组成:函数签名提供类型约束与调用契约,docstring 是自然语言规格说明(包含功能描述和示例),隐藏单元测试则是 3-8 个断言,覆盖正常情况与边界情况。这种设计把“自然语言→代码”的翻译任务标准化,就像把开放式作文改成了有明确评分点的应用题。
版式统一的题目让所有 AI 考生拿到同一款“答题卡”,避免了因题目格式怪异带来的无谓失分,也方便后续用脚本批量构造评测 prompt——这正是它能成为行业标准的原因之一。
- signature 函数签名声明函数名、参数列表与返回类型,给模型划定了严格的调用契约。
- docstring 功能描述用自然语言写出函数职责与输入输出示例,是模型理解需求的唯一依据。
- T_i 单元测试集合一组隐藏断言,构成判卷时的“标准答案”,模型看不到却必须全部通过。
pass@k 指标
真实考试中,一次发挥失常不代表水平不行。于是这套“AI 高考”设计了重考机制:允许考生提交 k 个答案,只要有一个被判满分,就算通过。这个“重考 k 次至少一次过关”的概率,就是 pass@k。注意:它说的是“k 次里有好的”,而不是“第 k 次的成绩”。
直接评估时,最简单的方法是让模型生成 k 个答案,看有没有一个通过测试,反复多次统计比例。但论文指出,这种“朴素估计”方差大、样本利用率低。于是他们改用另一个策略:对每个问题固定采样 n 个答案(论文中 n=100 或 200),数出其中通过测试的数量 c,再用组合数公式一次性估算任意 k 的 pass@k。
当从 n 个答案中随机抽取 k 个时,“恰好全部抽到错误答案”的概率是 \(\binom{n-c}{k} / \binom{n}{k}\)。用 1 减去它,就得到“至少抽中一个正确答案”的无偏估计,并且一次采样就能算出所有 k 值。
重考制度很公平:pass@1 看“一锤定音”的硬实力,pass@100 看“题海战术”的兜底能力。两个数字合在一起,才能看清一个 AI 考生的真实水平。
- n 表示采样总数对每道题,让模型独立生成 n 个候选代码,n 是评估者设定的“采样预算”。
- c 表示通过数量在这 n 个候选中,能通过全部单元测试的代码个数,c 越接近 n 说明模型越稳。
- k 表示尝试次数模拟“允许重考 k 次”的机会数,k 一般不大于 n。
- 组合数之比\(\binom{n-c}{k}\) 表示从错误答案堆里抽 k 个的组合数,\(\binom{n}{k}\) 是从全部答案里抽 k 个的组合数,两者相除就是“抽 k 次全是坏答案”的概率。
- 取补数得胜率用 1 减去“全军覆没”的概率,就是“至少抽到一个正确答案”的概率,即 pass@k。
评估流程
阅卷流程有固定套路:收答题卡、机器扫读、对照标准答案、汇总成绩单。HumanEval 的评估流程同样是这条流水线,每一步都有严格规范。
第一步,构造 prompt:把函数签名和 docstring 拼在一起,让模型“续写”出函数体。第二步,以一定的 temperature(如 0.8)为每个题目采样 n 个候选函数,增加答案多样性。第三步,在隔离沙箱中运行隐藏单元测试,判断每个候选的输出是否与期望一致。第四步,统计每题通过数 c,代入 pass@k 公式,再对所有题目取平均。
流程中的细节决定分数可信度:每个候选代码需要补上必要的 import、去掉重复生成、限制执行时间和内存防止恶意代码;测试结果取“全部用例通过才算过”的严格判定。最终得到整个基准上的平均 pass@k,这也是各家论文里报出的标准成绩。
就像教务主任会把各科成绩合成一张总表,164 道题的平均 pass@k 就是模型的“总成绩单”。有了这张成绩单,不同模型之间才能横着比、竖着比,看出真实差距。
- pass@k_i 表示单题成绩第 i 道题用组合数公式算出的重考通过率,先逐题计算。
- 求和符号 Σ把 164 道题各自的成绩逐个相加,得到总分。
- 除以题目总数乘以 1/164 得到平均成绩,消除题量影响,就是论文中报告的总分。
基准特征
这份试卷有两条铁规:第一,只看答案对不对,不看过程美不美;第二,全部题目原创手写,坚决防止泄题。
铁规一叫“功能正确性”:HumanEval 不奖励代码风格、注释,也不奖励和参考答案的文本相似度,只执行单元测试,输出一致才得分。这与以往用 BLEU 等文本重叠指标评测代码生成有本质区别——一个和参考代码字字相同但运行报错的答案,在 HumanEval 里就是零分。
铁规二叫“防数据泄漏”:164 道题全部由 OpenAI 工程师手工编写,发布前刻意避免出现在已知训练语料中,从而降低模型“背答案”的可能。此外,单元测试刻意覆盖边界条件,让“瞎蒙”更难得分。两道铁规叠加,保证了分数反映的是“能干活”,而不是“会模仿”。
考官蒙住眼睛,只摸答案纸——代码写得再“漂亮”,跑不出正确结果照样扣分。这正是 HumanEval 与文本相似度指标最本质的分野:它考的是“能不能跑”,不是“像不像”。
- ∀t ∈ T_i 表示逐条测试对题目 i 的每一个测试用例都要逐一检查,不能只抽查其中几个。
- run(g_i, t) 表示运行生成代码把模型写出的函数 g_i 在用例 t 上实际执行,得到真实输出。
- expected(t) 表示期望输出运行结果必须与标准答案完全相等,才算该用例通过。
评测局限
再好的模拟卷也有盲区:题量有限、难度有天花板、考生还容易受题干措辞影响。用 HumanEval 也有同样的烦恼。
第一是提示敏感性:同一个问题,prompt 里加不加示例、措辞是否精确,会让 pass@k 分数明显波动,因此单次评测的分数未必稳定。第二是题目范围窄:164 道题几乎都是十几行的小函数,不涉及多文件、第三方依赖、重构、调试这些真实工程场景。
第三是难度偏低:题目大多在“入门到中级”之间,对顶尖模型之间差异的区分度有限。第四,随着题库公开,后续训练的模型可能真的“见过”这些题,数据泄漏的阴影重新浮现。这些局限提醒我们:HumanEval 是“体检报告”,不是“工作能力鉴定书”。
就像高考状元进了职场也要从头适应一样,HumanEval 高分只能说明“基础扎实”,不能证明“工程高手”。读分数时要记得:这份模拟卷考不出真实世界的复杂度。
- prompt_A、prompt_B 表示两种措辞同一个编程需求用不同方式描述,模型理解难度随之变化。
- 分数差 Δ当 Δ 大于 0 时,说明评测结果随提示变化而波动,这就是提示敏感性。
实践应用
试卷的价值在于用起来:给学生排名、跟踪进步、诊断短板。HumanEval 在业界也正是这么用的,是代码大模型圈子里使用最频繁的“体检工具”。
最常见的用法是模型横向对比:在相同的采样预算与 prompts 下,报告各家模型的 pass@1 与 pass@k,直观看出版本之间的能力差距。其次是编程助手研发:GitHub Copilot、Codex 这类产品的团队把 HumanEval 作为回归测试集,每次发布前都跑一遍,防止新版本“能力退化”而不自知。
更进一步,可以分析失败样本的错误模式——是类型理解错误、边界条件遗漏,还是需求理解偏差,据此改进训练数据与推理策略。社区还发展出 HumanEval+、EvalPlus 等衍生基准,通过扩充测试用例来解决原版“测试不足、容易刷分”的问题。
分数是指南针,不是终点。用 HumanEval 量一量差距,再用失败样本指路,这就是它在 AI 编程时代最重要的价值——让每一次模型迭代都有据可依。
- 164 表示题目总数每道题都要单独构造 prompt 并生成候选代码,一道都不能省。
- n 表示每题的采样数生成 n 个候选代码需要 n 次模型推理调用,n 越大成本越高。
- 乘积为总调用次数一次完整评测的总推理开销,也是评估成本的下限。
AI 应用场景
编程助手发布前的“高考体检”
阶段:模型评估与回归测试
解决的问题:传统方式靠人工写一小撮测试用例或肉眼观察生成结果,费时费力且主观性强。用 HumanEval 可以自动生成 164 道题的候选代码并运行单元测试,快速量化“这次模型升级到底变强了还是变弱了”。
具体案例:某团队开发 AI 编程助手,每次更新基座模型后自动跑一遍 HumanEval,比对 pass@1 与 pass@k。某次升级后 pass@1 从 62% 掉到 55%,团队立刻定位到是训练数据清洗策略变化导致的,及时回滚并修正流程。
代码模型研发中的“错题本”诊断
阶段:迭代优化与失败分析
解决的问题:只知道分数高低不够,还要知道错在哪里。HumanEval 的失败样本天然自带“题面 + 模型输出 + 测试报错”,可以直接做错误模式分析,把模糊的“效果变差”变成具体的“哪里变差”。
具体案例:研究者把 164 道题按错误类型归类,发现模型高频错在“边界条件”和“类型转换”两类,于是针对性地往训练数据里补充边界用例,下一版模型的 pass@5 提升了 8 个百分点。
采购选型时的“统一考场”
阶段:模型选型评估
解决的问题:各家模型宣传时用的数据集和口径不一,难以横向比较。统一用相同的 prompts 与采样预算跑 HumanEval,可以得到可复现的对比表格,把选型决策从“看宣传”变成“看数据”。
具体案例:某企业要在两个代码大模型之间选型,在相同 n=200 采样下测得 A 模型 pass@1 为 52%、B 模型为 58%。结合成本和延迟后,企业最终选择 B 模型接入内部编程助手。
Python 演示
import mathimport random def comb(n, k): """组合数 C(n, k)""" if k > n: return 0 return math.comb(n, k) def pass_at_k(n, c, k): """HumanEval 论文 pass@k 无偏估计 1 - C(n-c, k) / C(n, k)""" return 1.0 - comb(n - c, k) / comb(n, k) def simulate_problem(true_p, n): """模拟一道真正通过率为 true_p 的题目 采样 n 次""" return sum(1 for _ in range(n) if random.random() < true_p) def main(): true_p = 0.4 n = 100 c = simulate_problem(true_p, n) print("题目真实通过率 {:.0%} 采样 {} 次 通过 {} 次".format(true_p, n, c)) for k in (1, 5, 10, 20, 50, 100): print("pass@{} = {:.4f}".format(k, pass_at_k(n, c, k))) theory = 1 - (1 - true_p) ** n print("理论值 1-(1-p)^n = {:.4f}".format(theory)) if __name__ == "__main__": main()
def pass_at_k(n, c, k):定义核心评估函数,对应论文中的无偏估计公式。return 1.0 - comb(n - c, k) / comb(n, k)用组合数之比算出“抽 k 次全是错误解”的概率,再取补数。def simulate_problem(true_p, n):模拟一道真实通过率已知的题目,生成 n 次采样结果。c = simulate_problem(true_p, n)获得 100 次采样中通过单元测试的数量。for k in (1, 5, 10, 20, 50, 100):用同一次采样数据估算不同 k 的通过率,验证机会越多胜率越高。print("pass@{} = {:.4f}".format(k, pass_at_k(n, c, k)))格式化输出每个 k 对应的指标。if __name__ == "__main__":让脚本既能被导入调用,也能独立运行。
常见误区
误区很多初学者觉得 pass@k 就是“第 k 次生成答案的正确率”,比如 pass@10 就是“第 10 个答案正确的概率”。于是想不通为什么 pass@10 总比 pass@1 高那么多。
正解pass@k 的准确定义是“从 n 个候选答案中抽取 k 个,至少有一个通过单元测试的概率”。它衡量的是“多次机会兜底”的能力,而不是第 k 次的发挥。就像“允许重考 k 次,只要一次及格就算过”:机会越多,通过率自然越高,所以 pass@10 一定大于等于 pass@1。
为何常见:从字面上容易把 @k 理解成“第 k 个”,而忽略了它其实是一个“抽 k 个里面有好货”的集合事件。
误区看到某个模型 HumanEval pass@1 达到 80%,就觉得它能胜任真实软件工程任务,直接放心地让它去写生产代码。
正解HumanEval 只有 164 道十几行的 Python 函数题,不含多文件、复杂依赖、重构、调试等真实工程场景。它更像“函数级单元测试”,是能力下限的探针,而不是工程能力的保证。高分说明基础不错,但真实业务还需要在私有数据上做更充分的评估。
为何常见:pass@k 数字直观,媒体又常只报分数,容易把“基准分数”误当成“真实能力”,忽略了评测场景与生产场景的巨大差距。
误区既然 164 道题是 OpenAI 工程师手写的,模型训练时没看过,所以这个基准永远安全,分数永远真实可信。
正解论文发布后题库就公开了,后续训练的模型完全可能在预训练语料中“读到”这些题和题解,导致分数虚高。这也是社区推出 HumanEval+、EvalPlus、要求模型报告是否接触过该基准的原因。防泄漏是发布那一刻的保护,不是永久的护身符。
为何常见:把“发布时间点以前没泄漏”误当成“永远不泄漏”,忽略了训练语料不断更新迭代的现实。
误区既然 pass@k 是“机会越多分数越高”,那评测时把 k 直接设成 1000,分数看起来就漂亮,模型之间也更好“刷榜”。
正解k 增大时 pass@k 会趋近 1,模型之间的区分度急剧下降,测评就失去了意义;同时采样成本按 164×n 线性上升。论文把 n 设为 100 正是为了平衡成本与精度。评测的目的是区分能力,而不是把所有人都刷成 100 分。
为何常见:把“指标最大化”等同于“评测最有价值”,忽略了区分度、成本和真实性之间的平衡。
面试题
Q1. HumanEval 基准由哪家机构提出?
- A.OpenAI 团队← 正确答案
- B.Google 团队
- C.Meta 团队
- D.微软研究团队
答案:A。HumanEval 是 OpenAI 在 2021 年《Evaluating Large Language Models Trained on Code》论文中提出的代码生成评测基准。其他选项都是知名 AI 机构,但并非该基准的提出者。
Q2. HumanEval 一共包含多少道手工编写的编程题?
- A.100 道题
- B.164 道题← 正确答案
- C.256 道题
- D.500 道题
答案:B。HumanEval 题库包含 164 道手工编写的 Python 编程题。题量被刻意控制在“小而精”,既能覆盖常见编程场景,又便于人工质检和快速评测。
Q3. 关于 pass@k 指标,下列理解正确的是?
- A.指的是第 k 次的成绩
- B.指 k 次至少一次通过的概率← 正确答案
- C.表示模型的推理速度
- D.表示测试用例总数量
答案:B。pass@k 衡量的是“从 n 个候选中抽取 k 个,至少有一个通过单元测试”的概率,相当于“重考 k 次只要一次及格就算过”。它讨论的是集合事件,不是第 k 次的单次成绩,更与推理速度、用例数量无关。
Q4. HumanEval 的每道题都包含哪些部分?
- A.函数签名的类型映射← 正确答案
- B.docstring 功能说明← 正确答案
- C.隐藏单元测试用例← 正确答案
- D.模型训练超参数表
答案:ABC。每道题由函数签名(类型约束)、docstring(自然语言功能描述与示例)和隐藏单元测试(标准答案)三部分组成。训练超参数属于训练环节的配置,不属于题目结构。
Q5. pass@k 公式中的 n 表示什么?
- A.基准包含的题目数
- B.每题生成的候选数← 正确答案
- C.单元测试的用例数
- D.模型参数量的大小
答案:B。在 pass@k = 1 - C(n-c,k)/C(n,k) 中,n 是每个问题生成的候选代码总数(采样预算),c 是通过测试的正确代码数。题目总数是 164,由公式外面的平均符号处理。
Q6. 为什么不直接反复采样统计“至少一次正确”的比例?
- A.因为该估计方差高样本利用率低← 正确答案
- B.因为该估计需要更多显卡资源
- C.因为该估计无法编写测试代码
- D.因为该估计和单元测试无关
答案:A。朴素的“生成 k 个看是否有通过”的方法在 k 较大时方差高,且每次只利用 k 个样本、浪费了固定采样预算。用 n 个样本的通过数 c 构造组合数估计,一次采样就能算出任意 k 的值,方差更小。
Q7. HumanEval 评测具有哪些局限性?
- A.题量少且覆盖场景有限← 正确答案
- B.结果对提示词写法敏感← 正确答案
- C.整体难度相对偏低← 正确答案
- D.评测结果无需人工检查
答案:ABC。HumanEval 只有 164 道小函数题,不涉及多文件和复杂依赖;结果会随 prompt 措辞波动;题目难度整体偏低,对顶尖模型区分度有限。而“自动判卷”本身是优点,不算局限。
Q8. HumanEval 的“功能正确性”评判标准是?
- A.代码风格是否整洁规范
- B.测试用例输出是否全部一致← 正确答案
- C.代码行数是否又短又少
- D.注释文档是否丰富完整
答案:B。功能正确性只看单元测试的输出是否与期望完全一致,不看代码风格、行数或注释。这是 HumanEval 与文本相似度指标最本质的区别——结果能跑才对,长得像不算。
Q9. 关于 HumanEval 数据泄漏风险,正确的说法是?
- A.手写命题永久安全不会泄漏
- B.公开后可能污染新训练语料← 正确答案
- C.泄漏只影响 pass@k 公式
- D.泄漏只影响非 Python 题目
答案:B。题库发布后会被爬虫收录,后续训练的模型可能在预训练语料中读到原题与题解,导致分数虚高。社区为此推出 HumanEval+ 等衍生基准并强调报告评测透明度。泄漏与公式无关,也与语言无关。
Q10. 下列哪些是 HumanEval 的合理用途?
- A.编程助手发布前回归测试← 正确答案
- B.作为唯一标准衡量代码能力
- C.微调后验证模型的改进效果← 正确答案
- D.对比不同模型的代码生成能力← 正确答案
答案:ACD。HumanEval 适合做回归测试、迭代验证和横向对比,但绝不该作为唯一标准。真实工程能力还需要私有数据评测、多文件任务和人工评估来补充,单一基准会带来严重的“刷分”风险。