Inspect AI — 这是什么 / 全景 / 阅读地图
30 秒导读: Inspect 是英国 AI 安全研究所(UK AI Security Institute, AISI)开源的大模型评测框架。你把一次评测写成一个
Task(数据集 + 求解步骤 + 打分器三件套),eval()就替你把每条样本喂给模型、跑完、打分、聚合成指标,并把全过程记进一份能回放的日志。本章只做导航:讲清它是什么、大盘怎么转、去哪读细节——不钻底层。
1. 这是什么(零基 础也能懂)
一句话定义: Inspect 是一个把"给大模型出一套题、让它答、然后自动判分"这件事标准化的 Python 框架。
它的口号写在自己的 README 里(README.md:3):a framework for large language model evaluations,由 UK AI Security Institute 创建。引用信息见 CITATION.cff(标题 "Inspect AI: Framework for Large Language Model Evaluations")。
解决谁的什么问题。 假设你想回答"GPT-5 和 Claude 谁更会做小学数学题""某模型会不会被诱导说危险内容"。手写脚本很快就乱:要拉数据集、要接不同厂商的 API、要处理多轮对话和工具调用、要判分、要重复多次取平均、要留证据以便复盘。Inspect 把这些都做成了可复用的部件,你只填三样东西。
三件套 = 一次评测的全部骨架:
| 部件 | 你要提供什么 | 白话 |
|---|---|---|
| Dataset | 一批 Sample(input, target) | 题目 + 标准答案 |
| Solver | 一串求解步骤(通常含 generate()) | 怎么让模型作答 |
| Scorer | 一个打分函数 | 怎么判对错 |
它能做什么(功能一览):
- 内置 prompt 工程、工具调用、多轮对话、模型判分(model-graded)等组件(
README.md:5)。 - 一套接口对接约三十家模型提供商(OpenAI / Anthropic / Google / 本地 vLLM…),换模型只改一个字符串。
- 自带 200+ 现成评测,可直接对任意模型跑(
README.md:9)。 - 全程事件化记录 + 可视化回放(
inspect view)。
用起来什么样(最小真实示例):
# 示意,非源码:一个最小的 Inspect 评测
from inspect_ai import Task, task
from inspect_ai.dataset import Sample
from inspect_ai.scorer import model_graded_fact # 让另一个模型判事实对错
from inspect_ai.solver import generate, system_message
@task # ① 注册成一个可被 eval 的 task
def theory_of_mind():
return Task(
dataset=[Sample(input="小明以为盒子里是糖…他会去哪找?", target="盒子")],
solver=[system_message("仔细推理后作答"), generate()], # ② 求解步骤链
scorer=model_graded_fact(), # ③ 打分器
)
命令行跑它(选模型只是换个字符串):
inspect eval theory_of_mind.py --model openai/gpt-4o
inspect view # 打开浏览器回放这次评测
一句话直觉: 把 Inspect 当成评测界的单元测试框架——Task 是一个测试套件,Sample 是一个测试用例,Solver 决定"怎么运行被测对象",Scorer 就是断言(assert),EvalLog 是带完整堆栈的测试报告。
2. 顶层全景(它大概怎么转)
先建静态心智模型,再看动态流水。
图 A:三件套的静态结构。 @task 把这三样注册成一个 Task。
┌─────────────── Task(被 @task 注册)───────────────┐
│ │
Dataset ───► Solver 链 ───► Scorer
一批 Sample 求解步骤序列 拿最终状态
(input, target) (最后常是 generate()) 打出 Score
图 B:一条样本的一生(运行主线)。 怎么读:从上往下是一条样本被处理的顺序;每条箭头旁的编号是该阶段干的事。
eval(task)
│ ① 解析 task、建 Model、按 epochs 把数据集展开成 N×epochs 个 Sample,并发调度
▼
每个 Sample
│ ② Solver 链依次执行,读写同一个 TaskState;generate() 调 Model 产出 output
▼
TaskState(messages / output / store / scores)
│ ③ 每个 Scorer 拿"最终 TaskState + target"打分
▼
Score(按 scorer 名存进 state.scores)
│ ④ 同一 Sample 的多个 epoch 分数,用 reducer 归并成一条(默认取均值)
▼
归并后的样本分数
│ ⑤ Metric 跨样本聚合(accuracy、stderr…)
▼
EvalLog(结果指标 + 全程事件 Transcript, 可回放)
三个词记住整张图:Solver 出 output,Scorer 出 Score,聚合出 Metric,全部留痕在 EvalLog。
3. 部件一句话职责表(各在哪个子包)
src/inspect_ai/ 下每个子包对应上图的一块。想深挖某块时,照下表跳。
| 部件 | 一句话职责 | 所在子包 / 关键文件 |
|---|---|---|
| Dataset | 提供样本集合,每条是 Sample(input, target, …) | dataset/(_dataset.py) |
| Solver | 可组合的求解步骤,改写 TaskState;generate() 是最常用一步 | solver/(_solver.py) |
| TaskState | 一条样本求解中的可变状态:消息历史、模型 output、store、scores | solver/_task_state.py:139 |
| Scorer | 拿"最终 TaskState + Target"打出 Score | scorer/(_scorer.py) |
| Metric / Reducer | Score → 跨样本聚合指标;多 epoch 分数归并 | scorer/_metric.py、scorer/_reducer/ |
| Model | 统一模型接口,背后挂各家 Provider | model/(_model.py) |
| Tool | 模型可调用的函数(@tool) | tool/(_tool.py) |
| Agent | 多轮"想—调工具—再想"循环(如 react),可当 solver 或 tool | agent/(_react.py) |
| Log | EvalLog 结构 + 事件 Transcript 的读写 | log/(_log.py、_transcript.py) |
| Sandbox | 隔离执行环境,给工具/不可信代码用 | util/_sandbox/(environment.py:92) |
| Registry | 装饰器注册表:@task/@solver/@scorer/@tool 等靠它按名查找 | _util/registry.py |
4. 对外入口与主线(从哪进去读代码)
包的门面在 src/inspect_ai/__init__.py。 它把散在各 _ 私有子包里的东西汇成公开 API(__init__.py:5-24)。你在用户代码里 from inspect_ai import ... 拿到的,基本都在这份导出清单里。
四个最该认识的公开符号:
| 符号 | 作用 | 定义处 |
|---|---|---|
eval / eval_async | 评测总入口:编排一切,返回 EvalLog | _eval/eval.py:109、_eval/eval.py:392 |
eval_set | 批量/断点续跑一组 task | _eval/evalset.py:120 |
Task / @task | 三件套的容器 / 注册装饰器 | _eval/task/task.py:61、_eval/registry.py:98 |
get_model | 按 "provider/name" 字符串拿到统一 Model | model/_model.py:1653 |
主线一句话: @task(声明) → eval()(编排,_eval/eval.py:109) → task_run()(单个 task 的运行,_eval/task/run.py:324) → task_run_sample()(单条样本,_eval/task/run.py:1037)。样本函数里两处是全片的心脏:
state = await plan(state, generate)—— 跑 Solver 链(_eval/task/run.py:1333)。score_result = await scorer(state, Target(sample.target))—— 逐个 Scorer 打分(_eval/task/run.py:1529)。
打完分后 eval_results(...) 做 epoch 归并 + Metric 聚合(_eval/task/run.py:784,实现于 _eval/task/results.py:88)。
这条链的每一环,下面各章会各展开一节。
5. 阅读地图(建议顺序与选章指引)
本组共 7 章(含本章)。推荐顺序 = 编号顺序,正好顺着"图 B"从上游走到下游。
| 章 | 讲什么 | 什么时候读它 |
|---|---|---|
index.md(本章) | 是什么 / 全景 / 路由 | 先读,建立心智模型 |
01-eval-loop.md | 主循环:eval() → 一条样本打完分的完整调度 | 想搞懂"谁在什么时候调用谁" |
02-solver-taskstate.md | Solver 协议、链式组合、TaskState 数据模型 | 想写自定义求解步骤 / 理解状态如何流动 |
03-model-layer.md | Model/ModelAPI 抽象、Provider 注册、generate | 想接新模型 / 理解一次 generate 内部 |
04-tools-agents.md | @tool 定义、工具调用回环、react 等 Agent | 做工具使用 / 多轮智能体评测 |
05-scorer-metrics.md | Scorer/Score、epoch reducer、Metric 聚合 | 想自定义判分 / 理解指标怎么算出来 |
06-log-transcript-sandbox.md | EvalLog 结构、事件 Transcript、Sandbox、可观测性 | 想读日志 / 做复盘 / 跑沙箱工具 |
选章速查(带着问题来):
- "换个模型怎么就能跑?" →
03。 - "我的 solver 里改的东西下一步怎么看得到?" →
02(TaskState)。 - "同一题跑 5 次,最后那个数怎么来的?" →
05(reducer + metric)。 - "评测崩在半路,怎么查?" →
01(错误处理)+06(Transcript)。 - "让模型用工具、开子智能体?" →
04。
6. 巧妙之处速览(这套设计好在哪)
三个反复出现的设计模式, 是读全库前值得先记住的"母题"。后续各章会落到具体代码。
① 装饰器注册表(Registry)——按名字解耦。 @task/@solver/@scorer/@tool/@metric/@modelapi 都是同一套 registry 的马甲。装饰器把函数连同参数登记进全局表(_util/registry.py:78 的 registry_add),之后命令行的一个字符串("theory_of_mind"、"openai/gpt-4o")就能反查出实现并携带参数重建。好处:用户代码、命令行、日志三方都只靠名字打交道,实现可插拔。以 @task 为例,包装器还会顺手记下 task 的源文件路径与参数(_eval/registry.py:120-160)。
② 统一 Model 抽象——一套接口驱动约三十家。 ModelAPI(抽象基类,model/_model.py:186)定义"一次 generate 长什么样",各家 Provider 各实现一份;providers.py 里用 @modelapi 登记了 27 个入口(model/_providers/providers.py,如 openai/anthropic/google/vllm…)。外层再包一个 Model(model/_model.py:601)统一处理重试、缓存、用量统计。所以上层 Solver/Scorer 永远只面对同一个 generate(),不关心背后是哪家。
③ 事件化 Transcript——过程即数据。 样本运行时,每个关键动作(模型调用、工具调用、打分、报错…)都作为一个事件写进 Transcript(log/_transcript.py:380),例如打分那步会 _event(ScoreEvent(...))(_eval/task/run.py:1541)。评测因此天然可回放、可审计——inspect view 显示的就是这条事件流。这也是"评测框架"和"随手写 的脚本"的关键分水岭。
7. 边界与说明(本章不覆盖什么)
- 本章是导航层,只给心智模型与跳转表;每个部件的真实机制、数据结构、坑,都在对应编号章里展开,此处不重复。
- 引用锚点截至
sourceCommit: 64e0ff05。行号可能随上游漂移,优先用符号名 grep 定位(见下方代码地图)。 - 前端 Web UI(
_view/ts-mono子模块)与命令行细节不在本组范围内。
8. 代码地图(全局导航索引)
一张跳转表:想从哪块下钻,直接按"符号名"到克隆里 grep(比行号抗漂移)。
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 公开 API 门面 | src/inspect_ai/__init__.py | 导出清单(eval、Task、task、get_model…) |
| 评测总入口 | src/inspect_ai/_eval/eval.py | eval / eval_async |
| 批量续跑 | src/inspect_ai/_eval/evalset.py | eval_set |
| task 注册装饰器 | src/inspect_ai/_eval/registry.py | task / task_register |
| 三件套容器 | src/inspect_ai/_eval/task/task.py | Task / Epochs |
| 单个 task 运行 | src/inspect_ai/_eval/task/run.py | task_run |
| 单条样本运行 | src/inspect_ai/_eval/task/run.py | task_run_sample(内含 plan、scorer 调用) |
| 结果聚合 | src/inspect_ai/_eval/task/results.py | eval_results |
| Solver 协议 | src/inspect_ai/solver/_solver.py | Solver / Generate / generate |
| 样本状态 | src/inspect_ai/solver/_task_state.py | TaskState |
| Scorer 协议 | src/inspect_ai/scorer/_scorer.py | Scorer / scorer |
| 分数 / 指标 | src/inspect_ai/scorer/_metric.py | Score / Metric / metric |
| epoch 归并 | src/inspect_ai/scorer/_reducer/registry.py | create_reducers / reducer_log_name |
| 统一模型层 | src/inspect_ai/model/_model.py | Model / ModelAPI / get_model |
| 提供商注册 | src/inspect_ai/model/_providers/providers.py | @modelapi(27 个入口) |
| 工具定义 | src/inspect_ai/tool/_tool.py | Tool / tool |
| Agent 循环 | src/inspect_ai/agent/_react.py | react |
| 日志结构 | src/inspect_ai/log/_log.py | EvalLog / EvalSample / EvalResults |
| 事件 Transcript | src/inspect_ai/log/_transcript.py | Transcript / transcript |
| 沙箱 | src/inspect_ai/util/_sandbox/environment.py | SandboxEnvironment |
| 注册表内核 | src/inspect_ai/_util/registry.py | registry_add / registry_create |
下一步:读 01-eval-loop.md,把"图 B"的每条箭头落到真实调用栈上。