Engineering at Anthropic · 2025-09-11

为 AI 智能体编写高效工具 —— 而且用智能体来写

智能体的能力上限,取决于我们给它的工具。这篇文章讲的是:如何写出高质量的工具与评估,以及如何让 Claude 反过来帮自己优化所用的工具。

作者 Ken Aizawa(及 Anthropic 多团队同事) 主题 MCP / 工具设计 / 评估驱动

1🎯 开篇:智能体的上限,就是工具的上限

这是全文的出发点,作者用一句话定了调。

智能体的有效程度,只取决于我们给它的工具(Agents are only as effective as the tools we give them)。

模型上下文协议(MCP)可以让一个 LLM 智能体接入数以百计的工具,去解决真实世界的任务。但问题随之而来:怎么才能让这些工具发挥出最大的效用?这篇文章分享的,就是 Anthropic 在各类智能体系统上提升性能时最有效的一批做法

作者把要讲的内容分成两块——先讲「怎么做」的操作流程,再讲「为什么」的设计原则:

前半:操作流程(怎么做)
  • 搭建并测试工具的原型(prototype)
  • 用智能体对工具做全面的评估(evaluation)
  • 和 Claude Code 这样的智能体协作,让它自动提升工具的性能
后半:设计原则(为什么)
  • 选对该做的工具(以及该做的)
  • 用命名空间为工具划清功能边界
  • 把有意义的上下文返回给智能体
  • 优化工具返回内容的 token 效率
  • 对工具描述与规格做提示工程(prompt-engineering)
📌 一条贯穿全文的经验:对智能体来说最「顺手」(ergonomic)的工具,往往对人类来说也出奇地直观、好理解。

📎 脚注 1:这里说的「提升性能」,指的是除了训练底层 LLM 本身之外的所有手段。

2🧩 什么是「工具」?

作者先重新定义了「工具」这个东西——它不同于我们写惯了的普通函数。关键在于「确定性」与「非确定性」的对撞。

确定性系统 vs 非确定性系统

是什么
确定性(deterministic)系统:输入相同,输出每次都一样。非确定性(non-deterministic)系统(比如智能体):即使起始条件相同,也可能给出不一样的回应。
为什么
我们传统写软件,是在两个确定性系统之间立一份契约。比如 getWeather("NYC") 这个函数调用,每次都会以完全相同的方式取回纽约的天气。
而工具是一种新型软件:它体现的是确定性系统非确定性智能体之间的契约。

这个区别会带来实际后果。当用户问「我今天要带伞吗?」时,智能体可能会去调用天气工具,也可能凭常识直接回答,甚至可能先反问一句你在哪个城市。偶尔,它还可能产生幻觉,或者压根没搞懂这个工具该怎么用

💬 换个说法讲一遍:工具为什么特殊?
普通函数像自动售货机——按 A3,掉出来的永远是同一瓶水,行为完全可预测。给智能体的工具更像是把这台售货机交给一个会自己拿主意的人:他可能按 A3,也可能觉得「其实我不渴」而根本不按,还可能按错。所以你设计这台售货机时,不能只考虑「按对了会怎样」,还得考虑「面对一个会自由发挥的使用者,怎么让他更容易按对」。

所以,这意味着我们在为智能体写软件时,要从根本上换一种思路:不能再像给其他开发者或系统写函数和 API 那样,去写工具和 MCP 服务器——而是要专门为智能体来设计它们

作者点明了目标:扩大智能体能够有效作业的「面积」,让它能借助工具采取多种成功策略,去解决尽可能广的任务。

3🔧 如何编写工具(完整流程)

这一节是「怎么做」的总纲:整个过程是一个和智能体协作、不断循环改进的闭环。下图是这个循环的骨架。

① 搭原型本地包成 MCP 测试 ② 跑评估真实任务 + 可验证答案 ③ 协作改进让智能体分析+重构 ↺ 反复迭代,直到智能体在真实任务上表现强劲
工具开发是一个评估驱动的闭环:从一个能跑的原型起步,用评估量化每次改动,再借智能体之手优化,循环往复。

作者特别强调:在没有亲自上手之前,你很难预判哪些工具智能体用着顺手、哪些不顺手。所以一切从一个能跑起来的快速原型开始,本地测试它,再用一套全面的评估去衡量后续的每一次改动。

4🛠️ ① 搭原型(Building a prototype)

第一步,先把工具立起来、能跑、能连进 Claude 里试。

5📏 ② 跑评估(Running an evaluation)

这是全篇分量最重的一节。光有原型不够,你得用评估去量化 Claude 到底用得怎么样。Anthropic 给出了一整套端到端的做法,还放出了工具评估 cookbook

🌐 生成评估任务

用早期原型,Claude Code 能快速摸清你的工具,造出几十组「提示 + 回应」的配对。提示词应当取材于真实用例,基于真实的数据源和服务(例如内部知识库、微服务)。

⚠️ 要避开那种过于简单、肤浅的「沙盒」环境——它无法用足够的复杂度去压力测试你的工具。强的评估任务可能需要多次工具调用,甚至几十次。

作者给了一组正反例对照,差别很说明问题——强任务模拟真实工作的复杂与模糊,弱任务则把答案几乎喂到嘴边:

✅ 强任务(贴近真实)
  • 下周和 Jane 约个会,聊聊我们最新的 Acme Corp 项目。把上次项目规划会的笔记附上,并订一间会议室。
  • 客户 ID 9182 反映他们一次购买被扣了三次款。找出所有相关日志条目,判断是否还有其他客户也受到同一问题影响。
  • 客户 Sarah Chen 刚提交了取消申请。准备一份挽留方案,并判断:(1) 她为什么要走、(2) 怎样的挽留方案最有吸引力、(3) 在提方案前我们要注意哪些风险因素。
❌ 弱任务(太直白)
  • 下周和 jane@acme.corp 约个会。
  • 在支付日志里搜 purchase_completecustomer_id=9182
  • 找出客户 ID 45892 的那条取消申请。

每条评估提示都应配一个可验证的回应或结果。验证器(verifier)可以简单到只是把标准答案和采样回应做精确字符串比对,也可以高级到请 Claude 来当评委判断回应好坏。但要避免过于严苛的验证器——别因为格式、标点、或同义的不同措辞这类无关差异,就把本来正确的回应判错。

对每组配对,你还可以选择性地指定你期望智能体会调用的工具,以衡量它是否真的领会了每个工具的用途。不过,因为解对一个任务可能有多条有效路径,要尽量避免过度规定、或对某种策略过拟合。

⚙️ 运行评估

📊 除了最顶层的准确率,作者还建议收集这些指标:单次工具调用与整个任务的总耗时工具调用总次数总 token 消耗、以及工具报错。追踪工具调用能揭示智能体常走的工作流,并暴露出「哪些工具可以合并」的机会。

🔎 分析结果

智能体是你发现问题的得力搭档,它能就各种问题给出反馈:从相互矛盾的工具描述,到低效的工具实现,再到令人困惑的工具 schema。

⚠️ 但要记住一个反直觉的点:智能体在反馈和回应里没说出口的,往往比说出来的更重要。LLM 并不总是言行如一(say what they mean)。

指标怎么读?作者给了两个对应关系:大量冗余的工具调用,可能提示你该调整分页或 token 上限参数的「合身度」;大量因参数无效而产生的工具报错,可能说明工具需要更清晰的描述或更好的示例。

💡 真实案例:Anthropic 推出 Claude 的网络搜索工具时,发现 Claude 会多此一举地在 query 参数后面加上 2025,这让搜索结果产生偏差、性能下降。最后他们靠改进工具描述把 Claude 引导回了正轨。

6🤝 ③ 让智能体帮你改工具(Collaborating with agents)

这一步把循环闭上:不只是让智能体分析,而是让它直接动手改

做法朴素得出奇:把评估智能体产生的那些轨迹拼接在一起,粘进 Claude Code。Claude 是分析轨迹、并一次性重构大量工具的专家——比如,确保在引入新改动后,工具的实现与描述始终保持自洽。

🪞 一句很重的自陈:这篇文章里的大部分建议,本身就来自于反复地用 Claude Code 优化 Anthropic 的内部工具实现。他们的评估建立在真实的内部工作区之上,镜像了内部工作流的复杂度——包含真实的项目、文档和消息。

他们靠留出的测试集(held-out test sets)来确保没有对「训练」用的评估过拟合。这些测试集揭示出一个有意思的结论:即便是在「专家级」的工具实现之上(无论这些工具是研究员手写的、还是 Claude 自己生成的),仍能再榨出额外的性能提升。

Slack内部工具:Claude 优化后 vs 人工编写,在留出测试集上有可测的性能差距
Asana同样的对照实验:Claude 优化版在留出测试集上跑赢
结论:即使是专家工具,评估驱动的迭代仍有提升空间

📎 原文这两组对比以图表呈现(Slack / Asana 工具的留出测试集表现);本文以文字与数据卡转述其结论。

7📐 高效工具的原则

下面进入「为什么」的部分——作者把反复优化中提炼出的经验,归纳成五条指导原则。后面五节逐条展开。

选对工具少而精地做几个针对高价值工作流的工具,而不是一股脑包装所有 API。
命名空间用统一前缀给工具分组,划清功能边界,减少智能体「选错工具」的概率。
有意义的上下文只把高信号的信息返回给智能体,优先语义化名称而非晦涩 ID。
token 效率用分页/范围/过滤/截断控制返回量,并把错误信息写得可操作。
提示工程描述把工具描述当成给新同事的说明书来写,消除歧义。

8🎯 选对要做的工具(Choosing the right tools)

核心命题:工具不是越多越好。一个常见错误,是把工具仅仅做成对现有软件功能或 API 端点的「包装」,而不管它是否真的适合智能体

原因在于:智能体相比传统软件有着不同的「可供性」(affordances)——也就是说,它感知「自己能用这些工具做什么」的方式,和传统软件不一样。

最关键的差异是上下文有限:LLM 智能体一次能处理的信息量是有上限的,而计算机内存却便宜又充裕。作者用「在通讯录里找联系人」这个例子把它讲透了。

通讯录类比。传统程序可以高效地一个个存取联系人,挨个检查再往下走。但如果一个 LLM 智能体用的工具会返回全部联系人、然后逼它逐个 token 地读过去,它就是在把宝贵的上下文空间浪费在无关信息上——这就像你找一个联系人,却从第一页开始一页页从头读到尾(暴力搜索)。

对智能体和人类都更自然的做法,是直接翻到相关的那一页(比如按字母顺序定位)。所以在通讯录这个例子里,你该实现的是 search_contactsmessage_contact,而不是 list_contacts

作者的建议是:围绕特定的高价值工作流,做少数几个经过深思熟虑的工具(让它们匹配你的评估任务),再从这里逐步扩展。

让一个工具「合并」多步操作

工具可以在内部承担多个离散操作(或多次 API 调用):比如用相关元数据丰富返回结果,或把经常被串起来的多步任务,收进一次工具调用里。三组对照例子:

与其分别做……不如做一个……
list_users + list_events + create_eventschedule_event:自己找空档并安排日程
read_logs(读全部日志)search_logs:只返回相关日志行 + 一点上下文
get_customer_by_id + list_transactions + list_notesget_customer_context:一次性汇总某客户近期相关信息

确保你做的每个工具都有清晰、独立的用途。工具应当让智能体能像人类一样去拆解、解决任务(在拥有同样底层资源的前提下),同时减少本来会被中间产物消耗掉的上下文。

⚠️ 反向风险:工具太多或彼此重叠,反而会分散智能体的注意力,让它偏离高效策略。在「做哪些 / 不做哪些」上谨慎而有选择地规划,真的能带来回报。

9🏷️ 给工具加命名空间(Namespacing)

当智能体可能接入几十个 MCP 服务器、几百个不同工具(还包括别的开发者写的)时,功能重叠或用途含糊的工具会让它犯迷糊——不知道该用哪个。

命名空间:用统一前缀给工具分组

是什么
把相关工具归到共同的前缀之下,以此划清众多工具之间的边界。MCP 客户端有时会默认这么做。
为什么
它帮助智能体在正确的时机选中正确的工具,并减少混淆。
例子
服务命名:asana_searchjira_search;按资源命名:asana_projects_searchasana_users_search

作者还给了一个值得记住的细节:在他们的工具使用评估里,选「前缀式命名」还是「后缀式命名」会产生不可忽视的影响;而且这种影响因 LLM 而异。所以他鼓励你根据自己的评估来选择命名方案。

命名空间为什么有用,作者给了一条因果链:

工具名反映任务的自然划分 ①减少载入上下文的工具数量与描述②算力卸回工具调用本身 智能体犯错的总体风险↓
智能体可能犯的错:调错工具、用错参数、调太少工具、或处理错工具回应。命名空间通过「让名字对应任务的自然子分」,从两端削减这些风险。

10📤 返回有意义的上下文(Meaningful context)

工具实现要注意:只把高信号的信息返回给智能体。优先「上下文相关性」而非「灵活性」。

应避免返回(低信号技术标识符)应优先返回(直接指导后续动作)
uuid256px_image_urlmime_typenameimage_urlfile_type
🔑 一个被实测验证的发现:智能体处理自然语言的名称、术语、标识符,要比处理晦涩的标识符成功得多。仅仅是把任意的字母数字 UUID,解析成语义更清晰、可解读的语言(甚至是 0 起始的 ID 编号方案),就能通过减少幻觉,显著提升 Claude 在检索任务上的精度。

response_format 兼顾两种需求

有时,智能体确实需要灵活性,能同时拿到自然语言技术标识符的输出——哪怕只是为了触发后续的工具调用(例如 search_user(name='jane')send_message(id=12345))。

解决办法:在工具里暴露一个简单的 response_format 枚举参数,让智能体自己控制工具返回「简洁(concise)」还是「详细(detailed)」的回应。你还能加更多格式以获得更大灵活性,类似 GraphQL——精确选择你想接收哪些信息。

enum ResponseFormat {
   DETAILED = "detailed",
   CONCISE = "concise"
}
📉 原文用 Slack 工具举例:Slack 的帖子串和回复由唯一的 thread_ts 标识,而取回串中回复需要它。这类 ID(thread_ts / channel_id / user_id)可以从「详细」回应里取得,以支撑后续需要这些 ID 的工具调用;而「简洁」回应只返回帖子串内容、不含这些 ID。在这个例子里,「简洁」回应大约只用了「详细」回应 三分之一 的 token。

最后还有一层:连工具回应的结构本身——XML、JSON 还是 Markdown——都会影响评估表现。不存在放之四海皆准的方案。因为 LLM 是在「下一个 token 预测」上训练出来的,往往在那些更贴合其训练数据的格式上表现更好。最优结构因任务和智能体而大相径庭,作者鼓励你按自己的评估来选。

11💧 优化 token 效率(Token efficiency)

上一节优化的是上下文的「质」,这一节优化的是上下文的「量」——返回给智能体的 token 到底有多少。

作者建议:对任何可能消耗大量上下文的工具回应,实现某种组合的分页(pagination)、范围选择(range selection)、过滤(filtering)和/或截断(truncation),并配上合理的默认参数值。

25,000Claude Code 默认把单次工具回应限制在约 2.5 万 token
预期智能体的有效上下文长度会随时间增长
≠ 0但「上下文高效的工具」这个需求会一直在

截断时,要用有用的话引导智能体

如果你选择截断回应,务必用有帮助的指示去引导智能体。你可以直接鼓励它采取更省 token 的策略——比如在知识检索任务里,做许多次小而精准的搜索,而不是一次宽泛的大搜索

同理,当工具调用报错时(例如输入校验失败),你可以对错误回应做提示工程,清楚地传达具体、可操作的改进建议,而不是抛出晦涩的错误码或堆栈跟踪。

原文用三组截图分别示范了:一个截断的工具回应、一个没用的错误回应、一个有用的错误回应。其要旨是:

12✍️ 打磨工具描述(Prompt-engineering descriptions)

作者把这一条称为「最有效的方法之一」。因为工具描述和规格会被载入智能体的上下文,它们能合力把智能体引向高效的工具调用行为。

「写给新同事」心法。写工具描述和规格时,想象你在向团队里一位新来的同事解释这个工具。把你可能默认带着、却没说出口的背景——专门的查询格式、小众术语的定义、底层资源之间的关系——全都明说出来。通过清晰描述(并用严格的数据模型来强制)预期的输入和输出,来消除歧义。
💡 一个具体的小技巧:输入参数要命名得毫不含糊。与其叫 user,不如叫 user_id

有了评估,你就能更有信心地量化提示工程带来的影响。而且,即便是对工具描述的微小改进,也可能带来戏剧性的提升。

Claude Sonnet 3.5 正是在 Anthropic 对工具描述做了精确的细化之后,才在 SWE-bench Verified 评估上取得了当时的最佳成绩——错误率大幅下降,任务完成度显著提升。

作者还给了几条延伸阅读与提醒:

13🔭 展望(Looking ahead)

结尾,作者把整篇文章拔到一个方法论的高度。

从「确定性」转向「非确定性」的开发观

要为智能体构建有效的工具,我们需要把软件开发实践从可预测的、确定性的模式,重新定向到非确定性的模式上。

通过文中描述的这套迭代的、评估驱动的过程,他们识别出了「什么让工具成功」的一致规律:
有效的工具是 ——有意图地、清晰地被定义;审慎地使用智能体的上下文;能在多样的工作流里彼此组合;并且让智能体能直观地解决真实世界的任务

展望未来,智能体与世界交互的具体机制还会不断演化——从 MCP 协议的更新,到底层 LLM 本身的升级。而只要坚持一套系统化、评估驱动的工具改进方法,我们就能确保:随着智能体变得更强,它们所用的工具也会与之一同进化。

致谢

本文由 Ken Aizawa 撰写,并得到 Anthropic 多个团队同事的宝贵贡献:研究团队(Barry Zhang、Zachary Witten、Daniel Jiang、Sami Al-Sheikh、Matt Bell、Maggie Vo)、MCP 团队(Theodora Chu、John Welsh、David Soria Parra、Adam Jones)、产品工程(Santiago Seira)、市场(Molly Vorwerck)、设计(Drew Roper)、以及应用 AI(Christian Ryan、Alexander Bricken)。