Mindket

给 AI 智能体写工具:模型用得好的名称、描述和返回结果

黑色横幅,标题是“Harness 工程”,下面是“智能体外围的循环、工具与护栏”。

AI 智能体的工具,是模型可以请你的代码去运行的函数,而模型只能通过名称、描述、参数和返回内容来认识每个工具。围绕完整的任务做少量工具,不要每个 API 端点各做一个;像给新同事讲解那样写描述;返回简短易读的结果,出错时说明下一步该怎么做;每次改动都用真实任务检验。

本节内容

  • 先想清楚一个人会用你的 API 完成什么活,再把这件活做成一次调用。
  • 工具返回的每个字段,模型都得读一遍,也都要花 token。
  • 让失败的调用教会下一次调用:指出哪个输入不对,再给一个能用的例子。
  • 留一组固定的、需要多步完成的难任务,每次改工具后都重跑一遍。
全部课程
本页目录
  1. 模型对你的工具知道些什么
  2. 围绕任务做工具,而不是围绕 API 端点
  3. 像给新同事讲解那样写描述
  4. 工具该返回什么
  5. 一次请求能放多少个工具
  6. 用答案已知的任务检验工具
  7. Ryan Lopopolo 谈安静的工具,以及让智能体列出自己缺什么
  8. 常见错误
  9. 动手试试
  10. 自测一下
  11. 常见问题
  12. 资料来源

模型对你的工具知道些什么

第 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 的评估结果有不小的影响,而且哪种更好因模型而异。

包装端点的工具,以及替代它们的任务型工具,来自 Anthropic 的《Writing effective tools for agents》(2025 年 9 月 11 日)
不要做改做这个工具做什么
list_users、list_events 和 create_eventschedule_event找到所有人都有空的时间,并把活动订下来
read_logssearch_logs只返回相关的日志行,每行附上一点前后文
get_customer_by_id、list_transactions 和 list_notesget_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"
}
  1. 用任务命名,而不是用操作命名。search_customer_orders 让模型知道什么时候该用它,query_db_orders 做不到。
  2. 像给新同事讲解那样写描述。把团队里不言自明的查询格式、专门术语和资源之间的关系都写出来。
  3. 给每个参数起一个不会被误读的名字。工具文章的例子是用 user_id,而不是 user。
  4. 说明会返回什么,也说明什么情况下不该用这个工具。OpenAI 的指南建议把“什么时候不用”写进系统提示词。
  5. 为复杂的输入加上示例。在 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 评估课程的内容,其中评估智能体那一课会给这样的完整运行打分。可以先从三个好任务和三个差任务开始,就像文章自己举的那样。

好的和差的评估任务,来自 Anthropic 的《Writing effective tools for agents》
好的任务差的任务
下周和 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。

Ryan Lopopolo, OpenAI Harness 工程文章的作者,现任 Google Cloud 首席工程师,引文为译文 · X (2026年11月6日 查阅)
Ryan Lopopolo(@_lopopolo)2026 年 3 月 7 日在 X 上说:prettier 和 pnpm 这类工具默认为人类可读而优化。给 prettier 加上 --silent,再用一个包装脚本让 pnpm 工作区的测试只输出失败项。
Ryan Lopopolo 在 X 上,2026 年 3 月 7 日
Ryan Lopopolo(@_lopopolo)2026 年 3 月 26 日在 X 上回复:把这段放进系统提示词,再把文件当作遥测收集。错误记进 MISTAKES.md,想要的上下文或工具写进 DESIRES.md,对环境的了解记进 LEARNINGS.md。
Ryan Lopopolo 在 X 上,2026 年 3 月 26 日
Anthropic 的 Alex Albert、John Welsh 和 Michael Cohen 谈用 MCP 和 Claude API 构建应用,其中一章讲管理上下文和工具。

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 那句话加进某个智能体的系统提示词,用上一周,看看它想要什么。

自测一下

  1. 智能体要在一本很大的通讯录里找一个人。Anthropic 的工具文章建议用哪个工具?

    查看答案

    C. search_contacts 搜索工具能直接找到对应条目,完整列表则会让上下文塞满模型得逐个读完的联系人。

  2. 文章建议给智能体返回哪些字段?

    查看答案

    A. name、image_url 和 file_type 可读的字段告诉模型下一步做什么,底层标识大多只是在花 token。

  3. OpenAI 的指南为一轮开始时可用的函数建议了什么宽松上限?

    查看答案

    D. 少于 20 个 OpenAI 建议少于 20 个,并说明这只是宽松的建议。工具很多时,它的指南另外提供了工具搜索。

  4. Anthropic 怎样确保工具的改进不只适合它自己的评估任务?

    查看答案

    B. 用留出的测试集检验 工具从没针对留出的任务调整过,所以能看出提升能否延续到新的工作上。

常见问题

怎样给 AI 智能体写好工具描述?

把它写给一位从没见过你系统的新同事。说清工具做什么、什么时候用、什么时候不用、每个参数的含义和格式,以及会返回什么。然后用真实任务衡量这次改动。

一个 AI 智能体能处理多少个工具?

比大多数人加载的要少。每多一个工具,每次请求都要多花 token,也多一次在相似工具之间选错的机会。最常用的几个保持加载,其余的放到工具搜索后面,Claude 和 OpenAI 较新的模型都支持这种做法。

工具应该返回 JSON、XML 还是 Markdown?

三种都行。Anthropic 发现最好的格式随任务和智能体而变,因为模型更擅长处理和训练数据相似的格式。在同一组评估任务上试两种格式,留下更好的那种。

MCP 工具和函数调用的工具有什么不同?

在模型眼里它们一样,都是一个名称、一段描述和一个输入结构,所以设计建议也一样。MCP 是从独立服务器提供工具的标准方式,详见什么是 MCP,它还允许服务器标出哪些工具会做破坏性的改动。

Claude 能替我改进工具吗?

能。把几次失败运行的记录交给它,问它是什么让智能体犯了糊涂,再让它一次改好描述和代码,让两者保持一致。只有在 Claude 没见过的任务上也站得住的改动,才值得保留。

资料来源

  1. Anthropic Engineering: Writing effective tools for agents, with agents (2026年11月6日 查阅)https://www.anthropic.com/engineering/writing-tools-for-agents
  2. Anthropic Engineering: Introducing advanced tool use (2026年11月6日 查阅)https://www.anthropic.com/engineering/advanced-tool-use
  3. OpenAI API docs: Function calling (2026年11月6日 查阅)https://developers.openai.com/api/docs/guides/function-calling
  4. Claude Cookbooks: Creating a customer service agent with client-side tools (2026年11月6日 查阅)https://github.com/anthropics/claude-cookbooks/blob/main/tool_use/customer_service_agent.ipynb
  5. anthropic-sdk-python: ToolResultBlockParam (is_error) (2026年11月6日 查阅)https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/types/tool_result_block_param.py
  6. Ryan Lopopolo on X: pass --silent to prettier (2026年11月6日 查阅)https://x.com/_lopopolo/status/2030357237731037528
  7. Ryan Lopopolo on X: MISTAKES.md, DESIRES.md and LEARNINGS.md (2026年11月6日 查阅)https://x.com/_lopopolo/status/2037307669464363433
  8. Anthropic on YouTube: Building with MCP and the Claude API (2026年11月6日 查阅)https://www.youtube.com/watch?v=aZLr962R6Ag

分享本节课

看看别人做的工具

浏览 Mindket 上的 MCP 服务器,看看每一个怎样给自己的工具命名和写描述。

浏览 MCP 服务器

本节课的 Markdown 版本 · Harness 工程:搭建 AI 智能体外围的代码 · 全部课程