跳到主要内容

打分与解析:Rubric 与 Parser

这章讲什么: rollout 跑完拿到一条对话轨迹,怎么变成一个数字奖励?这一章讲 Rubric(打分器,管一组奖励函数)和 Parser(从输出里抽答案)。两者是 RL 训练里「reward」信号的来源,也是评测里各项 metric 的来源。

3.1 Rubric:一组奖励函数的加权和

它要解决的小问题

一条轨迹的「好坏」往往不是一个维度。答案对不对是一维、格式规不规范是一维、用了几次工具是一维。Rubric 就是把多个奖励函数捆在一起、各给权重、加权求和

思路 / 直觉

Rubric 里有两个平行列表:funcs(奖励函数)和 weights(权重)。打分 = 每个函数算一个 float,乘权重,求和。权重为 0 的函数不影响 reward,只作为「指标」(metric)被记录——这就是 add_metric(权重默认 0)和 add_reward_func(权重默认 1)的区别(verifiers/rubrics/rubric.py:68:72)。

# 示意,非源码:rubric 打分的核心
reward = sum(func(...) * weight for func, weight in zip(funcs, weights))
metrics = {func.__name__: func(...) for func in funcs} # 每个函数单独记一项

巧妙点:按函数签名自动注入参数

这是 Rubric 最聪明的一处。你写奖励函数时,想要哪个参数就写哪个参数名,框架按名字喂给你:

# 示意,非源码:三种写法都合法
async def r1(completion, answer): ... # 只要 completion 和 answer
def r2(parser, completion, **kwargs): ... # 还要 parser;**kwargs 兜底
async def r3(state): ... # 只要整个 state

实现在 _call_individual_reward_func:182):先用 score_objects(state) 备好一大盘可注入的对象(prompt、completion、answer、state、info、task、parser……见 :123),再用 inspect.signature 看函数声明了哪些参数名,只把匹配的挑出来传进去。带 **kwargs 的函数则整盘都收。任何奖励函数抛异常都被兜住、记 0 分、不让一条坏轨迹拖垮整批(:202-:215)。

依据:Rubric.score_objectsverifiers/rubrics/rubric.py:123)、_call_individual_reward_func:182)、add_reward_func / add_metric:68/:72)。

个体打分 vs 分组打分

Rubric 区分两种奖励函数,靠看函数参数名是复数还是单数来判断(_is_group_func:104):

个体奖励分组奖励
参数名completion / answer(单数)completions / states(复数)
一次看一条轨迹同题的所有轨迹
用途答案对不对、格式分偏好/成对比较、算 advantage
入口score_rollout:317score_group:361

score_group:361)跑完所有函数后,给每条算 reward,再减去组内均值得到 advantage:404-:407),并把 reward/advantage 回填到轨迹的每一步(training 要用)。这就是上一章说的「RL 分组打分」在 rubric 侧的落地。

3.2 Parser:从输出里抠出「最终答案」

它要解决的小问题

模型的输出常常是一大段话,真正的「答案」埋在里面。奖励函数要拿答案和 ground truth 比,就得先抽出来Parser 就干这个。

三个由浅入深的 Parser

Parser抽答案的规则文件
Parser(基类)默认:拿最后一条 assistant 消息的全部内容verifiers/parsers/parser.py
ThinkParser去掉 <think>...</think> 再取后面的部分verifiers/parsers/think_parser.py
XMLParser按 XML 标签抽字段,如 <answer>...</answer>verifiers/parsers/xml_parser.py

基类 Parser 很薄:parse 默认原样返回,parse_answer:67)取最后一条 assistant 消息的文本内容。你可以传一个 extract_fn 做后处理(比如 extract_boxed_answer\boxed{} 里抠数字)。

XMLParser:结构化输出 + 格式奖励

XMLParser 是最有料的一个。你声明一组字段,它就能:

  1. 解析parse:57)用正则把 <field>内容</field> 抠出来,每个字段成为返回对象的一个属性。字段可以给多个别名,如 ("code", "answer") 表示这两个标签都认(:72-:87)。
  2. 生成格式说明get_format_str:115)产出给系统提示用的模板串。
  3. 格式奖励函数get_format_reward_func:128)返回一个奖励函数,检查模型输出是否遵守格式——是否每条消息都含至少一个字段、间距对不对、是否以第一个/最后一个字段开头结尾,加权算出 0~1 分。

这解决了一个很实际的 RL 问题:你想让模型学会「先推理、再在 <answer> 里给答案」,就用 XMLParser 的格式奖励当一路信号,配合答案正确性奖励一起训。

依据:XMLParser.parseverifiers/parsers/xml_parser.py:57)、get_format_reward_func:128)、format:249)。

3.3 两个开箱即用的 Rubric

MathRubric:把数学判分丢进子进程

数学答案判等很麻烦(1/2 = 0.5 = \frac{1}{2})。MathRubric 用第三方库 math-verify 做符号判等,但有两个工程考量(verifiers/rubrics/math_rubric.py):

  • 判分跑在 ProcessPoolExecutor:77):verify_response 是模块级顶层函数,能被 pickle 送进子进程——因为符号解析可能很慢甚至卡死,隔离到子进程 + 硬超时(HARD_TIMEOUT_SECONDS=120)不会拖垮主事件循环(:106-:123)。
  • 默认 parser 是 MaybeThinkParser + extract_boxed_answer:自动从 \boxed{} 抠答案(:69-:71)。

JudgeRubric:用另一个 LLM 当裁判

有些任务没有可程序化判等的标准答案(开放问答、创意写作)。JudgeRubricverifiers/rubrics/judge_rubric.py)让另一个模型来判分:

question + ground truth + 模型回答
│ 塞进 judge_prompt 模板

judge_client.chat.completions.create(judge_model)
│ 裁判模型回 "yes"/"no"

你的奖励函数把 yes/no 变成 1.0/0.0

默认 judge 模型是 gpt-4.1-nano,默认提示词让它只回 yes/no(DEFAULT_JUDGE_PROMPT:10)。judge 方法(:55)里带一个按 prompt 缓存的机制:同一条 judge_prompt 已经问过就直接返回缓存,省钱(:74-:75:136-:140)。注意 judge() 只是「问裁判拿一句话」——你自己写的奖励函数调用它、再把回答映射成分数。

RubricGroup:把多个 Rubric 拼起来

RubricGroupverifiers/rubrics/rubric_group.py)把几个 rubric 的奖励函数摊平合并,对外还是一个 rubric。ToolEnvMultiTurnEnv 就是这么给环境自动叠加「工具调用次数」「轮数」这类监控指标的(下一章讲)。

4. 代码地图

主题文件符号
打分器基类verifiers/rubrics/rubric.pyRubric
参数按签名注入verifiers/rubrics/rubric.pyRubric._call_individual_reward_func / score_objects
个体打分verifiers/rubrics/rubric.pyRubric.score_rollout
分组打分 + advantageverifiers/rubrics/rubric.pyRubric.score_group
判断个体/分组函数verifiers/rubrics/rubric.pyRubric._is_group_func
Parser 基类verifiers/parsers/parser.pyParser.parse_answer
XML 结构化解析verifiers/parsers/xml_parser.pyXMLParser.parse / get_format_reward_func
数学判分(子进程)verifiers/rubrics/math_rubric.pyMathRubric.correct_answer / verify_response
LLM 裁判verifiers/rubrics/judge_rubric.pyJudgeRubric.judge
多 rubric 合并verifiers/rubrics/rubric_group.pyRubricGroup