想象你是一位米其林评委会主席,要考察一位神秘的AI厨师。它自称什么菜都会做,可你总不能让它自由发挥——那样评判标准太模糊。于是你设计了一场“标准化厨艺考核”:每道考题有明确的菜名(函数签名)、菜谱描述(文档字符串)、以及评委的验收标准(测试用例)。菜不在多,而在于能检验基本功;评语也不看刀工花哨,只看“菜品是否达到预期味道”。这就是HumanEval——一套用于评估大语言模型写代码能力的经典基准。它不考长篇大论的项目工程,只考一个个精巧的函数题;它不依赖人工读代码打分,而是用测试用例自动判定对错。通过这篇讲解,你会理解HumanEval如何出题、如何判分、有什么局限,以及后续如何改进。

岗位关联

对AI从业者而言,HumanEval是你评估LLM编码能力的地基:理解它的设计与指标,才能看懂各类模型跑分、避免被表面分数误导,并为AgentOps的模型选型和回归测试建立科学方法。

目录
  1. 可视化演示
  2. 基准设计理念
  3. 数据集构建
  4. 测试用例构造
  5. pass@k指标
  6. 评测执行环境
  7. 现存局限
  8. 改进扩展方向
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示HumanEval基准的pass@k指标评估原理:多模型多k值对比曲线,以及从Prompt到代码通过的完整评测流程。

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

pass@k指标对比 M1(CodeLlama):pass@1=28% pass@10=78% M2:pass@1=35% pass@10=84% M3:pass@1=22% pass@10=72% pass@k衡量k次采样至少一次通过的概率,k越大通过率越高。

基准设计理念

要给AI厨师办考级,第一件事不是做满汉全席,而是先把考卷设计清楚。如果你让厨师自由发挥,出来的菜五花八门,评委根本没法横向比较。于是HumanEval选择了“函数级考题”:只考察从输入到输出的单点技能,相当于给你一盘固定食材和一张菜谱,看你能否按标准完成。这样一来,考卷既容易自动批改,又能精准衡量模型的基本功。

在2021年之前,评估代码生成主要靠人读生成结果,慢且主观。OpenAI团队用HumanEval想解决的就是“如何快速、自动、可复现地判断一个函数实现是否正确”。他们选择函数粒度的任务是刻意的——函数是工程中最重要的可复用单元,也最容易用单元测试验证。每个问题像一张“标准化菜卡”:有明确的食材清单(函数签名)、烹饪说明(文档字符串),还附上评委的验收标准(若干测试用例)。

这背后的设计哲学是:与其追求逼真的大型项目,不如先把小而精的题目做到绝对公平。所有题目由人类专家手工编写,刻意避开公开代码库,防止模型在预训练阶段“偷看过答案”。这就像考级委员会亲自研发新菜,而不是从某本畅销菜谱里原封不动抄题。

最终,HumanEval用“功能正确性”这一个维度统领全局:结果对,就通过;结果错,就失败。这份拳拳到肉的简单,正是它在众多复杂评估方案中脱颖而出的原因。

数据集构建

有了理念,下一步是出考卷。HumanEval的考卷一共包含164道手写编程题。164不是玄学,而是一个平衡点:既要覆盖足够多样的编程模式,又要让人工编写和评审的成本可控。每道题都精心设计,从“判断一个数是否为偶数”到“实现一个简单的图遍历算法”,难度大致落在入门到中等区间。

每道题都有统一的格式,像标准化试卷一样分成三部分。首先是函数签名,例如 def solution(nums: List[int]) -> int:,它规定了函数名、参数类型和返回类型;其次是文档字符串,用自然语言描述函数应该做什么;最后是3到8个测试用例,作为自动批改的依据。模型看到的是“前面两页”,批改系统则用“第三页”来打分。

在题目收集过程中,设计者注意控制难度分布,避免过多难题也避免全是简单题。更重要的是,他们坚持不使用任何第三方库的API,让题目只依赖Python标准库。这好比考食材只从基础菜市场采购,不涉及需要特殊进口调料的烹饪。每道题的平均解答长度不到20行,保证“读懂题目”本身不成为障碍。

于是,这164道手写题成了LLM代码能力评估领域流传最广的“考卷”——它不大,但每个字都经过人工推敲,在规模与质量之间找到了很好的平衡。

测试用例构造

考卷有了,还要有评委。在HumanEval里,评委不是人,而是测试用例集合。想象你品尝一道菜,只尝第一口是不够的,还要试试太辣、太甜、分量太小的极端情况。测试用例的设计正是这个道理:除了常规输入,还刻意加入空列表、负数、重复元素、极大数值等边界条件,专门考验函数的鲁棒性。

“全部通过才算合格”是这套考核的铁律。若一个实现只过了部分测试,哪怕只是某一个边界情况出错,评分系统也判它失败。这就像菜品只要有一道验收标准不达标,厨师就不能拿满分。评判过程不需要人读代码,系统直接调用生成函数,把每个测试用例的输入喂进去,再把返回值与期望输出比对。

用数学语言来说,判定方式是:

正因为测试用例是预先设计好的“评委清单”,不同模型在同一道题上的得分才具有可比性。这也解释了为什么HumanEval能实现“全自动流水线评分”:从输入到判定,全链路无需人工介入。

AC(f, T)=⋀t∈T(f(xt)=yt)\displaystyle \text{AC}(f,\ \mathcal{T}) = \bigwedge_{t \in \mathcal{T}} \left(f(x_t) = y_t\right)
这个式子表示:函数 f 只有在测试集 T 里的每一个用例都满足“实际输出等于期望输出”时,才算通过(AC全对)。
  1. 全对标记AC是“全部正确”的标记,值为1表示函数通过所有测试,0表示失败。
    AC(f, T)\text{AC}(f,\ \mathcal{T})
  2. 测试集合T由m个测试用例组成,每个用例 t 包含一个输入 x_t 和一个期望输出 y_t。
    T={t1,t2,…,tm}\mathcal{T} = \{t_1, t_2, \dots, t_m\}
  3. 运行比对把输入 x_t 传给函数 f,得到实际输出,然后与期望输出 y_t 做相等比较。
    f(xt)=ytf(x_t) = y_t
  4. 逐项合取逻辑与(∧)把所有用例的比较结果连接起来,只要有一个不相等,整个表达式就为假。
    ⋀t∈T(f(xt)=yt)\bigwedge_{t \in \mathcal{T}} \left(f(x_t) = y_t\right)

pass@k指标

即使AI厨师水平再高,也难免有一次发挥失常——采样温度一高,输出就可能偏离正确答案。如果只考一次就下定论,对模型并不公平。于是HumanEval引入了一个关键指标:pass@k,意思是“允许厨师尝试k次,只要其中有一次成功,就算过关”。这像极了考试中的重考机会,只是把“重考”内嵌到了评估指标里。

它的计算方式不是简单重复采样取平均值,而是一个严格的无偏估计。对某道题,我们让模型采样生成 n 个候选解,其中 c 个通过了全部测试。那么随机抽取 k 个候选,至少有一个能通过的成功概率是:

在实际使用中,pass@1和pass@100是两个最常被引用的数值:前者衡量模型“一次写对”的能力,贴近IDE实时补全场景;后者衡量模型在“多轮尝试”下的上限,贴近Agent自主编码场景。这也意味着你不能只看一个数字,而要根据业务场景选择合适k。

pass@k=1−(n−ck)(nk)\displaystyle \text{pass@}k = 1 - \frac{\binom{n-c}{k}}{\binom{n}{k}}
这个公式回答了一个直观问题:在 n 个方案里随机挑 k 个来“碰运气”,能碰到至少一个正确方案的概率有多大?当 k=1 时它就是单次通过率;当 k 增大时,成功概率单调上升。
  1. 生成数量 n对一道题,让模型采样 n 个候选解,n 通常取100甚至更多。
    n=候选解总数n = \text{候选解总数}
  2. 通过数量 cc 表示 n 个候选中真正通过全部测试用例的个数,它直接反映模型的真实水平。
    c=∣{i∣AC(fi,T)=1}∣c = \left|\{i \mid \text{AC}(f_i, \mathcal{T})=1\}\right|
  3. 失败组合数分子表示“只从失败的 n-c 个候选里抽出 k 个”的取法数量。
    (n−ck)\binom{n-c}{k}
  4. 全部组合数分母表示“从 n 个候选里任意抽出 k 个”的总取法数量。
    (nk)\binom{n}{k}
  5. 全失败概率两者相除,得到“抽到的 k 个候选全是失败品”的概率。
    (n−ck)(nk)\frac{\binom{n-c}{k}}{\binom{n}{k}}
  6. 成功率补集用1减去全失败概率,就是至少抽到一个通过候选的概率,也就是 pass@k。
    pass@k=1−(n−ck)(nk)\text{pass@}k = 1 - \frac{\binom{n-c}{k}}{\binom{n}{k}}

评测执行环境

有了考卷和评分标准,还要一个公平的考场。HumanEval的评测执行环境好比“透明玻璃厨房”:厨师在完全隔离的空间里做菜,评委隔着玻璃观察,既能看到制作过程,又防止厨师偷偷携带外部调料。在技术上,这意味着把模型生成的代码放进沙箱——通常是子进程或Docker容器——隔离执行。

沙箱的作用有三层。第一层是安全隔离:模型生成的代码不可信,可能包含恶意系统调用或死循环,必须限制文件读写、网络访问等操作。第二层是资源限制:每道题分配固定的超时时间和内存上限,超时即判失败,防止某个坏实现拖垮整个评估系统。第三层是标准判定:程序退出时只检查函数返回值是否匹配期望输出,不依赖标准输出文本,避免“打印结果碰巧正确”这种取巧情况。

在HumanEval最初的实现中,评测服务会为每个候选解启动一个独立进程,在干净的Python环境里执行导入和函数调用。任何运行时异常、超时或返回值不匹配,都会被记为失败。这种“考场上只看结果”的做法,最大程度保证了不同模型之间的公平性。玻璃厨房虽然隔离,但阳光透明——每一位裁判看到的都是同一套规则。

现存局限

再经典的考卷也有过时的一天,HumanEval同样不例外。第一个局限是规模偏小:只有164道题,覆盖的编程模式终究有限。好比一份只含十几道菜的菜单,很难测试出厨师的全部手艺。第二个局限是代码规模有限:题目平均解答不到20行,根本触及不到真实工程中的模块设计、异常处理、性能优化等复杂问题。

更令人担忧的是数据污染风险。HumanEval发布于2021年,此后大量模型的训练语料可能已经包含了这些题目和解答。就像一个考生偷偷拿到了考卷答案,再考一次超高分也就不能说明真实实力了。此外,随着模型能力快速提升,HumanEval的难度天花板逐渐触顶:新一代模型在pass@1上普遍能拿到80%~90%以上的分数,区分度明显下降。

这好比当初精心设计的入门级厨师考卷,如今被各路大厨轻松拿满分,作为晋升考核已经不够用了。HumanEval本身没有变,变的是AI厨师的整体水平。它依然有参考价值,但不再能单独作为衡量代码能力的标尺。

改进扩展方向

既然Old考卷不够用,业界自然开始了“升级考卷”的探索。一个方向是给原题加难度:EvalPlus在HumanEval原有测试用例基础上,通过自动生成和变异测试补充了大量新用例,把每道题的平均用例数从不到10个扩充到数百个。相当于原来的评委只尝一口,现在要闭着眼睛盲试几十种极端口味,让“碰巧通过”的模型原形毕露。

另一个方向是扩充题量。MBPP(Mostly Basic Python Problems)收集了约1000道更基础但也更多样的Python编程题,作为HumanEval的补充,尤其适合考察模型在简单任务上的稳定性。还有研究团队把HumanEval翻译成C++、Java、JavaScript等多语言版本,相当于用同一套菜谱考不同菜系的厨师,检验模型跨语言迁移能力。

最值得一提的是LiveCodeBench:它坚持“滚动更新题库”,定期加入新题,从而对抗数据污染。就像考级委员会每季度更换一批秘制新菜,彻底杜绝背答案的可能,让分数始终反映真实能力。这些改进方向共同指向一个目标——让LLM代码评估从“静态考卷”走向“动态、全面、抗污染”的新时代。

AI 应用场景

模型选型时的pass@k对比

阶段:模型评估与选型

解决的问题:不同LLM在代码任务上的能力差异往往不是单一的。传统做法只比较pass@1,容易忽略“多试几次成功率”的差异。通过HumanEval的pass@k曲线,可以判断模型是稳定型选手还是投机型选手。

具体案例:一家AgentOps平台要选择代码生成模型。评估时发现A模型pass@1为62%,pass@100为91%;B模型pass@1为70%,pass@100为78%。这说明A模型适合“先采样再筛选”的Agent多轮编码场景,B模型更适合实时补全。团队最终根据产品侧重点选择了不同模型。

AgentOps持续回归防线

阶段:持续集成与回归测试

解决的问题:模型微调或升级后,代码能力是否回退很难感知。把HumanEval变成CI流水线里的自动闸门,能在发布前发现性能滑坡,避免线上事故。

具体案例:某公司在部署新版本基座模型前,自动运行HumanEval的164道题并记录pass@1。某次升级使pass@1从76%掉到69%,系统立即阻断发布并触发告警。工程师随后定位到是SFT数据中混入了过多JSON生成样本,导致Python函数生成能力被稀释。

构建私有行业评估集

阶段:数据集构建与定制化评估

解决的问题:通用基准无法覆盖垂直领域的代码风格和API习惯。借鉴HumanEval“人工出题+测试用例自动判定”的方法论,可以快速构建私有代码评估集。

具体案例:一家量化交易公司想评估LLM生成“行情指标计算函数”的能力。他们从内部代码库抽取20个常用函数,编写脱敏版函数签名、文档字符串和边界测试用例(含除零、空数组、涨跌停等),形成私有评测集,并据此对比不同模型的可用性。

Python 演示

python56 行
"""一个小型的 HumanEval 风格评估演示"""from math import comb  def estimate_pass_at_k(num_samples, num_correct, k):    """用无偏估计公式计算 pass@k。"""    if k <= 0:        return 0.0    if num_correct >= num_samples:        return 1.0    return 1.0 - comb(num_samples - num_correct, k) / comb(num_samples, k)  def run_unit_tests(source_text, test_cases):    """在受控环境里执行函数源码,全部用例通过才算 True。"""    namespace = {}    try:        exec(source_text, namespace)    except Exception:        return False    func = namespace.get("solution")    if func is None:        return False    for args, expected in test_cases:        try:            if func(*args) != expected:                return False        except Exception:            return False    return True  def main():    docstring = (        "def solution(n: int) -> bool:\n"        '    """返回 n 是否为偶数。"""\n'    )    test_cases = [((2,), True), ((3,), False), ((0,), True), ((-4,), True)]     candidates = []    for i in range(10):        if i < 7:            candidates.append(docstring + "    return n % 2 == 0")        else:            candidates.append(docstring + "    return n % 2 == 1")     results = [run_unit_tests(src, test_cases) for src in candidates]    num_correct = sum(results)    print("候选解数量:", len(candidates), " 通过数量:", num_correct)    for k in (1, 2, 5, 10):        score = estimate_pass_at_k(len(candidates), num_correct, k)        print(f"pass@{k} = {score:.4f}")  if __name__ == "__main__":    main()
目的:演示如何用“执行测试用例”判断候选函数是否正确,并基于无偏估计公式计算pass@k指标。
  1. from math import comb导入组合数函数,为无偏估计公式做准备。
  2. exec(source_text, namespace)动态执行模型生成的代码,相当于在评估容器里运行候选解。
  3. namespace.get("solution")从执行环境中取出模型定义的函数对象。
  4. for args, expected in test_cases:逐个测试用例运行函数并比对输出。
  5. comb(num_samples - num_correct, k) / comb(num_samples, k)计算“抽到的全是失败解”的概率。
  6. 1.0 - ...用补集算出至少一个通过的概率,即无偏pass@k。

常见误区

误区“HumanEval得分90%的模型,拿去做真实项目肯定也很厉害。”

正解HumanEval只是“入门级考卷”,题目平均不到20行代码,且只需实现单个函数。真实工程涉及项目结构、依赖管理、异常处理、性能优化等,这些都不在考卷范围内。就像厨师小考全拿了高分,不代表能胜任整桌宴席。要评估工程能力,还需要结合更难的基准、真实项目任务和人工代码评审。

为何常见:HumanEval是传播最广的基准,分数直观可查,人们很容易把“基准分数高”等同于“综合能力强”。

误区“A模型pass@100比B模型高,A就更强。”

正解pass@k允许模型试k次,本质上是“碰运气”的成功率。pass@100高而pass@1低,说明模型需要多次采样辅助,稳定性不足。这就像允许厨师做100次菜只取最好一次,不能代表日常水平。正确做法是同时看pass@1和pass@100的差距,甚至画出整条pass@k曲线来判断模型特性。

为何常见:k越大分数越好看,大家自然倾向于用更好看的数字宣传模型,忽略了指标背后的含义。

误区“能通过HumanEval的测试用例,代码一定完全正确。”

正解HumanEval每道题只有几个到十几个测试用例,属于“抽样检查”而非全量验证。一个实现可能碰巧通过了这些用例,却在隐藏输入上出错。EvalPlus的研究表明,补充更多自动生成的测试用例后,原来看似正确的模型会“漏出”大量隐藏错误。测试用例覆盖是评估质量的关键,但不是全部。

为何常见:人天然相信“测试全过=正确”,尤其当测试由专家编写时,很容易忽略覆盖率不足的问题。

误区“代码在沙箱里跑,所以可以放心执行任何模型生成的代码。”

正解沙箱只降低风险,不等于绝对安全。子进程和容器方案历史上都出现过逃逸漏洞。真实的评估平台还应该加双重保险:限制系统调用、禁用网络、挂载只读文件系统、设置严格超时和内存上限,并且把模型输出当作“不可信输入”处理。安全性靠纵深防御,而不是靠一个“沙箱”标签。

为何常见:“沙箱”这个名字给人安全隔离的暗示,让人低估了恶意代码的破坏力。

误区“现在很多模型HumanEval都快满分,这个基准已经过时废弃了。”

正解HumanEval依然是代码能力评估的“标准起点”,并没有被废弃。它的问题是区分度下降,解决方式是用它搭配其他基准一起看:EvalPlus看鲁棒性,MBPP看基础广度,LiveCodeBench看抗污染能力。就像入门考卷虽然简单,但在选拔初级选手时仍然有效,只是不能只靠它来选拔冠军。

为何常见:看到头部模型分数普遍90%以上,容易得出“基准完全没用”的极端结论。

面试题

Q1. HumanEval 数据集一共包含多少道编程题?

单选题 · easy

  • A.100道题
  • B.164道题← 正确答案
  • C.230道题
  • D.955道题

答案:B。HumanEval 的题目数量是164,不多不少。它就像一份精挑细选的考卷,用有限但高质量的题目衡量模型的基本编码能力。如果题目太少则统计波动大,太多则人工编写成本过高。

Q2. HumanEval 要求模型输出通过什么检验才算正确?

单选题 · easy

  • A.代码风格是否优美
  • B.功能是否完全正确← 正确答案
  • C.运行时间是否最短
  • D.内存消耗是否最小

答案:B。HumanEval 的核心理念是功能正确性:函数输出与期望输出一致即通过。它不关心代码风格、运行时间或内存,这些维度的评估需要其他工具和基准来完成。就像厨师考级只关心菜品口味是否符合标准。

Q3. pass@k 公式中组合数 C(n-c, k) 代表哪种“抽法”?

单选题 · mid

  • A.从全部 n 个样本中抽取 k 个的方法
  • B.从失败的 n-c 个样本中抽取 k 个的方法← 正确答案
  • C.从通过的 c 个样本中抽取 k 个的方法
  • D.从未采样的 n-k 个样本中抽取的方法

答案:B。分子 C(n-c,k) 表示“抽到的 k 个全是失败样本”的抽法数。它与分母 C(n,k) 相除,得到“抽k个全失败”的概率,再用1减去就是 pass@k。理解这一点是掌握无偏估计公式的关键。

Q4. 当某题生成的 n 个候选全部没有通过(c=0)时,pass@k 等于?

单选题 · mid

  • A.一定为0← 正确答案
  • B.一定为1
  • C.等于k/n
  • D.无法计算

答案:A。如果没有任何候选解能通过测试(c=0),那么无论抽 k 次还是 n 次,都不可能抽到正确解,成功概率恒为0。就像一盘菜从头到尾都是失败的,给再多次机会也无济于事。

Q5. HumanEval 题目的典型构成不包括下列哪项?

单选题 · mid

  • A.函数签名与文档说明
  • B.单元测试用例集
  • C.完整项目工程文件← 正确答案
  • D.标准输入输出样例

答案:C。HumanEval 的题目由函数签名、文档字符串和测试用例组成,目标是独立函数,不涉及完整工程项目。它刻意做成“小而精”的考卷,就是为了用最少的成本快速考察函数级代码生成能力。

Q6. 为什么无偏估计要直接枚举组合,而不是反复重新采样来估算 pass@k?

单选题 · extreme

  • A.重采样会带来高方差与估计偏差← 正确答案
  • B.重采样会显著降低通过概率
  • C.重采样需要更多测试用例
  • D.重采样结果更贴近真实

答案:A。利用固定的 n 个样本做多次重采样会重复使用正确的样本,导致对小样本的偶然成功被放大,产生高方差和有偏估计。组合公式直接基于概率论计算期望值,避免了蒙特卡洛随机性。就像你手里只有有限几份答卷,反复重抽数学上等价于精确推演,不需要猜。

Q7. 下列哪个方案通过“滚动更新题目”来对抗数据污染?

单选题 · hard

  • A.EvalPlus 增强方式
  • B.MBPP 数据集
  • C.LiveCodeBench 更新← 正确答案
  • D.MultiPL-E 扩展

答案:C。LiveCodeBench 的设计目标是防止数据污染,它定期加入新题目并隔离旧题,确保模型不能靠背诵训练数据来得分。EvalPlus 重点是扩充测试用例,MBPP 是补充基础题库,MultiPL-E 是翻译多语言,它们都不是以“滚动更新”为核心。

Q8. 下列关于 pass@k 指标的说法正确的有?

多选题 · mid

  • A.k 越大允许尝试次数越多← 正确答案
  • B.pass@k 随 k 增大单调不减← 正确答案
  • C.pass@k 衡量代码风格优劣
  • D.pass@k 衡量测试用例数量

答案:AB。pass@k 表示给模型 k 次生成机会时至少一次通过测试的概率,因此 k 越大成功率越高,且该值单调不减。它衡量的是功能正确性成功率,与代码风格、测试用例数量无关。

Q9. HumanEval 的已知局限包括哪些?

多选题 · mid

  • A.题目数量只有一百多← 正确答案
  • B.函数平均代码量很小← 正确答案
  • C.测试用例覆盖极其完备
  • D.难度天花板相对较低← 正确答案

答案:ABD。HumanEval 只有164道题,平均解答短小,难度不高,这些都是它的局限。测试用例覆盖并没有“极其完备”,反而存在明显不足,这也是 EvalPlus 要扩充用例的原因。认清这些局限,才能避免过度解读跑分。

Q10. 以下哪些属于 HumanEval 后续改进或衍生方向?

多选题 · hard

  • A.EvalPlus 扩充测试用例← 正确答案
  • B.MBPP 提供更基础题目← 正确答案
  • C.LiveCodeBench 滚动更新← 正确答案
  • D.降低所有题目难度

答案:ABC。EvalPlus 用自动生成的强测试用例补足覆盖不足;MBPP 用大量更基础的题目扩展考察广度;LiveCodeBench 用滚动更新对抗数据污染。多语言扩展也是方向之一。降低所有题目难度会让基准更快饱和,是错误方向。