模型对你的工具知道些什么
第 1 课的 harness 示意代码用的工具,取自 Anthropic 的客服智能体示例,下面原样列出示例里的两个替身函数。请用模型的眼光读一读。订单 O1 已经发货,可 cancel_order("O1") 还是返回 True,而这一个词就是全部信息。拿到它的模型,除了告诉客户“订单已取消”,没有别的话可说。
这个示例是为了演示 API 写的,不是用来开店的。可要是你自己的智能体也这样写,那就是缺陷,因为模型只能通过四样东西认识工具:名称、描述、输入结构,以及返回的内容。Ken Aizawa 2025 年 9 月在 Anthropic 工程博客上发表的《Writing effective tools for agents》,把工具称为“确定性系统与非确定性智能体之间的契约”。问智能体“今天要带伞吗?”,它可能调用天气工具,可能凭常识回答,也可能先问你在哪里。
一次调用和它的结果怎样在你的代码和模型之间传递,Claude 工具调用那一课讲过,OpenAI 的做法见函数调用那一课。决定智能体能否把任务做对的,是这些消息块里装的内容。
def get_order_details(order_id):
# Simulated order data
orders = {
"O1": {
"id": "O1",
"product": "Widget A",
"quantity": 2,
"price": 19.99,
"status": "Shipped",
},
"O2": {
"id": "O2",
"product": "Gadget B",
"quantity": 1,
"price": 49.99,
"status": "Processing",
},
}
return orders.get(order_id, "Order not found")
def cancel_order(order_id):
# Simulated order cancellation
if order_id in ["O1", "O2"]:
return True
else:
return False围绕任务做工具,而不是围绕 API 端点
第一反应往往是每个端点做一个工具,而那篇工具文章说这是常见错误。普通软件逐条翻看通讯录几乎不花成本。模型读一个 list_contacts 的结果,却要为每一位联系人付出 token,而且上下文窗口有硬上限。所以文章建议做 search_contacts 或 message_contact 这样的工具,像人翻通讯录那样直接翻到对应的字母。
一个工具内部也可以调用好几次 API。Anthropic 举了三个这样合并的例子,列在表里,每一个都把人眼里算作一件事的几个步骤并成一次调用。工具更少、更大,上下文里的描述也更少,选错工具的机会也更少。
当好几个 MCP 服务器的工具摆在一起,名字就开始要紧了。文章建议加命名空间:按服务分,比如 asana_search 和 jira_search;按资源分,比如 asana_projects_search 和 asana_users_search。共同部分放在前面还是后面,对 Anthropic 的评估结果有不小的影响,而且哪种更好因模型而异。
| 不要做 | 改做 | 这个工具做什么 |
|---|---|---|
list_users、list_events 和 create_event | schedule_event | 找到所有人都有空的时间,并把活动订下来 |
read_logs | search_logs | 只返回相关的日志行,每行附上一点前后文 |
get_customer_by_id、list_transactions 和 list_notes | get_customer_context | 把一位客户近期的相关信息一次汇总返回 |
像给新同事讲解那样写描述
Anthropic 推出 Claude 的网页搜索工具时,Claude 总在搜索词后面加上“2025”,搜索结果因此跑偏。修好它的,是一段更好的工具描述。Anthropic 还说,正是对工具描述的精细修改,让 Claude Sonnet 3.5 在 SWE-bench Verified 上取得了当时最好的成绩。模型每次请求都会读到每一段描述,所以描述的作用就像提示词。
OpenAI 的函数调用指南用“实习生测试”得出同样的结论:只看你给模型的那些内容,一个人能不能把这个函数用对?函数调用那一课结合指南里关于枚举、以及代码已知参数的建议,把这个测试讲了一遍。
Bin Wu 2025 年 11 月的高级工具使用文章把一好一坏两个定义放在一起。坏的那个名字是一条数据库操作,描述什么也没说。好的那个名字说的是任务,描述讲清了能按什么搜索、会返回什么。
// Good
{
"name": "search_customer_orders",
"description": "Search for customer orders by date range, status, or total amount. Returns order details including items, shipping, and payment info."
}
// Bad
{
"name": "query_db_orders",
"description": "Execute order query"
}- 用任务命名,而不是用操作命名。
search_customer_orders让模型知道什么时候该用它,query_db_orders做不到。 - 像给新同事讲解那样写描述。把团队里不言自明的查询格式、专门术语和资源之间的关系都写出来。
- 给每个参数起一个不会被误读的名字。工具文章的例子是用
user_id,而不是user。 - 说明会返回什么,也说明什么情况下不该用这个工具。OpenAI 的指南建议把“什么时候不用”写进系统提示词。
- 为复杂的输入加上示例。在 Anthropic 的内部测试中,工具调用示例把复杂参数处理的准确率从 72% 提高到了 90%。
工具该返回什么
下面是按这些思路改写后的 cancel_order。这是本课自己写的代码,没有运行过。它的描述说明了什么时候调用、会返回什么。函数返回一句模型可以直接转告客户的话,失败时返回一条错误,说明原因和下一步该怎么做。
想在第 1 课的循环里试用它,就改动代码末尾注释里点名的那一行。新 cancel_order 返回的这一对值会成为工具结果的 is_error 标记,get_order_details 照样能用。这样 Claude 看到的 O1 取消失败,就会被标成一次失败的调用。
# The cookbook's two orders, trimmed to the fields this tool needs
ORDERS = {
"O1": {"product": "Widget A", "quantity": 2, "status": "Shipped"},
"O2": {"product": "Gadget B", "quantity": 1, "status": "Processing"},
}
CANCEL_ORDER = {
"name": "cancel_order",
"description": (
"Cancel one order that has not shipped yet. Call it only after the customer "
"has asked to cancel. Returns one sentence saying the order is cancelled, or "
"an error saying why it could not be cancelled and what to do instead."
),
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The order ID exactly as the customer or get_order_details gives it, such as O1.",
}
},
"required": ["order_id"],
},
}
def cancel_order(order_id):
"""Returns the text the model reads and whether it is an error."""
order = ORDERS.get(order_id)
if order is None:
return f"No order has the ID {order_id!r}. Order IDs look like O1. Ask the customer to check it.", True
if order["status"] == "Shipped":
return (
f"Order {order_id} ({order['product']}) has already shipped and cannot be cancelled. "
"Tell the customer. Do not call cancel_order again for this order."
), True
order["status"] = "Cancelled"
return f"Order {order_id} ({order['quantity']} x {order['product']}) is now cancelled.", False
# Lesson 1's loop has the line "output, failed = fn(**block.input), False".
# Replace it with these two lines, so the pair above sets is_error and get_order_details still works:
# result = fn(**block.input)
# output, failed = result if isinstance(result, tuple) else (result, False)- 返回模型用得上的内容。工具文章建议不要返回
uuid、256px_image_url、mime_type这类底层标识,而是返回name、image_url和file_type。把随机 ID 换成可读的名字,哪怕换成从 0 开始编号的 ID,也减少了幻觉,让 Claude 在检索任务里更准确。 - 让模型自己选详略。加一个
response_format参数,取值"concise"或"detailed",等后续调用真要用到 ID 时再返回它们。在文章的 Slack 例子里,详细回复用了 206 个 token,简洁回复用了 72 个,大约三分之一。 - 限制大小。Claude Code 默认把一次工具返回限制在 25,000 个 token。加上分页、筛选或截断,并设好合理的默认值;截断时告诉模型怎样少要一点,比如用几次窄的搜索代替一次宽泛的搜索。
- 错误信息写给模型看。看不懂的错误码或一大段堆栈,什么也教不会它。指出哪个输入不对、再给一个正确写法的错误信息,能引导下一次调用。
- 用评估来选返回格式。JSON、XML 和 Markdown 都可能在某个任务上胜出,文章没有找到一种适合所有任务和智能体的格式。
一次请求能放多少个工具
每个工具定义在每次请求里都占着上下文,不管模型用不用它。什么是上下文工程那一课算过:接五个 MCP 服务器,58 个工具在对话开始前就占掉大约 55K 个 token。准确率也会受影响。Anthropic 的高级工具使用文章说,最常见的失败是选错工具和填错参数,在 notification-send-user 和 notification-send-channel 这种名字相近的工具之间尤其如此。
OpenAI 的指南给出一个宽松的目标:一轮开始时可用的函数少于 20 个。函数很多或 schema 很大时,它建议把函数调用和工具搜索搭配使用:很少用到的工具延迟载入,等模型需要时再载入,这要用 gpt-5.4 或更新的模型。按需载入上下文那一课讲了延迟载入和工具搜索怎样运作。用 Claude 的 Tool Search Tool 时,延迟载入的工具不进上下文,直到 Claude 去搜索它们。
文章建议在工具定义超过 10K 个 token 或工具达到十个及以上时使用工具搜索,同时让最常用的三到五个工具始终加载。它省下的 token 和提升的准确率,按需载入上下文那一课都有数字。
用答案已知的任务检验工具
新写的描述或合并后的工具,在测量之前都只是猜测。工具文章的方法从基于真实使用的任务开始,每个任务配一个脚本能核对的结果,简单的可以是字符串完全匹配,复杂的可以让 Claude 来评判答案。好的任务要调用好几次工具,有时几十次。差的任务直接把查询语句交给模型,检验的只是搜索,而不是工具设计。
每个任务放进一个像第 1 课那样的简单循环里跑,一个循环一个任务,只给它你的工具。不要只看分数:文章列出的还有每次调用和每个任务的耗时、调用次数、总 token 数和工具错误。重复调用很多,说明分页或大小上限需要调整。无效参数的错误很多,问题多半出在描述上。
然后读运行记录,因为模型在自述里漏掉的东西,往往比它说出来的更要紧。Anthropic 把评估记录粘贴进 Claude Code,让它重构工具,文章里的大部分建议就来自这个循环。留出的测试任务保证了提升是真的。在这些任务上,改进后的工具胜过了 Anthropic 研究员手写的版本,也胜过了 Claude 生成的版本。
怎样从真实运行记录里搭出第一套测试集,是本路径后面 AI 评估课程的内容,其中评估智能体那一课会给这样的完整运行打分。可以先从三个好任务和三个差任务开始,就像文章自己举的那样。
| 好的任务 | 差的任务 |
|---|---|
| 下周和 Jane 约个会,讨论我们最新的 Acme Corp 项目。附上上次项目规划会议的笔记,并订一间会议室。 | 下周和 jane@acme.corp 约个会。 |
| 客户 ID 9182 反映一次购买被扣了三次款。找出所有相关的日志记录,并判断有没有其他客户受到同一个问题影响。 | 在支付日志里搜索 purchase_complete 和 customer_id=9182。 |
| 客户 Sarah Chen 刚提交了取消申请。准备一个挽留方案。判断:(1)她为什么要走,(2)什么挽留方案最有吸引力,(3)提出方案前要注意哪些风险。 | 按客户 ID 45892 找到取消申请。 |
Ryan Lopopolo 谈安静的工具,以及让智能体列出自己缺什么
Ryan Lopopolo 写了第 1 课背后那篇 OpenAI Harness 工程文章,现在是 Google Cloud 的首席工程师。2026 年 3 月 7 日,他在 X 上发帖说,prettier 和 pnpm 这类工具默认为人阅读而设计。他的办法是给 prettier 加上 --silent,再让智能体写个包装脚本,pnpm 工作区跑测试时只输出失败项。这相当于命令行工具版的简洁返回格式,任何带 shell 工具的智能体都能用。
3 月 26 日,他在 X 上的一条回复里给出一句用来发现缺口的系统提示词。它要求智能体把自己犯的错记进 MISTAKES.md,把希望拥有的上下文或工具写进 DESIRES.md,把对环境的了解记进 LEARNINGS.md,再把这些文件当作遥测数据收集起来。对工具设计来说,最该读的是 DESIRES.md:每一条都是智能体自己记下的、它缺少的工具或上下文。
Anthropic 2025 年 10 月发布的视频里,Claude Relations 的 Alex Albert 与 MCP 团队的 John Welsh、Claude API 团队的 Michael Cohen 一起出镜。John Welsh 也是那篇工具文章致谢的 MCP 工程师之一。14:20 开始的一章讲怎样用 MCP 管理上下文和工具。
prettier 和 pnpm 这类工具默认是为了让人读得懂而优化的。给 prettier 加上 --silent。

X 官方嵌入卡片的截图,拍摄于 2026 年 11 月 6 日,时间为 UTC。X 用“Show more”截断了第二条帖子。点击截图可到 X 上打开原帖。
常见错误
- 把每个 API 端点都包成一个工具,让模型自己去串联。
- 模型只需要一个名字和一个状态,工具却返回 UUID 和 API 的全部字段。
- 只返回
True、False或一个光秃秃的错误码,让模型对用户无话可说。 - 两个工具名字几乎一样,描述里却都没写清什么时候该用哪一个。
- 把每个 MCP 服务器的每个工具,都塞进每一次请求。
- 只拿几条精心挑选的提示词来评判新描述,没有留出的测试任务。
动手试试
下一课长时间运行的智能体,讲怎样借助进度文件、git 和测试,让一个智能体跨越许多个上下文窗口持续工作。
- 把改写后的
cancel_order和它的CANCEL_ORDER定义放进第 1 课的循环,并采用“工具该返回什么”一节里的改动。依次要求取消 O1、O2、O3,记下模型每次对用户说了什么。 - 挑一个你已有的工具,按讲给新同事的标准重写它的描述,数一数你补了多少条信息。
- 为你自己的工具写三个好任务和三个差任务,每个都配一个脚本能核对的答案。
- 把 DESIRES.md 那句话加进某个智能体的系统提示词,用上一周,看看它想要什么。
自测一下
智能体要在一本很大的通讯录里找一个人。Anthropic 的工具文章建议用哪个工具?
查看答案
C. search_contacts 搜索工具能直接找到对应条目,完整列表则会让上下文塞满模型得逐个读完的联系人。
文章建议给智能体返回哪些字段?
查看答案
A. name、image_url 和 file_type 可读的字段告诉模型下一步做什么,底层标识大多只是在花 token。
OpenAI 的指南为一轮开始时可用的函数建议了什么宽松上限?
查看答案
D. 少于 20 个 OpenAI 建议少于 20 个,并说明这只是宽松的建议。工具很多时,它的指南另外提供了工具搜索。
Anthropic 怎样确保工具的改进不只适合它自己的评估任务?
查看答案
B. 用留出的测试集检验 工具从没针对留出的任务调整过,所以能看出提升能否延续到新的工作上。
常见问题
怎样给 AI 智能体写好工具描述?
把它写给一位从没见过你系统的新同事。说清工具做什么、什么时候用、什么时候不用、每个参数的含义和格式,以及会返回什么。然后用真实任务衡量这次改动。
一个 AI 智能体能处理多少个工具?
比大多数人加载的要少。每多一个工具,每次请求都要多花 token,也多一次在相似工具之间选错的机会。最常用的几个保持加载,其余的放到工具搜索后面,Claude 和 OpenAI 较新的模型都支持这种做法。
工具应该返回 JSON、XML 还是 Markdown?
三种都行。Anthropic 发现最好的格式随任务和智能体而变,因为模型更擅长处理和训练数据相似的格式。在同一组评估任务上试两种格式,留下更好的那种。
MCP 工具和函数调用的工具有什么不同?
在模型眼里它们一样,都是一个名称、一段描述和一个输入结构,所以设计建议也一样。MCP 是从独立服务器提供工具的标准方式,详见什么是 MCP,它还允许服务器标出哪些工具会做破坏性的改动。
Claude 能替我改进工具吗?
能。把几次失败运行的记录交给它,问它是什么让智能体犯了糊涂,再让它一次改好描述和代码,让两者保持一致。只有在 Claude 没见过的任务上也站得住的改动,才值得保留。
资料来源
- Anthropic Engineering: Writing effective tools for agents, with agents https://www.anthropic.com/engineering/writing-tools-for-agents
- Anthropic Engineering: Introducing advanced tool use https://www.anthropic.com/engineering/advanced-tool-use
- OpenAI API docs: Function calling https://developers.openai.com/api/docs/guides/function-calling
- Claude Cookbooks: Creating a customer service agent with client-side tools https://github.com/anthropics/claude-cookbooks/blob/main/tool_use/customer_service_agent.ipynb
- anthropic-sdk-python: ToolResultBlockParam (is_error) https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/types/tool_result_block_param.py
- Ryan Lopopolo on X: pass --silent to prettier https://x.com/_lopopolo/status/2030357237731037528
- Ryan Lopopolo on X: MISTAKES.md, DESIRES.md and LEARNINGS.md https://x.com/_lopopolo/status/2037307669464363433
- Anthropic on YouTube: Building with MCP and the Claude API https://www.youtube.com/watch?v=aZLr962R6Ag


