> ## Documentation Index
> Fetch the complete documentation index at: https://starforge.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Verifier

> 判定任务是否完成的三种方式，以及各自的确切契约。

```json manifest.json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{ "verifier": { "kind": "rubric", "ref": "alice/arithmetic-correctness" } }
```

verifier 判定一个任务有没有完成。它永远是一个指向「已经拥有这个答案的东西」的**引用**，
而不是平台自己实现的东西。

## 三种

| Kind       | `ref` 是                                                 | 判断归谁所有    |
| ---------- | ------------------------------------------------------- | --------- |
| `rubric`   | 一个 [Rubric](/zh-Hans/extend/rubrics) 的 `<owner>/<name>` | 你团队写下来的标准 |
| `plugin`   | `kind: environment` 插件里的 `module:callable`              | 你的代码      |
| `endpoint` | 一个 HTTP URL                                             | 你运行的服务    |

刻意没有第四种，尤其没有「内置」这一种。

<Accordion title="为什么平台永远不做 verifier">
  上一次这个仓库长出内置打分器时，它写了自己的一套仿真流水线、带自己的权重，
  得出的判断和评测侧不一致，最后整块被回滚。

  住在控制平面里的 verifier，无论一开始多小，都会成为「什么算对」的第二个定义——
  而这两个定义恰恰会在最要紧的时候分歧。
</Accordion>

## 契约

无论哪一种，解析出来的 verifier 在平台侧长得都一样：

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
VerifyFn = Callable[[Mapping[str, Any], Any], float]
```

两个入参：任务，以及 harness 提交上来的轨迹。一个返回值：reward。

<Tabs>
  <Tab title="rubric">
    ```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    { "verifier": { "kind": "rubric", "ref": "alice/arithmetic-correctness" } }
    ```

    平台裁判按指定的 rubric 版本给轨迹打分。它拿到任务的 `prompt`、摊平后的轨迹文本，
    以及任务的 `reference`（如果有）。

    轨迹的摊平是平台替你做的——字符串保持原样，消息对象取它的 `content` 或 `text`，
    列表则拼接起来。各家 harness 形状不同，而 verifier 的契约关心的是「模型产出了什么」，
    所以这层折叠在这里做一次，而不是压给每个作者。
  </Tab>

  <Tab title="plugin">
    ```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    { "verifier": { "kind": "plugin", "ref": "verify:score_arithmetic" } }
    ```

    ```python verify.py theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    from typing import Any, Mapping

    def score_arithmetic(task: Mapping[str, Any], trajectory: Any) -> float:
        expected = str(task.get("reference", "")).strip()
        answer = str(trajectory).strip().split()[-1] if trajectory else ""
        return 1.0 if answer == expected else 0.0
    ```

    从一个已经过了 digest 三道闸的 `kind: environment` 插件里 import。
    延迟 import 且只 import 一次：import 失败会在第一个任务上暴露，
    那时它读起来像一个部署问题；如果在启动期暴露，会让人以为是环境本身坏了。

    返回值必须能被 `float()` 接受。抛异常是允许且正确的——见下文。
  </Tab>

  <Tab title="endpoint">
    ```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    { "verifier": { "kind": "endpoint", "ref": "https://verifier.corp/score" } }
    ```

    平台 POST 一段 JSON，期望拿回一个 reward：

    ```json Request theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    { "task": { "prompt": "...", "reference": "..." }, "trajectory": "..." }
    ```

    ```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    { "reward": 0.83 }
    ```

    响应里没有 `reward` 字段、或者它不是数字，都算错误，不算零分。默认超时 60 秒。
  </Tab>
</Tabs>

## 验证失败是错误，永远不是零分

<Warning>
  你的 verifier 答不出来时——端点挂了、模块 import 不了、rubric 没了——平台会抛异常。
  它不会报一个 `0.0` 的 reward。
</Warning>

这是写 verifier 的人最需要记住的一条，理由值得说白：
一个被悄悄归零的 reward，**和「答案确实是错的」无法区分**。
一次训练如果 verifier 在第 400 步悄悄坏掉，reward 曲线会继续产出、
继续看起来合理，并在这个作业剩下的几个小时里用噪声教模型。

所以在你自己的 verifier 代码里：

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
def score(task, trajectory):
    if not task.get("reference"):
        raise ValueError("this task has no reference; it cannot be scored")   # 对
        # return 0.0                                                          # 错
    ...
```

错误会被包装成 `VerificationError` 并带上失败的那个 ref，所以作业日志能说清是哪个 verifier 停下的、为什么。

## 怎么选

<Columns cols={3}>
  <Card title="选 rubric" icon="ruler">
    对错是判断问题时——质量、语气、一段解释是否站得住。
    也适用于「想让同一份标准同时用于训练 reward 和评测分数」。
  </Card>

  <Card title="选 plugin" icon="code">
    对错可计算，且计算便宜、在本地就能做：精确匹配、一个 parser、一个单元测试。
  </Card>

  <Card title="选 endpoint" icon="globe">
    已经有东西在做这个判断了——编译服务、仿真器、内部评分系统。
    不要在插件里把它重新实现一遍。
  </Card>
</Columns>
