当前位置:首页>排行榜>第 4 篇|benchmark 数据集:评测不准,多半不是工具问题,是"卷子"没出对

第 4 篇|benchmark 数据集:评测不准,多半不是工具问题,是"卷子"没出对

  • 更新时间 2026-10-13 23:02:04
第 4 篇|benchmark 数据集:评测不准,多半不是工具问题,是"卷子"没出对

很多团队做 Agent 测试的起点是:临时想几个问题,手动问一遍,觉得“还行”就上线。这等于考试不出题,随便在路上拉个人问“你觉得怎么样”——样本随机、标准主观、不可复现。上线后效果好不好,全靠用户投诉才知道。数据集解决的就是这件事:把“考什么”固定下来,让每次改动都能在同一套卷子上比分数。

学完你能干什么

  1. 1.能规划自己团队的数据集体系:分几个集、各自装什么、多久更新一次
  2. 2.能写出合格的 dataset item(含上下文轮、待判轮、期望轨迹与 metadata)
  3. 3.能说清参考答案从哪来,以及没有标准答案时该怎么判

一、四种来源、五类数据集:分工不同,别混在一起

benchmark 的数据到底从哪来、存成哪几类、为什么必须各管各的——这是本章要理清的一件事。很多团队的测试集越用越乱,根因就在这里:开发随手塞的题、线上回灌的题、红队攻击题全堆在一个目录里,最后谁也说不清某道题在考什么、该不该更新、能不能删。下面分两步讲清:先用一张表看「四种来源 → 五类数据集」的归集关系(来源本身的特性,以及它落到哪一类),再用一张表看「五类数据集」各自的定位和边界。

先不管分类,看源头。题目有四种来源,最终都会归集到五类数据集里;这张表讲「来源本身的特性 + 它落到哪一类」。注意「线上流量回灌」一行对应 weekly-* / ood-hard / regression-pitfalls 三样,因为它们都来自线上流量。

来源
冷启动占比
内容
质量要求
谁产出
落到哪类数据集
人工种子集
初期 100%
按业务场景手写:正常流 / 边界流 / 异常流各占 1/3
带期望轨迹与期望输出
测试
seed-core
(黄金基准卷)
开发自产
补充
开发联调时踩的典型 case
转成标准 item 格式
开发
cand-*
 候选池(审核通过才可能入卷)
线上流量回灌
稳定期主力
真实用户请求
多数无标准答案,配 judge 判
自动化脚本
weekly-*
 / ood-hard / regression-pitfalls(三样,均源自线上流量)
红队攻击库
安全专项
攻击样本 + 被打穿的样本
失败样本必须固化
Promptfoo 生成
redteam-fixed

冷启动建议:先手写 30~50 条种子题,覆盖三类场景各三分之一。别一上来就想做自动化回灌——没有基准卷,回灌回来的题也没法判好坏。


接着说五类数据集本身。为什么非得把数据分成五类、而不是一个大题库?因为五类的更新频率、判定方式、跑批位置完全不同:基准卷每次必考、低频修订;踩坑集只增不删、跑 PR 门禁;攻击集攻破即加;发散集只发版前跑。混在一起会出三件事——基准卷被稀释(对比变慢)、踩坑题被误删(防线失守)、攻击题混入常规评分(分数失真)。所以每类得单独管。下面这张表讲清五类各自的定位、更新节奏、用途,以及一个生活化类比,帮你判断一道题该往哪放。

数据集
定位
更新频率
用途
类比
seed-core
黄金基准卷(人工手写)
低频修订
每次跑批必考,版本对比的基准
教材课后习题
weekly-YYYYWww
线上真实流量回灌
周切版本
防漂移、贴近真实分布
每周时事测验
regression-pitfalls
历史踩坑题(线上事故 + 门禁漏放)
只增不删
防复发,PR 门禁全量跑
错题本
redteam-fixed
红队被打穿后固化的攻击
攻破即加
安全回归网
安全演习题库
ood-hard
发散硬题集(分布外硬题)
季度补一批(几十条即可,持续换新)
考泛化:遇到没见过的情况会不会胡编、乱调工具
竞赛压轴题

表里第五类 发散硬题集(ood-hard)最容易被漏掉:题目来自线上低概率场景、人工或模型变异改写、跨业务串场的问题,判的是「遇到没见过的情况会不会胡编、会不会乱调工具」,与基准卷(考基本功)互补。

  • •与基准卷互补:基准卷考基本功(正常 / 边界 / 异常全覆盖),它考泛化
  • •与踩坑集的区别:踩坑集是「已经出过事的题」,它是「还没出事、但很像会出事」的题

注意:它不进 PR 门禁(太贵也不稳定),只在 Nightly 或发版前跑。

1.1 谁能看到谁:开发集与测试集怎么分家

为什么要分开:开发自测集(开发拿去反复跑、调 prompt、验改动)和测试集(做版本判定、上线门槛)必须分开,根因只有一个——防数据串味(污染)。测试集的价值在于它「开发阶段没见过」,才能公正衡量模型真实水平;一旦开发能看到测试题,就会不自觉地针对它优化,测试集就退化成「练习题」,比出来的分量的是背题而不是能力,测试集随之作废。所以分开的本质,是让测试题在开发阶段始终对开发不可见。

怎么分开(保障不串):给题目设一道单向门——不分「谁写的 / 谁采集的」,只分「谁能看到」。所有题目进来先落候选池(开发可见、可自用),只有在候选池里过「评审 + 入卷」审核、被冻结后,才升级成私有卷「考卷」(只对测试可见)。下面这张表是两条轨道的边界,四条机制是「保障不串」的硬约束。

回灌题只有两条轨道,中间隔着一道单向门

轨道
谁能看到
拿来干什么
边界(越界就废)
候选池cand-*
全员可见(含开发)
开发自测、写用例、调 prompt;测试先做预筛
只是候选:题面与期望值随时会改,不能当版本对比的判定标准
题面只给测试;开发只拿得到聚合分与失败题号
版本对比、上线判定、回归防线
入卷即冻结,永不对开发开放逐题明细

保证不混的四条机制

  • •① 单向门:题只能从候选池流向私有卷,不能反向。审核通过(题面清晰、预期可判定、判据能写出来)才提升;提升后立刻从候选池移除,开发侧的数据集里不再出现这条。
  • •② 入卷即冻结:冻结 input、expected_output、metadata,并记一个 content_hash 与版本号。以后回灌再采到同一题(去重命中)只更新 metadata,不覆盖题目内容——否则「这周比上周掉了 5 分」分不清是代码退化还是题目被改了。
  • •③ 命名与权限隔离:数据集前缀就是可见性契约(cand-*开发可读,seed-core/ weekly-*/ pitfalls-*只有测试可读)。别靠「我们说好不使用那份数据」来隔离——靠命名和权限,机器才查得出来。
  • •④ 报告只给聚合:跑批报告对开发输出「通过率 + 失败题号」,不给题面;要复盘某道题,由测试陪同单条开出。开发拿到题面,就等于拿到了调参目标。

1.2 踩坑集的一个真实例子

线上事故:用户说"退订单 A1029",Agent 因字符串截断退成了 A102。复盘后测试做两件事:

  1. 1. 把这条 input 与期望轨迹存进 regression-pitfalls
  2. 2.在 PR 门禁加断言:assert order_id == "A1029"(验证不截断)

从此这个 bug 不可能再溜上线。踩坑集的价值 = 已经证明会出事的题,一条都不能少跑。

1.3 黄金基准卷怎么维护

  • •低频修订:只在业务口径变了(规则 / 话术 / 工具变化)或发现题目本身写错时才改;不允许为了「让分数好看」改题
  • •改题必动基线:任何加题、改期望都会让历史分数不可比,改完要重跑基线并升版本号,否则分不清「分数掉了」是代码退化还是题目变了
  • •加题走审核、记来源:新增题写清 source(人工 / 事故 / judge 低分)与 risk_level,便于按风险筛题

一句话:基准卷是「教材课后习题」,修订要慢、要留痕、要和基线同步。

1.4 题目多了怎么选着跑

集合怎么跑为什么
seed-core
全跑(规模控制在一两百条)
它是版本对比的地基,抽样就没法比了
regression-pitfalls
全跑
防线不能抽样,漏一条就可能复发
weekly-*
滚动窗口:最近 4 周全跑 + 历史档抽样
贴近当前真实分布
weekly-* 超过 90 天
转低频档,季度跑一次
分布已经过期,只作兜底

选题优先级:风险等级高 → 必跑;最近改动影响到的模块 → 定向跑;历史复现率高 → 提高权重;其余按分层抽样。一句话:重要的全跑,最新的一批全跑,其余抽样加归档。

1.5 防漂移到底在防什么

漂移 = 同一套题、同一套判定,分数却随时间下滑。防漂移就是拿固定卷子定期复考,因为下面这些变化都会让效果悄悄变差:

  • •模型侧:版本升级 / 下线、供应商默认模型被替换
  • •代码侧:prompt 改动、编排规则改动、工具返回值变化
  • •数据侧:上游字段含义变化、用户表达方式变化(去年说「退款」,今年说「退一下」)
  • •环境侧:检索库内容变旧、依赖服务超时变多

验证对象是测试环境 / 预发:题从线上 trace 只读采集而来,跑批在 staging 执行;生产只做在线评估与告警,绝不在生产跑批。节奏:Nightly 跑基准卷 + 最近 4 周回灌集,与基线比,掉出容忍区间就报警。


二、一条合格的 dataset item 长什么样

2.1 先分清四层单位:session / trace / turn / segment

写题之前先把这四层分清,否则很容易把「一轮对话」当成「一条 trace」:

单位是什么例子
session
一次连续会话(可含多个任务)
一个用户 10:00 到 10:05 的整段对话
trace
一次请求触发的完整执行(用户一句话 → 内部跑 N 圈)
用户说「帮我退掉订单 A1029」那一次
turn
用户的一句话
第 3 句:「钱退到哪」
segment
任务片段(同一任务的连续几轮)
「退订单」这一段,不含后面「改地址」那段

所以多轮对话 ≠ 一条 trace,通常是「同一 session 下的多条 trace」;用了 checkpointer 的框架也可能把整段会话做成一条 trace,那就变成一条 trace 含多个 turn。回灌切题的正确单位是 segment。

2.2 三段式 item 结构

推荐统一成三段式结构:

{  "context_turns": [    {"role": "user",      "content": "帮我退掉订单A1029"},    {"role": "assistant", "content": "确认为您退款吗?"}  ],  "target_turn": {"role": "user", "content": "确认"},  "expected_tools": [    {"name": "stage_refund", "arguments": {"order_id": "A1029"}}  ],  "expected_output": null,  "metadata": {    "source": "incident-2026-09-20",    "source_trace_id": "tr-c3d4",    "source_session_id": "sess-8899",    "context_dependent": true,    "case_type": "normal",    "risk_level": "high"  }}

三个字段最容易漏,但很关键:

字段
为什么必须有
context_turns
多轮依赖的题,脱离上下文就是废题("确认"两个字模型根本不知道确认什么)
metadata.source
三个月后没人记得这题为什么存在,无法判断能不能删
metadata.case_type
区分 normal / ambiguous_clarification(追问类)/ distractor(抗干扰类),跑批策略不同

追问类题目别丢:像"确认""好的""退这个"这类信息不全的输入,本身就是高价值考题——测的是 Agent 会不会主动追问,而不是瞎猜一笔订单直接退款。线上大量差评正源于 Agent "自作主张"。


三、参考答案从哪来(三种补法)

回灌题大多没有标准答案,按成本选补法:

补法
做法
适合
成本
用线上真实轨迹当 expected_tools
trace 里记录了实际调过的工具,直接转成参考轨迹
固定流程业务,配对照型判定
免费、确定性 100%
用线上 output 当弱参考
只用于对比时的差异比较,不作为断言
版本对比
免费
人工或 judge 标注
人工补期望输出
只给高价值样本(错误层 100%)
最贵

判断口诀:能免费的不用花钱的,能确定的不用波动的。judge 只用于"确实没有标准答案"的题。


四、维护机制:数据集是会腐烂的

数据集建完不管,三个月后就会变成一堆跑不通的脏数据。四个维护动作:

动作
规则
频率
去重
对 input 做哈希查重;同 session 的整段题按 source_session_id 去重
每次回灌
脱敏
手机号/身份证/地址必须清洗后才入库(合规红线)
每次回灌
归档
超过 90 天的 weekly 集转低频档(不删,只是不每次跑)
季度
增题审核
踩坑集增题需确认"确实是 bug 且已修复",避免塞进误报题拖慢门禁
持续

4.1 回灌脚本骨架

# sync_dataset.py —— 由 cron 每日触发import hashlibfrom langfuse import Langfuself = Langfuse()seen = load_existing_hashes("weekly-2026W39")for sess_id, turns in group_by_session(fetch_traces(since, until)).items():    for segment in split_segments(turns):          # 一 session 可能含多个任务        if not should_sample(segment):             # 分层采样:错误层 100%、正常层 1%            continue        item = build_item(segment)                 # context_turns + target_turn        item["metadata"]["source_session_id"] = sess_id        if hashlib.md5(str(item).encode()).hexdigest() in seen:            continue                               # 去重        item = desensitize(item)                   # 脱敏        if not dry_run_pass(item):                 # 空跑验证可用性            continue        lf.create_dataset_item(dataset_name="weekly-2026W39", **item)

dry_run_pass 这一步别省:新灌进来的题先空跑一次,确认 Agent 能正常响应(不是格式错乱的脏数据),通过才入库。自动化流水线最容易堆垃圾,这是唯一的闸门。

4.2 空跑在防什么

空跑(dry run)= 不判分、不固化结果,只把这条题真跑一遍看会不会炸。查三件事:

  • •能不能正常调通 Agent(不是 500、不超时)
  • •返回结构是否合法(JSON 能解析、字段齐全)
  • •有没有明显异常(空回复、死循环)

与正式跑批的区别:正式跑批的分数进基线与门禁;空跑只看「这条题能不能用」。回灌是自动化流水线,没有这道闸门,题库会被脏数据堆满,还会污染分数统计——几十条格式错的题就能把通过率拉下来。


五、避坑点

#
坑
后果
正确做法
1
只用一个大杂烩数据集
越滚越大、对比变慢、踩坑题被淹没
按五类分开建集
2
踩坑集因为「太难」被删
防线失守,同类 bug 复发
只增不删,超量转低频档
3
回灌时不脱敏
PII 入库,合规事故
灌题前强制走脱敏函数
4
回灌题没有 context_turns
一堆「确认」「好的」废题
按 session 整段拼装
5
只采好样本
数据集全是简单题,区分度为零
分层:错误层 100%、边缘层 50%、正常层 1%
6
建完不维护
三个月后变成跑不通的脏数据
季度归档 + 每次回灌去重脱敏

六、需要开发配合什么

#
配合项
频率
1
敏感字段清单(哪些是 PII),确认脱敏规则
一次性 + 新字段时
2
线上事故复盘后移交 trace id,供固化成踩坑题
事故时
3
各业务场景的规则与口径说明(写种子题的依据)
一次性
4
开发联调踩坑 case 同步给测试入库
持续(低频)

七、小结

  1. 1.数据集是评测体系最值钱的资产——工具可以换,题要一直养
  2. 2.五类数据集分工明确:基准卷 / 周回灌 / 踩坑集 / 攻击库 / 发散硬题集,尤其踩坑集只增不删
  3. 3.item 用三段式(context_turns + target_turn + expected),metadata 必须写清来源
  4. 4.参考答案优先用真实轨迹(免费确定),judge 只补没有标准答案的题
  5. 5.数据集会腐烂,去重/脱敏/归档/审核四个动作要常态化
  6. 6.题只能从候选池流向私有卷(单向门);入卷即冻结,开发只看聚合分与失败题号

自测清单(5 题)

    1. 1.你们团队目前有几个数据集?分别对应本篇五类里的哪一类?缺哪一类?
    2. 2.一条多轮题只存了最后一句"确认",为什么它是废题?正确结构该怎么写?
    3. 3.线上出了一次金额截断事故,应该固化成哪类数据集的题?还要做什么配套动作?
    4. 4.回灌题没有标准答案,有哪三种补法?你会优先选哪种?
    5. 5.三个月后 weekly 数据集涨到 5000 条,跑批要 2 小时,该怎么处理?

    下一篇预告:第 5 篇《线上流量自动回灌 dataset:把真实用户变成你的考官》——有了考卷,考题能不能自动生产?讲清分层采样、session 分段、定时任务与消息队列两种触发方式,并给出完整可跑的回灌脚本。

    随机文章