为智能体编写高效工具——借助智能体
模型上下文协议(Model Context Protocol,MCP)可以赋予 LLM 智能体成百上千个工具,用来解决真实世界任务。但我们如何让这些工具发挥最大效用?
在本文中,我们介绍在多种智能体式 AI 系统^1^ 中提升性能最有效的几种技巧。
首先,我们会介绍如何:
- 构建并测试你的工具原型
- 用智能体创建并运行针对工具的综合评测
- 与 Claude Code 这样的智能体协作,自动提升工具的性能
最后,我们会总结一路走来总结出的编写高质量工具的关键原则:
- 选择要实现的正确工具(以及不实现的工具)
- 用命名空间划清工具功能边界
- 让工具向智能体返回有意义的上下文
- 针对 token 效率优化工具响应
- 对工具描述与 spec 做提示工程

搭建评测能让你系统地衡量工具的性能。你可以用 Claude Code 针对该评测自动优化你的工具。
什么是工具?
在计算领域,确定性(deterministic)系统在输入相同时每次都产生相同的输出,而非确定性(non-deterministic)系统——例如智能体——即便起始条件相同,也可能产生多种不同的响应。
当我们传统地编写软件时,我们其实是在确定性系统之间建立契约。例如,getWeather("NYC") 这样的函数调用,每次被调用时都会以完全相同的方式获取纽约的天气。
工具是一种新的软件形态,它体现的是确定性系统与非确定性智能体之间的契约。当用户问「我今天要带伞吗?」时,智能体可能会调用天气工具,也可能凭一般知识作答,甚至会先追问一句地点。偶尔,智能体还可能产生幻觉,甚至完全没能理解该怎样使用某个工具。
这意味着,我们在为智能体编写软件时,思路必须从根本上转变:与其像为其他开发者或其他系统编写函数和 API 那样编写工具与 MCP 服务器,不如专门为智能体来设计它们。
我们的目标是扩大智能体得以施展的覆盖面,让它们能借助工具采用多种成功策略来解决广泛的任务。幸运的是,根据我们的经验,那些对智能体最「顺手」的工具,对人类来说也往往出奇地直观易懂。
如何编写工具
在这一节,我们介绍如何与智能体协作来编写、并持续改进交给它们的工具。先快速搭起一个工具原型并在本地测试;接着运行综合评测来衡量后续的改动;与智能体并肩工作,反复「评估—改进」工具,直到智能体在真实任务上表现优异。
构建原型
不亲手试一试,很难预料哪些工具对智能体顺手、哪些不顺手。先从快速搭一个工具原型开始。如果你正用 Claude Code 编写工具(很可能一次性完成),最好把工具所依赖的软件库、API 或 SDK(可能包括 MCP SDK)的文档交给 Claude。对 LLM 友好的文档通常可以在官方文档站点的扁平 llms.txt 文件中找到(例如我们 API 的这份)。
把你的工具包进一个本地 MCP 服务器或 Desktop 扩展(DXT),就能在 Claude Code 或 Claude 桌面应用中连接并测试这些工具。
要把本地 MCP 服务器连接到 Claude Code,请运行 claude mcp add <name> <command> [args...]。
要把本地 MCP 服务器或 DXT 连接到 Claude 桌面应用,请分别进入 Settings > Developer 或 Settings > Extensions。
工具也可以直接传入 Anthropic API 调用中,进行编程式测试。
亲自测试这些工具,找出各种生涩的边角。同时收集用户的反馈,建立对工具预期会支撑的用例与提示的直觉。
运行评测
接下来,你需要通过运行评测来衡量 Claude 使用你的工具的表现。首先,生成大量基于真实世界用例的评测任务。我们建议与智能体协作,让它帮你分析结果并判断如何改进工具。完整的端到端流程参见我们的工具评测 cookbook。

我们内部 Slack 工具在留出测试集(held-out test set)上的表现
生成评测任务
有了早期原型后,Claude Code 可以快速探索你的工具,生成几十对提示与响应。提示应当源自真实使用场景,并建立在现实的数据源与服务之上(例如内部知识库与微服务)。我们建议避免过于简单、流于表面的「沙盒」环境——它们无法用足够的复杂度来压测你的工具。有分量的评测任务可能要求多次工具调用,甚至多达几十次。
下面是一些好的任务示例:
- 下周与 Jane 开会,讨论我们最新的 Acme Corp 项目。附上我们上次项目规划会议的笔记,并预订一间会议室。
- 客户 ID 9182 报告称,他们一次购买尝试被扣了三次款。找出所有相关日志条目,并判断是否还有其他客户受到同一问题影响。
- 客户 Sarah Chen 刚刚提交了取消请求。准备一份挽留方案。请确定:(1)他们离开的原因;(2)什么样的挽留方案最有吸引力;(3)在我们开出条件之前需要注意哪些风险因素。
下面是一些较弱的任务:
- 下周与 jane@acme.corp 开个会。
- 在支付日志中搜索 purchase_complete 且 customer_id=9182。
- 查找客户 ID 45892 提交的取消请求。
每条评测提示都应配对一个可验证的响应或结果。你的验证器可以简单到在标准答案与采样得到的响应之间做精确字符串比对,也可以高级到请 Claude 来评判响应。要避免过于严苛的验证器——它们会因格式、标点或合理的替代措辞这类表面差异,而误杀掉本来正确的响应。
对每个「提示—响应」对,你还可以按需指定你期望智能体在解决任务时调用的工具,从而在评测中衡量智能体是否成功领会了每个工具的用途。不过,由于正确解决任务可能存在多条路径,要尽量避免过度规定或过度拟合到某种策略上。
运行评测
我们建议通过直接的 LLM API 调用来以编程方式运行评测。使用简单的智能体循环(while 循环,交替包裹 LLM API 调用与工具调用):每个评测任务一个循环。每个评测智能体应只拿到一条任务提示和你的工具。
在评测智能体的系统提示中,我们建议指示智能体不只输出结构化响应块(供验证),还要输出推理与反馈块。指示智能体在工具调用与响应块之前先输出这些内容,可能会通过触发思维链(chain-of-thought,CoT)行为来提升 LLM 的有效智能。
如果你用 Claude 运行评测,可以开启交错思考(interleaved thinking),开箱即用地获得类似的功能。这会帮助你探查智能体为什么调用或不调用某些工具,并凸显工具描述与 spec 中值得改进的具体环节。
除了顶层准确率,我们还建议收集其他指标,例如单个工具调用及单个任务的总运行时长、工具调用总次数、总 token 消耗量以及工具报错数。追踪工具调用有助于揭示智能体常采用的工作流,并带来某些工具合并的机会。

我们内部 Asana 工具在留出测试集上的表现
分析结果 智能体是你发现问题、提供反馈的得力伙伴,它们能就自相矛盾的工具描述、低效的工具实现、令人困惑的工具 schema 等方方面面给出意见。不过请记住,智能体在反馈与响应中略去的内容,往往比它们写出的更重要。LLM 并不总是说其所想。
观察智能体会在哪些地方卡壳或困惑。通读评测智能体的推理与反馈(或 CoT),找出生涩的边角。翻看原始记录(包括工具调用与工具响应),捕捉任何在智能体 CoT 中没有显式说明的行为。要读出字里行间的意思;请记住,你的评测智能体并不一定知道正确答案与正确策略。
分析你的工具调用指标。大量冗余的工具调用,可能意味着分页或 token 上限参数需要做适当的尺寸调整;大量因参数无效导致的工具报错,则可能说明工具需要更清晰的描述或更好的示例。在我们发布 Claude 的联网搜索工具时,我们发现 Claude 会无谓地往工具的 query 参数后面追加 2025,使搜索结果产生偏置、拉低表现(我们通过改进工具描述把 Claude 引回了正轨)。
与智能体协作
你甚至可以让智能体替你分析结果、改进工具。只需把评测智能体的记录拼接起来,粘贴进 Claude Code。Claude 是分析记录、一次性重构大量工具的专家——例如,在有新改动时,确保工具实现与描述之间保持自洽。
事实上,本文里的大部分建议,都来自我们用 Claude Code 反复优化内部工具实现的过程。我们的评测建立在我们内部工作区之上,复刻了内部工作流的复杂度,包含真实的项目、文档与消息。
我们依靠留出测试集,确保没有对「训练用」评测过拟合。这些测试集表明,即便与「专家级」工具实现——无论这些工具是由我们的研究人员手工编写,还是由 Claude 自己生成——相比,我们仍能额外榨出更多的性能提升。
下一节,我们将分享从这个过程中学到的一些内容。
编写高效工具的原则
在这一节,我们把学到的东西提炼为编写高效工具的几条指导原则。
为智能体选对工具
工具更多,并不总是带来更好的结果。我们观察到一个常见错误:有些工具只是把既有软件功能或 API 端点草草地包了一层,却不管这些工具是否适合智能体使用。这是因为智能体与传统软件有着不同的「可供性(affordances)」——也就是说,它们感知自己能够借这些工具采取何种潜在行动的方式不同。
LLM 智能体的「上下文」是有限的(即它们单次能处理的信息量存在上限),而计算机内存既便宜又充足。想想在通讯录中查找一个联系人的任务。传统软件程序可以高效地逐个存储与处理联系人列表,每查完一个再移到下一个。
然而,如果 LLM 智能体使用一个会返回全部联系人的工具,再被迫逐 token 通读每一个,它就是把宝贵的上下文空间浪费在了无关信息上(想象为了在通讯录里找一个联系人,从第一页逐页读到最后一页——也就是暴力搜索)。更好也更自然的做法(对智能体和对人皆然)是先跳到相关那一页(也许是按字母序定位到那里)。
我们建议构建少数几个精心设计、瞄准特定高影响工作流的工具,使它们与你的评测任务相匹配,再由此扩展。在通讯录的例子中,你或许应当实现一个 search_contacts 或 message_contact 工具,而不是 list_contacts 工具。
工具可以整合功能,在内部处理多个离散操作(或 API 调用)。例如,工具可以用相关的元数据来丰富工具响应,或在一次工具调用中处理频繁串联的多步任务。
下面是一些示例:
- 与其实现 list_users、list_events 和 create_event 工具,不如考虑实现一个 schedule_event 工具,让它查找可用时间并安排活动。
- 与其实现 read_logs 工具,不如考虑实现一个 search_logs 工具,只返回相关的日志行及少量上下文。
- 与其实现 get_customer_by_id、list_transactions 和 list_notes 工具,不如实现一个 get_customer_context 工具,一次性地把某位客户近期且相关的所有信息汇总起来。
要确保你构建的每个工具都有清晰、独特的用途。工具应当让智能体能够像人类在掌握相同底层资源时那样去细分并解决任务,同时削减本会被中间输出占据的上下文。
工具过多或功能重叠,同样会把智能体带离高效策略。审慎、有选择地规划要构建(或不构建)的工具,回报会非常可观。
为工具划分命名空间
你的 AI 智能体可能会拿到几十个 MCP 服务器、数百种不同工具的使用权——其中包括其他开发者编写的工具。当工具之间功能重叠、或某个工具用途含糊时,智能体会在「该用哪个」上犯迷糊。
命名空间(namespacing,把相关工具归入共同前缀)有助于在大量工具之间划清边界;MCP 客户端有时会默认这么做。例如,按服务划分工具(如 asana_search、jira_search)和按资源划分工具(如 asana_projects_search、asana_users_search),都能帮助智能体在恰当的时机选中恰当的工具。
我们发现,采用前缀式还是后缀式的命名空间,对我们的工具使用评测会产生不小的差异。其效果因 LLM 而异,我们建议你依据自己的评测来选定命名方案。
智能体可能调用错误的工具、用错误的参数调用正确的工具、调用过少的工具,或错误地处理工具响应。通过有选择地实现那些名称体现了任务自然划分的工具,你能同时减少被加载进智能体上下文的工具数量与工具描述,并把智能体的一部分计算负担从上下文里卸回工具调用本身。这降低了智能体整体犯错的风险。
从工具返回有意义的上下文
同理,工具实现应当注意只把高信号的信息返回给智能体。它们应优先考虑上下文相关性,而非灵活性,并避开低层技术标识符(例如 uuid、256px_image_url、mime_type)。像 name、image_url、file_type 这样的字段,远更可能直接为智能体的后续行动与回复提供依据。
智能体驾驭自然语言名称、术语或标识符的能力,通常也远比驾驭晦涩的标识符来得成功。我们发现,仅仅把任意的字母数字 UUID 解析成更富语义、更易解读的语言(甚至换成 0 起始的数字 ID 方案),就能通过减少幻觉显著提升 Claude 在检索任务上的精确度。
某些情况下,智能体或许需要同时与自然语言和技术标识符输出打交道,哪怕只是为了触发下游工具调用(例如 search_user(name='jane') → send_message(id=12345))。你可以在工具中暴露一个简单的 response_format 枚举参数来兼顾两者,让智能体自主决定工具返回「concise」还是「detailed」响应(见下图)。
你还可以加入更多格式以换取更大的灵活性,类似于 GraphQL——你可以精确选择想收到哪些信息。下面是一个用于控制工具响应详细程度的 ResponseFormat 枚举示例:
enum ResponseFormat {
DETAILED = "detailed",
CONCISE = "concise"
}
下面是一个 detailed 工具响应的示例(206 个 token):

下面是一个 concise 工具响应的示例(72 个 token):

Slack 会话线程及其回复由唯一的 thread_ts 标识,拉取线程回复时必须有它。thread_ts 及其他 ID(channel_id、user_id)可以从「detailed」工具响应中获得,从而触发需要这些 ID 的后续工具调用。「concise」工具响应只返回线程内容,不含这些 ID。在这个示例中,使用「concise」工具响应只用了约 1/3 的 token。
就连工具响应的结构——例如 XML、JSON 或 Markdown——也可能影响评测表现:这里没有放之四海而皆准的答案。这是因为 LLM 训练于下一 token 预测,在与其训练数据契合的格式上往往表现更好。最佳响应结构会因任务与智能体而千差万别。我们鼓励你基于自己的评测来挑选最合适的响应结构。
针对 token 效率优化工具响应
优化上下文的质量很重要。但优化工具响应返回给智能体的上下文的数量,同样重要。
我们建议,对任何可能消耗大量上下文的工具响应,采用分页、范围选择、过滤和/或截断的组合,并配以合理的默认参数值。对 Claude Code,我们默认将工具响应限制在 25,000 个 token。我们预计智能体的有效上下文长度会随时间的推移而增长,但对上下文高效工具的需求将一直存在。
如果你选择截断响应,务必用有助益的指引来引导智能体。你可以直接鼓励智能体采用更省 token 的策略,比如在知识检索任务中做多次小而精准的搜索,而不是一次宽泛的大搜索。同样,当一次工具调用报错时(例如在输入校验期间),你可以对错误响应做提示工程,清晰传达具体而可操作的改进方向,而不是给出晦涩的错误码或 traceback。
下面是一个被截断的工具响应示例:

下面是一个没有帮助的错误响应示例:

下面是一个有帮助的错误响应示例:

工具截断与错误响应可以把智能体引向更省 token 的工具使用行为(善用过滤器或分页),或给出输入格式正确的工具示例。
为工具描述做提示工程
现在要谈的是改进工具最有效的方法之一:为你的工具描述与 spec 做提示工程。由于这些内容会被加载进智能体的上下文,它们可以共同引导智能体形成高效的工具调用行为。
在编写工具描述与 spec 时,想一想你会怎样向团队里的新同事介绍这个工具。把那些你或许会心照不宣的背景——专用的查询格式、小众术语的定义、底层资源彼此之间的关系——都显式地写出来。清晰描述(并用严格的数据模型加以约束)预期的输入与输出,以消除歧义。尤其重要的是,输入参数的命名要无歧义:与其用 user 作参数名,不如用 user_id。
借助你的评测,你可以更有把握地衡量提示工程的成效。哪怕是对工具描述极其细微的打磨,也可能带来显著的提升。Claude Sonnet 3.5 正是在我们对工具描述做了精细打磨、大幅降低错误率并提高任务完成度之后,才在 SWE-bench Verified 评测上取得当时最优的表现。
你可以在我们的开发者指南中找到工具定义的其他最佳实践。如果你在为 Claude 构建工具,我们还建议阅读工具是如何被动态加载进 Claude 的系统提示的。最后,如果你在为 MCP 服务器编写工具,工具注解(tool annotations)有助于说明哪些工具需要开放式(open-world)访问,或会带来破坏性更改。
展望
要为智能体构建高效的工具,我们需要把软件开发实践从可预测、确定性的模式,重新定向到非确定性的模式上来。
通过本文介绍的这种以评测为驱动、反复迭代的流程,我们识别出了决定工具成败的一致模式:高效的工具定义刻意而清晰,审慎地使用智能体上下文,能够在多样化的流程中彼此组合,并让智能体得以直觉地解决真实任务。
展望未来,我们预计智能体与外部世界交互的具体机制会不断演进——从 MCP 协议的更新,到底层 LLM 本身的升级。有了这套系统化、以评测驱动的方法来改进供智能体使用的工具,我们就能确保:当智能体变得愈发强大时,它们所用的工具会与之一同进化。
致谢
本文由 Ken Aizawa 撰写,并得到 Research(Barry Zhang、Zachary Witten、Daniel Jiang、Sami Al-Sheikh、Matt Bell、Maggie Vo)、MCP(Theodora Chu、John Welsh、David Soria Parra、Adam Jones)、Product Engineering(Santiago Seira)、Marketing(Molly Vorwerck)、Design(Drew Roper)与 Applied AI(Christian Ryan、Alexander Bricken)等部门同事的宝贵贡献。
^1^ 指在训练底层 LLM 本身之外。