習(xí) / OPD】OpenClaw-RL 源碼閱讀筆記 --- (10)--- PRM 與 TaoToken 配置實戰(zhàn))
1. 從一次 PRM 打分失敗說起OpenClaw-RL 源碼閱讀里最容易卡住的環(huán)節(jié)如果你正在讀 OpenClaw-RL 的源碼大概率會在 PRM 這一層停下來。原因不復(fù)雜這個項目里叫 PRM 的東西跟教科書上的 Process Reward Model 不是一回事。它本質(zhì)是一個 zero-shot LLM Judge用同族模型Qwen3通過 prompt 對整條 response 打\boxed{1}/\boxed{0}/\boxed{-1}再做 majority vote 降噪。理解這一點之后源碼里那些_build_prm_judge_prompt、_majority_vote、at-least-one guarantee的寫法就順了。但真正讓人卡住的不是概念是環(huán)境。OpenClaw-RL 的 PRM Server 是一個獨立的 SGLangRouter 進(jìn)程Policy Server 是另一個 FastAPI 服務(wù)兩者通過 HTTP 通信。你在本地想跑通一次 PRM 打分鏈路需要同時把 Judge 模型的推理端點、Policy 側(cè)的調(diào)用地址、以及訓(xùn)練腳本里的模型 ID 對齊。任何一處不一致就會看到local proxy failed或者reading choices這類報錯。這篇筆記的目標(biāo)很具體在源碼閱讀的過程中用 TaoToken 統(tǒng)一 Key/API 通道把本地 PRM 打分鏈路跑通一次。你會拿到可復(fù)制的 settings 配置片段、Base URL 寫法、Model ID 對照以及從_prm_evaluate到_majority_vote的完整驗證步驟。適合已經(jīng)在讀 OpenClaw-RL 源碼、想動手驗證 PRM 模塊行為的人。先說清楚 TaoToken 在這里扮演什么角色。它是一個統(tǒng)一的模型 API 通道把不同廠商的模型收斂到同一個 Base URL 和同一套 Key 體系下。對 OpenClaw-RL 這種需要同時調(diào)用 Judge 模型和 Policy 模型的框架來說好處是你不用為每個模型單獨維護 endpoint 和鑒權(quán)。官網(wǎng)入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 。我試過在本地把 PRM Judge 指向 TaoToken 的通道然后用一個最小的 Python 腳本模擬_prm_evaluate的調(diào)用確認(rèn)返回的\boxed{}能被正確解析。下面把整個過程拆開寫。2. TaoToken 前置準(zhǔn)備Key、Base URL 與 PRM Judge 模型選型在動 OpenClaw-RL 的代碼之前先把 TaoToken 側(cè)的三個東西準(zhǔn)備好API Key、Base URL、以及你要用作 PRM Judge 的 Model ID。這三樣?xùn)|西后面會同時出現(xiàn)在 settings 配置和源碼里的self._prm_url調(diào)用中。2.1 獲取 API Key 與確認(rèn) Base URL登錄 TaoToken 控制臺后在 API Keys 頁面創(chuàng)建一個新的 Key。這個 Key 是后續(xù)所有請求的鑒權(quán)憑證??刂婆_地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keys 管理頁在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。創(chuàng)建完成后你會拿到一串以sk-開頭的字符串。把它存到環(huán)境變量里不要硬編碼進(jìn)源碼export TAOTOKEN_API_KEYsk-你的實際Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api注意 Base URL 是https://taotoken.net/api不帶任何路徑后綴。OpenClaw-RL 里 SGLang 的調(diào)用習(xí)慣是往 Base URL 后面拼/v1/chat/completions所以你在配置里填的應(yīng)該是根地址讓框架自己去拼路徑。這一點如果搞反了會直接導(dǎo)致 404。2.2 PRM Judge 的 Model ID 怎么選OpenClaw-RL 源碼里 PRM 用的是同族 Qwen3 模型做 zero-shot 評分。你在 TaoToken 通道下選 Model ID 時要選一個指令跟隨能力足夠強、能穩(wěn)定輸出\boxed{}格式的模型。因為 PRM 的 prompt 里明確要求 give your final score inside \boxed{}模型如果格式跟隨不好解析就會失敗。在 TaoToken 的模型列表里確認(rèn)你要用的 Model ID。常見的做法是選一個中等規(guī)模的指令模型作為 Judge因為 PRM 每個 turn 要跑 m3 次投票成本是 Policy 推理的三倍。Model ID 的準(zhǔn)確字符串以控制臺模型列表為準(zhǔn)不要憑記憶寫。你可以先用模型對話頁面快速驗證一下模型能不能按格式輸出。對話入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。把 PRM 的 system prompt 貼進(jìn)去看它是否返回帶\boxed{1}的回復(fù)。這一步花兩分鐘能省掉后面半小時的解析調(diào)試。2.3 三件套對照表把下面這張表填好后面配置時直接抄配置項值出現(xiàn)位置Base URLhttps://taotoken.net/apisettings、self._prm_urlAPI Keysk-...環(huán)境變量、請求頭Model ID控制臺確認(rèn)的字符串PRM Judge 調(diào)用參數(shù)這三件套在 OpenClaw-RL 里會出現(xiàn)在兩個地方一是 Policy Server 轉(zhuǎn)發(fā)請求時的上游地址二是 PRM Server 自己作為 Judge 被調(diào)用時的模型參數(shù)。如果你用的是 CC Switch 或 Cline MCP 這類工具來管理多模型配置也要把這三個值填進(jìn)對應(yīng)的 provider 配置里。3. 可復(fù)制配置settings 片段與 PRM 調(diào)用參數(shù)對齊這一節(jié)給你可以直接復(fù)制的配置片段。OpenClaw-RL 的配置分散在幾個地方訓(xùn)練腳本的環(huán)境變量、SGLangRouter 的啟動參數(shù)、以及 Policy Server 的上游地址。我們逐個對齊。3.1 環(huán)境變量配置片段在訓(xùn)練腳本的啟動環(huán)境里把 PRM 相關(guān)的變量指向 TaoToken 通道。下面是一個可復(fù)制的 shell 片段# TaoToken 統(tǒng)一通道 export TAOTOKEN_API_KEYsk-你的實際Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api # PRM Judge 配置對應(yīng)源碼里的 self._prm_url 與 PRM_MODEL_PATH export PRM_BASE_URL${TAOTOKEN_BASE_URL} export PRM_API_KEY${TAOTOKEN_API_KEY} export PRM_MODEL_ID你在控制臺確認(rèn)的Model ID export PRM_M3 # Policy Server 上游對應(yīng) openclaw_opd_api_server.py 的轉(zhuǎn)發(fā)目標(biāo) export POLICY_UPSTREAM_BASE_URL${TAOTOKEN_BASE_URL} export POLICY_UPSTREAM_API_KEY${TAOTOKEN_API_KEY} export POLICY_MODEL_ID你的Policy模型ID # 服務(wù)端口 export HOST0.0.0.0 export PORT30000這里的關(guān)鍵是PRM_BASE_URL和POLICY_UPSTREAM_BASE_URL都指向同一個 TaoToken 根地址。OpenClaw-RL 的架構(gòu)里PRM Server 和 Policy Server 是兩個獨立進(jìn)程但它們可以共用同一個上游通道只是用的 Model ID 不同。3.2 JSON 格式的 provider 配置如果你用 CC Switch 或類似的配置管理工具下面是一個 JSON 片段把 TaoToken 作為一個 provider 注冊進(jìn)去{ providers: { taotoken: { base_url: https://taotoken.net/api, api_key: ${TAOTOKEN_API_KEY}, models: { prm_judge: 你在控制臺確認(rèn)的Judge模型ID, policy: 你的Policy模型ID } } }, prm: { provider: taotoken, model: prm_judge, majority_vote_m: 3, timeout_seconds: 60 } }這個片段里的base_url和api_key就是三件套里的前兩件model字段對應(yīng)第三件。majority_vote_m對應(yīng)源碼里的PRM_M3。3.3 TOML 格式如果你用 Codex 風(fēng)格的配置有些工具鏈用 TOML 管理配置。下面是對應(yīng)的寫法[providers.taotoken] base_url https://taotoken.net/api api_key ${TAOTOKEN_API_KEY} [prm] provider taotoken model 你在控制臺確認(rèn)的Judge模型ID majority_vote_m 3 [policy] provider taotoken model 你的Policy模型ID3.4 源碼里self._prm_url的對齊OpenClaw-RL 的openclaw_api_server.py里PRM 調(diào)用是通過self._prm_url發(fā)起的。你需要確保這個 URL 的構(gòu)造方式跟你的配置一致。源碼里通常是這樣的模式# 源碼中的調(diào)用模式示意 self._prm_url f{PRM_BASE_URL}/v1/chat/completions headers {Authorization: fBearer {PRM_API_KEY}} payload { model: PRM_MODEL_ID, messages: _build_prm_judge_prompt(response_text, ns_text, ns_role), temperature: 0.7, max_tokens: 512, }注意temperature這里不是 0。源碼里 PRM 的隨機性是有意保留的因為后面要靠 majority vote 降噪。如果你把 temperature 設(shè)成 0三次投票結(jié)果會完全一樣majority vote 就失去意義了。_build_prm_judge_prompt返回的是一個 messages 列表里面包含 system prompt 和 user prompt。system prompt 里寫明了評分規(guī)則user prompt 里放了response_text和next_state_text。這個結(jié)構(gòu)跟 OpenAI 兼容的 chat completions 格式一致所以直接指向 TaoToken 的/v1/chat/completions就能用。3.5 一個容易忽略的點next_state_role源碼里_build_prm_judge_prompt的簽名是(response_text, next_state_text, next_state_role)。next_state_role有兩個取值user和tool。這個參數(shù)會拼進(jìn) user prompt 里告訴 Judge 這條 next_state 是用戶回復(fù)還是工具返回值。如果你在本地構(gòu)造測試數(shù)據(jù)時忘了傳這個參數(shù)Judge 的評分依據(jù)會不完整可能給出偏中性的分?jǐn)?shù)。在配置層面這個參數(shù)不需要你設(shè)置它是運行時從對話歷史里推斷的。但你在寫驗證腳本時要手動指定否則測出來的分?jǐn)?shù)不能反映真實行為。4. 驗證請求跑通一次 PRM 打分鏈路配置對齊之后下一步是實際發(fā)一次請求確認(rèn) PRM 打分鏈路能跑通。我們分兩步先用 curl 直接打 TaoToken 通道確認(rèn)鑒權(quán)和模型可用再用 Python 模擬_prm_evaluate的完整流程包括 majority vote。4.1 用 curl 驗證通道連通性先構(gòu)造一個最小的 PRM 評分請求。下面這個 curl 命令模擬了 Judge 收到一條 response 和一條 next_state 后的評分過程curl -s https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer ${TAOTOKEN_API_KEY} \ -H Content-Type: application/json \ -d { model: ${PRM_MODEL_ID}, messages: [ { role: system, content: You are a process reward model (PRM) evaluating an AI assistant. Decide whether the assistant output successfully fulfilled the user intent, using the next state as evidence. Scoring rules: boxed{1} good, boxed{-1} bad, boxed{0} neutral. Think step-by-step, then give your final score inside boxed{}. }, { role: user, content: ## Assistant response (turn t)\nThe square root of 1764 is 42.\n\n## Next state (turn t1) [role: user]\nGreat, that is correct. Can you also compute the square root of 2025?\n\nNow output your decision. } ], temperature: 0.7, max_tokens: 512 }如果通道正常你會拿到一個 JSON 響應(yīng)choices[0].message.content里應(yīng)該包含\boxed{1}。這一步驗證了三件事Key 有效、Base URL 正確、Model ID 存在。如果返回 401說明 Key 沒傳對或者過期了。如果返回 404大概率是 Base URL 多寫了或漏寫了路徑。如果返回的 content 里沒有\(zhòng)boxed{}說明模型格式跟隨不好換一個 Model ID 試試。4.2 用 Python 模擬_prm_evaluate與 majority votecurl 通了之后寫一個 Python 腳本完整模擬源碼里的評分流程。這個腳本會調(diào)用三次 Judge然后做 majority voteimport os import re from collections import Counter import requests BASE_URL os.environ[TAOTOKEN_BASE_URL] API_KEY os.environ[TAOTOKEN_API_KEY] MODEL_ID os.environ[PRM_MODEL_ID] PRM_M int(os.environ.get(PRM_M, 3)) def build_prm_judge_prompt(response_text, next_state_text, next_state_roleuser): system ( You are a process reward model (PRM) evaluating an AI assistant. Decide whether the assistant output successfully fulfilled the user intent, using the next state as evidence. Scoring rules: boxed{1} good, boxed{-1} bad, boxed{0} neutral. Think step-by-step, then give your final score inside boxed{}. ) user ( f## Assistant response (turn t)\n{response_text}\n\n f## Next state (turn t1) [role: {next_state_role}]\n{next_state_text}\n\n Now output your decision. ) return [{role: system, content: system}, {role: user, content: user}] def query_judge_once(messages): resp requests.post( f{BASE_URL}/v1/chat/completions, headers{Authorization: fBearer {API_KEY}}, json{ model: MODEL_ID, messages: messages, temperature: 0.7, max_tokens: 512, }, timeout60, ) resp.raise_for_status() content resp.json()[choices][0][message][content] match re.search(r\\boxed\{(-?[01])\}, content) if not match: return None return int(match.group(1)) def majority_vote(scores): valid [s for s in scores if s is not None] if not valid: return 0.0 counter Counter(valid) top counter.most_common(1)[0] if list(counter.values()).count(top[1]) 1: return 0.0 return float(top[0]) def prm_evaluate(response_text, next_state_text, next_state_roleuser): messages build_prm_judge_prompt(response_text, next_state_text, next_state_role) scores [query_judge_once(messages) for _ in range(PRM_M)] return majority_vote(scores), scores if __name__ __main__: score, raw prm_evaluate( response_textThe square root of 1764 is 42., next_state_textGreat, that is correct. Can you also compute the square root of 2025?, next_state_roleuser, ) print(fraw scores: {raw}) print(ffinal score: {score})這個腳本里的majority_vote函數(shù)跟源碼里的_majority_vote邏輯一致過濾 None取眾數(shù)平票返回 0.0。prm_evaluate對應(yīng)源碼里的_prm_evaluate內(nèi)部調(diào)用_query_judge_once三次。4.3 預(yù)期結(jié)果與解讀跑通之后你會看到類似這樣的輸出raw scores: [1, 1, 1] final score: 1.0或者raw scores: [1, 1, -1] final score: 1.0如果三次投票結(jié)果不一致比如[1, -1, 0]最終分?jǐn)?shù)會是 0.0對應(yīng)源碼里的平票保守策略。這個行為在訓(xùn)練時意味著這個 turn 的loss_mask會被設(shè)為 0不參與梯度更新。你可以改一下response_text故意給一個錯誤答案比如 The square root of 1764 is 43.看 Judge 是否給出 -1。這一步能驗證 Judge 的判別能力是否符合預(yù)期。4.4 驗證 at-least-one guarantee源碼里有一個特殊邏輯當(dāng)一個 session 的所有 turn 評分都是 0 時強制把第一條被評估的 turn 的loss_mask設(shè)為 1。你可以在腳本里模擬這個場景連續(xù)構(gòu)造幾條中性反饋看最終是否有至少一條樣本被保留。這個邏輯在源碼里的位置是_submit_turn_sample()核心判斷是self._session_effective.get(session_id, 0) 0。你不需要在驗證腳本里完全復(fù)現(xiàn)但要知道它的存在因為調(diào)試訓(xùn)練信號消失問題時這個保底機制是第一個要檢查的點。5. 常見報錯排查401、local proxy failed、reading choices、OAuth這一節(jié)對照真實報錯給出排查路徑。這些報錯是 OpenClaw-RL 本地配置時最常遇到的。5.1 401 Unauthorized報錯長這樣{error: {message: Invalid API key, type: invalid_request_error}}原因通常是 Key 沒傳對。檢查三處環(huán)境變量TAOTOKEN_API_KEY是否導(dǎo)出成功、請求頭里的Authorization是否是Bearer sk-...格式、Key 是否在控制臺被禁用或刪除。一個容易忽略的點如果你在 shell 里export了 Key但在另一個終端窗口跑腳本那個窗口是拿不到這個環(huán)境變量的。用echo $TAOTOKEN_API_KEY確認(rèn)一下。5.2 local proxy failed報錯長這樣local proxy failed: connection refused這個報錯通常出現(xiàn)在 Policy Server 轉(zhuǎn)發(fā)請求到上游時。OpenClaw-RL 的 Policy Server 監(jiān)聽 30000 端口它會把請求轉(zhuǎn)發(fā)到POLICY_UPSTREAM_BASE_URL。如果這個地址填的是localhost或某個不存在的端口就會 connection refused。檢查POLICY_UPSTREAM_BASE_URL是否指向https://taotoken.net/api。如果你之前把它填成了本地 SGLang 的地址比如http://localhost:8000改成 TaoToken 的根地址。另一個可能你的本地網(wǎng)絡(luò)環(huán)境對taotoken.net的解析有問題。用curl -v https://taotoken.net/api/v1/chat/completions看一下連接過程確認(rèn) DNS 解析和 TLS 握手正常。5.3 reading choices 相關(guān)報錯報錯長這樣KeyError: choices或者IndexError: list index out of range這個報錯說明響應(yīng) JSON 里沒有choices字段或者choices是空列表。原因通常是上游返回了一個錯誤響應(yīng)但你的代碼直接去取choices[0]了。排查方法在解析響應(yīng)之前先把原始響應(yīng)打出來。在query_judge_once里加一行print(resp.text)看上游到底返回了什么。常見的情況是返回了{(lán)error: ...}但代碼沒檢查resp.status_code就直接解析。源碼里的_query_judge_once有對響應(yīng)的校驗邏輯你在本地復(fù)現(xiàn)時要確保這部分沒被跳過。5.4 OAuth 相關(guān)報錯報錯長這樣OAuth token expired或者invalid_grant如果你用的是 Codex 風(fēng)格的auth.json來管理憑證可能會遇到這個。auth.json里的 token 有過期時間過期后需要刷新。檢查你的auth.json里expires_at字段是否已經(jīng)過了當(dāng)前時間。如果你同時用 TaoToken 的 API Key 和某個 OAuth 憑證確認(rèn)請求走的是哪條路徑。OpenClaw-RL 的 PRM 調(diào)用應(yīng)該走 API Key 路徑不走 OAuth。如果配置里混了把 OAuth 相關(guān)的字段清掉只保留base_url、api_key、model三件套。5.5 報錯對照表報錯關(guān)鍵詞最可能原因檢查點401 UnauthorizedKey 無效或未傳環(huán)境變量、請求頭格式local proxy failed上游地址錯誤POLICY_UPSTREAM_BASE_URLreading choices響應(yīng)無 choices 字段先打印原始響應(yīng)OAuth token expired憑證過期auth.json的expires_atboxed 解析失敗模型格式跟隨差換 Model ID 或調(diào) prompt5.6 一個隱蔽的坑temperature 與投票如果你把 PRM 的temperature設(shè)成 0三次投票會返回完全一樣的結(jié)果。這時候 majority vote 看起來總是通過但實際上沒有起到降噪作用。源碼里默認(rèn) temperature 是大于 0 的你在配置時不要為了穩(wěn)定把它改成 0。反過來如果 temperature 太高比如 1.5三次投票可能給出三個不同結(jié)果最終平票返回 0.0導(dǎo)致大量樣本被丟棄。建議保持在 0.7 左右跟源碼默認(rèn)值對齊。6. 繼續(xù)深入從 PRM 打分到 OPD 與 Combine 的配置延伸跑通 PRM 打分鏈路之后你可以沿著源碼繼續(xù)往下讀。OpenClaw-RL 的 PRM 在兩個分支里扮演不同角色Binary RL 分支里它是評分員輸出 ±1/0 直接作為 rewardOPD 分支里它是 hint 提取器輸出[HINT_START]...[HINT_END]文本再喂給 teacher 做 forward pass。如果你要驗證 OPD 分支需要額外配置 teacher 模型的調(diào)用。teacher 也可以走 TaoToken 通道只是 Model ID 換成 teacher 對應(yīng)的模型。配置結(jié)構(gòu)跟 PRM 一樣三件套對齊即可。Combine 分支則是同時跑兩條路徑同一個 turn 并發(fā)發(fā)起 PRM eval 和 hint judge 兩種 prompt分別決定是否發(fā) RL 樣本和 OPD 樣本。這時候你的 TaoToken 通道會同時承載 Judge 調(diào)用和 teacher 調(diào)用注意并發(fā)量和超時設(shè)置。對于長期跑編碼或 Agent 任務(wù)的場景可以考慮用 Coding Plan 來管理調(diào)用配額入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果你的驗證腳本需要頻繁調(diào)用模型接入文檔在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 有更詳細(xì)的參數(shù)說明。最后留一個實用技巧在本地調(diào)試 PRM 時把每次 Judge 的原始響應(yīng)寫到日志文件里包括response_text、next_state_text、raw_scores、final_score。這樣當(dāng)你發(fā)現(xiàn)某個 turn 的評分不符合預(yù)期時可以回溯到具體的 Judge 輸出看是 prompt 構(gòu)造問題還是模型判斷問題。這個日志在源碼里沒有現(xiàn)成的需要你自己在_query_judge_once外面包一層。