跳到主要内容

数据截至 (上游 commit 8b292c9f1b14)

第 6 章 巧妙之处、边界与代码地图

本章讲什么: 前五章讲清「它怎么转」;本章收「哪里值得学、哪里别踩」,以及 v0/v1 两套 API 怎么在同一个包里和平共处。

1. 巧妙之处(可借鉴的技术)

① 拦截即契约,进程即细节

harness 的唯一硬约束是「模型调用打向 endpoint + secret」,起不起进程随意(harness.py:262-284 的 launch docstring)。妙在哪: 评测框架通常和「程序怎么跑」深度耦合,verifiers 把契约收敛到网络出口一点——于是进程内循环、真实 CLI、远程沙箱里的程序,全都同构。这也是它能评「真实 Claude Code」而非「Claude Code 的模拟」的原因。

② 重试去重靠「标记 + 摘要」双条件

拦截层只在「带了重试标记的请求头」「请求体摘要相同」时才重放缓存响应(server.py:400-419)。妙在哪: 单看请求体会误伤合法场景(context compaction 会重新生成一模一样的请求),单看标记会漏掉 SDK 无标记重试。两个条件取交,是「宁可少合并、不可错记图」的保守正确性。

③ 等用户不烧 agent 预算

timeouts.agent 是累积预算,只在 harness 段真正运行时计费(rollout.py:136-139、:396-401)。妙在哪: 多轮交互评测里,「等用户/等别的 agent」的时间不算在 agent 头上——否则慢用户会饿死 agent 的预算,模拟用户类评测的结果会系统性失真。

④ 打分先 seed 占位、后执行、缺席留名

Task.score 先把所有信号名写进 trace(占位),再并发执行,最后逐个落账(task.py:212-243)。妙在哪: 某个 reward 函数抛了,报告里那个名字还在、值是空——缺测是显式的,不是静默少一行。对「几十个评分信号 × 几千条轨迹」的批量评测,这种可审计性比省一次异常好看得多。

⑤ 钩子按参数类型归边界

@stop/@intercept 不用注册位置,框架看注解参数是 Request/Response/Trace 就归到对应边界(session.py:39-53)。妙在哪: 少一张注册表,多一份类型安全——注解本身成了路由配置,写错形态在 import 期就炸。

⑥ Judge 模板的占位符替换用一遍扫描

Judge.build_messages 不用 str.format(自定义 prompt 里的 JSON 花括号会炸),也不顺序 replace(替换值里再含 {answer} 会被二次扫描),而是一次正则扫描、只替换该 judge 文档化的占位符,未知占位符原样保留(judge.py:135-147 注释与实现)。妙在哪: 三种常见写法的坑全躲开了,注释把「为什么不」写得明明白白。

2. 边界与局限(诚实清单)

  • subprocess runtime 仅供调试。 副作用会串门(一个子进程改了 harness 的全局配置,别的 rollout 遭殃),框架自己也只在调试时推荐它(docs/v1/architecture.md:11);EXECUTES_CODE=True 的 harness 落在 subprocess 上会在 env 初始化时被警告(env.py:123-137)。
  • token 预算是软一格的。 四条 RolloutLimits 都在回合间检查,跨线的那一轮会跑完(session.py:66-68)。要硬截断得靠 @stop
  • 一个 taskset 只出一种 task 类型(taskset.py:1-13)。混合题型要拆成多个 taskset 或在外层组合。
  • 无限 taskset 不能直接 shuffle——先 head(n) 再洗(taskset.py:80-88);评测时 num_tasks 必填(evaluation.md:40-41)。
  • finalize 里抛异常 = 整局失败重试,所以跨 trace 评判必须「严格校验、绝不记猜测分」(env.py:160-167 注释)。
  • 借来的 runtime 被属主中途拆掉是 bug 而不是数据:rollout 会把这错抛回调用方,而不是记在自己 trace 上(rollout.py:160-168)。
  • 深绑 Prime 生态。 Environments Hub、prime runtime、prime-rl 训练栈都是 Prime Intellect 的;modal/docker/subprocess 三条路径不依赖它,但最顺的体验在自家生态内。

3. legacy:v0 怎么零副作用地活着

v1 上线后,老的 v0 API(vf.SingleTurnEnvRubricload_environment 等)没有删,而是整体搬进 verifiers/legacy/(老的文档移到 docs/legacy/)。共存机制是全库最巧的一段代码——包根部的元路径查找器(verifiers/__init__.py):

  1. _legacy_modules()pkgutil.iter_modules 只读目录、不执行代码,列出 legacy 的子模块名(__init__.py:31-37);
  2. LegacyAliasFinder(__init__.py:39)插到 sys.meta_path 最前面(:132-133),把 verifiers.<v0 模块> 的导入解析到 verifiers.legacy.<模块>;
  3. 于是 import verifiers 本身零副作用——v0 那套(带 logging 配置等 import 副作用)只有真被碰时才加载(包 docstring,:1-8)。

对用户的含义:老代码 import verifiers as vf; vf.SingleTurnEnv 照常工作,但读源码时顶层那些目录已经不在原位——全在 legacy/ 下。新写环境请用 v1(官方 docs/v1/ 系列;docs/legacy/ 留给旧栈)。

4. 横向对比(同 shelf 兄弟)

侧重与 verifiers 的分工
verifiers环境/harness 执行 + 记录「让 agent 在真实 harness 里做题并记可训练的账」
verlRL 训练引擎verifiers 的 trace(token_ids/logprobs)喂给这类训练器;prime-rl 是其同门
agent-lightning给运行中的 agent 加 RL同样在「跑 agent→训 agent」之间架桥,但走 span 采集路线;verifiers 走「环境即考场」路线
inspect-ai评测编排同为评测框架;inspect-ai 自带 solver 循环,verifiers 评真实外部 harness
terminal-bench / osworld任务集/基准以 taskset 形式接入 verifiers(uv run eval primeintellect/terminal-bench-2)

5. 代码地图(导航索引)

主题文件路径符号名
任务数据卡verifiers/v1/task.pyTaskDataTaskTimeoutTaskResources
任务打分/钩子合并verifiers/v1/task.pyTask.scoreTask.hookstask_key
题库verifiers/v1/taskset.pyTaskset.loadheadshuffleSEED
编排verifiers/v1/env.pyEnv.runEnv.finalizerun_episodeRunSlot
harness 契约verifiers/v1/harness.pyHarness.launchHarness.resumeHarnessSessioninstall_skills
生命周期verifiers/v1/rollout.pyRollout.openRollout.stepRollout.close
预算/钩子路由verifiers/v1/session.pyRolloutLimitshook_boundaryRolloutSession
拦截服务器verifiers/v1/interception/server.pyInterceptionServer.handle_requestis_retried_request
轨迹verifiers/v1/trace.pyTraceBranchModelCallReward
一局verifiers/v1/episode.pyEpisodeWireEpisode
裁判verifiers/v1/judge.pyJudgeJudge.build_messages
错误边界verifiers/v1/errors.pyboundaryRolloutError 家族
预制 envverifiers/v1/envs/SingleAgentEnvUserSimEnvBestOfNEnvAgenticJudgeEnv
内置 harnessverifiers/v1/harnesses/claude_code / codex / bash / null / browser_use 等
runtimeverifiers/v1/runtimes/SubprocessDockerModalPrime
v0 兼容verifiers/__init__.pyLegacyAliasFinder_legacy_modules
示例任务集environments/alphabet_sort/alphabet_sort/taskset.pyAlphabetSortTaskset(无限生成器 + 脚本化用户)