数据截至 (上游 commit d8fc8fcbde74)
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:140 |
| 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:108) |
| 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:118、_eval/eval.py:408 |
eval_set | 批量/断点续跑一组 task | _eval/evalset.py:134 |
Task / @task | 三件套的容器 / 注册装饰器 | _eval/task/task.py:76、_eval/registry.py:118 |
get_model | 按 "provider/name" 字符串拿到统一 Model | model/_model.py:1928 |
主线一句话: @task(声明) → eval()(编排,_eval/eval.py:118) → task_run()(单个 task 的运行,_eval/task/run.py:464) → task_run_sample()(单条样本,_eval/task/run.py:1623)。样本函数里两处是全片的心脏:
state = await plan(state, generate)—— 跑 Solver 链(_eval/task/run.py:2023)。score_result = await scorer(state, Target(sample.target))—— 逐个 Scorer 打分(_eval/task/run.py:2249)。
打完分后 eval_results(...) 做 epoch 归并 + Metric 聚合(_eval/task/run.py:1355,实现于 _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。