章节导航 第 5 章 / 共 21 章
产出训练数据
HyTuner RL 集成、从评测轨迹到 SFT/DPO 数据、Trace 体系、Cache 体系、Reward 工具
评测和训练的关系
Nexus 的核心定位是研究框架,它天然连接评测和训练两个环节:
评测(Eval) 训练(Train)
Agent 在 Benchmark 上跑 → 收集对话轨迹
计算 Score → Score 作为 Reward
轨迹 + Score → SFT / DPO / RL 训练数据
这一章讲的就是右半部分——如何从 Nexus 的执行过程中提取可用于训练的数据。
Trace 体系:对话轨迹的采集
Nexus 有一套自研的轻量级 Trace 系统(不依赖 OpenTelemetry),通过 @observe 装饰器自动埋点。每次 Agent 的 LLM 调用、工具执行都会产生 Span。
Rollout 生命周期
UCSD + Adobe + 多伦多大学等联合发布的 GFCR 框架(arXiv: 2605.02913)将 RL 训练中的轨迹生成生命周期拆解为四个模块。对照 Nexus 的现有覆盖:
| GFCR 模块 | 功能 | Nexus 对应 | 覆盖程度 |
|---|---|---|---|
| Generate | 生成轨迹 | Trace 体系(@observe 装饰器自动埋点) | ✅ 完整 |
| Filter | 筛选高质量轨迹 | score ≥ 0.8 筛选逻辑(较简单) | ⚠️ 基础 |
| Control | 并发控制/超时/重试 | run-batch 参数(max_concurrent/timeout/retry) | ✅ 完整 |
| Replay | 轨迹重放与复现 | Cache 体系(pickle 格式缓存) | ✅ 完整 |
当前 Filter 策略(score ≥ 0.8)较为简单。更细粒度的过滤方案可参考 GFCR 的多维度筛选(如按步骤正确率、工具使用效率等)。
Processor 插件
Span 数据通过 Processor 插件来消费。在 YAML 中配置:
processors:
chat:
name: "chat_tracker"
tokens:
name: "token_counter"
args:
client_filter: "main" # 只统计 main client 的 token
内置 Processor:
| Processor | 注册名 | 产出 | 说明 |
|---|---|---|---|
| ChatTracker | chat_tracker | 完整对话历史 | 每条消息含 role/content/tool_calls/timing |
| TokenCounter | token_counter | token 用量统计 | 按 client 分组的 prompt/completion tokens |
| HyTuner | hytuner | token-level 训练数据 | prompt_ids / response_ids / logprobs |
| JsonLine | json_line | JSONL 文件 | 流式写入 span 事件 |
| ChromeTrace | chrome_trace | Chrome trace 文件 | 可用 chrome://tracing 查看时间线 |
print | 控制台输出 | 调试用 |
执行完成后,从 RunOutput.processor_results 获取数据:
result = await run(run_input)
# 对话轨迹
trajectory = result.processor_results['chat']
# trajectory = [{'role': 'system', ...}, {'role': 'user', ...}, ...]
# Token 统计
tokens = result.processor_results['tokens']
# tokens = {'prompt_tokens': 12500, 'completion_tokens': 3200, ...}
从轨迹到训练数据:三阶段管线
2026 年 tool-using agent 的主导后训练管线是三阶段递进:SFT → DPO → RL。ICLR 2026 论文”SFT Memorizes, RL Generalizes”验证了 RL 后训练可恢复 SFT 损失的 99% OOD 性能。Nexus 的 Trace 体系天然支撑这三阶段:
Stage 1: SFT 冷启动(筛选高分轨迹,覆盖格式和工具调用模式)
# 筛选高分轨迹
if result.task_output.score >= 0.8:
sft_sample = {
'messages': result.processor_results['chat'],
'score': result.task_output.score,
'task': run_input.task_name,
}
save_to_dataset(sft_sample)
Stage 2: DPO 对齐(同问题多次运行,高分 vs 低分组成偏好对)
# 在 Generator 中为同一实例生成多个 run
for instance in dataset:
for trial in range(5):
items.append(BatchItem(
config=RunInput(
task_name='swe_task',
task_config={'instance': instance},
# ...
),
run_id=f"{instance['id']}_trial{trial}",
))
# 批量执行后,按 instance 分组,取 max/min score 组成偏好对
Stage 3: RL 精调(HyTuner 在线 RL,score 作为 reward)
→ 详见下方 HyTuner RL 训练集成
RL 路线选择:当前主流 RL 方法基于 GRPO 组采样(SC-GRPO/TRACE/GAIN-RL 均在此框架内)。SAO(2026-07)提出了废除组采样的异步范式,已在 GLM-5.2 大规模验证。对 Nexus + HyTuner 用户:如果采用 SAO,需调整 rollout 生成策略(单 prompt 单 rollout)+ 配置价值模型 + 跳过观测 GAE 参数。建议 Nexus 的 HyTuner 集成文档标注支持的 RL 算法类型(GRPO-based / SAO-based)。
成本递增:Stage 1 纯离线(成本最低)→ Stage 2 离线+多次采样 → Stage 3 在线 RL(成本最高)。每阶段的数据需求来源:SFT 来自 ChatTracker 高分轨迹,DPO 来自同问题多次运行的偏好对,RL 来自 HyTuner 的在线交互。
HyTuner RL 训练集成
HyTuner 是腾讯内部的 RL 训练框架。Nexus 提供了开箱即用的集成——nexus.runner.hytuner 模块。
工作原理
HyTuner 集成的核心思想是:用 Nexus 作为 RL 的 Environment。训练循环中,HyTuner 控制模型生成,Nexus 负责执行动作并返回 reward。
HyTuner Training Loop
├─ 模型生成 response
├─ Nexus 执行 Task + Agent(使用模型 response)
├─ 收集 trajectory(prompt_ids, response_ids, logprobs)
├─ 计算 reward(task score)
└─ 训练更新
配置方式
from nexus.runner.hytuner import run, HyTunerRunInput
run_input = HyTunerRunInput(
imports=['nexus.extensions.projects.calculator'],
task_name='calculator_task',
task_config={
'user_prompt': 'Compute (7 @ 4 @ 9) @ 4 @ 3',
'ground_truth': '159',
},
agent_name='calculator_agent',
agent_config={'max_iterations': 10},
)
result = await run(
run_input,
tokenizer=tokenizer, # HyTuner 的 tokenizer
server_manager=server_manager, # HyTuner 的推理服务
sampling_params={'temperature': 1.0},
tool_parser=tool_parser, # 工具调用解析器
)
# result.segments[0].prompt_ids → 训练用 prompt token IDs
# result.segments[0].response_ids → 训练用 response token IDs
# result.segments[0].response_mask → attention mask
# result.reward_score → task score as reward
HyTunerRunOutput 结构
class HyTunerRunOutput:
segments: list[HyTunerSegment] # 轨迹片段(多段对应 context compaction)
reward_score: float # Task 评分,用作 RL reward
termination_reason: str # 终止原因
metrics: dict # 额外指标
success: bool # 是否成功执行
每个 HyTunerSegment 包含:
prompt_ids:prompt 的 token ID 序列response_ids:response 的 token ID 序列response_mask:哪些 token 参与 loss 计算response_logprobs:每个 response token 的 log probabilitynum_turns:对话轮数
Context Compaction 与多段轨迹
当对话超过上下文窗口时,Nexus 的 Compaction 机制会压缩历史。这会产生多个 segment:
- 段 1:原始对话(被压缩前)
- 段 2:压缩后的新对话
- …
- 最后一段:最终的完整对话
HyTuner 的训练代码需要处理这种多段结构。segments[-1] 是最终段(“主”段)。
外部对照:Harbor 的 RolloutDetail
同一件事 Harbor 也定义了一个结构。两者几乎同构——这不是谁抄谁,而是”要把一次对话还原成可训练的张量”这个约束逼出来的收敛:
| 字段 | Nexus HyTunerSegment | Harbor RolloutDetail |
|---|---|---|
| prompt token | prompt_ids | prompt_token_ids(每个元素 = 该轮含此前历史的完整 prompt) |
| response token | response_ids | completion_token_ids |
| loss 掩码 | response_mask | 无(由 prompt / completion 的切分隐含) |
| logprobs | response_logprobs | logprobs |
| 轮数 | num_turns | 隐含在三个 list 的长度里 |
| 扩展位 | 无 | extra: dict[str, list[Any]]——按轮存 provider 私有数据,源码注释举例 routed_experts |
| 多段约定 | segments[-1] 为最终段 | list[RolloutDetail],约定第一项是主 agent 的对话 |
两处差异值得单独记下来:
一是 extra。 Harbor 允许按轮挂任意 provider 私有数据,注释里举的例子是 routed_experts(MoE 的专家路由记录)。Nexus 的 HyTunerSegment 没有这个口子,这类信息只能塞进 metrics: dict——而 metrics 是 per-run 的,不是 per-turn 的,一旦塞进去就丢失了它与具体轮次的对齐关系。对本章后面讨论的 token 级信用分配(SC-GRPO)来说,这是结构性障碍,不只是少一个字段。
二是多段的语义根本不同。 Nexus 的 segments 来自 compaction(上下文超限被压缩,沿时间线一段段切下来),Harbor 的多个 RolloutDetail 来自 subagent 与 summarization(主 agent 之外还有子 agent 的历史,以及被总结掉的历史)。前者是”同一条时间线被切断”,后者是”多条时间线并存”。这意味着 Harbor 的结构能表达子 agent 轨迹,Nexus 的不能。若要把 Nexus 用于多 agent 场景的 RL(主 agent 调子 agent、两者都要学),segments 这个模型得先扩成”主轨迹 + 子轨迹引用”,而不是再往后加一段。
RL 训练新范式:RWML 世界模型学习
当前 HyTuner RL 集成的 3 阶段管线(SFT → DPO → RL)依赖任务成功/失败作为奖励信号。RWML(Reinforced World Model Learning, arXiv:2602.05842)提出了一种根本性不同的训练范式:
核心思想:不直接用任务成功/失败作为奖励,而是训练世界模型(World Model)预测环境状态转移,使用 sim-to-real 奖励——世界模型在模拟环境中学习,奖励信号从模拟迁移到真实任务。
实验结果:RWML 在 ALFWorld/WebShop 上超越直接任务成功 RL 6.9/5.7 分。
与 Nexus 的契合点:
- Trace 天然适配:Trace 系统记录的每一步状态转移(Agent action → 环境响应 → 下一步观察)可直接作为世界模型的训练数据
- HyTuner 辅助评估:世界模型可作为 HyTuner 的辅助评估器,提供更密集的中间奖励信号,不依赖昂贵的任务验证
- GFCR 互补:在 GFCR 框架中,世界模型可增强 Filter 模块——不仅按 score 过滤,还按”轨迹是否符合世界模型预测”来筛选
对 3 阶段管线的可能扩展:
SFT → DPO → World Model Pretraining → RL
↑ 新增阶段
用 Trace 数据训练世界模型
为 RL 阶段提供密集中间奖励
⚠️ RWML 目前仍在学术验证阶段,与 Nexus 的整合方案需要进一步工程验证。
Token 级信用分配:SC-GRPO
当前 3 阶段管线中 SFT 冷启动的 Filter 策略(score ≥ 0.8)是轨迹级粗粒度过滤。SC-GRPO(Self-Conditioned GRPO, arXiv:2606.18810, 2026-06-17)提出了 token 级细粒度信用分配:用模型在自身验证轨迹上条件化的 KL 散度作为 GRPO 梯度的乘法权重,自动识别哪些 token 对成功更重要。
与 Nexus 的契合点:
- Filter 模块增强:SC-GRPO 的 token 级重要性权重可作为 GFCR Filter 模块的附加维度——不仅筛选高分轨迹,还标记轨迹内”关键 token span”
- DPO 偏好对构造优化:当前 DPO 阶段用同问题多次运行的 max/min score 组成偏好对。SC-GRPO 的 token 级信号可细化偏好对比——不是在轨迹级比较,而是在关键 token 段比较
- Processor 候选:可新增
token_creditProcessor,在轨迹生成后计算 token 级重要性权重
效果:比 GRPO 高 8.1%,比 DAPO 高 5.9%,且 OOD 性能更强。
⚠️ SC-GRPO 与 Processor 的整合方案尚在概念阶段。
置信门控信用分配:Gated-BEPO
SC-GRPO 用 token 级 KL 权重做信用分配,但依赖”自身验证轨迹”这一额外信号。Gated-BEPO(“Confidence-Gated Bellman Credit Assignment for Large Language Model Agents,” arXiv:2608.06861, 2026-08-07)则提出了无需 critic 的置信门控 Bellman 信用分配:从经验 rollout 图(empirical rollout graph)推导步骤级信用,用均值备份 Bellman 不动点估计节点价值,再沿轨迹用 GAE 累积 TD residual 得到步骤级 Bellman advantage。
核心机制——置信门控:只有当某个状态出现多个不同后继(即有可比较的分支证据)时,才接入步骤级 Bellman credit;否则回退到 episode-level credit。这直接回应了”硬造步骤信用”的风险——没有分支证据的状态,强行分配步骤级信用只会引入噪声。
与 Nexus 的契合点:
- Filter 模块的信用分配升级:当前 GFCR Filter 模块的
score ≥ 0.8是轨迹级粗粒度过滤。Gated-BEPO 提供”步骤级信用”的第三选择——仅在 Trace 中存在可比较分支(同一状态多个后继)时才做步骤级筛选,与 SC-GRPO 的 token 级信号形成互补。 - Processor 候选:可新增
credit_gateProcessor,在轨迹生成后基于经验 rollout 图计算步骤级 Bellman advantage,并输出”哪些步骤有可靠的分支证据”。 - 效果参照:WebShop/ALFWorld/视觉 Sokoban 上语言模型与 VLM 一致提升;消融显示步骤级信用应”选择性接入”而非”均匀混合”。
⚠️ Gated-BEPO 依赖”可恢复的中间状态”(重复访问同一观测才能建图)。若环境无法恢复中间状态,其经验图构建的前提不成立。
当前提不成立时的替代:MileGPO(arXiv:2608.19803,2026-08-20,6 位作者)。 MileGPO 从分组 on-policy rollout 推导信用,而不是从可恢复状态建图——在成功 rollout 上识别 milestone、在失败 rollout 上识别反复出现的 trap,再用 Reliability-Calibrated Shaping(按 outcome 置信度加权,强化可靠的 milestone/trap、压低不确定的)与 Progress-Contrastive Calibration(检验候选是否反映局部进展、其入边转移是否优于同状态的其他分支)校准。论文明确「既不需要辅助模型,也不需要额外的环境交互」——这一条让它在无法恢复中间状态的环境里仍然可用,恰好补上 Gated-BEPO 的这一限制。ALFWorld / WebShop 达 SOTA,且 ID→OOD gap 小。
同一族的更新版本:GRAFT(arXiv:2609.28963,2026-09-24,4 位作者)。Gated-BEPO 讲的是”怎么做”,GRAFT 补的是”为什么必须这么做”,而且把论证放到了 RL 定义层面。原文的动机句可以直接引:GRPO 的 group-normalized advantage「在 response 级可靠,在 step 级系统性有偏」——因为粗粒度的 trajectory-level advantage 反映不出单步贡献,失败轨迹里也可能含有高价值的步骤。忠实于 RL 定义的做法本应在每个中间状态采样多个 action(GRPO 在单轮任务上的成功正来自它恪守”同一状态下多个 action 的回报均值才是可信的状态价值估计”这一条),但逐步采样代价太高。GRAFT 的绕法是把全部 rollout 嫁接成一张轨迹图(trajectory graph),在图上做 Bellman 迭代恢复节点状态价值,用节点价值之差给每条边分配信用,并提出 Graph GAE 把 GAE 推广到图上以压低价值估计偏差的影响。
与 Gated-BEPO 的差别值得记一句:Gated-BEPO 的门控是开关式的(有分支证据才接入 step 级,否则回退 episode 级),GRAFT 是全图估计式的(先把图建出来,再在图上算节点价值差)。前者便宜且保守,后者信息量更大但对价值估计偏差更敏感——Graph GAE 就是为这个敏感度设计的。两者共用同一条前提限制:都需要可恢复或可对齐的中间状态,前提不成立时同样退回 MileGPO。
⚠️ 以上为摘要级理解。Bellman 迭代在轨迹图上的具体递推式、Graph GAE 与标准 GAE 的差别,均属推论,实现前须读原文。
互补的另一侧:ProVer(arXiv:2609.36178,2026-09-28,10 位作者)。Gated-BEPO 与 GRAFT 都在问”每个状态的信用是多少”,ProVer 问的是”哪些状态值得算信用”。三阶段:Propose——用一个 LLM agentic judge 对比同一 GRPO 组内的成功与失败轨迹,提出一个可能造成分岔的片段;Verify——恢复该片段前后的环境状态,用当前策略采样新的 continuation,用经验终端成功率之差估这个片段的 advantage;Credit——只把正的估计叠加到该片段内 policy token 的原 GRPO advantage 上。设计上最关键的一点是把判断与验证分离:judge 只负责选段,advantage 由真实结果决定,不采信 critic 分数。Qwen3.5-2B / 4B 上相对 GRPO 提升 9.91% / 7.12%(ALFWorld、WebShop、SearchQA)。
ProVer 与 Gated-BEPO 需要的”恢复环境中间状态”是同一个前提,因此在下面的阶梯表里它俩占同一行、共用同一条限制。选型上的分工是:环境好恢复且你想全量估计 → GRAFT;环境能恢复但恢复成本高、只想把预算花在刀刃上 → ProVer;环境恢复不了 → MileGPO。
验证器侧信用追踪:VICT
SC-GRPO 与 Gated-BEPO 的共同前提是:信用必须从 rollout 侧推断。VICT(Verifier-Instrumented Credit Tracing,arXiv:2608.28128,2026-08-28,5 位作者)指出这是把验证器浪费成了标量——原文表述是「现有方法仍把判定成功的验证器当作一个标量奖励,丢弃了它内部的任务结构」。
VICT 的做法是把验证器暴露为可执行或有证据支撑的 atoms,沿依赖有效的证明边回溯到 action,只在那些边上重分配 group-relative advantage,把信用分配从”rollout 侧推断”转为”验证器侧追踪”。四个性质对工程落地特别友好:
- 保留原始终端奖励——不改奖励定义,不引入奖励黑客面
- 证据不足或歧义时弃权——不硬猜(与 Nexus Filter 的设计哲学一致)
- 只改训练时的 advantage tensor
- 不需要 critic、不需要过程标签、不需要分支 rollout、不需要推理时访问 verifier
对 Nexus 的意义:本章实战节(§「生产数据接入 RL 训练」)让读者为 Terminal-Bench 任务写 verifier_script——那个 verifier 内部的判定结构,就是 VICT 所说的 atoms。读者为评测写的验证器可以复用于训练期的信用分配,不需要额外标注、不需要额外采样。 集成改动局限在 Filter / Processor 侧(改 advantage tensor),GFCR 的 Generate 与 Replay 均不受影响。原文的消融还排除了两条看起来更省事的替代路径:dense atom rewards 与 final-commit credit。
⚠️ 以上为摘要级理解(性质描述逐条取自原文摘要)。atoms 的具体定义方式、证明边的构造算法,以及如何把 Nexus 的
verifier_script改造成 VICT 接口,均属推论,实现前须读全文。
没有标准答案时:用 rubric 做信用锚点(Dr.Credit)
前面两条路都默认一件事:reward 要么来自任务分数、要么来自 verifier 判定。可一旦任务是开放式的(deep research、调研报告、方案设计),根本不存在 canonical solution,依赖 ground truth 定义 process reward 的方法集体失效。Dr.Credit(arXiv:2609.34296,2026-09-28,9 位作者)给了第三条路。
它把任务 rubric 当作最终答案评测与过程监督的共享参照:评估每次工具返回的信息,相对于该 rubric 已接受的支持历史,提供了多少新增支持。这一句里有两个设计要点值得拆开看——「相对于已有支持历史」意味着它能区分新证据与轨迹里已经存在的证据(第二次拿到同一条证据不再计分),「承认部分支持」意味着 rubric 的每一条可以是 0 到 1 之间的中间态,不是二元的。得到的 process advantage 与 GRPO 的 outcome advantage 结合,在指导检索决策的同时保留对最终报告质量的监督。8B backbone 在四个 in-domain / out-of-domain 基准上超过被测的开源 deep research 基线。
对 Nexus 的工程含义是复用而非新增:本章 §「关键设计决策」第 3 条已经建议把 score 从二元 pass/fail 改成细粒度打分(未尝试=0、尝试未过=0.3、通过=1.0)。把这条从”打分”再往前推一步变成 rubric,同一套 rubric 就可以直接当训练期的信用锚点,不额外标注、不额外采样——这与 VICT 复用 verifier_script 的思路是同构的,只是一个复用判定结构、一个复用评分条目。
⚠️ “8B 达到与前沿闭源模型可比”为摘要自述,本轮未核具体主表;rubric 支持历史的记账方式(如何定义”已接受的支持”)属推论,实现前须读原文。
结构化路由:IAPO
与前述方法不同,IAPO(Influence-Aware Policy Optimization,arXiv:2608.24588,2026-08-25,4 位作者)不换信用来源,只换分发路径:把每条 rollout 表示为「可训练 agent action 上的 typed influence-dependency graph」,user 与 tool observation 作为证据,把 support-use / failed-use 结构转成路由权重,重分配同一个 trajectory-level advantage。原文强调它利用的是”已完成 rollout 本身已记录的信息与错误如何在 action 之间流动”,不依赖其他轨迹的对比证据或重采样延续。
对 Nexus 的实用价值在于零额外成本:IAPO 所需的图完全可从 GFCR Generate 阶段已产出的 rollout 重建(含 user/tool observation),不需要新增采样、不需要辅助模型、不需要 critic。Qwen3-4B / 8B 在 τ²-Bench、UserBench、AgentChangeBench 三个 service-agent 基准上超越多轮 RL 基线,BFCL-v4 Multi-Turn 显示增益不损害多轮 function-calling 能力。
(该工作来自复旦大学与微信腾讯团队——机构取自论文 HTML 全文的显式 Affiliation: 字段——其 service-agent 场景设定与本书的多轮 Trace 结构高度接近。)
Turn 级递归:AgentOPSD
AgentOPSD(arXiv:2608.05987,2026-08-06,13 位作者)补上了本书此前没有的 turn 级粒度:把 token 级 teacher-student log-prob 差距聚合成 turn 级证据,在 log-odds 空间递归更新 Bayesian 信念状态,通过相邻状态间的 marginal belief revision 识别 pivotal turns。critic-free、不需额外 rollout,与标准 policy optimization 完全兼容。Qwen2.5-7B 在 ALFWorld 达 89.1%,优于 GRPO 与强自蒸馏基线。
为什么这一档对 Nexus 特别合适:Nexus 的 Trace 本身就是多轮结构,turn 是与数据格式天然对齐的粒度——不像 step 那样需要定义切分规则、也不像 token 那样过细。
信用分配粒度阶梯(总览)
本章的信用分配讨论分散在各节,读者难以判断”该用哪一档”。下面这张梯子的读法是:粒度不是越细越好,而是取决于你手里有什么信号。
| 粒度 | 代表方法 | 信用来源 | 额外成本 | 前提 / 限制 |
|---|---|---|---|---|
| trajectory | GRPO 基线 | 终端奖励广播 | 无 | 粗,长程失效 |
| turn | AgentOPSD | teacher-student log-prob gap + 递归信念 | 无 critic / 无额外 rollout | 需 teacher |
| milestone | MileGPO | 成功 rollout 的 milestone / 失败的 trap | 无辅助模型 / 无额外环境交互 | 需分组 on-policy rollout |
| step | Gated-BEPO / GRAFT / ProVer | 经验 rollout 图或轨迹图的 Bellman 不动点;或只对选中片段做终端成功率差 | 无 critic(ProVer 需对片段重采样) | ⚠️ 需可恢复的中间状态 |
| token | SC-GRPO | 自身验证轨迹上的 KL | 需自身验证轨迹 | 与 Processor 整合仍在概念阶段 |
| 结构化路由 | IAPO | influence-dependency graph(重分配同一个 trajectory advantage) | 无(可从既有 rollout 重建) | 需 user/tool observation 作为证据 |
| 验证器侧 | VICT | verifier 内部 atoms + 证明边 | 只改训练时 advantage tensor | 需可暴露 atoms 的 verifier |
选型判据:有可暴露 atoms 的 verifier → 用 VICT(成本最低,且验证器你已经写好了);有可恢复状态 → 用 Gated-BEPO / GRAFT,恢复成本高则只验关键片段用 ProVer;只有 terminal reward + 多轮 Trace → 用 IAPO(从既有 rollout 重建图)或 MileGPO(从分组 rollout 找 milestone);有 teacher 模型 → 用 AgentOPSD;任务没有标准答案 → 用 Dr.Credit 的 rubric 锚点。
上表是”你手里有什么信号”的静态分档,不是”一条轨迹只能选一档”。 GACA(arXiv:2609.12424,2026-09-11,12 位作者)直接质疑了”选一档”这个前提:它的论点不是”再来一档”,而是粒度本身应当逐状态自适应。
原文的批评很具体。GRPO 把「一个 trajectory 级标量广播给每一步」,说不出是哪个决策导致了结果;GiGPO 用 anchor-state 分组恢复了 step 级信号,但它「在同一个固定权重下合并 step 级与 episode 级的估计」——于是对一个关键分支决策和对一个近乎确定的常规转移,花掉了同样的分辨率。GACA 主张正确的分辨率是状态相关的:用 rollout 自身已经记录下来的 NLL 给每一步打分(critic-free,不引入额外模型),按这个分数逐步混合两种 advantage——NLL 高于均值时偏向细粒度信号,低于均值时回退到 episode 级。论文还给出了这个混合的精确风险分解,并证明足够小的调制在方向对齐为正时优于固定混合。
读法上要做一次分离:先用上表选定可用的档位,再问是否需要逐档位自适应混合,两步不互斥。GACA 是加在已选档位之上的一层调制,不是第八档。
⚠️ GACA 为 2026-09-11 预印本,验证在 ALFWorld / WebShop 的 1.5B 与 7B 两档上,与本章的 SWE / Terminal-Bench 场景不同域;风险分解的前提条件本轮未读全文核验。
RL 训练效率优化:GAIN-RL
NeurIPS 2025 的 GAIN-RL(Gradient-driven Angle-Informed Navigated RL)提出了显著提升 RL 训练效率的方法:
核心机制:利用梯度方向的角度集中信号(angle concentration)指导训练。关键洞察——不是所有梯度方向都有价值,角度集中的梯度方向包含更多有效信息。
效果:2.5× 训练效率提升,同等性能下训练步数减少 60%。
与 Nexus + HyTuner 的整合可能性:
- HyTuner 效率优化:在 rollout 生成后用角度信号过滤低价值梯度,减少无效训练步
- Processor 插件候选:可新增
angle_concentrationProcessor,在轨迹生成后计算角度集中度,作为轨迹质量评估的附加维度 - 与 TSR 协同:角度信号可指导 TSR 树搜索的展开方向——优先展开角度集中度高的 action 分支
# 潜在配置方式(工程验证后)
processors:
chat:
name: "chat_tracker"
angle_concentration:
name: "angle_concentration"
args:
top_k_ratio: 0.4 # 保留角度集中度前 40% 的梯度
⚠️ GAIN-RL 与 Processor 插件的整合方案尚在概念阶段,需要验证角度信号在 Agent RL 场景下的有效性。
RL 范式变革:SAO 废除组采样的异步 RL
GRPO 的组采样(每 prompt 生成 G 条 rollout,组内比较计算 advantage)与异步训练天然矛盾——等组填满就失去了异步的意义。SAO(Single-rollout Asynchronous Optimization, arXiv:2607.07508, 2026-07-08, 清华 KEG + 智谱)从根本上废除了组采样:每 prompt 只生成 1 条 rollout,完成后立即训练。
核心机制:
- DIS(直接双边重要性采样):无需保存历史 checkpoint 处理离策略偏差,简化了异步训练的工程复杂度
- 价值模型解耦:每更新 1 次策略即更新 2 次价值模型,降低单 rollout 梯度方差
- 跳过观测 GAE:环境反馈 token(如工具输出)不参与 advantage 计算,避免噪声干扰
- 固定注意力参数:稳定价值模型训练,防止异步更新导致的 Q 值估计崩溃
生产验证:已部署于 GLM-5.2(750B-A40B)的 Agent RL 训练管线,可稳定训练 1000 步,AIME2025 84.2% → 97.3%(+13.1 点),SWE-Bench Verified 27.0% → 29.8%。
对 Nexus + HyTuner 的启示:
- SAO 与 TRACE 互补——TRACE 选择高信息量 rollout(“采样什么”),SAO 决定如何利用 rollout(“怎么训练”)
- SAO 与 GAIN-RL 互补——SAO 减少无效等待,GAIN-RL 过滤低价值梯度
- 如果 HyTuner 采用 SAO,需调整 rollout 生成策略(单 prompt 单 rollout)+ 配置价值模型 + 跳过观测 GAE 参数
- 建议 Nexus 的 3 阶段管线文档标注支持的 RL 算法类型(GRPO-based / SAO-based)
⚠️ SAO 与 HyTuner 的具体整合方案需工程验证。
RL 采样效率优化:TRACE
NeurIPS 2025 的 GAIN-RL 优化了”怎么用梯度”,清华+腾讯的 TRACE(Tree Rollout Allocation for Contrastive Exploration, arXiv:2606.11119, 2026-06-10)优化了”怎么分配采样预算”。核心发现:Agent RL 训练中约 80% 的 rollout 样本因奖励对比度太低,对策略更新几乎没有贡献。
核心机制:将 ReAct 风格 thought-action-observation 轮次建模为树节点,用共享预测器估算每个节点的条件成功概率,将有限的 rollout 预算导向最可能产生混合奖励(既有成功也有失败)的 prompt 根节点和中间前缀。结果是:Qwen3-14B 多跳 QA +2.8 点(同等采样成本),有效对比信号占比提升 25-34%。
与 Nexus + HyTuner 的整合可能性:
- 与 GAIN-RL 互补:TRACE 选择高信息量 rollout,GAIN-RL 在高信息量 rollout 上过滤低价值梯度——两者协同形成”采样→梯度”双重优化
- GFCR Filter 模块增强:TRACE 的节点级条件成功概率预测器可作为 GFCR Filter 模块的增强——不仅按 score 过滤,还按”轨迹内部的信息对比度”筛选
- 成本感知训练:TRACE 的预算分配思想可直接应用于 HyTuner 的 rollout 生成阶段,减少无效采样
⚠️ TRACE 目前主要在数学推理、多跳 QA、函数调用场景验证(8B-14B),在更大规模 Agent 场景的泛化性待验证。
GRPO 组内信号增强:AdaPrefix-GRPO
GRPO 在复杂问题上会遇到”组内全失败→无梯度信号”的问题——所有 G 条 rollout 都失败时,组内没有对比,梯度无法传播。AdaPrefix-GRPO(arXiv:2607.07674, 2026-07-08, Beliaev)解决了这个问题:通过反馈控制器动态调整解前缀长度,将成功率维持在 ~50%(GRPO 梯度信号最大处),训练结束后逐步撤销辅助前缀。0.6B 模型准确率 2.1× 提升,Qwen3-1.7B 1.6×,AIME 1.7×,同时轨迹长度缩短约一半。
五个维度的 GRPO 优化全景:
| 维度 | 方法 | 解决的问题 |
|---|---|---|
| 组采样架构 | SAO | ”不要等组”——废除组采样,单 rollout 异步训练 |
| 组内信号 | AdaPrefix-GRPO | ”组内没信号”——动态调难度保梯度 |
| 采样预算 | TRACE | ”选什么 rollout”——高信息量优先 |
| 梯度质量 | GAIN-RL | ”用哪些梯度”——过滤低价值梯度 |
| 归一化方式 | AsyncWebRL | ”归一化有偏”——1/ |
⚠️ AdaPrefix-GRPO 目前仅在 0.6B-7B 模型验证,大规模部署待确认。
GRPO 归一化修正:AsyncWebRL
AsyncWebRL(“Efficient Asynchronous Reinforcement Learning for Multi-Step Visual Web Agents,” arXiv:2606.05597, 2026-06-04, UIUC + Microsoft + CMU)从系统与算法两侧同时优化多步 Web Agent 的 RL 训练,其中算法侧的发现对 GRPO 信用分配有直接的理论澄清意义。
核心发现——归一化方式本身就是信用分配的关键变量:多步 GRPO 的每轨迹归一化项 1/|τᵢ|(除以轨迹长度)是长度偏置的根因——失败轨迹系统性地比成功轨迹更长,1/|τᵢ| 会稀释失败 token 上的负梯度权重,导致策略持续产出冗长、低效的轨迹(例如反复生成 verbose 的记忆模式)。AsyncWebRL 将 1/|τᵢ| 替换为常数 1/k(与轨迹长度解耦),打破了”失败越长→惩罚越弱”的耦合,在保持总体成功率的同时显著收缩轨迹长度。
与 Nexus + HyTuner 的契合点:
- 对 SC-GRPO/GAIN-RL 信用分配讨论的直接补充:SC-GRPO 用 token 级 KL 权重识别关键 token,GAIN-RL 用梯度角度过滤低价值梯度,但两者都默认了 GRPO 的每轨迹归一化方式。AsyncWebRL 揭示——在讨论”给哪些 token 更多信用”之前,应先修正”归一化方式”这个更底层的偏置源。
- HyTuner 配置候选:如果 HyTuner 的 GRPO 实现使用
1/|τᵢ|归一化,长程 Agent 任务(如 SWE-bench、Terminal-Bench)会系统性低估失败轨迹的梯度,导致 Agent 学不会”何时该收手”。建议在 HyTuner 集成文档中标注 GRPO 归一化参数,并针对长程任务评估1/k常数归一化。 - 效果参照:WebGym 分布外测试集 +5.8%(相对此前最佳 42.9%),困难切片收益最大(Medium +42%、Hard +48%)。
⚠️ AsyncWebRL 的归一化修正源自视觉 Web Agent 场景的实证观察(失败轨迹更长),在其他任务域的普适性待验证。
Cache 体系:轨迹重放与确定性复现
Nexus 的 Cache 系统允许你”录制”一次执行过程,然后在后续运行中”重放”——LLM 调用直接从缓存返回,不再请求 API。
启用方式
runner:
cache:
enabled: true
file_path: "/tmp/cache/trajectory.pkl"
ignore_args_hash: false
overwrite_tags: ['tool'] # 重放 LLM,但重新执行工具
使用场景
- 确定性复现:相同输入 → 相同输出,便于调试
- 节省 API 成本:调试工具逻辑时不需要重复调用 LLM
- 部分重放:
overwrite_tags: ['tool']只缓存 LLM 调用,工具每次都真实执行
Cache 文件是 pickle 格式,包含所有被 @cacheable 装饰的函数的输入输出。LLM Client 的 create_completion 和 Tool 的执行函数都标记了 @cacheable。
Reward System
nexus/utils/reward_system.py 和 nexus/utils/reward_score.py 提供了 reward 计算工具。
对于自定义 Task,在 TaskRunOutput 中使用 WithScore Mixin:
class MyTaskOutput(TaskRunOutput, WithScore):
pass
# Task 函数中
return MyTaskOutput(score=calculated_score)
Score 会自动被 HyTuner runner 提取为 reward_score。
导出训练数据
离线训练替代路径
HyTuner 在线 RL 效果好但成本高。OffSeeker(arXiv: 2601.18467)验证了 8B 模型纯离线训练(SFT → DPO)在 6 个基准上领先同规模模型,且与 30B 在线 RL 模型竞争力相当。这意味着从 Nexus 批量评测结果直接构造 SFT + DPO 数据是一种低成本的可行方案——无需在线 RL,只需跑足够多的评测批次。
离线路径的工作流:
Nexus 批量评测 → ChatTracker 轨迹 + Score
↓
筛选高分轨迹 → SFT 冷启动数据
筛选偏好对 → DPO 对齐数据
↓
离线训练(无需 HyTuner 在线交互)
Rollout 搜索增强
TSR(Trajectory-Search Rollouts, arXiv: 2602.11767)在训练时的 rollout 阶段执行轻量级树搜索(best-of-N / beam / shallow lookahead),选择高分 action 构建高质量轨迹。与 PPO/GRPO 配合,在 Sokoban/FrozenLake/WebShop 上最高提升 15%。核心思想:将搜索从推理时移到训练时的 rollout 阶段。
在 Nexus + HyTuner 集成中,TSR 可作为高级优化选项——在 HyTuner 的 rollout 阶段,不直接用模型采样的 action,而是执行轻量级搜索后选择最优 action,从而产出更高质量的训练轨迹。
导出训练数据(续)
批量评测完成后,Nexus 提供多种导出格式:
# JSONL 格式(每行一个 run 的完整结果)
nexus export results.db --format jsonl -o results.jsonl
# CSV 格式(汇总表)
nexus export results.db --format csv -o results.csv
# 完整目录结构(每个 run 一个子目录,含 config/output/trajectory/logs)
nexus export results.db --format full -o export_dir/
full 格式的目录结构:
export_dir/
├── 00000/
│ ├── config.json # RunInput
│ ├── output.json # RunOutput
│ ├── metadata.json # BatchItem metadata
│ └── processors/
│ ├── chat.json # ChatTracker 轨迹
│ └── tokens.json # TokenCounter 统计
├── 00001/
│ └── ...
└── summary.json # 批次汇总
实战:生产数据接入 RL 训练(Terminal-Bench 2.0 格式)
⚠️ 版本提示(2026-09-14):Terminal-Bench 现行版本为 4.0(2026-08-28 发布,66 任务),2.0(2025-11)与 2.1(2026-05)均已退役。本节代码按 2.0 格式编写,4.0 的
task.tomlschema 与目录结构兼容性尚未逐字段核对——读者照抄前请先比对官方仓库。⚠️ 不要只把 “2.0” 全局替换成 “4.0”:任务数(64+ / 89 → 66)与仓库链接都已变,直接替换会制造新的错误。这一节是读者会照着抄代码的一节,替换错了比不改更糟。
另需注意:本节与第 3 章
terminal_task行是同一问题的两个落点,两处需同步改。另可参见上文 §「验证器侧信用追踪:VICT」——本节教读者写的verifier_script,其判定结构可复用于训练期信用分配。⚠️ TB 是持续演进基准,不是离散版本序列:官方 README(2026-10-03 抓取)原文把 TB 定义为「a benchmark designed to measure the frontier of agent work with a diverse, difficult, high quality set of tasks that evolve over time」,并自称 continuous benchmark;数据集的 latest published version 挂在 Harbor Hub 上,而不是某个固定 tag。所以本节的兼容提醒不该读成”等 5.0 出来再迁一次”,而应读成:每次批量跑之前重新对齐任务集版本与
task.tomlschema。⚠️ 运行口径只认官方仓库:本轮在第三方榜单站(datalearner,页面标注更新于 2026-10-01)上见到”每任务跑 5 次 / 8 小时超时上限 / 报告 95% 置信区间 / 修复 19 个任务、移除 8 个任务”等表述。这些字面在官方 README 里一处都没有(
8 hours、5 times、95%均为零命中)。README 里唯一与 5 相关的句子是「run the oracle solutions 5x to confirm all tasks work as expected」——那是验证 oracle 解的次数,不是榜单重复次数,第三方很可能把二者混为一谈。落笔前不得采用;要写运行口径就逐字段抄官方仓库。同理,本节的”66 任务”本轮在 README 里也复现不出来(README 不列任务数),引用前需回官方仓库或 Harbor Hub 数据集页确认。
本节用完整可运行的代码走通:你按 Terminal-Bench 2.0 格式生产了一批任务目录,如何接入 Nexus 做 RL 训练。 真实格式参考:terminal-bench-2
Terminal-Bench 2.0 任务目录结构
每个任务是一个目录(不是 JSON 文件):
terminal-bench-2-tasks/
├── adaptive-rejection-sampler/ # 任务 1
│ ├── task.toml # 元数据 + 环境配置(TOML)
│ ├── instruction.md # Agent 指令(Markdown)
│ ├── environment/
│ │ └── Dockerfile # 沙箱镜像构建
│ ├── tests/
│ │ ├── test.sh # 验证脚本(bash)
│ │ └── test_outputs.py # 验证脚本(Python)
│ └── solution/ # 参考答案(可选)
├── bn-fit-modify/ # 任务 2
│ └── ...
└── ...(共 64+ 个任务)
task.toml 是核心配置,真实格式(来自 adaptive-rejection-sampler/task.toml):
version = "1.0"
[metadata]
author_name = "jvpoulos"
author_email = "poulos@berkeley.edu"
difficulty = "medium"
category = "scientific-computing"
tags = ["applied-statistics", "adaptive-rejection-sampling", "Bayesian-inference", "simulation"]
expert_time_estimate_min = 180.0
junior_time_estimate_min = 480.0
[verifier]
timeout_sec = 900.0
[agent]
timeout_sec = 900.0
[environment]
build_timeout_sec = 600.0
docker_image = "alexgshaw/adaptive-rejection-sampler:20251031"
cpus = 1
memory = "2G"
storage = "10G"
instruction.md 是 Agent 的指令(纯 Markdown,可能很长):
# Adaptive Rejection Sampling
Implement an adaptive rejection sampler based on Gilks et al. (1992).
## Requirements
1. Allow user to provide a function for the (potentially unnormalized) log density...
2. Perform input validation...
3. Check log-concavity during sampling...
Step 1:写 Generator — 遍历任务目录,产出 BatchItem
# my_production_tasks/generator.py
import json
import toml
from pathlib import Path
from nexus import BatchItem, RunInput
TASKS_DIR = Path(__file__).parent.parent / "terminal-bench-2-tasks"
def generate_configs() -> list[BatchItem]:
"""Terminal-Bench 2.0 任务目录 → Nexus BatchItem。"""
batch = []
for task_dir in sorted(TASKS_DIR.iterdir()):
if not task_dir.is_dir():
continue
task_toml = task_dir / "task.toml"
if not task_toml.exists():
continue
# 1. 读取 task.toml
cfg = toml.loads(task_toml.read_text())
# 2. 读取 instruction.md(作为 Agent prompt)
instruction = (task_dir / "instruction.md").read_text()
# 3. 读取验证脚本路径(用于后置评估)
test_sh = task_dir / "tests" / "test.sh"
test_py = task_dir / "tests" / "test_outputs.py"
verifier_script = None
if test_sh.exists():
verifier_script = str(test_sh)
elif test_py.exists():
verifier_script = str(test_py)
batch.append(BatchItem(
config=RunInput(
imports=["nexus.extensions.tasks.tb2_task"],
task_name="tb2_task",
task_config={
"task_id": task_dir.name,
"instruction": instruction,
"docker_image": cfg["environment"]["docker_image"],
"verifier_timeout": cfg["verifier"]["timeout_sec"],
"agent_timeout": cfg["agent"]["timeout_sec"],
"difficulty": cfg["metadata"]["difficulty"],
"category": cfg["metadata"]["category"],
"verifier_script": verifier_script,
},
runtime_provider_name="docker",
runtime_configs={
"main": {
"image": cfg["environment"]["docker_image"],
"cpus": cfg["environment"].get("cpus", 1),
"memory": cfg["environment"].get("memory", "2G"),
}
},
agent_name="react_agent",
agent_config={
"max_iterations": 50,
"timeout": cfg["agent"]["timeout_sec"],
},
openai_clients={
"main": {
"name": "openai",
"config": {
"client_args": {
"api_key": "${oc.env:OPENAI_API_KEY}",
"base_url": "${oc.env:OPENAI_BASE_URL}",
},
"request_args": {
"model": "${oc.env:MODEL_NAME}",
"temperature": 0.1,
}
}
}
},
),
run_id=task_dir.name,
))
return batch
关键点:
task.toml的[environment].docker_image直接映射到runtime_configs.main.imageinstruction.md全文作为 Agent 的user_prompttests/test.sh挂到task_config.verifier_script,Task 函数里后置执行
Step 2:写 Task — 执行 Agent + 运行验证脚本
# my_production_tasks/tb2_task.py
from nexus.core import (
TaskRunInput, TaskRunOutput, WithAgentRunner, WithScore,
task_registry, require_context,
)
from dataclasses import dataclass
import asyncio
@dataclass
class TB2Input(TaskRunInput, WithAgentRunner):
task_id: str
instruction: str
docker_image: str
verifier_timeout: float
agent_timeout: float
difficulty: str
category: str
verifier_script: str | None
@dataclass
class TB2Output(TaskRunOutput, WithScore):
verification_pass: bool
num_iterations: int
category: str
difficulty: str
@task_registry.register("tb2_task")
async def run_tb2_task(task_input: TB2Input) -> TB2Output:
ctx = require_context()
# 1. 调用 Agent(instruction.md 全文作为 prompt)
agent_result = await task_input.run_agent(
user_prompt=task_input.instruction,
)
# 2. 运行验证脚本(tests/test.sh 或 test_outputs.py)
verification_pass = False
if task_input.verifier_script:
# 把验证脚本复制到沙箱内执行
script_name = Path(task_input.verifier_script).name
await ctx.runtime.exec(
f"cp /task_mount/tests/{script_name} /tmp/ && bash /tmp/{script_name}",
timeout=task_input.verifier_timeout,
)
# 注意:需要根据脚本实际退出码判断,这里简化为 exec 是否成功
verification_pass = (await ctx.runtime.exec(
f"bash /tmp/{script_name}", timeout=task_input.verifier_timeout
)).exit_code == 0
# 3. 评分(支持 reward shaping)
if verification_pass:
score = 1.0
elif agent_result.get("num_iterations", 0) > 0:
score = 0.3 # 有尝试但未通过,给部分分
else:
score = 0.0
return TB2Output(
score=score,
verification_pass=verification_pass,
num_iterations=agent_result.get("num_iterations", 0),
category=task_input.category,
difficulty=task_input.difficulty,
)
Step 3:注册模块
# my_production_tasks/pyproject.toml
[project]
name = "tb2_tasks"
version = "0.1.0"
[tool.nexus]
tasks = { tb2_task = "my_production_tasks.tb2_task::run_tb2_task" }
Step 4:批量运行
# 检查 generator 产出(不真正跑,只打印配置)
nexus inspect-generator my_production_tasks/generator.py
# 批量运行(8 并发,每个任务最多跑 15 分钟)
nexus run-batch \
-g my_production_tasks/generator.py \
-d results/tb2.db \
--max-concurrent 8 \
--timeout 1800
# 查看进度
nexus inspect-batch results/tb2.db
# 重试失败的(最多重试 2 次)
nexus run-batch -d results/tb2.db --retry-only --max-retries 2
Step 5:导出 → RL 训练格式
# 导出完整目录(含每条轨迹的 chat.json)
nexus export results/tb2.db --format full -o export/tb2/
转换成 HyTuner 训练格式:
# scripts/convert_tb2_to_hytuner.py
import json
from pathlib import Path
from typing import Any
def convert(export_dir: str, output_path: str) -> None:
"""
Terminal-Bench 2.0 评测结果 → HyTuner RL 训练格式。
每条样本包含:
- prompt: 初始 system + user message
- response: Agent 完整回复轨迹
- reward: task score(0.0 / 0.3 / 1.0)
- task_id: 任务目录名
- category / difficulty: 从 task.toml 读取
- trajectory: 完整 chat 轨迹(用于 SFT warm-start)
"""
samples: list[dict[str, Any]] = []
for run_dir in sorted(Path(export_dir).iterdir()):
if not run_dir.is_dir():
continue
config = json.loads((run_dir / "config.json").read_text())
output = json.loads((run_dir / "output.json").read_text())
# chat.json 可能不存在(如果没配 ChatTracker processor)
chat_path = run_dir / "processors" / "chat.json"
chat = json.loads(chat_path.read_text()) if chat_path.exists() else []
# 提取 Agent 回复(所有 assistant 消息拼接)
assistant_replies = [
msg.get("content", "")
for msg in chat
if msg.get("role") == "assistant"
]
samples.append({
"task_id": config["task_config"]["task_id"],
"category": config["task_config"]["category"],
"difficulty": config["task_config"]["difficulty"],
"prompt": chat[0]["content"] if chat else "",
"response": "\n\n".join(assistant_replies),
"reward": output.get("score", 0.0),
"verification_pass": output.get("verification_pass", False),
"trajectory": chat,
})
Path(output_path).write_text(
json.dumps(samples, ensure_ascii=False, indent=2)
)
print(f"转换完成:{len(samples)} 条样本 → {output_path}")
if __name__ == "__main__":
convert("export/tb2", "export/tb2_rl.json")
完整数据流
Terminal-Bench 2.0 任务目录(64+ 个)
├── task.toml ← 元数据 + 环境配置
├── instruction.md ← Agent 指令
└── tests/test.sh ← 验证脚本
↓ generator.py(遍历目录,解析 TOML + MD)
Nexus BatchItem 列表(每个任务一个 BatchItem)
↓ nexus run-batch
SQLite 结果(agent 输出 + score + 轨迹)
↓ nexus export --format full
完整目录(config + output + chat 轨迹)
↓ convert_tb2_to_hytuner.py
HyTuner 训练格式(JSON,含 reward)
↓ HyTuner 训练循环
更新后的 Policy
关键设计决策
- Generator 是目录遍历器 — 新增任务只需往目录里扔一个新文件夹,generator 自动发现
- instruction.md 全文作 prompt — TB2.0 的指令可能很长(含代码要求、测试说明),直接透传给 Agent
- Reward Shaping —
score设计细粒度(未尝试=0,尝试未过=0.3,通过=1.0),比二元 0/1 效果好 - 轨迹保留 —
chat.json保留完整对话,用于 SFT warm-start 或 DPO 偏好对构造 - 按 category/difficulty 分层 — 导出时保留这些字段,RL 训练时可以做 curriculum learning
常见坑
| 问题 | 原因 | 解法 |
|---|---|---|
| 验证脚本找不到 | 沙箱内路径和宿主机不同 | 用 runtime.exec 在容器内跑,或挂载任务目录到 /task_mount |
| Docker 镜像拉取慢 | 64 个任务可能用不同镜像 | 预热:docker pull 提前拉取 |
| instruction.md 太长 | 某些任务指令超过 10k token | Agent config 里开 truncate_prompt |
| Reward Hacking | Agent 碰巧通过 test.sh 但没真正解决问题;或任务目录本身携带可作弊路径(答案文件、可直读的 test.sh、公开的 solution) | 人工 review 一批高分轨迹,加细粒度 rubric;并先检查你所接入的基准是否已有反作弊静态检查 | | 数据分布不均 | 某个 category 特别多(如 coding) | Generator 里做 stratified sampling | | 分数都很高 | 任务太简单 | 增加难度梯度,或加 adversarial instance |
上面 Reward Hacking 那一行的”先查基准侧”不是泛泛而谈,Terminal-Bench 官方仓库在 2026-09-29 一天内把这件事做成了 CI:
| commit | 日期 | 做什么 |
|---|---|---|
Add static check for task cheat directories (#2072) | 09-29 | 对任务目录做静态检查,查是否存在可让 agent 作弊的路径 |
Remove cheat directories from tasks (#2058) | 09-29 | 把已发现的作弊目录从任务里删掉 |
Check README metadata against task.toml (#2082) | 09-29 | README 元数据与 task.toml 对账 |
Add /regrade to re-verify existing trials with a PR's verifiers (#2109) | 09-29 | 用 PR 里的新 verifier 重验已有 trial |
Harden leaderboard workflows against fork-controlled content (#2081) | 09-23 | 榜单流水线加固,防 fork 控制内容 |
含义有两层。一是作弊面的位置变了:不只是”agent 碰巧通过”,还包括”任务目录里本来就有可作弊的路径”,而这条现在已经是可被静态检查的——自己造任务时,同一套检查思路(答案文件是否可读、test.sh 是否可被直读、solution 是否留在镜像里)应当进你自己的 CI。二是 /regrade 直接服务本章的 verifier 复用主题:已经跑过的 trial 可以拿更新后的 verifier 重验,不必重跑 agent——改了评分口径之后回测历史数据的成本因此大幅下降。
延伸:从生产数据 → TB2.0 格式
如果你自己生产了一批任务,想复用这套接入方式,只需把数据转换成 TB2.0 目录结构:
# scripts/convert_production_to_tb2.py
from pathlib import Path
import toml
def convert_production_task(sample: dict, output_dir: Path) -> None:
"""
sample = {
"task_id": "my_task_001",
"instruction": "...",
"docker_image": "myimage:latest",
"category": "coding",
"difficulty": "medium",
"test_script": "#!/bin/bash\n...",
}
"""
task_dir = output_dir / sample["task_id"]
task_dir.mkdir(parents=True, exist_ok=True)
(task_dir / "tests").mkdir(exist_ok=True)
# 写 task.toml
cfg = {
"version": "1.0",
"metadata": {
"author_name": "auto-generated",
"difficulty": sample["difficulty"],
"category": sample["category"],
},
"verifier": {"timeout_sec": 300.0},
"agent": {"timeout_sec": 300.0},
"environment": {
"docker_image": sample["docker_image"],
"cpus": 1,
"memory": "2G",
},
}
(task_dir / "task.toml").write_text(toml.dumps(cfg))
# 写 instruction.md
(task_dir / "instruction.md").write_text(sample["instruction"])
# 写验证脚本
(task_dir / "tests" / "test.sh").write_text(
sample.get("test_script", "exit 0")
)
关联进展:长程工具 Agent RL
上述 Terminal-Bench 接入实战以离线生成 + 后置验证为主。对于需要在线 RL 训练长程工具调用 Agent 的场景,近期工作(arXiv:2608.10357)采用 rollout workers + GRPO 的训练管线,与本节的生成器设计形成互补:
- 本节路线:Generator 遍历任务目录 → 批量评测 → 导出轨迹 → 离线 SFT/DPO/RL(适合预算有限、追求可复现性的场景)
- rollout workers 路线:常驻 rollout worker 池持续产生轨迹 + GRPO 在线更新(适合追求训练吞吐、可接受异步离策略偏差的场景)
两者的共同点是都把”任务目录格式(task.toml + instruction.md + tests/)“作为环境标准。若后续 HyTuner 集成需要在线 RL 训练长程工具 Agent,可将本节 Generator 的产物(BatchItem)改为由 rollout workers 持续消费,复用同一套 Task/Agent 定义。