Guide · 使用教程
Inspect AI Python 教程:Agent 工具调用轨迹、评分与执行限额
使用固定版本与官方模拟模型,验证工具名、参数、返回关联和最终答案,设置执行限额并解释评分与费用边界。
一个 Agent 回答“订单已送达”,并不能证明它查询了正确订单。本教程用 Inspect AI 建立只读订单 fixture,把工具名、参数、结果和最终答案一起评分,再给执行设置限额。示例使用固定版本和官方 mock 模型,先确认评估器会区分正确与错误轨迹。
验证日期为 2026-10-17,固定 inspect-ai==0.3.281。本地通过八组规则测试和五条真实框架内的模拟轨迹;没有调用付费模型、真实订单服务或写接口。模拟模型输出由代码预设,因此这些结果验证评估管线,不证明真实 Agent 的能力。
1. 明确每个样本的通过条件
本例要求只调用一次 lookup_order,订单号必须是 1001;工具返回必须与调用 ID 关联,且没有执行错误;最终答案必须是与结果一致的严格 JSON。下面这种“最后一句正确、过程没查询”的情况应判失败。
| 轨迹 | 应有结果 |
|---|---|
| 查询 1001 成功后返回对应 JSON | 通过 |
| 未查询,直接猜中 delivered | 失败 |
| 查询 1002,却回答 1001 已送达 | 失败 |
| 工具返回错误后仍肯定回答 | 失败 |
| 额外查询其他订单 | 失败 |
这里只测试一条允许路径。真实业务允许重试、多条有效顺序或缓存时,应显式改写规则和 reference,再验证判分器。不能把“只调用一次”推广成所有 Agent 的标准答案。
2. 安装固定版本
在新的项目目录运行,保留两个 Python 文件与日志目录。示例不需要 API Key。
python3 -m venv .venv
.venv/bin/pip install 'inspect-ai==0.3.281'
Inspect Task 组合数据集、执行与 scorer;Python 工具通过 @tool 注册,再由 use_tools 和 generate 参与模型循环。工具参数类型与说明用于生成工具契约。官方工具文档
3. 实现工具、轨迹检查与 scorer
保存为 tool_eval.py。两个订单状态均为合成 fixture,1003 专门模拟工具故障。没有生产身份、账户授权或订单读取能力;正式接入仍应由服务端落实身份和对象权限。
"""Tested with inspect-ai==0.3.281. Uses only local synthetic order data."""
import json
from inspect_ai import Task, task
from inspect_ai.dataset import Sample
from inspect_ai.model import ChatMessageAssistant, ChatMessageTool
from inspect_ai.scorer import CORRECT, INCORRECT, Score, accuracy, scorer
from inspect_ai.solver import TaskState, generate, use_tools
from inspect_ai.tool import ToolError, tool
@tool
def lookup_order():
async def execute(order_id: str) -> str:
"""Read the status of a synthetic order.
Args:
order_id: Synthetic order ID to query.
"""
if order_id == "1003":
raise ToolError("Synthetic service unavailable")
orders = {"1001": "delivered", "1002": "cancelled"}
if order_id not in orders:
raise ToolError("Synthetic order not found")
return json.dumps({"order_id": order_id, "status": orders[order_id]})
return execute
def judge_trace(messages, completion, order_id, status):
calls = [c for m in messages if isinstance(m, ChatMessageAssistant) for c in (m.tool_calls or [])]
results = [m for m in messages if isinstance(m, ChatMessageTool)]
expected = {"order_id": order_id, "status": status}
flags = {"exact_call": False, "successful_result": False, "exact_answer": False}
if len(calls) == 1:
call = calls[0]
flags["exact_call"] = (call.function == "lookup_order" and call.arguments == {"order_id": order_id} and not call.parse_error)
if len(results) == 1:
result = results[0]
try:
flags["successful_result"] = (result.tool_call_id == call.id and result.function == "lookup_order" and result.error is None and json.loads(result.text) == expected)
except (TypeError, ValueError):
pass
try:
flags["exact_answer"] = json.loads(completion) == expected
except (TypeError, ValueError):
pass
return flags
@scorer(metrics=[accuracy()])
def trajectory_check():
async def score(state: TaskState, target):
flags = judge_trace(state.messages, state.output.completion, state.metadata["order_id"], target.text)
return Score(value=CORRECT if all(flags.values()) else INCORRECT,
answer=state.output.completion, metadata=flags,
explanation=json.dumps(flags))
return score
@task
def order_eval():
return Task(
dataset=[Sample(id="order-1001", input='Query synthetic order 1001 with lookup_order. Reply with only JSON containing order_id and status.', target="delivered", metadata={"order_id": "1001"})],
solver=[use_tools(lookup_order()), generate()],
scorer=trajectory_check(),
message_limit=12, turn_limit=3, token_limit=2000, time_limit=30,
)
判分同时看调用和返回,并要求 tool_call_id 一致,避免把另一调用的结果拿来支持答案。最终 JSON 用完整对象比较,多余字段、错误状态和不可解析文本都会失败。它是本任务的确定性 scorer,不是通用语义或安全判断。
Score 记录 verdict、答案、解释和三项 flags,便于在日志中定位是参数、工具结果还是答案失败。评分规则与 reference 也可能写错,因此要用已知正反例测试评分器。自定义 scorer 文档
4. 用官方 mock 模型跑通完整工具循环
保存为 run_mock.py。它预设一次工具请求和最终回答,但执行仍经过 Inspect 的工具调用与 scorer,不是直接把预期分数写进报告。
import json
from pathlib import Path
from inspect_ai import eval
from inspect_ai.model import ChatCompletionChoice, ChatMessageAssistant, ModelOutput, get_model
from inspect_ai.tool import ToolCall
from tool_eval import order_eval
def call_output(calls):
return ModelOutput(model="mockllm", choices=[ChatCompletionChoice(
message=ChatMessageAssistant(content="", tool_calls=calls), stop_reason="tool_calls")])
def run_case(name, outputs, log_dir):
model = get_model("mockllm/model", memoize=False, custom_outputs=outputs)
log = eval(order_eval(), model=model, display="none", log_dir=str(log_dir),
max_samples=1, retry_on_error=0)[0]
if log.status != "success" or len(log.samples or []) != 1 or log.samples[0].error:
raise AssertionError(f"{name}: evaluation execution failed")
sample = log.samples[0]
score = sample.scores["trajectory_check"]
return {"case": name, "score": score.value, "flags": score.metadata,
"log_file": log.location}
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--log-dir", default="./inspect-fixture-logs")
args = parser.parse_args()
report = run_case("correct", [
call_output([ToolCall(id="lookup-1", function="lookup_order", arguments={"order_id":"1001"})]),
ModelOutput.from_content("mockllm", '{"order_id":"1001","status":"delivered"}')
], Path(args.log_dir))
assert report["score"] == "C", report
print(json.dumps(report, ensure_ascii=False, indent=2))
运行命令:
.venv/bin/python run_mock.py --log-dir ./inspect-fixture-logs
.venv/bin/inspect view --log-dir ./inspect-fixture-logs
预期报告的 score 是 C,三个 flags 都为 true。Inspect View 中应能看到工具请求、合成返回和最终回答。分享日志前先检查内容;真实模型、工具或 API 错误不能只用一个不完整的日志文件代表成功。日志与查看说明
5. 限额和预算怎样设置
本任务配置 message_limit=12、turn_limit=3、token_limit=2000、time_limit=30,runner 限定一个并发样本并关闭样本级错误重跑。这些限制作用范围不同;并不保证模型供应商或任意外部工具的账单被硬性封顶。超时与限额终止都要保留日志、部分输出及终止原因。
mock 返回的 usage 是测试用途,不应当作真实模型账单。切换真实模型时,先配置被测和 grader 模型的当前价格数据,再使用 cost_limit。当前 Inspect 文档要求所有相关模型有 cost 数据,缺失会报错;费用检查发生在 generate 调用处,仍应考虑在途请求、并发和外部工具费用。官方限额与模型价格配置
建议先限制样本总数、重复次数与并发,再设置供应商账户预算;从一两个公开或合成样本验收 usage 和费用,再扩大回归。不要直接把 mock 模型替换为付费模型后,就让全量样本无限重试。
6. 验证评估器,而不只验证正例
本地内容包提供 test_tool_eval.py。八组规则测试覆盖正确轨迹、猜中但未调用、错参数、额外调用、工具错误、错配返回、缺失或重复返回,以及错误答案或格式。另有五条官方框架内模拟轨迹,对照上表分别产生 C / I verdict,并保留 .eval 日志。
.venv/bin/python -m unittest discover -s aimazing/assets/inspect-ai -p 'test_tool_eval.py' -v
上面的路径用于在本项目根目录复跑随包测试;单独复制两个教程文件时,使用前面的 run_mock 命令即可。受限执行环境中,Inspect 可能还需要写自身运行缓存;应给测试进程合适的工作目录或缓存权限,不要为此更改系统凭据或业务服务权限。
7. 错误、未评分与错误答案分开记录
被测模型给出错误答案,应有明确失败 verdict。scorer 自身出错或无法判断则是评估器问题,不能假装这条样本通过,也不能不说明就从统计中删除。当前 Inspect 的评分错误、unscored 和普通错误答案对指标分母的影响不同,因此同时报告计划样本数、成功运行数、评分覆盖、错误数与终止原因。评分政策
本例把工具失败后仍肯定回答视为失败。若任务要求正确处理服务不可用,应增加相应目标与允许回答,并单独验收;不要沿用本例的 delivered reference。真实 Agent 的多路径、并行调用和租户权限也不在这组 fixture 的证明范围内。
把真实失败去除敏感信息后加入数据集,固定样本、工具契约、模型和 scorer 版本,才能在升级前后解释变化。选择框架参见 Inspect AI 工具页、Agent 评估框架推荐、与 DeepEval 的对比 及 与 Braintrust 的对比。
常见问题
- 教程会调用付费模型或真实订单吗?
- 不会。示例固定官方 mock 模型和合成只读 fixture,验证评估管线,不证明真实 Agent 的能力。
- 为什么答案正确还可能判失败?
- 如果没有查询、查询错对象、工具失败或有额外动作,就不满足该任务要求,不能只看最终文本。
- 这个 scorer 能直接用于所有 Agent 吗?
- 不能。它只允许一条只读查询路径;真实重试、多路径、缓存和权限规则需另行定义并验证。
- 消息、轮次、token 和时间限制是账单硬上限吗?
- 不是。模型、并发、在途请求和外部工具费用需分别控制,真实 cost_limit 也需要完整模型价格配置。
- 八组规则测试和五条轨迹分别验证什么?
- 规则测试验证正反例判分,五条官方框架模拟轨迹验证工具循环与 scorer 接入。未作真实模型能力或生产安全基准。
- 评分错误和错误答案能一起算吗?
- 应区分。错误答案是被测行为 verdict;scorer 故障与无法评分属于测量问题,需同时报告错误、覆盖和分母。