
在实际模型评估项目中一个很容易被忽略的问题是模型分数到底应该和谁比。大家习惯看榜单上的绝对分但榜单背后的任务定义、评分口径和人类基线往往比排名更能解释模型能力的边界。以 EBR-bench 这类证据推理基准为例大量讨论集中在“AI 难以企及人类基线”如果人类基线怎么采集、模型分怎么换算、对比时看哪个指标都没有统一这个结论就很难被复现。下面围绕一个最小可运行的 EBR-bench 评测流程讲清楚人类基线的构建方式、模型评估流水线、结果拆解方法以及实际项目中最容易被忽略的坑。1. EBR-bench 到底在测什么从“及格线”到“可观察差距”1.1 证据推理评测与一般问答有什么不同从名字看EBR 可以理解为 Evidence-Based Reasoning也就是证据推理。它和普通问答最大的区别在于题目不能靠模型记忆或常识直接回答模型必须从给定材料中定位证据再判断一个结论是否成立。比如“这段对话中用户是否因为重复注册被冻结”模型不能凭经验说“重复注册一定会被冻结”而要在材料里找到冻结原因、重复注册记录和客服确认语句才能给出可靠答案。这种任务更接近真实业务场景。客服工单、司法文书、医疗报告、审计记录里大量判断都要求“有据可查”。EBR-bench 这类基准正是想把这些场景抽象成可度量、可重复的评测集。和普通百科问答相比它多了一层约束结论必须落在证据上而不是落在参数里。这里要特别注意一个容易混淆的点。证据推理评测不是“找出包含答案的句子”这么简单。一个完整题目通常包含四个部分材料、问题、候选证据、目标结论。模型不但要给出“成立/不成立/不确定”的判断还要指出支撑判断的是哪一段证据。换句话说它需要同时完成定位和判别两个环节只要有一个出错最终答案就是不可信的。1.2 人类基线不是一个数字而是一套回答分布很多论文里会写“Human baseline: 92.3%”但落到工程上这个数字并不能直接照搬。人类基线受到采集方式、标注人数、答案归一化规则、是否允许查资料等多重因素影响。举个例子。某道题让 10 名标注员独立作答6 人回答“成立”2 人回答“不成立”2 人回答“不确定”。如果按多数票这道题的人类答案是“成立”如果按全同意率这道题的难度系数会很低如果按置信度加权还要额外收集每个人对答案的把握。因此一个可信的人类基线应该包含三类信息题目层级的人类答案、整体指标的统计方式、标注协议版本。只有把这三者固定下来才能拿模型分数做横向比较。这也是“AI 难以企及人类基线”这句话经常被误读的原因。人类基线并不是一个天然存在的神圣数字而是一套由数据标注流程产生的结果。不同流程产生不同基线有时候差距不是模型能力造成的而是评分口径造成的。后面第 4 节会专门讲如何统一口径。1.3 “AI 难以企及”背后可能是评估设计差异如果某次评测中模型整体落后人类基线一大截先不要急着给模型下结论。至少要先排除三个评估设计差异。第一人类在答题时是否有额外信息。如果标注员可以看到题目来源、讨论材料、甚至可以在网络上检索而模型只拿到一段截断文本这种对比并不公平。业务系统里人可以做多轮查证模型往往只能单轮生成二者承担的推理负担不同。第二题目本身是否偏向人类思维。人类编写的评测通常会不自觉地依赖“人类共识”比如常识、社会经验、措辞习惯。模型缺少这些隐含背景自然会在这类题目上吃亏。这不代表模型在更客观的规则推理上也不行。第三输出格式对模型是否友好。很多评测要求模型输出严格 JSON一旦多写一句解释就算答案错误。人类标注时则可以直接勾选选项。若模型因为格式扣分误判率会被放大。合理的评测设计应当把“格式正确性”和“推理正确性”分开计分至少要在日志里区分失败原因。本节其实在表达一个核心判断评估一个模型不能只看它输了还是赢了更要看评估协议是否给了双方相同的答题条件。2. 准备一套可复现的 EBR-bench 评测材料2.1 统一数据格式每一道题都应该是自包含单元评测数据的第一步是定格式。建议使用 JSON Lines每行一个自包含的样本不依赖额外索引文件。这样无论是写 Python 脚本还是用其他工具处理都非常直接。下面给出一个推荐的结构示例。这里的字段是示例不是 EBR-bench 官方定义落地时可以根据实际任务扩展。{id: ebr-0001, material: 客服对话录屏转写。用户A声称账号被封客服核实后确认其账号存在三次异地登录并在回答中提及由于安全策略您账号已被临时冻结。, question: 根据材料用户账号被冻结的直接原因是什么, conclusion: 账号被冻结的直接原因是异地登录触发安全策略。, evidence: 客服在对话中明确提到由于安全策略您账号已被临时冻结, gold_answer: 成立, difficulty: easy, evidence_type: explicit, source: client-support-sim-001}每个字段都有自己的作用id是全局唯一标识用于关联人类答案和模型答案。material是模型唯一能读取的内容禁止把答案写在材料里。question和conclusion分开因为同一个材料可以衍生多个结论。evidence是人工标注的支撑证据主要用来检查模型引用的证据是否命中。gold_answer是标准答案但真正评测时还要结合人类基线一起看。difficulty和evidence_type用于后续分层分析。统一格式还有一个好处可以很容易地用脚本检查数据质量。比如校验空字段、重复 id、答案是否在合法枚举内。把这些检查写入评测流程比人工一个个看快得多。2.2 人类基线采集标注流程、质量控制与样本量人类基线不是简单找几个同事答一遍题就行。建议按下面这套流程走。先写一份标注手册至少包含任务背景、材料阅读规则、答案枚举定义、证据标注规则、常见边界例子。然后做一轮试标用 10 到 20 道题让标注员熟悉规则并统计试标一致率。如果一致率过低应先修改手册而不是直接进入正式标注。正式标注时推荐每道题至少安排 3 名标注员独立作答。独立的意思是标注员之间不能商量、不能看到对方的答案。作答完成后先做答案归一化把“成立”“支持”“是”等不同表达映射到统一枚举再计算一致率。一致率怎么用要看目标。如果目标是得到一个较稳定的题目标签可以采用多数投票如果目标是估计人类的原始表现则保留每个人经过归一化后的答案再按人聚合计算准确率。两种含义不同建议在评测报告里说明采用的是哪一种。样本量方面如果评测集只有 50 题人类基线的置信区间会非常宽。用二项分布估算50 题下 80% 准确率的标准误大约在 5 到 6 个百分点95% 置信区间大约在正负 10 个百分点以上。也就是说基线上下的浮动可能掩盖真实差距。因此小规模试跑可以回答“流程是否跑得通”但不能回答“AI 是否真的追不上人类”。2.3 学习环境与生产环境的数据管理差异很多团队在评测初期用的是临时 Excel 和 Jupyter Notebook这在学习环境里没有问题但进入正式项目后会出现数据版本混乱。建议从一开始就做好区分。学习环境重点关注能跑通加载、提示词构造、模型调用、结果输出即可。生产环境还需要关注数据版本、标注协议版本、答案归一化规则、模型版本、提示词版本、评分脚本版本的六方绑定。任何一方变化都应当生成新的评测报告而不是覆盖旧结果。下表是一个简单的差异对照。维度学习环境正式评测环境数据规模20 到 100 题建议至少 500 题起标注流程可临时人工审核必须独立标注并保存分歧数据集版本无固定记录每种修改都进版本管理模型调用本地小模型或 API 均可固定模型名和参数并记录评分脚本可手工检查必须纳入代码仓库并冻结报告保存本地文件带评测元信息的长久存储3. 构建模型评估流水线用代码跑通一个最小闭环3.1 环境准备与依赖模型评估的核心不是调用大模型而是把所有步骤串成可重复执行的流水线。下面使用 Python 实现一个最小闭环。主要依赖只有pandas、requests、numpy不绑定具体模型厂商。pip install pandas requests numpy如果使用 OpenAI 兼容接口通常在代码里配置三个环境变量API_BASE、API_KEY、MODEL_NAME。这样换模型时不需要改代码只需要改环境变量。export API_BASEhttps://your-api-endpoint.example.com/v1 export API_KEYyour-api-key export MODEL_NAMEyour-model-name这里不推荐把密钥写在代码里。无论学习还是生产都建议通过环境变量或密钥管理服务注入。3.2 加载数据集与构造提示词先写一个加载 JSONL 的函数再把每条样本转成提示词。import json def load_dataset(path): items [] with open(path, r, encodingutf-8) as f: for line in f: line line.strip() if line: items.append(json.loads(line)) return items def build_prompt(item): system 你是一个证据推理评测助手。只能根据给定材料判断结论是否成立不能使用外部知识。 user f材料\n{item[material]}\n\n问题\n{item[question]}\n\n结论\n{item[conclusion]}\n\n请判断结论是“成立”还是“不成立”并给出支撑证据。只输出 JSON格式为 {{\answer\: \成立或不成立\, \evidence\: \原文证据\}}。 return [ {role: system, content: system}, {role: user, content: user} ]构造提示词时要注意三点。第一system和user的分工要清楚把任务约束放在system中把具体材料放在user中。第二要求模型只输出 JSON可以减少解析成本但很多模型在复杂题目上仍然会多输出解释所以解析函数必须兼容。第三如果评测任务有 few-shot 示例可以在user内容里插入 2 到 3 个示例但示例数量不要太多否则会占用上下文也可能引导模型模仿格式而忽略推理。3.3 调用模型并解析输出调用接口时建议固定temperature和top_p。对于推理类任务温度一般设为 0 或很小的值减少随机性。如果一次评测中温度和随机种子不一致模型分数会包含额外噪声不利于比较不同版本。import os import requests API_BASE os.environ[API_BASE] API_KEY os.environ[API_KEY] MODEL_NAME os.environ[MODEL_NAME] def call_model(messages, temperature0.0, max_tokens512): resp requests.post( f{API_BASE}/chat/completions, headers{Authorization: fBearer {API_KEY}}, json{ model: MODEL_NAME, messages: messages, temperature: temperature, max_tokens: max_tokens, }, timeout60, ) resp.raise_for_status() return resp.json()[choices][0][message][content]模型输出往往不是干净 JSON。下面这个解析函数会先尝试直接json.loads失败后再用正则提取大括号部分把“成立/正确/支持”等表达归一化。import re import json def normalize_answer(text): text text.strip().lower() if any(k in text for k in [成立, 正确, 支持, 是, yes]): return 成立 if any(k in text for k in [不成立, 错误, 不支持, 否, no]): return 不成立 return 解析失败 def parse_response(raw): try: data json.loads(raw) if isinstance(data, dict): return normalize_answer(str(data.get(answer, ))), str(data.get(evidence, )) except Exception: pass m re.search(r\{.*\}, raw, re.S) if m: try: data json.loads(m.group(0)) return normalize_answer(str(data.get(answer, ))), str(data.get(evidence, )) except Exception: pass return normalize_answer(raw), raw解析函数里最容易被忽略的是如果answer字段已经丢失直接返回原始文本作为 evidence 会让后续统计失真。建议在评测日志里保留raw_output字段方便回头检查。3.4 指标计算与结果落盘拿到模型答案后最基础的计算是准确率和不一致样例列表。import pandas as pd def run_eval(dataset_path, output_path): items load_dataset(dataset_path) rows [] for item in items: messages build_prompt(item) raw call_model(messages) pred_answer, pred_evidence parse_response(raw) rows.append({ id: item[id], gold_answer: item[gold_answer], pred_answer: pred_answer, pred_evidence: pred_evidence, raw_output: raw, }) df pd.DataFrame(rows) df[correct] df[gold_answer] df[pred_answer] print(准确率:, df[correct].mean()) df.to_json(output_path, orientrecords, linesTrue, force_asciiFalse) if __name__ __main__: run_eval(sample_ebr.jsonl, model_output.jsonl)这个脚本虽然简短但已经构成了一个最小闭环。后面要加模型对照、人类基线对比只需要在df上按id关联其他结果。4. 与人类基线对比时应该看哪些数而不是只听结论4.1 统一指标口径不要直接比较原始准确率模型准确率和人类准确率如果来自两套不同的答案归一化规则直接比较没有意义。例如模型把“不成立”写成了“不”人类标注员写的是“不支持”如果不归一化两个答案会被误判为不一致。所以第一步是确保所有答案都进入同一套枚举。常见的指标包括以下几种。指标计算方式适用场景准确率预测与标准答案一致的比例分类式结论判断Macro-F1对每个答案类别分别算 F1 再平均答案类别不均衡时更稳健精确匹配答案和证据都完全一致的比例同时要求定位和判断正确证据召回标注证据是否被模型引用只关心定位能力人类一致性模型答案与多数人类答案一致的比例对比模型是否和人类思维一致如果人类基线是按多数票得到的题目标签模型分数也应使用同一套标签计算不能拿模型去和每个标注员单独比除非目标本身是预测标注员个体行为。4.2 按难度、证据类型和推理步骤拆解结果只看全局准确率会掩盖有价值的信号。建议至少按三个维度拆开difficulty、evidence_type、has_distractor。例如全局准确率模型是 70%人类基线是 92%看起来差距很大。但拆开看显式证据题目上模型可能达到 85%而需要多步推理和排除干扰项的题目上只有 55%。这种拆分能告诉研发团队应该优先优化哪类能力而不是笼统地说“模型不够强”。下面这段代码演示了按difficulty分组比较模型和人类基线human pd.read_json(human_baseline.jsonl, linesTrue)[[id, human_answer]] model pd.read_json(model_output.jsonl, linesTrue)[[id, gold_answer, pred_answer]] meta pd.read_json(sample_ebr.jsonl, linesTrue)[[id, difficulty, evidence_type]] merged model.merge(human, onid).merge(meta, onid) merged[model_correct] merged[gold_answer] merged[pred_answer] merged[human_correct] merged[gold_answer] merged[human_answer] grouped merged.groupby(difficulty)[[model_correct, human_correct]].mean() print(grouped)输出示例difficulty easy 0.86 0.95 medium 0.72 0.91 hard 0.55 0.84这里要注意示例数据只是说明输出形态真实评测应使用实际数据集。4.3 “AI 难以企及”的三个工程层面原因如果做完全面评估模型仍然明显低于人类基线原因通常落在工程层面。这里列三个最常见的原因并按可处理程度排序。第一推理自由度不同。人类标注员在答题时通常会反复阅读材料、回看上下文甚至做笔记模型在单次评测中只看到一个提示词没有二次确认机会。这个差距可以通过“多轮推理”或“思维链 自校验”来缩小但要控制评分方式一致。第二答案粒度不同。人类标注员可以直接在材料中标记证据区间而模型只能输出字符串。如果模型引用的是同义转述而非原文精确匹配会判错。在对证据要求严格的 EBR-bench 中这个差异会显著放大模型劣势。第三样本内在偏差。人类编写的基准确认了人类普遍认可的证据规则但模型训练语料里不一定覆盖这类细粒度规则。简单说人类基线成立得太“自然”而模型在自然语言表面上学会了揣测反而容易在需要严格理解规则时出错。这三条不是要证明“AI 不可能接近人类”而是提醒团队看到差距后先分析原因再决定是调整评测协议还是投入优化模型。5. 常见问题与排查清单5.1 模型输出格式不稳定导致罚分这是一个非常高发的坑尤其在要求模型输出 JSON 时。问题现象常见原因检查方式处理建议模型输出带解释性文字JSON 解析失败提示词约束不足或模型不擅长结构化输出打开raw_output查看原始文本增强提示词约束并使用兼容正则解析必要时用函数调用或 JSON Mode答案字段出现“成立。”多一个句号归一化规则不完整统计所有pred_answer取值归一化时去除标点和空白映射别名模型输出了空evidence模型只判断了结论没有定位证据检查证据为空样本比例评测指标同时计算证据召回避免漏报处理这类问题时不要频繁改提示词后重新跑全部数据。更稳妥的做法是先用小样本试跑确认解析正确率在 95% 以上再进入大批量评测。5.2 人类基线与模型分数没有可比性另一个常见问题是人类基线的采集环境和模型评测环境不一致。比如人类可以阅读完整材料而模型收到的是截断文本或者人类标注时能看到答案选项而模型是自由生成。这些差异会让比较失去意义。排查顺序如下检查两者使用的material是否完全一致。检查两者使用的答案枚举是否一致。检查人类是否允许多次修改答案模型是否单次输出。检查评分脚本是否独立避免“人评人”和“机评机”两套标准。如果需要更公平地对比可以在实验中加入“模型多轮查证”条件让模型在回答前先输出需要查证的点再基于补充材料作答。这类设计比较复杂但结论更有说服力。5.3 小样本下人类基线波动过大如果评测集只有几十题人机分数差几个百分点可能只是随机波动不是真实能力差距。可以用自助抽样Bootstrap估计人类基线的置信区间。import numpy as np def bootstrap_mean(values, n_bootstrap1000, seed42): rng np.random.default_rng(seed) means [] arr np.asarray(values) for _ in range(n_bootstrap): sample rng.choice(arr, sizelen(arr), replaceTrue) means.append(sample.mean()) return np.quantile(means, [0.025, 0.975])假设当前 100 题的人类准确率序列是human_correct调用函数后得到 95% 置信区间。如果模型准确率落在这个区间内就说明两者差异在统计上并不显著。这个步骤虽然简单但能避免得出“AI 难以企及”这种过度结论。5.4 一套可复用的评测前检查清单在每次跑正式评测前建议逐项确认以下清单数据集是否有正式版本号是否冻结不允许改动。每条样本的id是否唯一。标准答案是否进入合法枚举集合。人类基线是否记录标注协议版本和参与人数。模型提示词模板是否已经固定并在报告中记录版本。模型调用参数是否固定尤其是温度和随机种子。解析归一化规则是否包含所有常见别名。指标脚本是否固定结果是否带评测元信息保存。这份清单可以写进仓库的CONTRIBUTING.md或评测脚本的注释里让团队每次评测都走同一套流程。6. 从“AI 难以企及”到评测体系的长期建设6.1 数据集版本化与基线冻结基准评测最怕的是“边改题边比成绩”。一旦数据集发布就应该冻结题目、标准答案、人类基线和评分脚本。发现数据错误时不要偷偷改题而应该派发新版本并说明改动内容。推荐用 Git 做代码版本管理用 DVC 或 Git LFS 管理评测数据。评测报告需要记录数据集的 commit、评分脚本的 commit、模型版本和提示词版本。只有记录完整后续发现结果不可复现时才能定位是哪一层出了问题。6.2 记录评测卡让结论可追溯建议每次评测都写一张评测卡至少包含下表字段。字段说明数据集名称与版本例如ebr-bench-sample-v1.2任务定义材料、结论、证据的定义人类基线采集方式标注人数、独立作答、投票规则模型名称与版本需要精确定位模型权重版本模型推理参数temperature、top_p、max_tokens提示词模板版本对应仓库中的模板文件 commit指标与脚本版本对应评分脚本 commit评测日期方便与模型上线时间关联结论摘要人机差距、分层结果、已知异常这张表可以保存在评测报告首页也可以直接放在 JSON 或