1. 从一次跑不通的 OpenClaw-RL 说起:Agentic RL 源码阅读到底难在哪
Agentic RL 是把强化学习用在智能体工具调用场景里的训练范式,OpenClaw-RL 则是围绕这个思路搭出来的一套在线训练框架,适合已经写过一点 PPO、想搞清楚“多轮工具调用怎么做信用分配”的工程师。我第一次把仓库拉下来跑openclaw-rl的时候,卡住的不是算法,而是环境:模型侧要一个能稳定调用的 API 通道,训练侧要一个能对齐 tokenizer 的模型 ID,两边对不上,rollout 直接空转。后来我把模型调用统一收口到 TaoToken 的 OpenAI 兼容通道,才把“先跑通链路、再读源码”这条路走顺。
源码阅读最容易犯的错,是一上来就扎进trainer.py看 loss 怎么算。OpenClaw-RL 的价值不在某一个 loss 函数,而在它怎么把“环境反馈”变成“过程奖励信号”。它支持三种模式:openclaw-rl走二元奖励的 GRPO,openclaw-opd走后见之明提示的在线策略蒸馏(On-Policy Distillation,OPD),openclaw-combine把 RL reward 和 OPD teacher signal 放进同一次 PPO 更新。这三种模式的差异,本质上是三个不变量在不同位置上的取舍。
我试过先读openclaw_api_server.py里的 session 管理,再回头看训练入口,理解速度会快很多。因为 Agentic RL 和单轮 RL 最大的区别是:单轮 RL 一条 response 对应一个 reward,梯度信号密度是 100%;而 Agentic RL 一个 episode 有 T 步,可能只有最后一步有 terminal reward,信号密度掉到 1/T。OpenClaw-RL 用 turn-level 独立评分绕开了这个问题——每一轮都有 next_state 反馈,不需要等 episode 结束。这个设计选择决定了后面所有模块的形态。
所以这篇笔记的顺序是:先讲清楚三个不变量和四个要点,再讲怎么用 TaoToken 把模型通道配好,然后给出可复制的配置和验证请求,最后把源码阅读中常见的报错对照着排一遍。你不需要先成为 RL 专家,只要能把链路跑通,源码里的loss_mask、at-least-one、hint-reject这些词就会自己变得具体。
2. 三个不变量与四个要点:OpenClaw-RL 源码的总体骨架
2.1 三个不变量:训练闭环的耦合边界
把 Agentic RL 看成一个在真实环境里持续交互、持续采样、持续更新的策略学习系统,最重要的不是这一步用哪种 RL 算法,而是训练闭环能不能长期守住三个底层条件。这三个不变量不是数学上严格恒定的量,而是会天然漂移、但必须被不断拉回可学习区间的边界。
第一不变量:策略可探索空间不能过早塌缩。塌缩的意思是模型“认定”了某种回复模式,放弃探索其他可能性。训练前P("让我分析一下")=0.18、P("首先...")=0.12,分布还算均匀;过早塌缩后P("让我分析一下")=0.87,其他候选全被压到 0.05 以下。这里有个关键区分:token 级熵高不等于行为级支撑集完整。模型可能每个 token 位置词表分布都很散,但生成的所有回答都遵从同一种模式,比如永远走“长链推理 → 答案”的固定模板。行为级支撑集崩塌(Support Collapse)才是真正危险的。
第二不变量:学习信号必须持续非退化。退化不等于信号为零,而是信号失去判别力。从梯度角度看,∂L/∂θ = ∑_t A(s_t,a_t)·∇_θ logπ_θ(a_t|s_t),当 advantage 趋近 0,参数就不更新。Agentic RL 天然容易让信号塌缩,因为时间跨度越长信号越稀疏。20 步 episode 只有最后 1 步有梯度,有效信号密度 1/20=5%,其余 19 步 reward=0、advantage≈0、梯度≈0。
第三不变量:训练采样、参数更新和真实部署之间的偏移必须可控。单轮 RL 一条 response 就是一个训练单元,生成后立即训练,off-policy gap 很小。Agentic RL 一个 episode 有 T 步,episode 开始时用的是 policy_old,T 步后 policy 可能已经更新多次,前面步骤的数据相对当前策略更加 off-policy,gap 是 episode 长度的函数。OpenClaw-RL 靠 PPO clip(ε=0.2,ε_high=0.28)兜底,ratio 超出 [0.8, 1.28] 被截断,形成隐式 KL 约束。
2.2 四个要点:从源码模块反推设计意图
围绕三个不变量,可以扩展出四个可操作的要点,它们直接对应源码里的模块。
保护探索多样性,靠温度和 KL 约束。OpenClaw-RL 三种方法都没有显式 entropy 正则(--no-entropy-reg),实际依赖用户输入的自然多样性和短期训练窗口。这是框架层面的选择,不是各方法独立决定的。
维持 advantage 的方差,靠归一化和 rejection sampling。Binary RL 用 majority vote(m=3)降低 None 概率,at-least-one保证 session 全 score=0 时强制第一条loss_mask=1。OPD 用hint-reject直接 drop 而非置零,保证进队的都是高信噪比样本。
控制 off-policy 偏移,靠 staleness 上限和解耦 PPO。OPD 特有的额外保障是teacher_lp - rollout_lp的梯度方向等于“向 teacher 靠拢”,形成隐式 KL 拉力,防止 policy 漂离有 teacher 指导的区域。
解决 long-horizon 信用分配,靠 turn discount 和 dense reward shaping。OpenClaw-RL 用 turn-level 独立评分,每轮都有 next_state 反馈,天然免疫稀疏奖励广播和因果污染。
2.3 总览矩阵:三种方法在不变量上的取舍
| 不变量 | Binary RL | OPD | Combine |
|---|---|---|---|
| Policy Entropy | 无正则,依赖用户多样性 | 无正则,依赖用户多样性 | 无正则,依赖用户多样性 |
| 梯度信号非退化 | at-least-one + majority vote | hint-reject + teacher 拉力 | 3-way dispatch,双信号对冲 |
| On-Policy Gap | PPO clip 兜底 | PPO clip + 软 KL 约束 | PPO clip,最高风险最强保障 |
| 有效样本率 | loss_mask=0 样本仍入队 | 只有高质量 hint 才进队 | OPD+RL 才入队,最严格 |
at-least-one解决的问题是防止 reward 全零导致训练信号完全消失。源码在openclaw-rl/openclaw_api_server.py:615-622遍历 session 中所有 turn 的 record,维护_session_effective计数器,当发现某 turn 的 score != 0 时递增,若最终为 0 则对第一条 record 设置exclude=False强制参与训练。
2.4 设计哲学:三种方法的信号纯度取舍
Binary RL 宁愿噪声多,不放弃任何数据,at-least-one加全入队。OPD 宁愿数据少,只要高纯度信号,hint-accept才入队。Combine 精准门控,按信号类型分路,最大化信噪比。这三种哲学没有绝对优劣,取决于你的任务对信号纯度和数据量的敏感度。
源码里 OPD 的 hint 注入逻辑在openclaw_opd_api_server.py,_select_best_hint(votes)从多数投票结果中选出最长有效 hint(>10 字符),无有效 hint 则返回 None;_append_hint_to_messages()负责 deep-copy messages,找到最后一条 user 消息,把"\n\n[user's hint / instruction]\n{hint.strip()}"追加到该消息 content 末尾。这个设计让 teacher 不断提供“另一种策略的参数方向”,防止 student 完全锁定在单一模式。
3. 可复制配置:TaoToken 统一 Key 与 OpenClaw-RL 环境接入
3.1 为什么要在训练框架里统一模型通道
OpenClaw-RL 的 rollout 阶段要频繁调用模型,judge 阶段也要调用模型打分。如果 rollout 用一个供应商、judge 用另一个,tokenizer 和 chat template 的差异会让loss_mask对不齐,训练时表现为 loss 不降或者 advantage 全零。把模型调用统一收口到 TaoToken 的 OpenAI 兼容通道,Base URL 和 Key 一套配置贯穿 rollout、judge、teacher 三个角色,能省掉大量对齐工作。
TaoToken 的 API 地址是https://taotoken.net/api,兼容 OpenAI 的/v1/chat/completions接口。你需要在控制台创建一个 Key,然后把它写进环境变量。模型 ID 要和训练侧配置的 tokenizer 对应,比如claude-sonnet-4-5这类标识,具体以控制台模型列表为准。
3.2 环境变量与 settings 片段
先配环境变量,这是最不容易出错的方式:
export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api" export OPENCLAW_MODEL_ID="claude-sonnet-4-5" export OPENCLAW_JUDGE_MODEL_ID="claude-sonnet-4-5"如果你用 Cline 或 Claude Code 这类工具做源码阅读辅助,可以在 settings 里配 MCP 或 provider。以 Cline 的 MCP 配置为例,路径是~/.cline/mcp_settings.json:
{ "mcpServers": { "taotoken": { "command": "npx", "args": ["-y", "@taotoken/mcp-server"], "env": { "TAOTOKEN_API_KEY": "sk-你的Key", "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_MODEL_ID": "claude-sonnet-4-5" } } } }如果你用 Codex,auth.json的路径是~/.codex/auth.json,需要写全三件套 Base URL、Key、Model ID:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model": "claude-sonnet-4-5" }3.3 OpenClaw-RL 侧的模型配置
OpenClaw-RL 的模型配置通常在configs/model_config.yaml或环境变量里。把 rollout 和 judge 都指向 TaoToken:
rollout: provider: openai_compatible base_url: https://taotoken.net/api api_key: ${TAOTOKEN_API_KEY} model_id: ${OPENCLAW_MODEL_ID} temperature: 0.8 max_tokens: 2048 judge: provider: openai_compatible base_url: https://taotoken.net/api api_key: ${TAOTOKEN_API_KEY} model_id: ${OPENCLAW_JUDGE_MODEL_ID} temperature: 0.0 max_tokens: 512 teacher: provider: openai_compatible base_url: https://taotoken.net/api api_key: ${TAOTOKEN_API_KEY} model_id: ${OPENCLAW_MODEL_ID} temperature: 0.7注意 judge 的 temperature 设 0.0,因为打分需要稳定;rollout 设 0.8 保留探索多样性,对应第一不变量的保护。teacher 设 0.7,让 hint 有多样性。
3.4 训练启动命令
配置好之后,启动 Binary RL 模式:
python -m openclaw_rl.train \ --mode openclaw-rl \ --config configs/model_config.yaml \ --no-entropy-reg \ --kl-coef 0.0 \ --clip-ratio 0.2 \ --clip-ratio-high 0.28 \ --at-least-one \ --majority-vote 3启动 OPD 模式:
python -m openclaw_rl.train \ --mode openclaw-opd \ --config configs/model_config.yaml \ --hint-reject \ --force-drop \ --clip-ratio 0.2启动 Combine 模式:
python -m openclaw_rl.train \ --mode openclaw-combine \ --config configs/model_config.yaml \ --three-way-dispatch \ --at-least-one \ --hint-reject这些参数直接对应前面讲的不变量:--no-entropy-reg和--kl-coef 0.0说明框架没有显式 entropy 正则,--at-least-one是第二不变量的兜底,--clip-ratio是第三不变量的约束,--hint-reject和--force-drop是第四不变量里 OPD 的样本过滤。
4. 验证请求:确认 TaoToken 通道与 OpenClaw-RL 链路跑通
4.1 先用 curl 验证模型通道
在跑训练之前,先用一个最小请求确认 TaoToken 通道可用:
curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "messages": [ {"role": "user", "content": "用一句话解释什么是 on-policy distillation"} ], "temperature": 0.0, "max_tokens": 128 }'正常返回的 JSON 里choices[0].message.content应该有内容,usage字段有 token 计数。如果返回 401,说明 Key 不对;如果返回 404,说明模型 ID 写错了;如果返回local proxy failed,说明 Base URL 配错了,检查是不是漏了/api或者多写了/v1。
4.2 用 Python 验证 rollout 调用
OpenClaw-RL 的 rollout 用的是 OpenAI SDK 风格,你可以单独跑一段验证:
import os from openai import OpenAI client = OpenAI( base_url=os.environ["TAOTOKEN_BASE_URL"], api_key=os.environ["TAOTOKEN_API_KEY"], ) resp = client.chat.completions.create( model=os.environ["OPENCLAW_MODEL_ID"], messages=[ {"role": "system", "content": "你是一个工具调用智能体。"}, {"role": "user", "content": "帮我查一下北京今天的天气,然后决定要不要带伞。"}, ], temperature=0.8, max_tokens=512, ) print(resp.choices[0].message.content) print("usage:", resp.usage)如果这段能跑通,说明 rollout 通道没问题。接下来验证 judge 通道,把 temperature 改成 0.0,prompt 换成打分任务:
judge_resp = client.chat.completions.create( model=os.environ["OPENCLAW_JUDGE_MODEL_ID"], messages=[ {"role": "system", "content": "你是一个评分器,对回复质量打 -1、0、1 三档。"}, {"role": "user", "content": "回复:北京今天晴,不需要带伞。请打分。"}, ], temperature=0.0, max_tokens=64, ) print(judge_resp.choices[0].message.content)4.3 验证 OpenClaw-RL 的 session 管理
OpenClaw-RL 的 session 管理在openclaw_api_server.py,你可以单独启动 API server 验证:
python -m openclaw_rl.api_server \ --host 0.0.0.0 \ --port 8000 \ --config configs/model_config.yaml然后用 curl 发一个 session 请求:
curl -s http://localhost:8000/session \ -H "Content-Type: application/json" \ -d '{ "session_id": "test-001", "messages": [ {"role": "user", "content": "帮我写一个 Python 快速排序"} ] }'正常返回里应该有turn_id、score、loss_mask字段。如果loss_mask全是 0,说明 judge 没打出有效分,检查 judge 的 prompt 和 temperature。如果score全是 0,可能是at-least-one还没触发,或者 judge 模型对这类任务不敏感。
4.4 验证训练信号非退化
跑几十步训练后,看日志里的 advantage 统计。健康的信号是 advantage 均值接近 0 但方差大于 0,批次内有正有负。如果 advantage 方差趋近 0,说明对比度坍缩,可能是任务太简单(全成功)或太难(全失败)。如果 advantage 均值不为 0 但 loss 不降,说明评分噪声大,需要加大 majority vote 的 m 值。
grep "advantage" logs/train.log | tail -20你会看到类似advantage_mean=0.02, advantage_std=0.87的输出。std 大于 0.5 通常说明信号还有判别力,std 小于 0.1 就要警惕了。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
5.1 401 Unauthorized
报错长这样:
openai.AuthenticationError: Error code: 401 - {'error': {'message': 'Invalid API key'}}原因通常是 Key 没配、Key 过期、或者环境变量没生效。排查步骤:先echo $TAOTOKEN_API_KEY确认变量有值,再确认 Key 没有多余空格。如果你在 Docker 里跑,检查docker run有没有传-e TAOTOKEN_API_KEY。如果你用.env文件,确认python-dotenv在训练入口之前加载了。
5.2 local proxy failed
报错长这样:
openai.APIConnectionError: Connection error: local proxy failed这个报错说明请求根本没发出去,通常是 Base URL 写错或者网络配置有问题。检查TAOTOKEN_BASE_URL是不是https://taotoken.net/api,不要写成https://taotoken.net/api/v1,因为 SDK 会自己拼/v1/chat/completions。如果你在容器里跑,检查容器的 DNS 和出网策略。如果你本地有 HTTP_PROXY 环境变量,先unset HTTP_PROXY HTTPS_PROXY再试。
5.3 reading choices 报错
报错长这样:
KeyError: 'choices'或者:
TypeError: 'NoneType' object is not subscriptable这个报错说明返回的 JSON 里没有choices字段,通常是模型 ID 写错导致返回了错误结构,或者请求被限流返回了空 body。排查步骤:先用 curl 单独发一次请求,看原始返回。如果返回里有error字段,按 error message 处理。如果返回是空的,检查max_tokens是不是设成了 0,或者 prompt 太长超过了上下文限制。
5.4 OAuth 相关报错
报错长这样:
Error: OAuth token expired, please re-authenticate如果你用 Claude Code 或 Codex 这类工具,它们可能默认走 OAuth 而不是 API Key。你需要显式配置 API Key 模式。以 Claude Code 为例,在 settings 里把 provider 切成openai_compatible,填 Base URL 和 Key。以 Codex 为例,~/.codex/auth.json里不要留 OAuth 的 refresh token,直接写api_key字段。
5.5 loss_mask 全零
这个不是报错,但训练日志里会看到effective_sample_rate=0.0。原因是 judge 对所有 turn 都打了 0 分,at-least-one应该兜底但可能没生效。检查--at-least-one参数有没有传,检查openclaw_api_server.py里的_session_effective计数器逻辑有没有被改动。如果 judge 模型本身对任务不敏感,换一个更强的 judge 模型,或者调整 judge prompt 让它输出 -1/0/1 三档而不是二值。
5.6 hint-reject 率过高
OPD 模式下如果hint_accept_rate低于 0.1,说明 teacher 生成的 hint 大部分被拒。检查 teacher 的 temperature 是不是太低导致 hint 重复,或者_select_best_hint的>10 字符阈值是不是太严。你可以临时把阈值调低到 5 字符观察,但生产环境建议保持 10 字符以上,因为太短的 hint 信噪比低。
6. 把源码阅读变成可复现的工程动作
读 OpenClaw-RL 源码最有价值的动作,不是把每个函数都看一遍,而是把三个不变量映射到具体代码行。第一不变量对应--no-entropy-reg和用户多样性依赖,第二不变量对应at-least-one和majority vote,第三不变量对应--clip-ratio和--clip-ratio-high,第四不变量对应hint-reject和force-drop。你把这四个映射写在笔记本上,再回头看trainer.py的 loss 计算,会发现每个分支都有存在的理由。
如果你想继续深入,下一步可以读openclaw_opd_api_server.py里_append_hint_to_messages的实现,理解 teacher hint 是怎么注入到 student 的 context 里的。这个机制是 OpenClaw 维护支撑集最直接的手段,也是 OPD 和普通蒸馏的区别所在。读完可以自己写一个最小复现:构造两条策略路径,用 teacher log-prob 减 student log-prob 算梯度,观察 student 的概率分布怎么变化。
模型通道方面,TaoToken 的 API Keys 页面可以创建和管理 Key,接入文档里有 OpenAI 兼容接口的详细说明。如果你要长期跑 Agentic RL 训练,Coding Plan 适合需要稳定调用和批量并发的场景。验证模型行为是否正常,可以用模型对话页面单独测 prompt。源码阅读辅助工具如果走 MCP,配置里记得写全 Base URL、Key、Model ID 三件套,缺一个都会在 rollout 阶段报错。