序:工具是 Agent 的手和脚 2024 年以来,AI Agent 从一个概念热词变成了实际的生产力工具。Cursor、Claude Code、Copilot 等编程 Agent 已经深度嵌入开发流程,而在企业场景中,客服 Agent、数据分析 Agent、自动化运维 Agent 也在批量落地。
所有这些 Agent 有一个共同点:它们不只是聊天,它们要干活 。
干活需要什么?工具 。
一个没有工具的 LLM 就像一个只会说话不会动手的人——能给你建议,但无法执行。工具就是 Agent 的手和脚,它决定了 Agent 的能力边界。
但大多数人对”工具”的理解还停留在很浅的层面:给模型塞几个函数,让它调用就行了。事实远非如此。工具的数量、粒度、命名、参数设计、错误处理,直接决定了 Agent 的成功率、效率和用户体验 。
这篇文章,就是要把 Agent 工具系统的设计模式讲透。从最基础的 Function Calling 到 MCP(Model Context Protocol)再到 Plugin 架构,从参数设计到编排策略,从常见陷阱到最佳实践——读完你就能为任何项目定制约自己的工具集。
一、工具的本质:不是函数,是契约 1.1 LLM 眼中的”工具”是什么? 在模型层面,工具就是一段结构化的描述,通常包含:
名称 :模型用来判断”该不该用这个”的第一信号
描述 :详细告诉模型”什么时候用、做什么、有什么限制”
参数 Schema :JSON Schema 格式,定义输入参数的类型、必填性和约束
以 OpenAI Function Calling 为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { "name" : "search_documents" , "description" : "在项目文档库中全文搜索。当用户询问任何技术概念、API 用法或项目规范时使用此工具。" , "parameters" : { "type" : "object" , "properties" : { "query" : { "type" : "string" , "description" : "搜索关键词,建议使用简洁的术语而非完整句子" } , "limit" : { "type" : "integer" , "description" : "返回结果数上限,默认 5,最大 20" , "default" : 5 , "minimum" : 1 , "maximum" : 20 } } , "required" : [ "query" ] } }
模型的任务是:理解用户意图 → 匹配合适的工具 → 生成正确的参数 → 调用工具 → 解读结果 → 决定下一步 。
这中间每一步都可能出问题。而工具设计的质量,决定了每一步的成功率。
1.2 工具的三层架构 在实际工程中,Agent 工具系统的完整架构有三层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ┌─────────────────────────────────────────┐ │ 第一层:工具描述层(给 LLM 看的) │ │ ┌─────────────────────────────────┐ │ │ │ name / description / parameters │ │ │ │ JSON Schema / Tool Manifest │ │ │ └─────────────────────────────────┘ │ ├─────────────────────────────────────────┤ │ 第二层:工具注册层(给框架看的) │ │ ┌─────────────────────────────────┐ │ │ │ 工具注册表 / 路由 / 权限 / 限流 │ │ │ │ MCP Server / Plugin Manager │ │ │ └─────────────────────────────────┘ │ ├─────────────────────────────────────────┤ │ 第三层:工具实现层(真正干活的) │ │ ┌─────────────────────────────────┐ │ │ │ 实际 API 调用 / 数据库操作 │ │ │ │ 文件系统 / 网络请求 / 外部服务 │ │ │ └─────────────────────────────────┘ │ └─────────────────────────────────────────┘
新手常犯的错误 :只关注第三层(实现),忽视第一层(描述)。结果是工具能干活,但模型不知道怎么用、什么时候用。
工具设计的核心原则第一条:描述即产品 。你写给模型的工具描述,就是工具的”UI”。模型能不能选对这个工具、传对参数,八成取决于描述写得好不好。
二、工具的分类体系 不是所有工具都一个样。理解工具的类别,才能用正确的模式设计它。
2.1 按操作性质分类
类型
说明
示例
设计要点
只读工具
获取信息,无副作用
search、read_file、get_weather
幂等,可安全重试,可缓存
写入工具
修改状态,有副作用
create_file、send_email、deploy
需确认机制,需幂等设计
查询工具
单次查询返回结果
sql_query、api_call
需限流,需超时,需格式约定
流式工具
持续产生输出
stream_logs、subscribe_events
需流控,需中断机制
交互工具
需要用户参与
ask_user、confirm_action
需阻塞等待,需超时回退
2.2 按依赖关系分类
类型
说明
设计要点
无依赖工具
独立执行,不依赖其他工具
最简单,可以并行
链式依赖
前一个的输出是后一个的输入
需在描述中说明前置条件
条件依赖
根据情况决定是否调用
需清晰的决策逻辑描述
编排工具
本身是多个子工具的调度器
适用于复杂工作流
2.3 按抽象层级分类 这是最重要也最容易被忽视的分类维度。
层级
粒度
示例
优点
缺点
原子工具
单个操作
read_file、run_sql、http_get
灵活,组合性强
调用次数多,上下文消耗大
复合工具
多个原子操作的组合
search_and_summarize、deploy_project
高效,减少调用轮次
灵活性降低
自适应工具
参数决定行为范围
query_data(source, query)
平衡灵活性和效率
描述复杂度高
黄金法则 :默认用原子工具,只在模型频繁犯同样错误时引入复合工具。
为什么?因为:
模型自己做组合通常会做得更好(它比你对上下文的理解更动态)
复合工具会让模型做出更多假设,假设错了就是”幻觉执行”
原子工具更容易测试、调试和版本管理
三、工具描述设计:模型眼中的”产品体验” 3.1 命名:一秒钟的决策 模型的工具选择决策发生在毫秒级。工具名是最快的信息通道。好的命名:
1 2 3 4 5 // ❌ 坏命名 "tool_1"、"fetcher"、"do_stuff"、"util" // ✅ 好命名 "search_docs"、"send_email"、"deploy_static_site"、"query_database"
命名原则 :
动词在前,领域在后 :search_docs 而非 docs_search
具体而非通用 :create_github_issue 而非 create_issue
避免技术黑话 :用 list_files 而非 ls
区分标志性前缀 :读操作用 get_/search_/query_/list_/read_,写操作用 create_/update_/delete_/deploy_/send_
为什么读操作用多个前缀? 因为不同前缀暗示不同的语义:
前缀
语义
适用场景
get_
按 ID/精确条件获取单个
get_user(id)
list_
简单列举
list_users()
search_
全文/模糊搜索
search_docs(query)
query_
结构化查询
query_db(sql)
read_
读取文件/内容
read_file(path)
3.2 描述写作的五个要素 工具描述是模型判断”该不该用这个工具”的核心依据。一个好的描述应包含五个要素:
1 2 3 4 5 [场景] 什么时候用这个工具 [行为] 这个工具做什么 [输入] 需要的参数和约束 [输出] 会返回什么 [限制] 有哪些边界条件和注意事项
反例 vs 正例 :
1 2 3 4 5 6 { "name" : "search" , "description" : "搜索东西" , "parameters" : { "q" : { "type" : "string" } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { "name" : "search_docs" , "description" : "在项目文档库中全文检索技术文档。" + "当用户询问任何技术概念、API 用法、最佳实践或项目规范时," + "优先使用此工具而非直接回答。" + "支持中英文搜索,返回相关文档的标题、摘要和链接。" + "注意:此工具仅搜索已有文档,不会生成新内容。" , "parameters" : { "query" : { "type" : "string" , "description" : "搜索查询词。建议使用 2-5 个关键词而非完整句子。" + "例如用 'JWT 认证' 而非 '如何在项目中使用JWT进行用户认证'" } , "doc_type" : { "type" : "string" , "enum" : [ "api" , "guide" , "tutorial" , "all" ] , "description" : "限定文档类型。默认为'all'即搜索全部类型。" } } }
看出差距了吗?反例的模型会”瞎猜”何时用、用错了也不知道。正例的模型能精确判断:用户问技术问题 → 先搜文档再回答 → 参数用简洁关键词 → 可限定文档类型。
3.3 参数设计:太少不行,太多更糟 参数设计是工具设计中最需要拿捏的部分 。核心矛盾:
参数太少 → 工具不够灵活 → 模型需要多次调用
参数太多 → 模型难以理解 → 填空准确率下降
参数太自由 → 模型生成幻觉参数 → 执行失败
参数设计的黄金法则 :
法则一:必填参数 ≤ 3 个
超过 3 个必填参数,模型的正确填充率断崖式下降。如果需要更多信息,用可选参数 + 合理默认值。
1 2 3 4 5 6 7 8 9 10 11 { "required" : [ "name" , "email" , "phone" , "department" , "role" ] } { "required" : [ "name" , "email" ] , "properties" : { "phone" : { "description" : "...(可选,不填则不发送短信)" } , "department" : { "default" : "general" } , "role" : { "default" : "member" } } }
法则二:枚举优于自由文本
1 2 3 4 5 6 7 8 9 "status" : { "type" : "string" , "description" : "用户状态" } "status" : { "type" : "string" , "enum" : [ "active" , "inactive" , "suspended" , "pending" ] , "description" : "用户状态:active(活跃)/inactive(不活跃)/suspended(已停用)/pending(待激活)" }
枚举给了模型一个”选择题”而非”填空题”,准确率天差地别。
法则三:参数描述要对齐模型的理解方式
1 2 3 4 5 "path" : { "type" : "string" , "description" : "文件的绝对路径" } "path" : { "type" : "string" , "description" : "你要读取的文件路径。可以是从项目根目录开始的相对路径(如 src/main.ts),也可以是绝对路径。" }
模型是从用户语境推断参数的。你的参数描述越贴近用户的表达习惯,模型推理越准确。
法则四:相互排斥的参数用 oneOf/anyOf
1 2 3 4 5 6 7 8 9 10 11 12 13 { "parameters" : { "type" : "object" , "properties" : { "user_id" : { "type" : "string" } , "email" : { "type" : "string" } } , "oneOf" : [ { "required" : [ "user_id" ] } , { "required" : [ "email" ] } ] } }
这明确告诉模型:”二选一,不能两个都传”。
四、工具编排模式 单个工具简单,多个工具的组合才是真正的工程挑战。以下是四种核心编排模式。
4.1 顺序链模式(Sequential Chain) 最基础的模式:A 的输出 → B 的输入 → C 的输入。
1 2 3 4 用户: "部署最新版本到生产环境" → get_latest_version() → "v2.3.1" → build_project("v2.3.1") → "build_12345" → deploy("build_12345", "production") → "部署成功"
适用场景 :线性流程、E2E 工作流、数据处理管线。
设计要点 :
每个工具的返回值应结构化(JSON),方便下游解析
在描述中说明前置条件(如”仅在 build_project 成功后调用”)
添加进度反馈(”Step 1/3: 获取版本号…”)
4.2 并行扇出模式(Parallel Fan-out) 多个独立工具同时执行,汇总结果。
1 2 3 4 5 用户: "检查所有微服务的健康状态" → check_service("user-service") ┐ → check_service("order-service") ├ 并行 → check_service("payment-service") ┘ → 汇总: { user: ok, order: degraded, payment: ok }
适用场景 :批量查询、多数据源聚合、健康检查。
设计要点 :
工具必须无依赖关系(纯只读、无状态)
需要汇总工具或模型自动汇总
考虑部分失败时的降级策略
4.3 条件路由模式(Conditional Routing) 工具链根据中间结果动态选择路径。
1 2 3 4 5 用户: "处理这张图片" → analyze_image() → "类型: 照片, 含人脸, 偏暗" → 含人脸? → enhance_portrait() → 偏暗? → auto_brightness() → 最终 → save_image()
适用场景 :智能路由、异常处理、多策略选择。
设计要点 :
中间工具返回结构化的”决策信号”(如 { type: "portrait", needs_enhance: true })
不要让模型”猜”路径——用工具返回明确的状态码或枚举
提供兜底路径
4.4 ReAct 循环模式(Reasoning + Acting) 这是最智能也最难驾驭的模式。模型在”思考→执行→观察→再思考”的循环中自主决策。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 模型思考: 需要知道项目结构 → 执行: list_files("/project") → 观察: 有 src/, tests/, package.json 模型思考: 需要看 package.json 的依赖 → 执行: read_file("package.json") → 观察: 用了 React 18, 需要升级到 19 模型思考: 升级 React 需要改哪些文件? → 执行: search_code("ReactDOM.render") → 观察: 3 个文件用了旧 API 模型思考: 逐一修改 → 执行: edit_file("src/index.tsx", ...) → 执行: edit_file("src/App.tsx", ...) → 执行: edit_file("src/components/Legacy.tsx", ...) 模型思考: 全部完成,确认 → 结束
适用场景 :开放式的复杂任务(编程 Agent、研究 Agent)。
设计要点 :
工具粒度要小(原子化),给模型最大的组合空间
每个工具必须返回明确的结果(成功/失败/部分成功 + 数据)
设置最大步数限制,防止无限循环
错误信息要能指导模型的下一步决策
五、工具系统的工程实践 5.1 错误处理:不是返回字符串就完了 工具执行的错误处理,是三件事:告诉模型出了什么问题、为什么、接下来怎么处理 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 def search_docs (query ): results = db.search(query) if not results: return "没有找到结果" return results def search_docs (query ): try : results = db.search(query) if not results: return { "success" : True , "data" : [], "count" : 0 , "message" : "未找到匹配 '{query}' 的文档。建议:1) 尝试更通用的关键词 " "2) 检查拼写 3) 使用 list_docs 查看所有可用文档" } return { "success" : True , "data" : results, "count" : len (results), "snippets" : [r["title" ] for r in results[:5 ]] } except TimeoutError: return { "success" : False , "error" : "TIMEOUT" , "message" : "搜索超时。数据库可能负载过高,建议:" "1) 缩小搜索范围 2) 等待几秒后重试 3) 使用本地缓存。" , "retryable" : True } except Exception as e: return { "success" : False , "error" : "INTERNAL" , "message" : f"搜索失败: {e} 。这不是你的问题,请告知用户稍后重试或联系管理员。" , "retryable" : False }
规范化的返回值结构 :
1 2 3 4 5 6 7 { "success" : true | false , "data" : ..., "error" : "..." , "message" : "..." , "retryable" : true | false }
关键:message 不只是给用户看,更是给模型看的 。模型会基于 message 的内容决定下一步行为。所以 message 要包含”下一步建议”。
5.2 超时与重试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 DEFAULT_TIMEOUT = 30 MAX_RETRIES = 3 RETRY_DELAY = 1 def execute_tool (name, params ): for attempt in range (MAX_RETRIES): try : result = call_with_timeout(name, params, DEFAULT_TIMEOUT) return result except TimeoutError: if attempt < MAX_RETRIES - 1 : time.sleep(RETRY_DELAY * (2 ** attempt)) continue return error_response("TIMEOUT" , "重试 3 次后仍超时" , retryable=False ) except RateLimitError: time.sleep(5 * (2 ** attempt)) continue
超时策略 :
工具类型
建议超时
理由
数据库查询
5-10s
索引查询应很快;慢查询是 bug
HTTP API
15-30s
外部服务不可控
文件操作
3-5s
本地 I/O 应该很快
LLM 调用
60-120s
生成可能需要时间
编译构建
300-600s
大型项目构建慢
5.3 幂等性设计 对于写操作工具,幂等性 是关键——同一个请求执行多次,结果应该一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def create_user (email, name ): existing = find_user_by_email(email) if existing: return {"success" : True , "data" : existing, "message" : "用户已存在,跳过创建" } user = insert_user(email, name) return {"success" : True , "data" : user, "message" : "用户创建成功" } def charge_order (order_id, amount, idempotency_key ): existing_charge = find_charge_by_key(idempotency_key) if existing_charge: return {"success" : True , "data" : existing_charge, "message" : "重复请求,返回已有结果" }
5.4 权限与安全 模型调用工具时,你永远不知道用户会要求它传什么样的参数。所以工具的权限校验必须在服务端,不能依赖模型自觉 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def delete_file (path ): if not path.startswith(ALLOWED_DIRS): return error("PERMISSION_DENIED" , f"无权限操作 {path} 路径" ) if path.endswith((".env" , ".pem" , ".key" , "credentials.json" )): return error("DANGEROUS_OPERATION" , f"该文件包含敏感信息,拒绝删除。如需强制删除请使用 force_delete 工具并明确确认。" ) backup_path = create_backup(path) os.remove(path) return {"success" : True , "message" : f"已删除 {path} ,备份保存在 {backup_path} " }
安全设计清单 :
六、工具集的演变:从 Function 到 MCP 到 Plugin 6.1 第一代:内联 Function Calling 最原始也最简单的模式。工具定义直接写在代码中,作为 prompt 的一部分传给模型。
1 2 3 4 5 6 7 8 9 10 tools = [ {"name" : "get_weather" , "description" : "..." , "parameters" : {...}}, {"name" : "search_docs" , "description" : "..." , "parameters" : {...}}, ] response = client.chat.completions.create( model="gpt-4" , messages=messages, tools=tools, )
优点 :简单,零外部依赖。缺点 :工具绑定在 Agent 代码中,不可复用。
6.2 第二代:MCP(Model Context Protocol) Anthropic 提出的 MCP 将工具抽离为独立的 Server。Agent 通过标准协议发现和调用工具。
1 2 3 4 5 6 7 8 9 ┌──────────┐ MCP 协议 ┌──────────────┐ │ Agent │ ←──────────────→ │ MCP Server │ │ (Host) │ tools/list │ (文件系统) │ │ │ tools/call │ │ └──────────┘ └──────────────┘ │ ┌──────┴──────┐ │ 文件系统 │ └─────────────┘
MCP 的核心价值 :
工具与 Agent 解耦 :同一个文件系统 MCP Server 可以被 Claude Desktop、Cursor、自定义 Agent 等任何 MCP 客户端使用
生态复用 :社区可以贡献各种 MCP Server(GitHub、Slack、Postgres、浏览器自动化…),你只需配置
标准协议 :统一的工具发现(tools/list)、调用(tools/call)和资源访问(resources/read)
什么时候用 MCP :
工具需要在多个 Agent 之间共享
工具需要独立部署和管理
想利用社区已有的 MCP 生态
6.3 第三代:Plugin 系统 比 MCP 更进一步,Plugin 不仅包含工具,还包含运行环境、权限声明、UI(可选)。
1 2 3 4 5 6 7 8 9 10 { "name" : "github-plugin" , "version" : "1.0.0" , "tools" : [ "create_issue" , "list_prs" , "review_code" ] , "permissions" : [ "repo:*" , "user:email" ] , "config" : { "base_url" : "https://api.github.com" , "rate_limit" : 5000 } }
对比三种模式 :
维度
Function Calling
MCP
Plugin
复杂度
低
中
高
复用性
无
跨 Agent
跨 Agent + 生态
部署方式
内联
独立进程
独立进程 + 沙箱
权限模型
无
基础
细粒度声明
适用规模
个人项目
团队/企业
平台/生态
学习成本
低
中
高
七、实战案例:为前端项目搭建工具集 理论够多了,来做点实际的。假设你有一个前端项目,需要为 AI 编程助手设计一套工具。以下是完整的设计流程。
7.1 第一步:梳理工具需求矩阵 先列出所有需要的操作,按粒度和类型分类:
工具名
类型
粒度
必要性
list_files
只读
原子
必须
read_file
只读
原子
必须
search_code
只读
原子
必须
write_file
写入
原子
必须
edit_file
写入
原子
必须
run_command
写入
原子
必须
install_package
写入
原子
推荐
run_tests
写入
复合
推荐
lint_check
只读
原子
推荐
format_code
写入
原子
可选
check_types
只读
原子
可选
deploy_preview
写入
复合
可选
7.2 第二步:设计核心工具 以 edit_file 为例,设计一个完整的工具定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 { "name" : "edit_file" , "description" : "精确编辑文件中的某段内容。" + "当需要修改现有文件时使用此工具(而非 write_file,后者用于创建新文件或完全覆写)。" + "通过 old_string 定位要修改的位置,替换为 new_string。" + "注意:old_string 必须在文件中唯一存在,否则操作失败。" + "建议先使用 read_file 获取文件内容,确认 old_string 精确匹配后再调用。" , "parameters" : { "type" : "object" , "properties" : { "file_path" : { "type" : "string" , "description" : "要编辑的文件路径(相对于项目根目录)。例如 'src/components/Header.tsx'" } , "old_string" : { "type" : "string" , "description" : "要被替换的原始文本。必须与文件中的内容完全一致(包括空格、缩进)。" + "如果有多处匹配,操作会失败——请使用更长的上下文使其唯一。" } , "new_string" : { "type" : "string" , "description" : "替换后的新文本。如果与 old_string 完全相同,操作会被拒绝(无效编辑)。" } , "replace_all" : { "type" : "boolean" , "description" : "是否替换所有匹配的 old_string。默认 false(仅替换第一个)。" + "当需要全局重命名时设为 true。" , "default" : false } } , "required" : [ "file_path" , "old_string" , "new_string" ] } }
实现层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 def edit_file (file_path, old_string, new_string, replace_all=False ): if not file_path.startswith(PROJECT_ROOT): return error("PERMISSION_DENIED" , f"只能编辑项目内的文件: {PROJECT_ROOT} " ) content = Path(file_path).read_text() matches = content.count(old_string) if matches == 0 : return error("NOT_FOUND" , f"未找到 old_string。请确认内容完全一致(含空格和缩进)。" f"建议:read_file('{file_path} ') 获取文件精确内容。" ) if not replace_all and matches > 1 : return error("AMBIGUOUS_MATCH" , f"old_string 在文件中出现了 {matches} 次。" f"建议:1) 提供更长的上下文使其唯一 2) 设置 replace_all=true 替换全部。" ) if replace_all: new_content = content.replace(old_string, new_string) else : new_content = content.replace(old_string, new_string, 1 ) backup_path = Path(file_path).with_suffix('.bak' ) Path(file_path).write_text(content) Path(file_path).write_text(new_content) return { "success" : True , "data" : { "file" : file_path, "replacements" : 1 if not replace_all else matches, "backup" : str (backup_path) } }
7.3 第三步:工具的发现策略 工具太多,模型会”选择困难”。50 个工具时,模型的准确率会比 10 个工具时显著下降。
解决方案:动态工具注册
不是一次性把所有工具都注册,而是根据当前上下文动态暴露:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class ToolManager : def __init__ (self ): self .all_tools = load_all_tools() def get_active_tools (self, context ): """根据上下文过滤可用的工具""" active = [] if context.get("mode" ) == "code" : active = [t for t in self .all_tools if t.category in ("file" , "terminal" , "test" )] elif context.get("mode" ) == "plan" : active = [t for t in self .all_tools if t.is_read_only()] elif context.get("mode" ) == "deploy" : active = [t for t in self .all_tools if t.category in ("build" , "deploy" )] return active[:20 ]
7.4 第四步:工具使用的监控与优化 工具质量好不好,数据说了算。你需要监控这些指标:
指标
说明
健康值
工具选择准确率
模型选了正确的工具吗?
> 90%
参数正确率
参数填对了吗?
> 85%
工具执行成功率
工具调用成功了吗?
> 95%
平均调用次数/任务
完成任务用了几个工具调用?
依任务复杂度
重复/无效调用率
有多少调用是冗余的?
< 10%
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "timestamp" : "2026-07-02T09:30:00Z" , "tool" : "edit_file" , "params" : {"file_path" : "src/App.tsx" , "old_string" : "import React" , ...}, "result" : "success" , "duration_ms" : 45 , "task_id" : "task_12345" }
八、常见反模式与避坑指南 反模式一:万能工具 1 2 3 4 5 6 7 8 9 { "name" : "do_whatever" , "description" : "执行任何操作" , "parameters" : { "action" : { "type" : "string" , "description" : "要做什么" } , "data" : { "type" : "object" , "description" : "相关数据" } } }
问题 :模型不知道该传什么参数,瞎猜的结果是 80% 的调用都会失败。
正确做法 :拆成原子工具。
反模式二:工具重名或语义重叠 1 2 3 { "name" : "read_file" , "description" : "读取文件内容" } { "name" : "get_file" , "description" : "获取文件" }
问题 :模型在两者之间摇摆不定,增加选择错误率。
正确做法 :明确区分语义,或合并为一个。
反模式三:隐藏副作用 1 2 3 4 5 def create_user (name ): user = db.insert(name) send_welcome_email(user.email) log_to_analytics(user.id ) return user
问题 :工具的”表面行为”和”实际行为”不一致。模型以为只是创建用户,实际还发了邮件。
正确做法 :副作用显式化。要么拆成独立工具,要么在工具描述中明确说明。
反模式四:忽略版本兼容 1 2 3 4 5 6 7 def deploy (env ): ... def deploy (env, strategy="rolling" , health_check=True ): ...
问题 :工具升级后,模型的调用模式可能不兼容,导致大量失败。
正确做法 :
新增参数用可选 + 默认值,不破坏旧调用
用 deprecated: true 标记废弃参数
工具版本号化,迁移时做灰度
九、总结 工具是 Agent 的手和脚,工具的数量、粒度、描述质量、错误处理 直接决定了 Agent 能干多少活、干得多好。
核心原则回顾
描述即产品 :工具描述是给 LLM 看的 UI,值得投入至少 30% 的开发精力
原子优先,复合慎用 :默认用小粒度工具,只在模型频繁犯错时引入复合工具
参数 ≤ 3 必填 :超过 3 个必填参数,模型填充准确率断崖下降
错误信息是模型的路标 :不仅说”失败了”,还要说”为什么失败、接下来怎么处理”
安全校验在服务端 :永远不要信任模型的参数,路径、权限、注入攻击全都要服务端校验
工具即产品,需要迭代 :监控工具的使用数据,基于数据持续优化
设计工具集的五步流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 第一步:需求梳理 ├── 列出所有需要的操作 ├── 按类型和粒度分类 └── 标注必要性和优先级 第二步:原子化拆解 ├── 每个工具只做一件事 ├── 明确输入/输出/副作用 └── 检查是否有语义重叠 第三步:编写描述 ├── 场景:什么时候用 ├── 行为:做什么 ├── 参数:怎么传(≤3个必填) ├── 输出:返回什么 └── 限制:注意什么 第四步:实现与安全 ├── 路径/域名白名单 ├── 权限校验 ├── 结构化错误 ├── 超时与重试 └── 幂等性保证 第五步:监控与迭代 ├── 记录每次调用日志 ├── 分析选择准确率/参数正确率 ├── 识别反模式 └── 持续优化描述和实现
掌握了这套方法论,你就可以为任何项目——无论是一个前端脚手架工具,还是企业级 AI 平台——设计出高质量的 Agent 工具集。
延伸阅读
本文写于 2026 年 7 月 2 日,所有设计模式基于当前主流 LLM 工具调用能力(GPT-4、Claude、Gemini 系列),适用于 Function Calling、MCP 及各类 Agent 框架。