跳到主要内容

第 4 章:RL 环境与多分量奖励

这是整个项目设计含量最高的一章。开放式数据研究没有唯一正确答案(一份报告好不好很主观),纯「对/错」奖励行不通。DeepAnalyze 的解法是:把奖励拆成过程 + 结果的多个分量,各打分再平均。本章拆 deepanalyze_env.pyutils.py

4.1 环境怎么 rollout

DeepAnalyzeEnv(继承 SkyRL 的 BaseTextEnv)每收到模型一段输出 action,就走一遍 step(deepanalyze_env.py:184-253):

step(action):
turns += 1
action = 截断到 </Answer> 或 </Code> # _postprocess_action
记进 chat_history(role=assistant)
done = ( "<Answer>"&"</Answer>" in action ) 或 turns>=max_turns # _is_done
if done: reward = _get_reward(...) → 返回,结束
else:
解析 <Code> → 在 workspace 执行 Python(PythonCodeExecutorToolGroup)
观察包成 <Execute>...</Execute> 回灌
记录 code_pass(出错记 0,成功记 1)

两个细节:

  • 执行用子进程 + 超时:python_tool.py:118-136multiprocessing.Process,超时(默认 10s)就 SIGTERM 杀掉,返回 [Error]\nPython code execution timed out after {timeout} seconds.(python_tool.py:129)。比参考实现的裸 exec 安全。注意这串 [Error] 才是 RL 版超时的返回;字面量 [Timeout] 是另外两版的超时返回(API 版 execute_code_safe,utils.py:159;Docker 版,docker_executor.py:255),别和 RL 版搞混。
  • 逐轮记 code_pass:观察里有 [Error]: 记 0,否则记 1(deepanalyze_env.py:230-236)。这串 0/1 后面会变成「代码通过率」奖励。

下面把三版执行的超时返回串列清楚,免得引用串错:

执行版本超时返回串出处
RL python_tool[Error]\nPython code execution timed out after N seconds.python_tool.py:129
API execute_code_safe[Timeout]: execution exceeded N secondsAPI/utils.py:159
Docker 沙箱[Timeout]: execution exceeded N secondsdocker_executor.py:255

4.2 第一道门:格式不合直接 -1

奖励只在 done 时算(_get_reward,deepanalyze_env.py:95-169)。第一件事是格式硬门槛:把整段 chat_history 拼起来,只要缺 <Analyze>/</Analyze>/<Answer>/</Answer> 任一个,直接 return -1.0(deepanalyze_env.py:100-106)。

意思是:没有「先分析、最后给答案」这套结构的轨迹,无论内容多对都重罚。这把「按协议输出」变成了 RL 要优化的硬约束,而不只靠 SFT 记忆。

datatask/openresearch 还有第二道码门:check_valid_code_block(utils.py:366-377)要求每个 <Code> 块必须是 ```python ... ``` 规整围栏,否则也 -1。

4.3 三路奖励

reward_spec["method"] 分流:

(a) qa —— 表格问答类

两分量平均(deepanalyze_env.py:107-122):

分量怎么算函数
qa_acc_reward<Answer> 里的答案,和 ground truth 精确字符串相等 → 1 否则 0compute_tableqa_score_single(utils.py:110-119)
qa_analysisc_rewardLLM 裁判给分析过程质量打 1–5 分,除以 5 归一llm_as_judgement_analyze(utils.py:249-269)

直觉:结果对不对(精确匹配)+ 过程好不好(LLM 评分),各占一半。光对不行,得有像样的分析。

(b) datatask —— 有客观答案的数据任务

动态调用 reward_spec["function"] 里指定的奖励函数(deepanalyze_env.py:124-152),再加一项:

  • 代码通过率 datatask_code_reward = mean(code_pass) —— 前面逐轮记的 0/1 取平均。让模型不仅最终答对,中途每段代码都尽量别报错

(c) openresearch —— 开放式深度研究(最复杂)

在 datatask 基础上再加轮数奖励(deepanalyze_env.py:154-161):

# 示意,提炼自 deepanalyze_env.py
turns = 统计 chat_history 里 role==assistant 的条数
turn_reward = min(turns / 10, 1) # 鼓励多探索,10 轮封顶
rewards["openresearch_turns_reward"] = turn_reward

这一项很有意思:开放研究鼓励模型多做几轮探索(越多越好,到 10 轮封顶),因为深度研究就该多角度挖。对应的多维质量评分是 llm_as_judgement_opendomain(utils.py:333-363),让 LLM 按 usefulness / richness / soundness / interpretability / readability 五维各打 1–5 分,返回一个分量字典。

最终奖励都是 sum(rewards.values()) / len(rewards)——所有分量等权平均(deepanalyze_env.py:163)。

4.4 LLM 裁判长什么样

utils.py 里几个 llm_as_judgement_* 是奖励的核心。套路一致:

  1. 抽出模型答案,填进一个评分 prompt;
  2. 调一个裁判模型(env_config.llm_judgement_model,OpenAI 兼容);
  3. 正则抠出 SCORE: N### Final Score: N,归一成奖励。

比如分析质量裁判的 prompt(ANALYZE_PROMPT,utils.py:228-246)给了 1–5 分的明确 rubric(从「adequate」到「exceptional」),还要求裁判先找弱点再打分——这是为了让评分更挑剔、避免一律给高分。开放研究的 OPENDOMAIN_PROMPT(utils.py:272-330)更长,五个维度各有详细 rubric,要求返回 JSON。

这一招把「报告好坏」这种没法用 == 判定的目标,变成了可微调的标量奖励——代价是奖励信号依赖裁判模型,慢且有噪声。

4.5 巧妙之处小结

  • 格式当硬门槛:缺 Analyze/Answer 直接 -1,把协议遵守焊进 RL 目标(deepanalyze_env.py:100-106)。
  • 过程奖励:逐轮 code_pass 平均成代码通过率,逼模型「每一步都能跑通」,而不只看终点(deepanalyze_env.py:148-152)。
  • 轮数奖励专给开放研究:别的任务越短越好,深度研究反而奖励多探索(turns/10 封顶)——任务相关的奖励塑形。
  • 多维 LLM 裁判:用带 rubric、先挑弱点的 prompt 把主观质量量化(utils.py:272-330)。

4.6 边界与局限

  • 精确匹配很脆:qa_acc_rewardanswer == reference 字符串全等(utils.py:113),格式差一点(大小写、空格、单位)就判 0。靠 LLN 裁判那一半来兜底。(inferred)
  • 依赖外部裁判:奖励要调 LLM,训练成本和噪声都来自这里;裁判输出格式不对就 fallback 成 0(utils.py:181-186)。
  • sql_tool 被 import 但仓库里没有:deepanalyze_env.py:7 from examples.deepanalyze.sql_tool import SQLCodeExecutorToolGroup,但该目录下只有 python_tool.py,没有 sql_tool.py。直接跑会 ImportError——可能是上游漏提交或被裁掉的代码。这是个真实的运行边界。

4.7 代码地图

主题文件符号
环境 step / rolloutdeepanalyze/SkyRL/skyrl-train/examples/deepanalyze/deepanalyze_env.pyDeepAnalyzeEnv.step
奖励主分流同上DeepAnalyzeEnv._get_reward
完成判定 / 截断同上DeepAnalyzeEnv._is_done_postprocess_action
子进程执行 + 超时examples/deepanalyze/python_tool.pyPythonCodeExecutorToolGroup.python
QA 精确匹配examples/deepanalyze/utils.pycompute_tableqa_score_single
分析质量裁判同上llm_as_judgement_analyzeANALYZE_PROMPT
开放研究五维裁判同上llm_as_judgement_opendomainOPENDOMAIN_PROMPT
码块格式门同上check_valid_code_block