A·Agent Guide 从经典智能体到前沿工程 ← 返回主博客
AGENT GUIDE / READING NOTES

第 4 章 工具设计与执行边界

设计工具契约,处理参数、资源范围、版本冲突与副作用

4.1 先设计可用接口,再要求模型选对工具

本章目标:能为一个工具写完整契约,区分参数格式与权限校验,处理超时、重试和不确定的副作用。

给运费 Agent 一个叫 do_everything(text) 的工具,模型可能把“读文件、改代码、运行测试、提交”全部写进字符串。运行时很难知道它究竟准备做什么,也难以单独限制写入或识别失败位置。

拆成 read_file(path)、replace_file(path,content)、run_tests(),每种动作的输入、产物和副作用都清楚。教学实验将测试命令固定在工具内部,不接收模型生成的任意 shell。这样可以检查代码而不给策略无限制命令接口;真实开发工具可能需要更广的能力,但也应明确执行范围。

4.2 一份工具契约要包含什么

契约项 read_file 的例子 缺失后会发生什么
用途与不适用范围 读取允许的项目文件,不读取任意宿主路径 模型误选工具或越界
输入 非空相对路径、可选行范围 参数含糊或输出过大
输出 路径、版本、内容、是否截断 无法引用或判断完整性
错误 不存在、拒绝、编码失败 所有错误被误当“空文件”
副作用 无写入;可能计费或留访问记录 重试安全性判断错误
资源限制 最大字节数、超时 占满上下文或长时间等待

工具描述也应该回答“何时该用它”。两个工具都叫“搜索”,一个找文件名,一个检索全文,模型容易混淆。更清楚的用途与返回示例可以减少误选,但真正执行时仍要由程序检查。

4.3 Schema 只通过了第一道门

工具名、参数形状、资源语义与授权四层校验

{
  "name": "read_file",
  "description": "读取工作区内获准文件,返回内容和版本;不写文件",
  "parameters": {
    "type": "object",
    "properties": {"path": {"type": "string", "minLength": 1}},
    "required": ["path"],
    "additionalProperties": false
  }
}

{"path":"../secret.txt"} 完全可能满足这个 schema,但超出了工作区。可靠校验通常有四层:工具名是否允许、参数形状是否正确、业务语义是否合理、当前身份是否有权操作该资源。写文件还要检查容量、目标类型和预期版本。

# 教学片段:resolve 处理 .. 与已有符号链接,再检查目录包含关系。
from pathlib import Path

def resolve_in_workspace(root, raw_path):
    if not isinstance(raw_path, str) or not raw_path:
        raise ValueError("invalid path")
    root = Path(root).resolve()
    target = (root / raw_path).resolve()
    if not target.is_relative_to(root):
        raise PermissionError("outside workspace")
    return target

这个片段说明路径检查的思路,并不是生产沙箱:并发的符号链接替换仍可能引入检查与使用之间的竞态。实际隔离还要结合 OS 能力和文件打开方式。repair_lab.py 在受控临时目录中采用更窄的白名单,只允许 pricing.py;它同样没有降低 Python 进程的宿主权限。

4.4 输出格式决定下一步能否正确判断

不要把所有结果都压成一个字符串。至少区分执行状态、业务结果、来源版本与完整性。

{
  "status": "ok",
  "path": "pricing.py",
  "source_hash": "sha256:...",
  "content": "def shipping_fee(...): ...",
  "truncated": false
}

若日志过长,应返回开头/关键错误/结尾的结构化片段,并告诉策略还有多少内容、怎样获取完整结果。静默截断可能丢掉真正的失败原因。若工具报 NOT_FOUND,不应伪装成空内容;若测试命令退出 1,应提供失败检查而不只写“命令异常”。

错误信息要能支持决策:PERMISSION_DENIED 通常需要调整范围或交给用户,不应反复同参重试;临时网络 TIMEOUT 可以在有限预算内尝试恢复;格式错误应修参数。错误细分不是为了枚举漂亮的状态码,而是为了定义不同下一步。

4.5 超时的副作用为什么难处理

只读查询超时,通常可以再查;发送邮件超时,可能邮件已送出但响应丢失。盲目重试会重复发送。“客户端没有收到成功”并不等于“服务端没有执行”。即使是读接口,也可能计费或触发其他行为,因此重试依据应来自具体契约。

对有副作用操作可定义状态 prepared → dispatched → confirmed。若响应丢失,记录为 unknown,先查询操作状态,不能直接当作 failed 再发一次。这里的状态名是应用设计示例。

幂等键使同一个逻辑操作的重复请求能被服务识别;前提是服务端真的支持,并规定键的作用范围和有效期。文件修改可携带 expected_hash,只在目标版本仍一致时应用,避免覆盖并行更新。多工具中的幂等性、版本控制和事务不能互相替代。

4.6 并行工具:先画依赖,再节省时间

读取两个独立文件可以并行;先写补丁再测代码有依赖,不能同时开始。若并行测试与修改同一文件,结果可能对应混合状态,即使输出“通过”也不可靠。

日志应记录调用 ID、开始/结束、参数摘要、结果版本、字节数、错误码和重试序号。要比较并行收益,记录的是墙钟时间,不是把每个工具耗时简单相加;资源竞争和排队仍会影响实际速度。

4.7 动手练习与参考检查

运行 python3 agent/examples/repair_lab.py --scenario invalid,观察越界请求在哪里被拒绝。再在脚本副本中直接调用 Tools.execute,分别传入未知工具、缺少 path、多余字段、非字符串内容、超大内容。每个请求都应得到明确错误,不应修改文件。

增加一个带 expected_hash 的写入工具:先读取哈希;模拟另一执行者修改文件;再提交基于旧哈希的内容。参考验收是返回版本冲突并保留新文件。这个练习比仅增加一个 schema 字段更完整,因为它检验了冲突时的实际行为。

本章交付物:一份工具契约、一个错误到下一步的映射表、一次版本冲突实验。能说明副作用在超时后如何确认,才具备可靠工具集成的基础。


Agent Guide · 从反馈闭环走向可靠系统基于 Markdown 构建