CodexQA

行业与实践工具与框架

Inspect 自定义评分器:Score、未评分样本和评分模型

CodexQA 团队阅读约 6 分钟

英国 AI Security Institute 的 Inspect 文档:自定义评分器接收 TaskState 和 Target,返回 Score。说明 value 的 C/I/P/N、返回 None 与 Score.unscored() 的差别,以及用 grader 角色调用模型。附 close_enough、includes 和 model_graded_qa 的代码。

本文目录

自定义评分器

概述

自定义评分器是这样的函数:接收 TaskState 和 Target,产出一个 Score。评分器(scorer)负责给模型输出打分。TaskState 是当前样本的任务状态,里面有输入和模型输出。Target 是样本的标准答案。Score 是一次打分的结果。

async def score(state: TaskState, target: Target):
     # Compare state / model output with target
     # to yield a score
     return Score(value=...)

先说明核心对象 Score 和 Value,再给出几个自定义评分器的例子,把做法说具体。

示例

这个评分器从模型输出里取出最后一个数。当它落在 target 的相对容差之内时,把样本判为正确。它用 @scorer 登记指标,从 state 读取模型输出,与 target.text 比较,并返回带 answer 和 explanation 的 Score。指标(metric)是对许多样本的分数做汇总,例如正确率。

import re

from inspect_ai import Task, task
from inspect_ai.dataset import Sample
from inspect_ai.scorer import (
    CORRECT,
    INCORRECT,
    Score,
    Target,
    accuracy,
    scorer,
    stderr,
)
from inspect_ai.solver import TaskState, generate


@scorer(metrics=[accuracy(), stderr()])
def close_enough(rel_tol: float = 0.01):
    async def score(state: TaskState, target: Target) -> Score:
        numbers = re.findall(
            r"-?\d+(?:\.\d+)?", state.output.completion
        )
        if not numbers:
            return Score(
                value=INCORRECT, 
                explanation="No number found in output."
            )

        answer = numbers[-1]
        expected = float(target.text)
        correct = abs(float(answer) - expected) <= rel_tol * abs(expected)
        return Score(
            value=CORRECT if correct else INCORRECT,
            answer=answer,
            explanation=state.output.completion,
        )

    return score


@task
def arithmetic():
    return Task(
        dataset=[
            Sample(
                input="What is 18 * 7?",
                target="126"
            ),
        ],
        solver=generate(),
        scorer=close_enough(),
    )

下面各节说明这个例子用到的部件。

Score

Score 的组成部分包括:

字段类型说明
valueValue赋给样本的分值(例如 "C" 或 "I",或一个原始数值)。
answerstr从模型输出中抽出、用来比较的文本(可选)。
explanationstr分数的说明,例如完整模型输出或评分模型的输出(可选)。
reasonScoreReason str`
metadatadict[str,Any]要写入日志文件的、关于这次分数的额外元数据(可选)。

例如,下面这些都是合法的 Score 对象:

Score(value="C")
Score(value="I")
Score(value=0.6)
Score(
    value="C" if extracted == target.text else "I",
    answer=extracted,
    explanation=state.output.completion
)

Score.value 可以是你的指标知道如何解释的任何 Value。内置的正确性评分器使用常量 CORRECT("C")、INCORRECT("I")、PARTIAL("P")和 NOANSWER("N")。指标如 accuracy() 默认使用的 value_to_float() 转换器,把这些值分别映射为 1.0、0.0、0.5 和 0.0。它也转换数值、数字字符串,以及常见的布尔字符串,例如 "yes" / "no" 和 "true" / "false"。

你可以返回其他字符串,但汇总指标需要一个能理解它们的转换器。例如:

from inspect_ai.scorer import accuracy, value_to_float

accuracy(
    to_float=value_to_float(correct="pass", incorrect="fail")
)

如果你是从一次补全内部抽取答案(例如用正则找文本,或看补全的开头或结尾),应尽量在 Score 里始终返回 answer。查看评测日志时,这会让打分细节容易理解得多。

未评分样本

当样本不在该评分器的范围内时,返回 None。例如,混合数据集里的代码测试评分器,可以对作文题返回 None。这样不会产生分数条目,也不会增加 unscored_samples。

当本来预期要给出判断、但没能得到时,使用 Score.unscored(),例如评分器的裁决无法解析:

return Score.unscored(
    reason="grader_failed",
    answer=extracted,
    explanation="Grader did not return a parseable verdict.",
)

这会创建一条值为 NaN 的分数,并保留所给的 reason、answer 和 explanation。指标会排除这个值。覆盖计数在 epoch 归约之后计算:默认的均值归约器忽略未评分的 epoch,其他归约器可以不同地处理它们。见 Interpreting coverage。

可选的 reason 接受标准的 ScoreReason 标签或自定义字符串。用 reason 描述结局,用 explanation 放上理解它所需要的细节。

对错误答案,或没有遵守任务要求格式的情况,返回一条已评分的裁决。对执行失败或非法的评分器输入,抛出异常,以便 Inspect 应用本次运行的错误处理设置。这些结局如何影响结果,见 Scoring Policy。

分值

Value 是主要标量类型,以及这些类型的 list 或 dict 的联合:

Value = Union[
    str | int | float | bool,
    Sequence[str | int | float | bool],
    Mapping[str, str | int | float | bool],
]

绝大多数评分器会使用 str(例如用 "C" 和 "I" 表示正确/不正确)或 float(其他类型是为了更复杂的情形)。要记住:评分器里使用的任何 Value 类型,都必须被该评分器声明的指标所支持(下文还会谈到)。

接下来看两个内置评分器的源码,作为实现自己的评分器的起点。如果正在写自定义评分器,也应阅读 Scoring Workflow,其中有优化开发过程的提示。

评分器里的模型

实现评分器时,常常要用到模型。最好的做法是用模型角色指定评分模型,让它们在运行时再选定。默认情况下,模型评分器查找名为 "grader" 的模型角色:

grader_model = get_model(role="grader")

也可以用 get_model() 取得当前被评测的模型,或其他模型接口。例如:

# use the model being evaluated for grading
grader_model = get_model()

# use another model for grading
grader_model = get_model("google/gemini-2.5-pro")

用 get_model() 的 config 参数覆盖默认生成选项:

grader_model = get_model(
    "google/gemini-2.5-pro",
    config = GenerateConfig(
        temperature = 0.0
    )
)

示例:Includes

下面是内置 includes() 评分器的源码:

@scorer(metrics=[accuracy(), stderr()])   # <1>
def includes(ignore_case: bool = True):

    async def score(state: TaskState, target: Target):   # <2>

        # check for correct
        answer = state.output.completion
        target = target.text   # <3>
        if ignore_case:
            correct = answer.lower().rfind(target.lower()) != -1
        else:
            correct = answer.rfind(target) != -1

        # return score
        return Score(
            value = CORRECT if correct else INCORRECT,    # <4>
            answer=answer  # <5>
        )

    return score
  1. 函数使用 @scorer 装饰器,并登记两个供该评分器使用的指标。
  2. score 函数声明为 async。这样它就能参加 Inspect 为昂贵的模型生成调用所做的优化调度(这个评分器不调用模型,但其他评分器会)。
  3. 使用 Target 上的 text 属性。这是一个方便属性,用来从 Target 取出简单文本值(因为目标在技术上可以是字符串列表)。
  4. 分值使用特殊常量 CORRECT 和 INCORRECT。accuracy()、stderr() 和 bootstrap_stderr() 知道如何把这些常量转换成浮点值(分别是 1.0 和 0.0)。
  5. 把完整的模型补全作为分数的 answer(answer 可选,但强烈建议提供,评测开发时经常要回看它)。

示例:模型评分

下面是 model_graded_qa() 评分器代码的一个有所简化的版本:

@scorer(metrics=[accuracy(), stderr()])
def model_graded_qa(
    template: str = DEFAULT_MODEL_GRADED_QA_TEMPLATE,
    instructions: str = DEFAULT_MODEL_GRADED_QA_INSTRUCTIONS,
    grade_pattern: str = DEFAULT_GRADE_PATTERN,
    model: str | Model | None = None,
) -> Scorer:

    # resolve grading template and instructions,
    # (as they could be file paths or URLs)
    template = resource(template)
    instructions = resource(instructions)

    # resolve model
    grader_model = get_model(model)

    async def score(state: TaskState, target: Target) -> Score:
        # format the model grading template
        score_prompt = template.format(
            question=state.input_text,
            answer=state.output.completion,
            criterion=target.text,
            instructions=instructions,
        )

        # query the model for the score
        result = await grader_model.generate(score_prompt)

        # extract the grade
        match = re.search(grade_pattern, result.completion)
        if match:
            return Score(
                value=match.group(1),
                answer=match.group(0),
                explanation=result.completion,
            )
        else:
            return Score.unscored(
                reason="grader_failed",
                explanation="Grade not found in model output: "
                + f"{result.completion}",
            )

    return score

如果分数无法解析,这个评分器会记一条未评分结果,并把评分模型的输出当作 explanation。如何选择分数结局,见 未评分样本。

注意对 model_grader.generate() 的调用使用了 await。这一点很关键,才能让评分器正确参加生成工作的调度。

还要注意,我们用 TaskState 的 input_text 属性取得原始用户输入的字符串版本,再代进评分模板。使用 input_text 有两个好处:(1)它保证覆盖数据集里的原始输入(而不是 messages 里被改写过的提示);(2)它把输入归一成字符串(输入本来也可能是消息列表)。

内置评分器的全部定制选项,见 Model Grading。

UK AI Security Institute,Custom Scorers,文档页无单独发布日期(检索于 2026-10-04),https://inspect.aisi.org.uk/custom-scorers.html ,MIT License(Copyright (c) 2024 UK AI Security Institute)

觉得有用,转给同事

微信扫码

用微信扫一扫,在手机上打开后即可转发。

用 RSS 订阅

提交勘误