Agent 工具调用 Prompt:把能不能调用交给规则

0阅读10分钟

普通 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_idcustomer_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 在收到用户请求后,按这个流程判断是否调用、调用哪个工具、需要哪些确认:

流程图画布 · 115%
Mermaid 流程图加载中...

这个流程里有三个关键分支:参数校验、风险分级、失败处理。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 操作是否等待用户确认后才执行

参考资料

评论 0

0 / 1000