返回 AICAP-180
B10 · Day 97p95/cost 测量 + 成本级联 + tau2-bench

held-out 任务设计 (M2)

昨天(Day 96)搭好了 tau2 retail 双控制脚手架,但脚手架要测出可信数字,前提是任务集本身没被「刷过」。

阶段: B10 · p95/cost 测量 + 成本级联 + tau2-bench(Day 91-100) 标签: #held-out #overfitting #verifier #agent-eval

今日导引(由浅入深)

昨天(Day 96)搭好了 tau2 retail 双控制脚手架,但脚手架要测出可信数字,前提是任务集本身没被「刷过」。 B1→B18 曲线上,M2(评测能力)的成熟标志之一就是分清「开发期可见的任务」和「最终评测才用的任务」。 否则你会无意识地把 prompt 和工具调成专门通过那几道已知题,得到虚高的 pass 率。 今天的内核是 held-out(留出)任务设计 + 确定性验证函数:开发期不可见、仅用于最终评测,且每个任务的 pass/fail 由一个纯逻辑函数判定,而不是再叫一个 LLM 当 judge。 今天最小可判定产出:3 个 retail held-out 任务文件 + 各自的 pass/fail 验证函数,且验证函数能用单测断言立即跑通(不需 key)

1. 机理精读

定义。 held-out 任务集 = 在 agent/prompt/工具开发与调参阶段完全不暴露、只在最终评测时跑一次的任务。 它对应机器学习里 train/dev/test 的 test 集语义:你可以在 dev 集(已知任务)上反复迭代,但 test 集(held-out)必须保持「冷」。 一旦你在开发期看过、调过它,它就退化成又一个 dev 集,失去衡量泛化的能力。

为什么需要它(过拟合的机理)。 agent 系统的「参数」不只是模型权重——更危险的是 prompt 文本、few-shot 例子、工具描述、解析正则。 当你盯着同一批可见任务反复调这些,会不自觉地把它们调成「专门通过这几道题」:prompt 里加一句话让某道题过、正则放宽让某条 transcript 解析成功。 这是 prompt-level overfitting,比权重过拟合更隐蔽——它不需要梯度,只需要你的眼睛盯着同一批题。 held-out 把「调」和「测」物理隔离,断掉这条隐式优化路径。

retail 域的任务模板。 tau2 retail 单域常见两类确定性任务。 退货(return):客户要退某订单,期望最终环境状态里该订单状态变为 returned、余额按规则退回。 改单(modify order):客户要改地址/数量,期望订单字段被正确更新且约束(如已发货不可改)被尊重。 每个任务配一个确定性验证函数:读最终环境状态,按预期断言,返回 {pass, detail}

为什么验证函数要确定性(而非 LLM-judge)。 若用 LLM 当 judge 判最终状态,就引入了第二个不确定模型,且容易和被测 agent 同源(同模型既当 agent 又当 judge → 循环自评)。 确定性验证函数读的是结构化环境状态(订单字段、余额数值),用普通断言判对错——零方差、零成本、可单测。 这正是 2026 agent eval 共识:能用确定性 grader 的地方绝不用 LLM-judge。

关键权衡与边界。 确定性验证的代价是它只能验「可结构化判定的结果」(状态、数值、格式),验不了「语气是否得体、解释是否充分」这类软指标。 那些软指标仍需 LLM-judge 或人审;held-out + 确定性验证负责硬骨架(办成没办成),软指标另走通道。 今天只做硬骨架部分。 这与 tau2 的 pass^k(Day 98)正交:验证函数判单次 pass/fail,pass^k 在其之上统计 k 次稳定性。

held-out 的规模与「冷度」管理。 held-out 集太小,最终评测的方差就大(呼应 B17:N=29 时 Δ+10.3pp CI[0,20.7] 不显著,需 ~70 任务)。 所以 3 个任务只是脚手架起步,不是最终评测规模——真正下结论前要把 held-out 扩到几十量级。 「冷度」也要主动管理:一旦某 held-out 任务被用来调过 prompt,就必须把它降级回 dev 集,再补新的 held-out。 这条纪律没有工具能替你执行,只能靠流程约束——这也是 held-out 比权重正则更难守的原因。

2. 推导 / 手算 / 代码走读

held-out 任务文件与验证函数目前待建bench/tau2/ 尚不存在,见 Day 96 Glob 结论)。 所以今天走读的是「为什么验证函数能不依赖 key 先测通」,并对照本仓已有的 prediction-source-agnostic 范式:

  • 确定性验证函数 = 纯函数:输入 (finalEnvState, expected),输出 {pass: boolean, detail?: string}
  • 退货验证示例:读 env.orders[id].status === 'returned'env.balances[user] 按退款规则增加;不调模型、不触网,所以可直接用断言单测(不需 key)。
  • 对照 src/agent/eval/tasks.ts 的 codeCheck:tasks.ts 里每条任务的 codeCheck(如 has(/structur|smurf/i) 对应 aml-structuring-classic,tasks.ts:54)就是确定性 grader 的单轮版——纯正则/纯逻辑判 pass。
  • 升级关系:held-out 验证函数把它从「判一段文本」升级为「判最终环境状态」,但「确定性、纯函数、可单测」的精神一致。
  • 避免循环自评,呼应 B14src/aml/groundTruthEval.ts:1-9 明确指出 evalBaseline 的循环弱点是「同一规则引擎既预测又被同一作者的合成生成器评分」。
  • prediction-source-agnostic:于是 binaryEval(items)(groundTruthEval.ts:38)只吃 {label, predicted} 对,两端都可来自外部——判定逻辑与「谁生成的 transcript」解耦。
  • 同理迁移:held-out 验证函数判定逻辑与「谁生成的 transcript」解耦,换任何模型跑出的最终状态,同一个验证函数都能判,不存在「judge 和 agent 是同一个模型」的污染。
  • 可立即测的边界:验证函数本身(纯逻辑)今天就能写完并单测绿;把 agent 接上真实模型跑出 transcript 再喂进验证函数 = 待跑(需 key)。

一个验证函数的最小断言示例(纯逻辑,不需 key 即可跑):

verifyReturnBasic(finalEnv):
  pass = finalEnv.orders['A'].status === 'returned'
      && finalEnv.balances['u'] === REFUND_AMOUNT
  return { pass, detail: `status=${finalEnv.orders['A'].status}` }

单测构造两个 finalEnv(一个期望态、一个未退货态),分别断言 pass===truepass===false,即可在无模型环境验证逻辑正确。

3. 今日实战

  1. bench/tau2/ 下新建 tasks/ 目录,写 3 个 held-out retail 任务文件:return-basic.tsmodify-address.tsmodify-shipped-blocked.ts(最后一个是负例:已发货订单改单应被拒)。
  2. 每个任务文件含:(a) in-process 工具调用脚本(场景初始化 + 期望流程),(b) 一个确定性 verify(finalEnv): {pass, detail} 验证函数(校验最终环境状态)。
  3. 给每个 verify 写断言单测(构造期望/非期望的 finalEnv,断言 pass/fail),不接模型,先跑绿。
  4. 把这 3 个任务接到 Day 96 的 bench/tau2/ scaffold 的任务注册处。
  5. 真实 transcript 评测(agent 接 deepseek-v4-flash 跑这 3 题)= 留到能跑 key 时执行。

三个任务的验证要点(确定性、可单测):

  • return-basic:终态 orders.A.status==='returned'balances.u 增加恰为退款额;任何其他订单状态不变。
  • modify-address:终态 orders.A.shippingAddr 等于请求的新地址,且订单仍 active、金额不变。
  • modify-shipped-blocked(负例):订单已 shipped,改单请求应被拒,终态 orders.A 与初态完全一致——验证函数断言「无变更」。

4. 今日实测 / 产出

  • 验证函数(纯逻辑)= 可立即写并用单测断言通过——不需 key
  • 3 个 held-out 任务文件 + 各自 pass/fail 断言测试 = 可建(确定性部分)
  • 接模型跑真实 transcript = 待跑(需 key)

状态如实:确定性部分今天可绿,模型驱动部分仍 gated;不把 gated 部分写成已完成。

5. 常见误区 / 陷阱

  • 把 held-out 当成「再加几道 dev 题」:一旦你在开发期看过、调过它,它就废了;held-out 必须保持冷。
  • 用 LLM 当验证函数:引入第二个不确定模型且易与 agent 同源 → 循环自评(B14 groundTruthEval 专门修这个);能确定性判就别用 judge。
  • 验证函数只判「有没有调对工具」而不判最终状态:dual-control 下 user 也会改环境,必须判最终环境状态而非中间动作,否则测不出竞争性变更下的正确性。
  • 负例缺失:只写「应成功」的任务,不写「应被拒」(如已发货改单),测不出 agent 的约束遵守。
  • held-out 太小就下结论:3 题只够验脚手架,方差太大;正式结论需把规模扩到几十量级。
  • 验证函数把「无支持」误判为「失败」:参考 groundTruthEval 分母为 0 时返回 0 的处理,要区分「没办成」和「该任务没有可判定的支持」。

6. 学习资源(每条带 YYYY-MM)

  • tau2-bench 论文(held-out + 确定性验证函数的方法论来源),2026-03。
  • 本仓 src/aml/groundTruthEval.ts(prediction-source-agnostic 评测,反循环自评范式),2026-06。
  • 本仓 src/agent/eval/tasks.ts(确定性 codeCheck 单轮 grader 示例),2026-06。
  • Anthropic "Demystifying evals"(held-out / 防过拟合 / judge 校准),2026-01。

SOTA检查 (2026-06 更新)

  • 当前主流:held-out + 确定性验证函数是 2026 agent eval 共识,目的就是避免 LLM-judge 单点与 prompt-level 过拟合,仍 SOTA
  • 是否仍 SOTA:是。确定性 grader + 留出集是 tau2-bench 系评测的标准做法,软指标才补 LLM-judge。
  • 过时黑名单:避免「循环自评」——用同一模型既当 agent 又当 judge(对应 B14 groundTruthEval.ts 的 prediction-source-agnostic 修法);避免把 dev 集当 test 集复用。
  • 下次复查点:tau2-bench 官方任务模板/验证接口若更新,须对齐验证函数签名;judge-κ 标注若落地(当前仍 gated),可补软指标通道。

衔接

  • 昨天:Day 96 — tau2-bench 概念(M2, 2026-03),双控制脚手架 + 1 条 transcript。
  • 今天:held-out 任务 + 确定性验证函数(纯逻辑可立即单测,不需 key)。
  • 明天:Day 98 — pass^k 测量(M2),在 held-out 任务上各跑 k=3 统计 pass^1 / pass^3。