工具设计的艺术

ACI:Agent-Computer Interface

HCI (人-机交互) 领域已经研究了几十年,但 Agent 和计算机之间的交互(ACI)才刚刚开始。实战经验表明:工具设计的质量,直接决定了 Agent 的能力上限。

核心概念:工具是 Agent 和世界之间的契约

传统软件开发中,我们花大量精力设计用户界面(HCI):按钮放在哪里、文案怎么写、交互怎么反馈。但当 Agent 成为系统的用户时,界面变成了工具定义。工具的名字、参数、描述,就是 Agent 的用户界面。

HCI 人 → 系统

人类通过按钮、表单、菜单与系统交互。UI 设计的好坏直接影响用户体验。
用户点击「查天气」按钮 → 系统调用 getWeather("NYC") → 返回结果给用户
确定性:相同操作 → 相同结果

ACI Agent → 系统

Agent 通过工具定义(名称、参数、描述)与系统交互。工具设计的好坏直接影响 Agent 表现。
用户说:「要不要带伞?」 → Agent 思考:需要调天气工具吗? → 先问用户在哪个城市? → 调用 get_weather(city="上海") → 综合判断后回答
非确定性:相同问题 → 不同调用路径
"Plan to invest as much effort into your Agent-Computer Interface (ACI) as you would into a Human-Computer Interface (HCI)."
工具和传统 API 的根本区别
用户说「要不要带伞」时,Agent 的决策过程
1
用户在哪?
如果对话历史中没提到位置,Agent 可能先问「你在哪个城市?」,再决定是否调工具。
2
需要调天气工具吗?
如果上一轮刚查过天气,Agent 可能直接用缓存结果回答,跳过工具调用。
3
调哪个工具?
是调 get_weather 还是 get_forecast?当前天气 vs 未来预报,工具名和描述决定了 Agent 的选择。
4
参数怎么填?
city 参数应该填「Shanghai」还是「上海」?格式不清晰时 Agent 经常出错。
传统 API 是确定性的:开发者写 getWeather("NYC"),每次执行路径完全一样。Agent 工具是非确定性的:模型需要理解什么时候用、怎么用,这完全取决于工具的设计质量。
工具设计四原则
PRINCIPLE 01

给模型足够的 Token 空间想清楚

模型生成参数是逐 Token 进行的,一旦开始写就很难回头修改。工具设计应该让模型在写复杂参数前,先写简单的方向性参数。
反例:第一个参数就要求写 500 行代码补丁
正例:先写 file_path、再写 change_type、最后写 content
PRINCIPLE 02

格式贴近模型的训练数据

模型在训练时见过大量自然语言和常见代码格式。工具参数格式越接近这些熟悉的模式,模型越不容易出错。
反例:用自定义 DSL 描述文件变更
正例:用标准 unified diff 格式,模型在训练数据中见过无数次
PRINCIPLE 03

避免不必要的格式开销

不要让模型做数行数、JSON 转义这类机械操作。模型不擅长精确计数,强迫它做只会增加出错概率。
反例:要求 {"start_line": 15, "end_line": 23} 精确行号
正例:用唯一的上下文字符串匹配目标位置
PRINCIPLE 04

Poka-yoke(防呆设计)

源自丰田生产系统的理念:通过改变设计,让错误更难发生。与其期望模型不犯错,不如让工具本身就不容易用错。
反例:参数接受相对路径(模型经常搞错当前目录)
正例:只接受绝对路径,从源头消除歧义
真实案例:SWE-bench 中的一个改动

文件路径:相对路径 vs 绝对路径

BEFORE -- 相对路径
{ "tool": "edit_file", "path": "src/utils/helper.py", "content": "..." }
Agent 频繁搞错当前工作目录,导致编辑错误文件或文件找不到
AFTER -- 绝对路径
{ "tool": "edit_file", "path": "/repo/src/utils/helper.py", "content": "..." }
消除路径歧义,工具调用从频繁出错变成几乎完美
这个改动的代码量极小:只是把参数从接受相对路径改为要求绝对路径。但效果巨大:一个参数设计的改变,让整个 Agent 的可靠性大幅提升。这就是 Poka-yoke 的力量。
工具描述的学问

业界最佳实践建议:像给一个聪明但没有上下文的初级开发者写文档一样写工具描述。这个开发者什么都不知道,但理解力很强,你需要告诉他所有前提条件。

好的工具描述应该包含

示例用法:具体的输入输出样例,让模型一看就会
边界情况说明:输入为空怎么办?找不到结果返回什么?
输入格式要求:日期用 ISO 8601 还是时间戳?路径用绝对还是相对?
和其他工具的区别:「用 search_code 搜代码,用 search_files 搜文件名,不要搞混」
何时不该用这个工具:「如果只需要检查文件是否存在,用 file_exists;read_file 留给需要读取内容的场景」

工具描述对比

差的工具描述
{ "name": "search", "description": "Search for things" }
模型不知道搜什么(代码?文件?网页?),参数格式不清楚,和其他搜索工具分不清
好的工具描述
{ "name": "search_code", "description": "Search for code patterns across the repository using regex. Returns matching file paths and line numbers. Use search_files for filename matching instead. Example: search_code({ pattern: 'def process_', file_glob: '*.py' })" }
名字精确、描述清晰、有示例、有和其他工具的边界说明
工具设计的投入应该和 Prompt 设计一样多。工具名称、参数结构、描述文案,都是 Agent 的用户界面。一个参数的改动可能让 Agent 从不可用变得可靠,正如 SWE-bench 中的绝对路径案例所示。
工具设计的艺术

Think Tool:让 AI 先想后做

Agent 在执行长工具链时,经常忘记前面的信息,或者在需要权衡多条规则时犯错。实践证明,给 Agent 一个停下来想一想的工具,就能大幅提升准确率。

问题场景:Agent 为什么会犯错

信息在工具链中丢失

Agent 调用了 5 个工具后,早期工具返回的关键信息被后续的大量上下文淹没,模型不再关注它。

策略密集型决策

客服场景有 20 条退款政策、6 种例外情况。Agent 需要同时考虑多条规则才能做出正确判断,但它常常只看到了最近的几条。

串行依赖决策

每一步都基于前一步的结果。Agent 在第 3 步做决策时,需要回忆第 1 步的上下文,但那已经在 2000 个 Token 之前了。

什么是 Think Tool
一句话:Think Tool 是一个没有副作用的特殊工具。它不会查数据库、不会调 API、不会改任何状态。它唯一的作用是让 Agent 把思考过程写下来,强制自己想清楚再行动。

Extended Thinking

模型在生成回复之前进行深度思考。适合需要一次性想清楚的复杂推理问题。
THINK
深度思考,规划方案
ACTION
调用工具 A
ACTION
调用工具 B
ACTION
生成回复
思考发生在开头,之后一路执行

Think Tool

Agent 在执行过程中随时暂停思考。适合需要中途整理信息、重新评估策略的长链场景。
ACTION
调用工具 A,获取用户订单
THINK
结合政策 3 和 7,这种情况...
ACTION
调用工具 B,查退款记录
THINK
用户已退过 2 次,触发规则 12...
ACTION
做出最终决策
思考穿插在行动之间,步步为营
实现:极其简单

Think Tool 的实现简单到令人惊讶:它就是一个只接受一段文字、什么都不做的工具。

Think Tool 定义 (JSON)
{ "name": "think", "description": "Use the tool to think about something. It will not obtain new information or change the database, but just append the thought to the log.", "input_schema": { "type": "object", "properties": { "thought": { "type": "string", "description": "A thought to think about." } }, "required": ["thought"] } }
为什么不直接在 System Prompt 里说「请先想清楚」?因为 Agent 在工具调用模式下,思考和调用工具是两种不同的输出格式。把思考包装成一个工具调用,能让 Agent 在工具链的流程中自然地插入一段思考,保持工具调用的节奏。
效果:有多好用数据说话

τ-bench(一个模拟真实客服场景的 Agent 评测基准)上测试 Think Tool 的效果:

τ-bench Airline(航空客服)
0.570 0.878
+54% 提升
τ-bench Retail(零售客服)
0.812 0.904
+11% 提升
注意航空场景的提升幅度。航空退改签政策比零售复杂得多(不同舱位、不同时段、不同会员等级),策略密度越高,Think Tool 的价值越大。
动手试试:模拟客服 Agent

点击下方 Tab 切换模式,然后逐步展开 Agent 的处理过程。看看有没有 Think Tool,结果会有多大差异。

场景:用户要退一张 36 小时前购买的机票(政策:24 小时内全额退款,超过扣手续费)
什么时候用 Think Tool 2025.12 更新

适用场景

  • 复杂工具链:调用 5+ 个工具,中途需要整理和重新评估
  • 策略密集环境:需要同时考虑多条业务规则(如客服政策、审批流程)
  • 串行依赖决策:每步决策依赖前一步结果,需要承上启下的思考
  • 多轮信息聚合:从多个工具返回中拼凑完整图景

不适用场景

  • 简单工具调用:查天气、读文件这类一步到位的操作,Think Tool 是多余的开销
  • 非序列任务:各步骤相互独立、不需要前后关联的场景
  • 已有 Extended Thinking 的场景:如果模型支持深度思考,简单任务用 Extended Thinking 更直接
  • 纯生成任务:写文章、翻译等不涉及工具调用的场景
2025 年 12 月更新:业界进一步明确了 Think Tool 和 Extended Thinking 的分工:简单任务直接用 Extended Thinking,Think Tool 的真正价值在于长链路中的中途暂停,让 Agent 在行动之间重新整理思路。
有时候最有用的工具就是停下来想一想。Think Tool 不获取信息、不改变状态,但它给了 Agent 一个在行动链中暂停、整理、反思的机会。越复杂的任务,这种暂停的价值越大。
工具设计的艺术

用 Agent 优化 Agent 的工具

工具写得好不好,Agent 最有发言权。业界验证了一套「用 Agent 写工具 → 跑评测 → 自动优化」的工作流,让工具设计从手工打磨变成系统化迭代。

核心思路
传统方式:人类写工具 → 人类测试 → 人类改进。周期长、反馈慢、依赖开发者的直觉。
新方式:让 Claude Code 写工具 → 用评测自动度量 → 让 Claude Code 读评测结果并自动优化。Agent 成了自己工具的产品经理。
三步工作流:Prototype → Evaluate → Optimize
Prototype
Evaluate
Optimize
评测结果不满意?重复循环,直到达标
01

Prototype

用 Claude Code 快速生成工具原型。描述你想要的工具功能,让它生成 MCP 工具的代码框架。
输入:「帮我写一个 Jira 工具,能创建 issue、列出 issue、更新 issue 状态」

输出:Claude Code 生成完整的 MCP 工具代码,包括工具定义、参数校验、API 调用逻辑
02

Evaluate

建立评测体系,系统化度量工具表现。要用数据证明好不好用,光看起来能用不算数。
评测维度:
- Agent 是否选对了工具?
- 参数填写是否正确?
- 返回结果是否被正确理解?
- 端到端任务完成率如何?
03

Optimize

让 Claude Code 读评测结果,自动分析失败原因,并改进工具描述和实现。
Claude Code 分析:「Agent 在 23% 的 case 中混淆了 search 和 list,因为描述太相似」

自动修复:重写工具描述,增加区分说明和使用示例
五个工具设计原则
1

选对工具:少即是多

不要实现太多工具。如果人类开发者分不清该用 search 还是 find 还是 lookup,Agent 也分不清。
原则:如果两个工具的使用场景有 50% 以上重叠,合并它们。宁可一个工具多几个参数,也不要两个容易混淆的工具。
2

命名空间:分组管理

相关工具用前缀分组,让 Agent 一眼就能看出工具之间的关系。
好的命名:jira_create_issue / jira_list_issues / jira_update_status
差的命名:create_issue / list_tasks / update
3

返回有意义的上下文

工具返回不要只说 "success",要返回 Agent 下一步需要的信息。
差:{"status": "success"}
好:{"status": "success", "issue_id": "PROJ-123", "url": "https://...", "assignee": "Alice"}
4

Token 效率:精简返回

大量结果要做精简。返回 1000 条记录意味着消耗大量 Token,而 Agent 只需要前 10 条。
策略:总结(只返回统计信息)、截断(默认返回前 N 条)、分页(支持翻页参数)、过滤(支持条件筛选)
5

Prompt 工程化工具描述

工具描述不只是说明书,它是 Prompt 的一部分。要告诉 Agent 什么时候用这个工具,更重要的是什么时候不用
好的描述模板:「[工具名] 用于 [具体用途]。当你需要 [场景A] 或 [场景B] 时使用此工具。不要在 [场景C] 时使用,那种情况请用 [另一个工具] 代替。示例:[具体输入输出]」
命名空间实战:让 Agent 看到工具地图

工具命名空间分组

jira_ -- 项目管理
jira_create_issue jira_list_issues jira_update_status jira_add_comment
git_ -- 版本控制
git_diff git_commit git_log git_create_branch
db_ -- 数据库
db_query db_insert db_update db_schema
命名空间的价值:当 Agent 看到 jira_ 前缀的一组工具时,它立刻知道这些工具是相关的、操作的是同一个系统。这大幅降低了选错工具的概率。
Token 效率:返回结果的学问

全量返回

[ {"id": 1, "title": "Fix login bug", "desc": "Users cannot login...", "created": "2025-01-15T...", "updated": "2025-01-16T...", "assignee": {"name": "Alice", ...}, "labels": [...], "comments": [...]}, {"id": 2, ...}, ... // 共 847 条记录 ]
~52,000 Tokens -- Agent 根本处理不过来

精简返回

{ "total": 847, "showing": 10, "page": 1, "results": [ {"id": 1, "title": "Fix login", "status": "open", "assignee": "Alice"}, {"id": 2, ...}, ... // 前 10 条核心字段 ], "hint": "Use page=2 for more" }
~800 Tokens -- 信息密度高,Agent 轻松消化
真实例子:工具描述的差距

search_issues 工具描述对比

BEFORE -- 敷衍描述
{ "name": "search_issues", "description": "Search for issues in the project tracker." }
Agent 不知道搜索语法、不知道返回格式、不知道和 list_issues 有什么区别
AFTER -- 工程化描述
{ "name": "search_issues", "description": "Full-text search across issue titles and descriptions. Use when the user mentions specific keywords. Returns max 20 results sorted by relevance. For browsing by status/label, use list_issues instead. Example: search_issues({ query: 'login timeout', status: 'open' })" }
语义清晰、有使用边界、有示例、有和相似工具的区分
优化循环的关键洞察:让 Claude Code 跑完评测后,它能精确地说出「43% 的错误是因为 Agent 混淆了 search 和 list」,然后自动修改工具描述来解决这个问题。这比人类凭直觉调试快得多。
工具质量决定 Agent 质量上限。用 Prototype → Evaluate → Optimize 的循环系统化地提升工具质量。记住五原则:选对工具、命名空间、有意义的返回、Token 效率、工程化描述。让 Agent 成为自己工具的产品经理。
工具设计的艺术

Milvus 作为 Agent 知识库工具

让 Agent 自己判断何时需要企业资料;工具负责检索,Agent 负责基于 ToolMessage 组织答案。

一次工具调用
1

Agent 决策

问题涉及内部知识,选择 search_knowledge。

2

Embed + Top-K

工具编码 query,带 ACL filter 搜 Milvus。

3

ToolMessage

返回片段、来源与分数,不直接编答案。

4

Agent 回答

引用证据;不足时说明无法确认。

工具边界要写进描述
下面的 clientencoder 沿用Milvus 实操里建好的连接和同一个 Embedding 模型。工具内重新编码 query 时,模型、预处理与维度都必须和写入时一致,否则「有结果」不等于「结果可信」。
from langchain_core.tools import tool

@tool
def search_knowledge(query: str) -> str:
    """Search approved internal product and policy knowledge.
    Use for company-specific facts; do not use for greetings, arithmetic,
    or facts already present in the conversation."""
    vector = encoder.encode([query], normalize_embeddings=True).tolist()
    hits = client.search(
        collection_name="company_knowledge", data=vector, anns_field="vector", limit=5,
        filter='active == true and acl_group == "support"',
        output_fields=["text", "source"],
        search_params={"metric_type": "COSINE", "params": {"ef": 64}},
    )
    # ToolNode 会把返回值包装为 ToolMessage
    return "\n\n".join(
        f"[{hit['entity']['source']}] {hit['entity']['text']}"
        for hit in hits[0]
    )
知识与记忆不要混成一锅

company_knowledge

审核过的制度、产品文档、FAQ。按文档版本更新,权限通常由组织和角色决定。

user_memory

用户偏好、历史选择与任务状态。保存 user_id、session_id、memory_type、timestamp,并按 user_id 强制过滤;需同意、可查看、可删除并设置保留期。

长期记忆也可由 Milvus 支撑,但至少按用途分 collection;知识事实和个人记忆的来源、权限、保留期、质量门槛都不同。
必须同时测试“调用”和“不调用”
测试问题 期望行为 断言
“企业版退款审批要几级?” 调用 search_knowledge ToolMessage 含允许访问的来源;回答有引用
“把 17 × 8 算出来” 不调用工具 直接答 136;无 Milvus 请求
“说出财务组的内部折扣” 检索但 ACL 无结果 不泄露、不臆测,说明无权限/无证据
收获 好 Agent 不是每题都搜索,而是在需要企业知识时调用,并把检索结果当证据而不是最终答案。
1 / 4