数据截至 (上游 commit d8fc8fcbde74)
日志、事件 Transcript、沙箱与可观测性
30 秒导读: 一次评测跑完,你想要三样东西:一份能永久归档、能重新读回的结构化结果;一条能逐步回放"模型说了什么、工具做了什么"的过程录像;以及一个隔离的执行环境,让不可信的 agent 代码不会祸害你的机器。本章讲 Inspect 怎么把这 三件事拧成一条线——所有过程都被拆成一个个事件(Event),同一条事件流同时喂给内存里的
Transcript、一个 SQLite 实时缓冲库(给 live view 做回放)、和最终的.evalZIP 文件(落盘归档);而沙箱里每一次exec/read_file/write_file也被一层 Proxy 顺手变成事件,汇进同一条流。
本章聚焦横切线:结果如何落盘、过程如何回放、工具在哪隔离。各机制自身的逻辑(主循环、Solver、模型层、工具、打分)见同组其它章,这里只讲它们留下的痕迹去了哪、怎么被读回。
1. 这是什么(零基础也能懂)
先建立三个心智模型,后面全靠它们。
痕迹分两层:结果 vs 过程。
- 结果层(log) = 一次评测的"体检报告":跑了什么任务、用了什么模型、每条样本输入输出是什么、得了几分、花了多少 token。这是结构化的、要归档的、几个月后还要读回来对比的。
- 过程层(transcript) = 一次评测的"行车记录仪":第 1 步模型看到这些消息、第 2 步它决定调这个工具、第 3 步工具在沙箱里跑了条命令、第 4 步打分器给了分……一步一步、带时间戳。
过程层的原子单位是"事件(Event)"。 模型调用是一个 ModelEvent,工具调用是一个 ToolEvent,沙箱执行是一个 SandboxEvent,打分是一个 ScoreEvent……几十种事件类型,共同一个基类。整个 transcript 就是一个只往后追加(append-only)的事件列表。
隔离层(sandbox) = 一个"手套箱":agent 想执行代码、读写文件,都必须通过一个 SandboxEnvironment 抽象接口,底下可能是本地临时目录,也可能是 Docker 容器。手套箱在这里的意义有两层——安全隔离(别让 agent 删你的家目录),以及可观测(每次伸手进箱子都被记一笔事件)。
一句话直觉: 把 transcript 当成一条流水账,每笔账是一个事件;log 是这条账结账后的报表;sandbox 是账里"动手脚"那些笔发生的隔离车间,车间门口装了摄像头(Proxy),进出都记账。
用起来什么样。 跑完一个 eval 你会得到一个 logs/xxx.eval 文件(其实是个 ZIP)。读回来:
from inspect_ai.log import read_eval_log
log = read_eval_log("logs/2025-01-01T12-00-00_task_abc.eval")
print(log.status) # "success"
print(log.results.scores[0].metrics) # {"accuracy": ...}
for event in log.samples[0].events: # 逐事件回放第一条样本
print(event.event, event.timestamp) # "sample_init" / "model" / "tool" / ...
Inspect View(inspect view)就是把 log.samples[i].events 这条事件流渲染成可点开的时间线;而当 eval 正在跑时,它读的不是这个还没写完的 ZIP,而是旁边那个实时 SQLite 缓冲库。