Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

3.3 自定义工具的设计与实现

🎯 本节目标:学会如何设计高质量的工具——不是"怎么写代码",而是"怎么思考设计"。


核心观点:工具设计质量 > 模型能力

很多 Agent 表现不佳的原因,不是模型不够聪明,而是工具没设计好

  • 工具描述模糊 → LLM 不知道什么时候该用 → 要么不用,要么乱用
  • 参数没有验证 → LLM 传入非法值 → 工具崩溃
  • 错误信息不清晰 → LLM 无法理解发生了什么 → 不能自我纠错

一个设计精良的工具 + GPT-4.1-mini 的效果,往往优于设计糟糕的工具 + GPT-4.1。工具就是 Agent 的“感官器官”——眼睛(搜索)、耳朵(监听)、手(操作)。感官越灵敏、信号越清晰,大脑的决策就越准确。


设计原则一:单一职责

一个工具只做一件事,做好这一件事。

这不是软件工程教条——它对 Agent 有直接的量化影响。

一个“瑞士军刀式工具”通常会把搜索、分析、摘要、格式转换等能力塞进同一个入口里。结果是模型每次调用时都要同时判断:要不要分析、要不要摘要、返回什么格式、取多少结果、参数之间是否互斥。决策空间越大,模型越容易漏填参数或传错值。

更好的方式是拆成多个职责清晰的小工具:

工具只负责什么为什么更适合 Agent
search_web搜索网页并返回候选结果模型只需要判断“是否需要搜索”
analyze_data对已有数据做分析输入输出边界清晰
summarize_text对文本做摘要不会和搜索、分析逻辑混在一起

为什么这很重要? 因为 LLM 选择工具时是在做多分类问题。每增加一个可选参数或功能分支,决策空间就指数级膨胀。3 个单职责工具的组合空间是 种用法;而 1 个多功能工具的参数组合可能达到几十种,模型更容易出错。


设计原则二:描述写给 LLM 看

工具的 description 不是给人看的文档——它是给另一个 AI 读的"使用说明书"。这意味着写法要完全不同于传统 API 文档。

好的 vs. 不好的描述对比

维度不好的好的
功能说明"处理邮件""向指定邮箱发送邮件,支持 HTML 格式"
使用时机(缺失)"仅在用户明确要求发送时调用"
排除时机(缺失)"不适用于读取邮件或查询收件箱"
参数格式{"email": string}"邮箱地址,如 boss@company.com"

描述的黄金公式

一句话功能说明(动词 + 对象 + 核心特征)→ 适用场景(何时用)→ 不适用场景(何时不用)→ 返回值格式(让 LLM 知道拿到结果后该怎么解读)


设计原则三:输入必须验证

LLM 生成的参数不总是合法的。它可能传入:

异常输入典型问题工具应该怎么处理
空字符串用户没有提供必要信息返回“缺少必填字段”,提示需要补充什么
不完整邮箱,如 user@example格式不符合预期返回期望格式,如 user@example.com
超长文本可能导致超时、内存问题或费用暴涨设置长度上限,并说明如何缩短
缺失必需参数模型漏填字段返回字段名和补充建议
类型错误期望字符串却传了对象返回期望类型和实际类型

最小可行的输入验证模式

不需要一上来写复杂框架,先建立这个原则:工具入口必须先验证,再执行。 一个可靠的工具至少要完成四件事:

  1. 类型检查:参数是不是期望的类型。
  2. 格式检查:邮箱、日期、URL、股票代码等是否符合格式。
  3. 范围检查:数量、时间跨度、文本长度是否超过安全范围。
  4. 错误转译:把底层异常转成人和模型都能理解的提示。

如果使用 Python,后续实战中可以用 Pydantic、dataclass 或框架自带 Schema 来完成这些校验;如果使用 TypeScript,也可以用 Zod 等工具。这里先记住目标:不要相信模型传来的参数一定正确。

为什么推荐 Schema 校验而不是到处手写判断?

  • 类型声明即验证规则,文档和校验更容易保持一致
  • 错误信息可以自动包含字段名和原因
  • 与 Structured Outputs、Function Calling 等机制天然配合

设计原则四:错误信息是给 LLM 看的

这是最容易被忽略的原则:

错误信息LLM 能否自我修正原因
Invalid input很难不知道哪个字段错了,也不知道应该怎么改
“邮箱地址格式不正确,期望如 user@example.com,收到 user@example容易模型知道错误字段、期望格式和当前值
“搜索超时,请缩短关键词或稍后重试”容易模型可以换关键词、减少范围或告知用户

好的错误信息能让 LLM 自行纠错:

  • 参数格式错 → LLM 修正格式重试
  • API 超时 → LLM 换更简单的查询
  • 权限不足 → LLM 告知用户需要授权

设计原则五:考虑缓存

Agent 在一次推理中可能多次调用同一个工具:

用户:“北京天气怎么样?适合户外活动吗?湿度大吗?” → LLM 可能在推理过程中分别查询天气 2-3 次 → 如果每次都调付费 API → 浪费錢和时间

解决方案:对相同参数的结果做短期缓存(TTL 通常设为几分钟到几小时)。

💡 缓存不需要自己实现。大多数 Agent 框架(LangChain、CrewAI)都有内置的工具缓存机制。了解这个概念就够,具体实现交给框架。


一个完整的工具设计清单

在把任何新工具接入 Agent 之前,用这张清单自检:

□ 单一职责:这个工具是否只做一件事?
□ 名称清晰:函数名能否一眼看出功能?(get_weather ✅ / process ✅)
□ 描述完整:是否包含 适用/不适用/返回格式?
□ 参数有类型注解 + 描述?
□ 输入验证:Pydantic 或等价的校验逻辑?
□ 错误处理:所有异常都被捕获并转为可读字符串?
□ 安全评估:最坏情况下这个工具能造成什么破坏?
□ 必要时加了缓存?

📝 动手练习

练习:为以下需求设计一个工具 Schema(只写 JSON 定义,不写函数体):

需求:一个股票查询工具,可以查股票的当前价格、涨跌幅、市值。支持 A 股(带 .SS/.SZ 后缀)和美股(纯字母代码)。

参考方案

一个合格的股票查询工具 Schema 应该包含这些要素:

字段建议设计作用
工具名get_stock_info名称明确,模型容易选择
功能说明查询股票实时行情数据告诉模型这个工具能做什么
适用场景当前价格、涨跌幅、市值等实时指标帮助模型判断何时调用
不适用场景历史 K 线、技术指标计算、投资建议避免模型把所有股票问题都丢给它
symbol 参数字符串,支持 AAPL600036.SS000001.SZ0700.HK明确不同市场代码格式
metrics 参数可选列表,如价格、涨跌幅、市值、市盈率限制可查询指标,减少幻觉
返回格式symbolpricechange_percentmarket_capcurrency让模型知道如何解读结果

关键点:

  1. description 中明确区分了适用和不适用场景
  2. symbol 的描述包含了不同市场的代码格式示例
  3. metrics 用固定枚举限制可选值范围,减少模型幻觉

小结

原则核心要点
单一职责一个工具一件事,降低 LLM 决策复杂度
描述质量写给 LLM 看,不是给人看——包含适用/不适用场景
输入验证用 Pydantic 做类型安全 + 格式校验
错误信息返回给 LLM 的结构化错误,而非抛异常
缓存策略对重复调用的耗时/收费工具加缓存
安全自检每个工具上线前评估最坏情况的影响

下一节:3.4 工具描述的编写技巧