Agent 工具调用 Prompt:把能不能调用交给规则
普通 Prompt 出错只影响文本,工具调用出错影响系统
普通的文本生成 Prompt 写得不精确,最坏的结果是回答不准确,用户重新问一次就能修正。Agent 一旦具备工具调用能力,Prompt 里的任何模糊地带都可能被翻译成一次真实的系统操作:读了一条不该读的用户数据、向客户发了一封措辞不对的邮件、或者把测试订单写进了生产数据库。
工具调用把 Prompt 从「建议」变成了「指令」。这个区别决定了工具调用 Prompt 不能沿用普通生成 Prompt 的写法,它需要更接近接口文档的精确度,同时又保留自然语言对意图的表达。
我在多个项目里反复踩过同一个坑:工具定义写得像 API 文档的缩略版,以为模型能自己推断边界。结果模型确实会调用,但调用方式往往超出预期。问题不在于模型能力不足,而在于 Prompt 没有把规则说清楚。
工具调用 Prompt 的设计原理
从上下文工程的角度理解工具调用
Anthropic 在讨论 Agent 上下文工程时提出一个核心观点:上下文是有限资源,每增加一个 token 都会消耗模型的注意力预算(attention budget)。工具定义、参数说明、调用规则都在争抢同一个上下文窗口。设计工具调用 Prompt 的本质是在有限的上下文里放入高信号密度的规则,让模型在最少的 token 消耗下做出正确的调用决策。
Anthropic 的工具设计指南里有一条很实用的判断标准:如果一个人类工程师拿到你的工具描述后无法确定该不该调用,那模型也不可能做得更好。这个标准把工具 Prompt 的写作从「让模型猜到意图」拉回到「让人能读懂规则」。
五要素框架
BetterYeah 在 Agent 开发指南中总结了 Prompt 设计的五要素:角色定义、任务描述、输入格式、输出约束、异常处理。映射到工具调用场景:
- 角色定义:Agent 在调用工具时代表谁、拥有什么权限
- 任务描述:当前对话目标是什么,工具调用如何服务于这个目标
- 输入格式:每个参数的类型、来源、合法性约束
- 输出约束:调用成功/失败后的输出格式和后续动作
- 异常处理:参数缺失、工具失败、超时、权限不足时的降级策略
OpenAI 的 Prompt Engineering 指南强调了另一个关键点:developer 消息和 user 消息有不同的优先级权重,工具调用规则放在 developer 消息里会比放在 user 消息里更稳定。这意味着工具边界、参数约束这些核心规则应该写在系统级 Prompt 里,而不是期望用户输入来补充。
安全边界不能交给模型
Kalvium Labs 在生产环境 Guardrails 实践中指出了一个重要区分:在 system prompt 里加安全指令不等于有了 Guardrails。Prompt 级别的安全规则是「软约束」,模型可能违反;真正的安全边界需要在应用层做硬校验。
这条原则对工具调用 Prompt 设计的启示是:Prompt 负责表达意图和规则,但参数校验、权限检查、速率限制必须在代码层实现。两层防护缺一不可。
三个真实场景的修复过程
案例一:查询工具越权读取
场景:一个客服 Agent 配备了订单查询工具 get_order,参数包括 order_id 和 customer_id。
问题:上线后 Agent 开始用 customer_id 批量拉取用户的所有历史订单,即使用户只问了一笔订单的状态。工具定义里 customer_id 是可选参数,但 Prompt 里没有说明什么时候该传、什么时候不该传。
原始 Prompt:
# 错误示范:参数用途模糊,缺少调用条件
tools:
- name: get_order
description: 查询订单信息
parameters:
order_id:
type: string
description: 订单编号
customer_id:
type: string
description: 客户 ID模型拿到这个定义后,会尝试用一切可用参数来查询,因为没有任何规则告诉它「用户没提就不要查全部」。
修复后的 Prompt:
# 正确做法:明确参数来源和调用条件
tools:
- name: get_order
description: |
查询单笔订单的详细信息。仅在用户明确提及某笔订单时使用。
parameters:
order_id:
type: string
description: |
订单编号,必须来自用户明确提供的编号或对话上下文中已确认的订单号。
禁止自行构造或猜测订单编号。
customer_id:
type: string
description: |
客户 ID,仅用于服务端权限校验,由系统自动注入。
Agent 不得主动传入此参数。
required_conditions:
- 用户必须提供明确的订单编号,或对话上下文中已有确认的订单号
- 如果用户只问「我的订单」但未指定具体编号,先追问而非调用
forbidden:
- 禁止使用 customer_id 批量查询用户所有订单
- 禁止在用户未提及订单时主动调用此工具修复的核心变化:给每个参数标注了来源和约束条件,增加了「什么时候不该调用」的明确规则。
案例二:写入工具缺少确认环节
场景:一个项目管理 Agent 有创建任务工具 create_task,用户说「帮我建个任务明天提醒我开会」,Agent 直接创建了一个标题为「开会」的任务,没有设置提醒时间,没有确认截止日期。
问题:Prompt 里没有区分只读操作和写入操作的确认策略,模型默认所有工具都可以在理解意图后直接调用。
修复方案——在 Prompt 里引入操作分级:
operation_levels:
read:
description: 查询、搜索、获取信息
confirmation: 不需要确认,可直接调用
examples: [search_orders, get_user_info, list_tasks]
write:
description: 创建、更新、删除资源
confirmation: |
调用前必须向用户确认以下信息:
1. 列出将要执行的操作和影响的资源
2. 列出从用户输入中推断出的参数值,请用户确认
3. 明确标注哪些字段是推断的、哪些是用户原文
examples: [create_task, update_order, delete_comment]
critical:
description: 发送消息、支付、批量操作、不可逆操作
confirmation: |
必须逐字展示将要发送的内容或执行的 SQL,
等待用户明确回复「确认」后才能调用。
examples: [send_email, process_payment, bulk_delete]这段规则把工具按风险分了三个等级,每个等级有明确的确认要求。模型不再需要在「要不要问用户」这个问题上做自由裁量。
案例三:工具失败后编造结果
场景:Agent 调用天气查询工具失败(超时),但回复用户「北京今天晴,25°C」。模型从训练数据里编造了一个看起来合理的结果。
问题:Prompt 里没有任何关于工具失败后行为的约束。模型默认需要给用户一个答案,工具调用失败和「没有答案」之间,它选择了后者。
修复方案——在 Prompt 里增加失败处理规则:
failure_handling:
general_rules:
- 工具调用失败时,不得编造、猜测或使用训练数据中的信息来替代工具返回结果
- 必须明确告知用户工具调用未成功,并说明已知的原因
- 如果是临时性错误(超时、网络),可以建议用户稍后重试
- 如果是参数错误,分析可能的原因并请求用户补充信息
response_template: |
[工具名称] 调用未能成功。
原因:[从错误信息中提取的具体原因,如「请求超时」「参数格式错误」]
建议:[可操作的下一步,如「请检查订单号是否正确」「建议稍后重试」]
escalation:
- 同一工具连续失败 2 次后,停止重试并建议用户联系人工客服
- 失败事件必须记录:工具名称、输入参数、错误类型、时间戳工具调用的决策流程
下面是一个完整的工具调用决策流程。Agent 在收到用户请求后,按这个流程判断是否调用、调用哪个工具、需要哪些确认:
这个流程里有三个关键分支:参数校验、风险分级、失败处理。Prompt 的作用就是让模型在每个分支点做出正确的判断。
四种常见问题的对比分析
工具描述精确度对比
| 维度 | 模糊描述(容易误调用) | 精确描述(可控调用) |
|---|---|---|
| 功能说明 | 「查询订单」 | 「根据用户提供的订单编号查询单笔订单的详情信息」 |
| 参数来源 | 不说明 | 标注每个参数必须来自用户输入还是系统注入 |
| 调用条件 | 不说明 | 明确「必须满足什么条件才能调用」 |
| 禁止场景 | 不说明 | 列出「禁止在哪些场景下调用」 |
确认策略对比
| 操作类型 | 无确认策略 | 分级确认策略 |
|---|---|---|
| 查询订单 | 直接调用 | 直接调用(read 级别无需确认) |
| 创建任务 | 直接调用 | 展示参数后确认(write 级别) |
| 发送邮件 | 直接调用 | 逐字展示内容并等待明确确认(critical 级别) |
| 删除数据 | 直接调用 | 展示影响范围并等待明确确认(critical 级别) |
参数约束策略对比
| 约束方式 | 风险 | 可靠性 |
|---|---|---|
| 不做约束,模型自由生成参数 | 高:可能编造 ID、使用错误枚举 | 低 |
| 在 description 里建议参数格式 | 中:模型可能忽略建议 | 中 |
| 在 Prompt 里强制参数来源 + 应用层 schema 校验 | 低:双重校验 | 高 |
失败处理策略对比
| 处理方式 | 用户体验 | 数据风险 |
|---|---|---|
| 静默失败,编造结果 | 表面上好,实际误导用户 | 高:错误数据可能被采信 |
| 报错但不给建议 | 差:用户不知道怎么办 | 中:不会编造,但问题没解决 |
| 说明原因 + 给出可操作建议 + 记录日志 | 好:用户知道发生了什么以及下一步 | 低:有审计追踪 |
代码对比:四种典型错误及修复
对比一:工具描述缺少边界
# ❌ 模糊:模型不知道什么时候该用、什么时候不该用
- name: search_users
description: 搜索用户
parameters:
query:
type: string
# ✅ 精确:边界清晰,模型能做正确判断
- name: search_users
description: |
根据姓名或邮箱搜索平台注册用户。
仅用于用户主动要求查找特定用户时使用。
禁止用于批量导出或遍历用户列表。
parameters:
query:
type: string
description: |
搜索关键词,必须是用户明确提供的姓名或邮箱片段。
最小长度 2 个字符,禁止传空字符串或通配符。
limit:
type: integer
default: 10
maximum: 20
description: 返回结果数量上限,不可超过 20。注释:好的描述不只说明工具做什么,更重要的是说明不做什么、参数的合法范围是什么。
对比二:写入操作缺少确认
# ❌ 危险:模型收到「帮我退款」就直接调用了
- name: process_refund
description: 处理退款
parameters:
order_id:
type: string
amount:
type: number
# ✅ 安全:工具描述里内嵌确认要求
- name: process_refund
description: |
处理订单退款,属于 critical 级别操作。
调用前必须:
1. 向用户展示退款金额和受影响的订单
2. 获得用户明确的「确认退款」回复
3. 确认退款原因已记录
禁止在用户未明确确认的情况下调用。
parameters:
order_id:
type: string
description: 必须是用户明确提及的订单编号
amount:
type: number
description: 退款金额,不得超过订单实付金额
reason:
type: string
enum: [商品问题, 物流问题, 用户主动取消, 其他]
description: 必须从用户描述中推断或追问得到注释:critical 操作的确认要求直接写在工具描述里,而不是散落在 Prompt 的其他位置,这样模型在决定调用时就能读到。
对比三:失败处理缺失
# ❌ 没有失败处理,模型可能编造结果
- name: check_inventory
description: 查询库存
# ✅ 工具描述里包含失败行为约束
- name: check_inventory
description: |
查询指定 SKU 的实时库存数量。
调用约束:
- sku_id 必须来自对话上下文中已确认的商品信息
- 如果工具返回错误或超时,必须如实告知用户「库存查询暂时不可用」
- 绝对禁止编造库存数字或引用历史数据代替实时查询结果
- 失败时建议用户稍后重试或联系客服注释:把失败行为的约束直接写在工具描述里,让模型在决定如何处理错误时有明确的规则可依据。
对比四:系统级 Prompt 结构
# ❌ 工具规则散落在 Prompt 各处,模型容易遗漏
system: |
你是一个客服助手。
你可以查询订单、创建工单、发送邮件。
调用工具时要注意参数正确。
如果失败了告诉用户。
重要操作要确认。
# ✅ 结构化分段,规则优先级清晰
system: |
## 身份与权限
你是客服助手,可访问以下工具:订单查询、工单创建、邮件发送。
你的权限等同于普通客服角色,无法执行管理员操作。
## 工具调用规则
按以下分级执行确认策略:
- read(查询):直接调用,无需确认
- write(创建/更新):展示参数后请用户确认
- critical(发送/删除/支付):逐字展示内容,等待用户回复「确认」
## 参数约束
- 所有 ID 类参数必须来自用户输入或对话上下文,禁止自行构造
- 枚举类参数必须使用定义中的值,禁止自由发挥
- 参数缺失时先追问,不要用默认值填充业务字段
## 失败处理
- 工具失败时如实告知用户,禁止编造结果
- 记录失败的工具名、参数、错误类型
- 连续失败 2 次后建议转人工注释:结构化的 Prompt 让模型更容易定位和遵循规则。OpenAI 推荐使用 Identity → Instructions → Examples → Context 的四段式结构,上面的写法与之对齐。
工具调用 Prompt 检查清单
设计阶段
- 每个工具都有清晰的一句话描述,说明用途和适用场景
- 每个参数都标注了类型、来源(用户输入/系统注入/上下文提取)和合法性约束
- 已为所有工具标注操作风险等级(read / write / critical)
- 已定义每个风险等级对应的确认流程
- 已列出每个工具的禁止调用场景,而不仅仅是预期用途
实现阶段
- 工具描述写在 system/developer 消息中,不依赖 user 消息补充规则
- 参数约束在 Prompt 层和应用层都有校验(双重防护)
- 枚举参数使用 enum 约束,避免模型自由生成非法值
- 可选参数有明确的默认值说明,且默认值不会产生业务风险
- 工具返回结果有 token 限制,防止上下文溢出
容错阶段
- Prompt 中明确规定工具失败时禁止编造结果
- 已定义失败后的响应模板(包含原因和建议操作)
- 已设定重试策略(重试次数、间隔、退出条件)
- 已定义转人工的触发条件(连续失败、敏感操作等)
- 每次工具调用都会记录审计日志(工具名、参数、结果、时间戳)
验证阶段
- 已用正面用例测试工具能否在正确场景被调用
- 已用反面用例测试工具在禁止场景下不会被调用
- 已测试参数缺失时模型是否追问而非自行填充
- 已测试工具失败时模型是否如实报告而非编造
- 已测试 critical 操作是否等待用户确认后才执行
参考资料
- Writing Effective Tools for AI Agents — Anthropic
- Effective Context Engineering for AI Agents — Anthropic
- Prompt Engineering Guide — OpenAI
- How to Implement LLM Guardrails — OpenAI Cookbook
- LLM Guardrails That Actually Work in Production — Kalvium Labs
- Agent 开发完全指南 — BetterYeah
- Building Guardrails for Large Language Models — arXiv
- AI Agent 全方位优化指南 — CSDN 智能体社区