模型决定"要不要用",工具决定"用得好不好"。
2026年,Agent能力的天花板基本等于它的工具集。同样的模型,工具设计得好能干一整天活,设计得差就只会在两个坑之间反复跳:要么选错工具,要么以为成功了。
工具调用(Tool Calling / Function Calling)看起来是"注册个函数"这么简单,实际生产环境里绝大多数翻车都出在四个地方:schema 写得模型看不懂、失败不分类就重试、工具自报成功、以及把能力当成了权限。
本文按「分域 → 描述 → 失败分类 → 假成功 → 权限 → 结果处理 → 并行 → 观测」的顺序讲清楚。
别按业务模块分工具,按副作用等级分。这是最省事的权限治理方式。
| 域 | 典型工具 | 副作用 | 默认策略 |
|---|---|---|---|
| 读 | 搜索、抓取、读文件、查库 | 无 | 默认开放,可并行、可重试 |
| 算 | 计算、转换、裁剪、渲染 | 无(纯函数) | 默认开放,随便重试 |
| 写 | 发文、改文件、下单、入库 | 有,可回滚 | 显式注册 + 幂等键 |
| 通信 | 发消息、发邮件、调 webhook | 有,对外可见且不可撤回 | 最严:白名单 + 审批 |
纯函数工具(算域)是最便宜的能力:无副作用就意味着可以无限重试、可以并行、出错也不留垃圾。能让模型"算"就别让它"写"。
写域和通信域必须区别对待:写错了能删,话发出去了收不回来。凡是发出去就收不回的工具,权限级别要单独提一档。
模型看不到你的函数实现,也看不到宿主语言里的默认参数。它看到的只有 工具名 + 参数名 + 类型 + description。这四样写不好,再强的模型也会瞎调。
| 差 | 好 | 原因 |
|---|---|---|
q: string |
search_keyword: string |
参数名即提示,缩写要靠猜 |
format: string |
format: enum[png, jpg, webp] |
枚举把无效值挡在模型侧,省一轮重试 |
t: int |
time_range_days: int, default 7 |
单位、含义、默认值都要显式 |
| 只写"搜索网页" | "搜索公开网页;不要用于查本地文件或需要登录的私有页面" | 负例比正例更能降低误调用 |
data: object |
逐字段列出子字段与必填项 | 嵌套对象是最常见的调用失败源 |
三条经验:
default 7 写进去,模型就不会每次瞎猜一个数字。publish_article 优于 do_action。同名前缀(xhs_、tt_)能让模型分清同领域的多个工具。常驻工具超过三四十个后,选择准确率明显下降,而且每次请求都要重发全部 schema——这就是一笔常驻上下文税。
做法是三跳:先给一个"工具检索"入口,按需拉到工具描述,再调用。
1) tool_search("发布文章到头条") → 返回候选工具名
2) tool_describe("toutiao_publish") → 返回该工具的完整参数
3) tool_call("toutiao_publish", {...}) → 真正执行
这样上下文里常驻的只有三个入口,而不是几百个 schema。能力可以很多,同时暴露的行为要很少。
把错误分成六类,每一类对应一个动作。"出错就重试"是最贵的反模式。
| 错误类型 | 典型信号 | 正确动作 |
|---|---|---|
| 参数/校验 | 400、schema invalid | 改参数,不重试 |
| 目标不存在 | 404、空结果 | 换目标或换策略,不重试 |
| 限流/瞬时 | 429、503、无 Retry-After 的 5xx | 退避重试,最多 2 次 |
| 权限/认证 | 401 / 403 / 登录页 | 停止,报人工;重试只会锁号 |
| 部分成功 | 批量返回逐条状态 | 只重试失败条目 |
| 结果未知 | 超时、连接重置 | 先查上次到底成没成,再决定 |
第五条和第六条是重点:
false 会让 Agent 把成功的 18 条再做一遍。要返回逐条状态。规则一:同一原因失败两次,停下来想,不要第三次重试。
第三次重试常常把一个 5 分钟能修的问题拖成 40 分钟的故障——尤其是那些越试越糟的路径(重试登录失败被风控、重试提交产生重复订单)。重试前先分类:这是"上下文错"还是"瞬时错"?只有后者值得退避重试。
规则二:写工具必须带幂等键。
# 幂等键 = 任务ID + 内容指纹,同一逻辑操作重试不产生第二条
key = f"{task_id}:{sha1(title + body)[:12]}"
if store.seen(key):
return store.get(key) # 直接返回上次的产物引用
没有幂等键的写工具,等于给 Agent 装了一个"重复扣款"按钮。
实测中最常见的坑:工具返回了 success: true,但事情根本没发生。
三个来源:
code != 0;或者站点在风控挑战下对所有路径都返回同一页 200 骨架 HTML——此时的 200 没有任何信息量。写工具的返回值里,必须有一个可寻址的产物引用:URL、内容 ID、文件路径 + 修改时间戳。拿不到这个引用的写工具,设计就是不合格的。
# 不好的返回
return {"success": True}
# 好的返回
return {
"accepted": True, # 已受理
"done": True, # 已回读验证落地
"artifact_url": "...", # 可寻址产物
"artifact_id": "7682...",
"verified_at": "2026-09-16T09:44:12+08:00",
}
把"受理"和"完成"拆成两个字段,调用方才有机会做判断。一个布尔永远表达不了这三件事:提交成功了吗、执行成功了吗、产物在哪。
判据要外置:让"动作之外的证据"说话——列表页里的新条目、数据库里的新行、下游系统的读取痕迹。工具自报的成功率是自述,不是测量。
对模型来说,能力和权限是同一件事:你没注册删除工具,它就删不了;你注册了,它就一定会有一天用上——不是在恶意场景,而是在"为了完成任务"的场景。
所以权限治理不写在提示词里,写在工具注册表里:
| 档位 | 手段 | 适用 |
|---|---|---|
| 只读默认 | 读/算域工具常态开放 | 检索、计算、转换 |
| 写需显式开 | 启动参数/白名单控制写工具注册 | 发文、改文件、入库 |
| 不可逆需审批 | 工具内强制外部确认,人当断路器 | 删除、支付、对公众发布 |
| 凭据不进上下文 | 密钥由工具内部持有,模型只看到工具名 | 所有涉及密钥的场景 |
另外三条工程纪律:
一个抓取工具返回十万字正文,就吃掉了这一轮几乎全部预算,后面所有推理都在挤牙膏。
原则很朴素:
{"saved_to": "…/page.md", "bytes": 102431, "preview": "前 40 行…"}。"truncated": true, "total": 102431。静默截断会让模型理直气壮地用半份数据下结论。一句话:上下文是工作台,不是仓库。仓库在外面的文件系统里。
每次工具调用至少记六样:工具名、参数、开始时间、耗时、状态、产物引用。有了这六样,任何一次失败都能回放,而不是靠猜。
值得盯的五个指标:
| 指标 | 说明什么 |
|---|---|
| 分工具调用成功率 | 哪个工具在拖后腿 |
| 参数校验失败率 | 工具描述写得好不好(高 = 描述该改) |
| 同因重试率 | 重试纪律有没有破 |
| 重试深度分布 | 长尾是否在放大故障 |
| 写工具回读失败率 | 假成功有没有被抓住 |
改动要跑基线。 换工具实现、换模型、换提示词之后,用同一批任务重放,比较产物,而不是比较"感觉变聪明了"。工具改动的收益必须能被外部复算,否则你不知道自己是改好了还是只是换了种错法。
工具调用的水平,不体现在"能不能调起来",而体现在出错的时候你能不能立刻说清是哪一层坏的,以及成功的时候你能不能证明它真的成了。这两条做到了,Agent 才从演示进到生产。
相关阅读
📚 更多创作资源: https://afdian.com/a/felix007