跳到主要内容
AI教程

使用 ToolReplay 审计 AI 代理工具调用

本教程将介绍如何安装 ToolReplay、准备严格的 JSONL 记录和范围文件、运行 replay、scope、seal 与 verify 审计、解读确定性报告、将检查集成到 CI,并了解项目的安全模型与局限性。

使用 ToolReplay 审计 AI 代理工具调用

ToolReplay 的功能

ToolReplay 无需调用真实工具,即可审计记录的 AI 代理工具调用。它会检查调用记录,识别三类问题:非确定性响应、冗余调用,以及超出代理声明权限范围的调用。它还可以将调用记录封存为 SHA-256 哈希链,并验证封存文件是否被编辑过。

由于 ToolReplay 重放记录的响应,而不是进行实时调用,因此审计过程安全、无副作用、可复现,即使原始工具不可用也能执行。

安装并运行 ToolReplay

ToolReplay 要求使用 Python 3.11 或更高版本,且没有第三方运行时依赖。它不会访问网络。

在源代码检出目录中,直接运行模块:

python -m toolreplay version

也可以安装控制台脚本:

pip install .
toolreplay version

两种方式都会输出已安装的版本,例如:

toolreplay 0.6.0

了解调用记录格式

调用记录是一个 JSON Lines 文件。每个非空行代表一次工具调用,并且必须恰好包含四个字段:

  • index:从 0 开始、每次严格递增 1 的整数。
  • tool:被调用工具的名称。
  • args:包含工具参数的对象。
  • response:包含记录工具响应的对象。

有效记录如下:

{"index": 4, "tool": "write_file", "args": {"path": "docs/intro.md", "text": "edited"}, "response": {"ok": true}}

解析过程有意设计得十分严格。未知字段、缺失字段、无效对象类型、非整数索引以及顺序错误的索引都会导致严重错误。即使 Python 将 bool 视为 int 的子类,也不接受布尔值作为整数。

ToolReplay 使用规范化 JSON 比较调用:工具名称和参数会按键排序编码,且不包含不必要的空白。因此,键顺序不同的参数对象仍表示相同的调用。ToolReplay 检查确定性时,响应也使用相同的规范化编码。

运行重放审计

replay 命令会根据调用记录中自身记录的响应检查调用记录。试试仓库中附带的脏数据样例:

python -m toolreplay replay samples/session-dirty.jsonl

该命令会生成确定性的逐行报告:

calls: 6
divergence: index 5
findings: 2
index 2: redundant-call: tool 'read_file' repeats the identical call at index 1 with no state change between them
index 5: non-determinism: tool 'search' returned a different response than the identical call at index 3

报告头部按从上到下的顺序读取。calls: 6 表示已解析出六条有效记录。divergence: index 5 标识确定性重运行结果首次与记录不一致的位置。findings: 2 表示后续列出的发现数量。

发现会先按索引排序,再按类型排序,因此对同一输入重复审计会产生逐字节相同的输出。

非确定性

ToolReplay 会记住每个规范化调用的首次响应。如果之后相同的调用出现不同的记录响应,就会报告非确定性。在样例中,针对 install 的 search 调用出现在索引 3 和 5,但响应报告的命中数不同。因此,索引 5 成为分歧点。

这对代理很重要,因为基于第一个答案做出的决策,可能会因返回第二个答案而改变。该发现表明,此会话依赖于记录期间并不稳定的信息。

冗余调用

只有当两次调用之间没有任何已记录事件可能改变状态时,重复调用才属于冗余。默认的状态修改工具包括 write_file、delete_file、create_file、move_file 和 run_command。相同调用之间出现任何不同调用时,也会采取保守处理,将其视为可能发生了状态变化。

在样例中,read_file 在索引 1 和 2 读取 docs/intro.md,期间没有状态变化,因此索引 2 被标记。若在 write_file 调用之后再次读取,则不会被视为冗余。

检查代理权限

如果已有代理的权限声明,请使用 scope:

python -m toolreplay scope samples/session-dirty.jsonl samples/scope.json

该样例会生成:

calls: 6
findings: 1
index 4: permission-overreach: tool 'write_file' is not in the declared scope for agent 'docs-reader'

重放和权限范围检查被有意设计为两个独立命令。重放会根据会话自身进行评估,只需要调用记录。权限范围检查则会将每次调用与单独的权限文件进行比较。

权限范围文件是一个 JSON 对象,其中包含代理名称和获准工具的精确列表:

{"agent": "docs-reader", "allowed_tools": ["read_file", "list_dir", "search"]}

工具名称必须完全匹配,并区分大小写。缺失字段、类型错误或非字符串条目都会被拒绝。权限范围检查基于名称,不会检查参数,也不会判断获准工具是否针对获准路径。

封存调用记录

seal 命令会将调用记录转换为哈希链式 JSONL 文件:

python -m toolreplay seal samples/session-dirty.jsonl > sealed.jsonl

每个封存链接都包含原始记录字段,以及 prev 和 digest。第一个链接使用 64 个十六进制零字符作为前一个摘要。之后的每个 SHA-256 摘要都包含前一个摘要和当前记录的规范字节,包括其索引、工具、参数和响应。

由于每个链接都依赖前一个链接,修改较早的记录会改变该记录的摘要以及之后的所有摘要。因此,该链会使编辑可见,并确定第一个断裂链接出现的位置。

验证封存文件

使用以下命令验证完整的链:

python -m toolreplay verify sealed.jsonl

未发生更改的文件会报告:

chain: intact

如果修改了记录的响应或任何其他链式字段,验证会报告第一个断裂链接,以及预期摘要和实际找到的摘要:

chain: broken
first broken link: index 3
expected: 34d19a9dfb112baa07afa37994ddcbbebe2973674385f3942340653d08d205bd
found: 40aadf3b8156217f0b5b1d95010b6b79f82a26ccf5e780aed0f5f311f865028a

封存可以检测封存后的编辑,包括对顺序、索引、工具、参数和响应的更改。它不是数字签名:任何能够编辑文件的人也能重新封存文件。要证明来源,需要使用由 ToolReplay 之外管理的密钥对最终摘要进行签名。

在 CI 中使用 ToolReplay

ToolReplay 的退出代码使其适合用作构建门禁:

  • 0:审计通过或链完整。
  • 1:存在发现项或链已断裂。
  • 2:发生使用错误,例如输入格式错误或文件缺失。

直接在 CI 作业中运行重放和范围检查:

python -m toolreplay replay session.jsonl
python -m toolreplay scope session.jsonl scope.json

退出代码 1 表示工具已完成审计并发现了需要报告的问题。退出代码 2 表示审计无法运行,因此自动化流程可以将格式错误的输入与实际发现项区别处理。

如需进行可复现的比较,请封存每次运行并比较结果:

python -m toolreplay seal run-a.jsonl > run-a.sealed.jsonl
python -m toolreplay seal run-b.jsonl > run-b.sealed.jsonl
git --no-pager diff --no-index run-a.sealed.jsonl run-b.sealed.jsonl

由于报告不包含时间戳或随机值,相同的输入会产生相同的输出。第一个不同的封存行会指出两次运行开始不一致的位置,而摘要的变化表明后续链接依赖于该差异。

高级用法和实际限制

  • 保持记录便于追加。 JSON Lines 允许生产者为每次调用追加一条记录,也便于在版本控制中审查更改。
  • 保留已记录的响应。 重放不会调用真实工具;它只能检测记录中体现的问题。
  • 谨慎解读冗余。 默认的变更工具列表是固定的,不在该列表中的自定义变更工具会在冗余分析中被视为只读工具。
  • 记住比较边界。 只有在相同的规范调用重复出现时,系统才会检测非确定性。对于语义相关但调用不同的响应,不会评估其等价性。
  • 区分权限与目标。 范围检查只检查工具名称,无法发现已获许可的工具被用于禁止资源的情况。
  • 使用封存保证完整性,而非身份。 链可以显示文件在封存后是否发生变化,但无法证明是谁创建了该链。

运行测试套件

项目使用 Python 标准库中的 unittest 框架,不依赖第三方测试包。在仓库根目录运行:

python -m unittest discover -s tests

测试涵盖严格的记录解析、规范编码、哈希链创建和篡改检测、重放发现项、范围强制执行、CLI 行为以及退出代码。

结论

ToolReplay 为 AI 代理会话提供了专注的审计流程。先使用 replay 查找不一致或浪费资源的行为,再使用 scope 检查声明的工具权限,并使用 seal 加 verify 检测记录后的更改。严格的输入验证和确定性的报告使其适用于调试、审查、版本控制和 CI 强制检查。