这是 Agent 学习笔记的第 3 篇,讲工具调用。我一开始卡住的三个术语(stakes、Schema 校验、Few-shot)也在这篇里讲清。
先记住一个判断:
没有工具调用,严格来说不算 Agent。工具是 Agent 的"手和脚",也是工程上最容易出错的地方。
本系列导航
- 00 开篇:Agent 是什么?与 Chatbot / Workflow / RAG 的边界
- 01 推理范式:ReAct / Plan-and-Execute / Reflexion
- 02 记忆系统:四类记忆与冲突处理
- 03 工具调用:Schema 设计与工具幻觉防护(本篇)
- 04 可靠性工程:循环、假终止、多 Agent 与安全
- 05 成本、上下文、评测与项目故事
一、Schema 怎么写:写"什么时候用",不是"是什么"
反例(差):
get_weather:获取天气正例(好):
get_weather:当用户问天气、温度、是否适合出行时使用;参数 city 必填,如"北京"为什么差别这么大?
因为 Schema 是给模型看的,模型要根据它决定"该不该调、调哪个"。只写"获取天气",模型不知道"适不适合跑步"这种间接意图该不该调它;写上触发场景,意图匹配就准了。
我的理解:工具描述本质上是"路由提示词"。写得好,模型选工具就准;写得含糊,就会出现"该调不调"和"乱调"。
Schema 的结构(用天气工具举例)
{"name":"get_weather","parameters":{"city":{"type":"string","required":true,"description":"城市名,如北京"},"date":{"type":"string","required":false,"description":"日期,默认今天"}}}它规定了三件事:
- 工具名是
get_weather; city必须是字符串,必填;date是字符串,可选。
二、三个高频术语(我当时问的)
1. stakes = 风险 / 利害关系
原意是赌博里押的钱,引申为"这件事搞砸了后果有多严重"。
- high stakes:高风险,搞错了后果很严重;
- low stakes:低风险,搞错了也没啥大不了。
| 场景 | stakes | 原因 |
|---|---|---|
| 推荐餐厅 | low | 推荐错了,用户换一家就行 |
| 推荐电影 | low | 不喜欢就不看 |
| 修改用户昵称 | low | 改错了再改回来 |
| 退款 500 元 | high | 钱的事,错了有损失 |
| 医疗建议 | high | 可能影响健康甚至生命 |
| 法律意见 | high | 可能影响官司 |
| 删除生产数据库 | high | 数据没了就没了 |
| 支付转账 | high | 钱直接出去 |
为什么高 stakes 要转人工?因为 Agent 会犯错:低 stakes 犯错可以容忍,高 stakes 犯错代价太大。
面试口述版:“stakes 就是风险等级。high stakes 指搞错了后果严重的场景,比如涉及金额、医疗、法律、安全,这类操作不能完全交给 Agent,需要人工审批或兜底。我们系统退款超过 500 元就转人工。”
补一句加分:stakes 不仅用于"转人工",也用于"要不要自动更新记忆"(见上一篇)。
2. Schema 校验 = 检查参数是否符合定义
Schema可以理解成"表格模板":规定有哪些字段、每个字段什么类型、哪些必填。
Schema 校验就是:Agent 调用工具之前,检查它传的参数是否符合 Schema。
{"tool":"get_weather","parameters":{"city":123}}→city应该是字符串,传了 123(整数)→校验失败,拦截。
{"tool":"get_weather","parameters":{}}→city必填但没传 →校验失败。
用什么做校验:Python 里常用Pydantic:
frompydanticimportBaseModelclassWeatherParams(BaseModel):city:strdate:str="today"try:params=WeatherParams(**agent_output)exceptValidationErrorase:returnf"参数错误:{e}"3. Few-shot = 给几个示例,让模型照着学
| 方式 | 给几个例子 |
|---|---|
| zero-shot | 不给例子,直接问 |
| one-shot | 给一个例子 |
| few-shot | 给几个例子(通常 2–5 个) |
例子:工具调用
Zero-shot:
用户:北京天气 Agent:? ← 模型可能不知道怎么调工具Few-shot:
用户:北京天气 正确调用:get_weather(city="北京") 用户:上海明天天气 正确调用:get_weather(city="上海", date="明天") 用户:杭州天气 正确调用:? ← 模型看到前两个例子,就知道第三个怎么调为什么有用:LLM 是"模仿机器"。Few-shot 能告诉模型工具名怎么写、参数格式什么样、什么时候该调。
关键关系(面试爱问):Few-shot 让模型"尽量写对",Schema 校验负责"兜底拦住错的"。两者不是二选一,而是配合使用。
三、工具幻觉:四层防护
工具幻觉 = Agent 编了一个不存在的工具,或者参数乱填。
例子:Agent 想发邮件,生成了{ "tool": "send_email", ... },但系统里只有send_sms。
四层防护:
| 层 | 做法 | 作用 |
|---|---|---|
| ① 白名单 | 只允许调用列表里的工具(allowed_tools: [get_weather, get_order, send_sms]) | 不存在的工具直接拦截 |
| ② Schema 校验 | 检查参数类型、必填项 | 拦住类型/缺参错误 |
| ③ Prompt 约束 | 系统提示写明"只能用以下工具,不要编造" | 从源头降低概率 |
| ④ Few-shot | 给 3 个"用户问 X → 正确调用 Y"的示例 | 让模型模仿正确格式 |
校验失败之后怎么办?(这一步很关键)
不能只是报错,要返回错误 + 可用工具列表:
“工具 send_email 不存在,可用工具:[send_sms, send_wechat]”
Agent 看到后重新规划,改成send_sms。
这就是"可恢复的错误处理":拦截的目的不是让任务失败,而是把模型导回正确路径。
四、另外两类真实故障
1. 参数类型错误
例子:order_id="12345"是字符串,但 API 要整数。
处理:
- 用Pydantic 自动转换(能转就转);
- 转换失败 →把错误信息交回 LLM 重新生成参数。
2. 工具结果过长
例子:搜索 API 返回 50 篇论文、共 25000 字,直接塞进上下文会挤爆窗口、拉高成本。
处理:只取Top-5、每篇摘要 200 字,总长控制在1000 字左右。
这条我特别有共鸣——它和 RAG 那边"只放最相关的 3–5 个 chunk"是同一个思路:上下文是预算,不是垃圾桶。
五、面试口述版
“工具幻觉是 Agent 生成了不存在的工具名或错误参数。防护四层:白名单限制可用工具、Schema 校验参数类型和必填项、Prompt 约束只能用提供的工具、Few-shot 给正确示例。校验失败时返回错误和可用工具列表,触发重规划。我们系统工具幻觉率从 12% 降到 1%。”
被追问"怎么保证不传错参数"(文档最后留的那道题),我的答法:
“三个角度:Few-shot 让模型照着正确格式写;Schema + Pydantic 做类型和必填校验;校验失败把错误和可用工具返回给 Agent,触发重新生成。另外工具描述要写清’什么时候用’,减少选错工具的概率。”
六、我的复盘
这一篇让我把"工具调用"理解成一条流水线,而不是一个动作:
工具描述(路由提示)→ 模型选择工具 → 参数生成 → Schema 校验 → 执行 → 结果处理(截断/摘要) ↑ ↓ └──── 失败时返回错误并要求重规划 ────┘每一环都有对应的兜底:
| 环节 | 失败模式 | 兜底 |
|---|---|---|
| 选择工具 | 工具幻觉 | 白名单 + Prompt 约束 + Few-shot |
| 生成参数 | 类型错、缺参 | Schema/Pydantic + 重新生成 |
| 执行结果 | 结果过长 | Top-K + 摘要 + 长度上限 |
| 整体 | 反复失败 | 转人工(见下一篇) |
下一篇讲可靠性工程:无限循环与假终止怎么治、多 Agent 的死锁和成本爆炸、以及 Prompt 注入和越权怎么防。