工具设计原则
粒度、命名、面向任务而非接口、返回值精简、错误信息可行动。
一句话理解:工具要按“Agent 要完成的任务”来设计,而不是把后端接口逐个翻译过去;好工具粒度合适、名字清楚、返回精简,出错时还会告诉模型下一步怎么办。
最省事的做法,是把现有的 50 个 REST 接口一对一包装成 50 个工具。结果通常很糟:模型要连调五六个接口才能拼出一个答案,上下文被大段 JSON 塞满,工具之间职责重叠导致频繁选错。Anthropic 在工程博客里总结过一句话:工具不是越多越好。
工具是 Agent 和业务系统之间的接口层。它的使用者不是前端工程师,而是一个上下文有限、按 Token 付费、会被噪音干扰的模型。设计时要围绕这个使用者来考虑。
原则一:面向任务,而不是面向接口
传统 API 为程序设计:原子化、可组合、字段齐全。程序调用十次接口几乎不花成本;Agent 每多调一次,就多一轮推理、多一段上下文,也多一次出错的机会。
以 SEO 诊断 Agent 为例:
| 接口直译式 | 面向任务式 |
|---|---|
list_pages() 返回全站 3000 个页面 |
search_pages(query, issue_type) 按问题类型筛选页面 |
get_page(url)、get_meta(url)、get_links(url) 三个接口 |
audit_page(url) 一次返回标题、描述、H1、状态码、内链数与问题列表 |
get_rankings(keyword, date) 逐天查询 |
get_ranking_change(keyword, from, to) 直接返回变化与拐点 |
以诊断一个页面为例,两种设计在调用过程上的差别一目了然:
Anthropic 的例子也类似:与其提供 list_users、list_events、create_event,不如提供一个 schedule_event,内部完成查空闲和创建日程;与其让模型 list_contacts 再自己翻找,不如直接给 search_contacts。
判断标准是:人类专家完成这一步时,脑子里的动作是什么? 电商客服排查售后时想的是“看这单卡在哪一步”,而不是“先调订单接口,再调支付接口,再调发货接口”。另外,OpenAI 的指南建议把总是连续调用的函数合并成一个,已知的参数不要让模型填。
当然也别合并过头。如果一个工具需要 action 参数在查询、修改、删除之间切换,读写就混在一起了,权限和审批很难区分对待。读和写至少要分开,这一点在 安全的工具设计 里展开。
原则二:粒度适中,数量克制
工具数量直接影响选择准确率和成本。OpenAI 的指南给过一个“软建议”:一轮开始时可用的函数最好少于 20 个;工具更多时,可以用工具搜索(tool search)等机制按需加载,而不是全部塞进上下文。Claude 同样提供工具搜索和 defer_loading,思路见 渐进式披露。
粒度可以用三个问题来校准:
- 这个工具单独调用有意义吗? 只返回一个 ID、必须再查一次才有用的工具,通常可以合并。
- 两个工具的触发场景能说清楚区别吗? 说不清就合并,或者在描述里互相指路。
- 它的风险等级一致吗? 同一个工具里既有只读又有写入,就应该拆开。
原则三:命名让选择变简单
命名规则在 工具定义三要素 里讲过,这里补充设计层面的两点:
- 按业务域加命名空间:
orders_get_order、payments_get_payment、tickets_search_tickets。接入的系统越多,前缀越重要。 - 名字体现粒度与副作用:
search_*返回列表,get_*返回单个对象,create_*、update_*有副作用,preview_*只预演不执行。
原则四:返回值精简、高信号
工具返回的每个字符都会进入上下文,占用 Token,也分散模型的注意力(见 上下文腐化)。常见做法:
- 只返回下一步推理需要的字段。 订单系统返回 80 个字段,工具只挑出状态、卡点、异常原因等 8 个。
- 用有语义的标识。 Claude 的文档建议返回有意义且稳定的标识(如 slug),而不是模型看不懂的内部引用。能返回“杭州主仓”,就不要只返回
warehouse_id: 33010612。 - 提供详略开关。 Anthropic 的做法是加一个
response_format参数,让模型按需选择简洁或详细;在他们的例子里,简洁版只用了约三分之一的 Token。 - 分页、过滤与截断要有合理的默认值。 列表默认返回前 20 条,并告诉模型“共 356 条,可用
page翻页或用status过滤”。
换个角度看,工具层就是模型与业务系统之间的一道过滤器:
对比一下同一次查询的两种返回:
{
"order_id": "ORD2026090142",
"status": "pending_shipment",
"blocker": { "code": "PAYMENT_PENDING", "meaning": "支付尚未到账,仓库不会发货" },
"last_update": { "time": "2026-09-16T18:02:00+08:00", "by": "支付系统" },
"days_since_last_update": 1,
"next_actions_hint": "可调用 get_payment 查看支付流水,或用 search_tickets 查看客户是否已提交售后工单"
}
而不是把订单表的整行记录、所有历史状态变更和内部审计字段一股脑儿返回。
原则五:错误信息要可行动
模型读到错误后会尝试自我修正,前提是错误信息告诉它 哪里错了、下一步怎么做。Claude 的文档举过例子:与其返回 "failed",不如返回“触发限流,请 60 秒后重试”。
| 不可行动 | 可行动 |
|---|---|
Error 500 |
订单系统暂时不可用(HTTP 503)。不要重复调用,请告知用户稍后再试。 |
invalid param |
order_id 格式错误:收到 "2026090142",应为 ORD 加 10 位数字。请向用户确认完整订单号。 |
not found |
未找到订单 ORD2026090142。常见原因:订单号输错,或订单超过 90 天已归档。请让用户核对订单号。 |
permission denied |
当前用户无权查看该订单(不属于其所在账号)。不要尝试其他订单号,请提示用户联系管理员。 |
错误要通过工具结果(Claude 的 is_error: true,MCP 的 isError: true)返回给模型,而不是直接抛异常让整个 Agent 崩掉。这部分和 错误恢复与重试 相衔接。
用评测驱动工具设计
工具设计好不好,靠评测说话,而不是靠感觉。Anthropic 推荐的流程是:准备一批需要多次调用工具的真实任务,让 Agent 跑,观察它在哪里选错、重复调用、读漏结果,再改工具。关注这几个信号:
- 同一个任务平均调用几次工具?有没有明显多余的调用?
- 哪些工具从来没被用过?哪些总被误用?
- 工具结果平均多少 Token?有没有超长的返回?
常见误区
- “工具越原子化越灵活。” 对程序是这样,对模型不是。原子化意味着更长的调用链,每一步都可能出错。
- “把完整数据返回,让模型自己挑。” 模型确实能挑,但要付 Token 成本,还会被无关字段干扰。
- “一个万能工具最省事。”
run_query(sql)或call_api(path, body)看似万能,却把设计责任全推给了模型,还打开了安全缺口。
设计要点
拿到研发的工具清单时,逐条确认:
- 每个工具对应专家工作流里的哪一步?有没有“必须连调才有意义”的工具可以合并?
- 初始暴露的工具有多少个?超过 20 个时,是否考虑命名空间与按需加载?
- 返回值是否只保留了推理需要的字段?列表是否有分页、过滤和默认上限?
- 每类错误的返回文案,能否让模型知道下一步做什么?
- 有没有评测任务覆盖每个工具的典型用法和易混淆场景?
练习
为「订单售后诊断 Agent」设计一组只读工具(45 分钟):
- 写下客服专家处理一个售后订单的步骤(例如:看订单状态 → 看支付与退款流水 → 看发货记录 → 看历史售后工单 → 给结论)。
- 按“面向任务”原则设计 4–5 个工具,例如
get_order、get_payment、get_shipment、search_tickets,写出名称、一句话用途和返回字段清单。 - 为每个工具写两条可行动的错误信息。
- 自查:有没有哪两个工具总是连续调用、应该合并?
延伸阅读
- Writing effective tools for agents:Anthropic 关于工具选择、命名空间、返回值与评测驱动改进的工程实践。
- Define tools(Claude):工具合并、命名空间与高信号返回的建议。
- Function calling(OpenAI):函数设计最佳实践与工具数量建议。
下一步
工具设计得再好用,只要能写数据、能查任意数据,就有风险。下一课「安全的工具设计」讲怎么在工具层把风险挡住。