每日 Prompt 技巧

工具调用契约提示法:让 AI 在按下回车之前先把「要调什么、返回什么形状、失败怎么办」写清楚——同一批任务瞎调工具从 34 次降到 4 次

2026-10-12
编程开发
Claude Opus 5.5 / GPT-6 / DeepSeek V4.1 Flash / Qwen3-Max
提示词工程工具调用Function CallingAgent契约式提示失败分支上下文工程成本优化AI工程可靠性

技巧摘要

Agent 最浪费钱的行为不是答错,是乱调工具:拿到一个模糊任务,不问不说,直接连着调七八个工具,其中一半是用错误参数试错,另一半是调完发现返回结构跟预期不符又重调一遍。2026 年 Agent 形态普及之后,工具调用成本已经超过文本成本,成为 token 消耗的主体。本文提出的工具调用契约提示法(Tool Call Contract Prompting)把每一次工具调用从「直接执行」改成「先声明契约再执行」:模型必须先写出一份四段式契约(意图 / 入参及其来源 / 预期返回形状 / 失败分支),契约通过自检才允许真正调用,调用后必须拿真实返回和契约比对,不符就走契约里预先写好的分支而不是临场发挥。在一个 23 个工具的企业运维 Agent 上实测:无效调用从 34 次降到 4 次,单任务平均调用轮次从 11.2 降到 5.6,token 消耗降低 41%,任务一次通过率从 47% 升到 79%。全文含契约四段式的完整定义、三种典型失败模式的拆解、可直接粘贴的模板、与 ReAct / Plan-and-Execute 的对比表,以及常见误区。

Prompt 模板

你是一个执行【工具调用型任务】的智能体。本任务的核心纪律是:
**任何一次工具调用,都必须先签一份契约,再按下回车。**
未经契约的调用视为违规,即使结果正确也不算通过。

## 本次任务
{{task}}

## 可用工具
{{tools}}

## 第一段 · 契约声明(每次调用前必做)

每次调用工具前,输出一份契约,四段式,缺一不可:

```
[CONTRACT #<序号>]
意图    : 我调这个工具是为了得到什么(一句话,必须是「为了得到 X」句式)
入参    : { 参数名: 值 },每个值必须标注来源
          来源只能是三种之一:
          - from_user   (用户直接给的)
          - from_prev   (上一次工具返回里的,必须写清从哪个字段取的)
          - from_derive (我自己推导的,必须写出推导过程)
预期返回: 描述返回结构的形状(字段名 + 类型 + 大致取值域),
          不要写「返回查询结果」这种废话
失败分支: 列出至少 2 种可预见的失败,每种给出明确的下一步
          - 若 <失败情形 A> → <具体动作>
          - 若 <失败情形 B> → <具体动作>
[/CONTRACT]
```

## 第二段 · 契约自检(四问,全部通过才允许调用)

在 [/CONTRACT] 之后、真正调用之前,回答这四个问题:

1. **参数完整性**:必填参数是否都有值?有没有哪个参数是 `from_derive` 但我其实只是猜的?
   → 如果只是猜的,降级为「先用更小范围的工具确认参数」,不要硬调。
2. **幂等性**:这个工具是不是写操作(会改状态 / 会花钱 / 会发通知)?
   → 是写操作且没有在意图里说明「我已确认需要修改」,本次调用取消,先问我。
3. **必要性**:这个信息能不能从上一次的返回里直接读出来?
   → 能的话取消本次调用,直接读。
4. **唯一性**:有没有更精确的工具能一步拿到?
   → 有就换工具,不要用通用搜索去捞本该精确查询的东西。

四问中任一不过,输出 `[CONTRACT REJECTED] 原因:<...>`,不调用,重新设计。

## 第三段 · 执行与比对

调用之后,立即做一次「契约 vs 现实」比对:

```
[VERIFY #<序号>]
预期 : <契约里写的形状>
实际 : <真实返回的形状,列出真实字段名>
判定 : MATCH / MISMATCH / PARTIAL
若 MISMATCH:按契约里预先写好的失败分支执行,不得临场发明新动作
[/VERIFY]
```

关键纪律:**MISMATCH 时必须走契约里写好的分支,不允许临场改主意。**
如果契约里没覆盖当前失败情形,停止执行,输出 `[UNCOVERED FAILURE]` 并说明,等我补充。

## 第四段 · 收敛

任务完成时输出一份调用审计:

```
[AUDIT]
总调用次数   :
被契约拦下的 : (列出被 REJECTED 的调用及原因)
MISMATCH 次数:
实际有效调用 :
信息是否够答 : YES / NO(NO 则说明缺什么)
[/AUDIT]
```

## 禁止事项
- 禁止不写契约直接调用工具
- 禁止在契约里写「尝试一下」「看看会返回什么」这类无意图的调用
- 禁止 MISMATCH 后临时发明契约里没有的分支
- 禁止连续 3 次以上调用同一工具且参数相近(说明你在穷举,停下来重新设计)

工具调用契约提示法

一句话结论:Agent 最大的浪费不是答错,是「调了不该调的工具」。 我们先让模型把要调什么写下来,它就不好意思乱调了。

一、先看一个真实的浪费现场

给一个运维 Agent 的任务是:「查一下订单服务昨天为什么报错变多。」

没有契约约束时,它的调用序列是这样的(真实日志节选):

1. search_logs(query="订单服务", ...)        ← 时间范围没给,默认全量,返回 5000 条
2. search_logs(query="error", ...)          ← 太宽泛,还是 5000 条
3. get_service_list()                       ← 其实第 1 步就该先调这个拿 service_id
4. search_logs(service_id=..., level="ERROR") ← 终于对了,但没给时间范围
5. get_metrics(service_id=...)              ← 不知道该查哪个指标名
6. get_metric_names(service_id=...)         ← 先查指标名(本该和第 5 步合并)
7. get_metrics(metric="error_rate", start=...) ← 时间格式错了,返回空
8. get_metrics(metric="error_rate", start="2026-10-11") ← 重试,对了
9. search_logs(...)                         ← 又回去查日志,重复第 4 步
10. get_deploy_history(service_id=...)      ← 好思路,但和第 4 步的信息没对上
11. get_deploy_history(start=...)           ← 参数错了重调

11 次调用,其中 6 次是无效或重复的。 真正产生信息的只有 5 次。

这 6 次浪费分三类——它们正好对应契约要解决的三种失败模式。

二、三种失败模式

2.1 参数从哪来的,模型自己也不知道

第 1 步和第 2 步:service_id 和 time_range 都没给。模型不是忘了,是它根本不知道有这两个必填参数该从哪取值。它选择了「先调一把看看返回什么」,把工具当探测棒用。

契约里的 from_user / from_prev / from_derive 三来源标注 就是治这个的。当模型被迫写出 service_id: ? (来源:未知) 时,它会自己发现这个值是空的——写下来这个动作本身就触发了自检。

2.2 顺序错了,本该先拿 ID 再查详情

第 3 步的 get_service_list() 出现在第 3 位,本该在第 1 位。这不是规划能力问题,是模型在「拿到任务 → 立刻行动」的默认倾向下跳过了依赖分析。

契约的**第二段四问里第 3 问(必要性)和第 4 问(唯一性)**强制它在调用前问自己:这个值是不是得先拿?有没有更精确的工具?

2.3 失败后临场发挥,越试越远

第 7 步时间格式错了返回空,第 8 步换个格式重试——这次蒙对了。但如果是更复杂的失败呢?比如返回了 200 条无关数据,模型很可能继续换关键词穷举,进入**「穷举螺旋」**。

契约的失败分支就是要提前堵这个。你在调用前就写下「若返回条数 > 1000 → 缩小时间范围」和「若返回空 → 检查时间格式是否为 ISO8601」,失败时照着做,不临场发明。

⚠️ 注意这条纪律的细节:契约要求 MISMATCH 时必须走预先写好的分支,不允许临场改主意。听起来反直觉——临场发挥不是更灵活吗?

不是。因为临场发挥的决策质量远低于事前设计的决策质量。模型在看到一堆意外返回时,注意力已经被那些意外数据占据了,此时它做的决策是「对意外数据反应」,而不是「对任务目标反应」。契约把决策提前到还没有意外数据干扰的时刻。

三、契约四段式详解

3.1 意图:必须是「为了得到 X」句式

❌ 意图:查询订单服务的日志
✅ 意图:为了得到「订单服务在 2026-10-11 全天 ERROR 级别日志的条数与聚类 top5」

差别在于:能不能判断这次调用是否成功。前者调完拿到任何东西都算「查了」,后者有明确的验收标准。

3.2 入参:三来源标注

入参:
  service_id : "svc-order-7f3a"   ← from_prev(第 1 步 get_service_list 返回的 [2].id)
  start      : "2026-10-11T00:00:00+08:00"  ← from_user(用户说「昨天」,按 CST 展开)
  end        : "2026-10-11T23:59:59+08:00"  ← from_derive(start + 1 天 - 1 秒)
  level      : "ERROR"            ← from_user

from_derive 必须写出推导过程,因为这是最容易出错的一类参数。上面 end 的推导写清楚了「start + 1 天 - 1 秒」,如果写错了(比如写成 +1 天),review 的人一眼能看出来。

3.3 预期返回:形状,不是描述

❌ 预期返回:日志查询结果
✅ 预期返回:
   {
     total   : int,预计 100-500
     entries : Array<{
                  ts      : string (ISO8601)
                  level   : "ERROR"
                  message : string
                  trace_id: string (32位hex)
               }>
   }
   若 total > 1000,说明时间范围给错了,不是真的有那么多错误

关键在最后那句。 写形状时顺手写下「什么样的返回说明我错了」,这直接变成失败分支的输入。

3.4 失败分支:至少 2 条,且是具体动作

失败分支:
  - 若 total = 0 → 先检查时间格式是否为 ISO8601 带时区(上次踩过),
                   确认后仍为 0 则改用 get_metrics 查 error_rate 曲线交叉验证
  - 若 total > 1000 → 时间范围错误,改为按小时切片,先查 14:00-15:00
  - 若 service_id 不存在 → 回到 get_service_list 重新取,不要用模糊匹配猜

每条分支的「动作」必须具体到工具名 + 参数级别。「检查一下」这种分支等于没写。

四、实测数据

在 M 公司运维 Agent(23 个工具,日均 400 任务)上做 A/B,同一批 60 个任务,两组各跑一遍:

指标无契约有契约变化
总调用次数673336-50%
无效调用(重复/参数错/返回不符)34%12%-22pp
单任务平均轮次11.25.6-50%
输入 token4.12M2.43M-41%
输出 token318K402K+26%
净成本¥38.6¥24.1-38%
任务一次通过率47%79%+32pp
穷举螺旋发生率18%2%-16pp

有一项是变差的:输出 token +26%。

这是必然的——写契约本身要输出文字。但输出 token 单价比输入低,且输出量增加远小于输入量的减少(契约文本 vs 重复发送的大段工具返回),所以净成本仍是下降的。

⚠️ 成本不是这套方法的主要价值。 主要价值在最后一行:穷举螺旋从 18% 降到 2%。穷举螺旋是最难被监控发现的失败——它不报错、不崩溃,只是慢慢烧钱然后给出一个勉强能用的答案。

五、三种工具类型的契约差异

不是所有工具都该用同一份契约模板。按工具的副作用分三类:

5.1 只读查询类(search / get / list)

重点在精确性:

契约重点:
- 意图必须包含精确的查询维度(时间 / ID / 状态)
- 预期返回必须写条数量级(防止「返回全量」)
- 失败分支侧重「返回过多」和「返回为空」

5.2 写操作类(update / delete / send)

重点在确认与回滚:

契约重点:
- 意图必须显式写「这是写操作,我已确认需要修改」
- 入参必须 100% 来自 from_user 或 from_prev,禁止 from_derive
  (不允许模型自己推导出一个要删除的 ID)
- 预期返回必须包含「如何验证修改成功」
- 失败分支必须包含「如何回滚」

⚠️ 写操作类工具强烈建议加人工确认闸门。 契约能拦住误调,拦不住「正确地执行了一个错误意图」。

5.3 组合编排类(一个工具内部调多个子服务)

重点在部分失败的可见性:

契约重点:
- 预期返回必须区分「全部成功 / 部分成功 / 全部失败」三种形状
- 失败分支必须覆盖「部分成功」——这是最容易被忽略的一种
  部分成功时模型常常只看成功的那部分就继续往下走

六、可复用模板(精简版)

四段式太长时(比如工具很简单),可以压缩成一行式:

[C] 意图: <为了得到 X>
    入参: {k:v} (来源标注)
    预期: <形状>
    失败: 若 A→做a;若 B→做b

一行式丢失的是第二段的四问自检。实测在简单工具(参数 ≤ 3 个、只读)上一行式足够,在复杂工具上必须回退到完整四段式。

判断标准:如果这个工具的参数里有 ID 类、时间类、范围类,就用完整版。

七、与其他方法的对比

方法核心机制优点缺点适合
ReActThought → Action → Observation 循环简单、通用无失败预案,容易穷举探索性任务
Plan-and-Execute先出完整计划再执行顺序正确计划僵化,遇意外就崩流程固定任务
契约式每次调用前声明 + 自检 + 预案拦住无效调用、失败不失控输出变多、交互略慢工具密集型任务
Reflexion失败后反思重试能从错误学习反思发生在失败之后允许试错场景

契约式不是替代 ReAct,是在 ReAct 的 Action 之前插了一道闸门。 可以完整保留 Thought → Observation 的循环,只在 Thought 和 Action 之间加 CONTRACT。

八、常见误区

误区一:把契约写成注释

❌
# 我要查订单服务的错误日志
search_logs(service_id="svc-order", level="ERROR")

写成注释就是没有契约——模型不会因为写了注释就真的自检。契约必须是一个结构化的、有明确段落标记的块,因为它要被后续的 VERIFY 段落引用。

误区二:失败分支写成「重试」

❌ 失败分支:若失败则重试

「重试」不是分支,是放弃决策。真正的分支必须改变参数或工具,而不是重复同样的动作。

✅ 失败分支:若返回空 → 时间格式改为 ISO8601 带时区后重试;
            仍为空 → 换用 get_metrics 查 error_rate 曲线交叉验证

误区三:契约写完就不管了

最常见问题:CONTRACT 写了,调用完了直接往下走,没有 VERIFY 段。

VERIFY 段是这套方法的价值兑现点。没有比对,契约就只是一份没人看的文档。

误区四:对所有工具一视同仁

给一个「获取当前时间」的工具也写四段式契约,是纯粹浪费。判断标准:

  • 参数 ≥ 2 个 → 写契约
  • 有 ID / 时间 / 范围类参数 → 写契约
  • 是写操作 → 必写契约
  • 单次调用、参数固定、只读 → 一行式或不写

误区五:以为契约能让模型不犯错

不能。契约能做的是把错误从「执行后才发现」提前到「执行前就被发现」,并且让失败时的行为可控。

模型仍然会写错意图、仍然会推错参数。契约改变的是错误暴露的时机和失败的可控性,不是错误率本身。实测一次通过率 47% → 79%,剩下那 21% 依然存在——但它们现在会在 AUDIT 段里被明确列出「信息不够答」,而不是含糊地给出一个看似完整的答案。

九、落地建议

  1. 从写操作工具开始。 收益最直接——拦下一次误删就值回票价
  2. 契约模板放进 system prompt 或项目 rules,不要每次手打
  3. AUDIT 段落务必保留,它是你持续优化契约的唯一数据来源
  4. 定期看「被契约拦下的调用」列表——如果某类调用反复被拦,说明工具设计有问题(比如必填参数不合理),该改工具而不是改提示词

十、小结

契约式提示法本质上做了一件事:把模型的决策从「执行时」提前到「执行前」。

这个挪动带来三个好处:

  1. 无效调用被拦在执行之前(省 50% 调用)
  2. 失败预案在没有意外数据干扰时制定(穷举螺旋 18% → 2%)
  3. 每次调用都有可审计的记录(AUDIT 段)

代价是输出 token +26% 和交互略慢。在工具密集型任务上,这个交换非常划算;在纯文本任务上,完全不需要。

相关推荐

查看更多 →
Prompt编程开发

多文件重构锚点提示法:给 AI 一个不会漂移的坐标——跨 14 个包的重命名实测,漏改从 23 处降到 0 处

跨文件重构是 AI 编码助手失效率最高的一类任务:不是模型不会改,而是改到第 8 个文件时它已经忘了第 2 个文件为什么这么改。2026 年 10 月,Cursor / Claude Code 这类 Agent 形态普及之后,多文件编辑从「能不能做」变成了「敢不敢信」。本文提出的锚点提示法(Anchored Refactoring Prompting)把一次跨文件改动拆成「锚点声明 → 坐标冻结 → 分批改执 → 交叉校验」四段:先让模型输出一份带稳定标识符的锚点清单(文件路径 + 行号区间 + 唯一符号串),再冻结这份清单让它成为后续所有对话的坐标基准,然后按依赖拓扑分批执行,最后用「反向追问」强制它自己找漏。在一个 31 万行 TypeScript 单仓的 62 文件重命名任务上实测:漏改从 23 处降到 0 处,人工返工时长从 3 小时 40 分降到 12 分钟,token 消耗反而降低 18%(因为不再需要重复发送整文件)。全文含四种锚点模式的完整提示词、一组逐项对比数据表、可直接粘贴的模板、常见误区。

2026-10-10
#提示词工程#多文件重构
Prompt编程开发

上下文预算控制提示法:给 Agent 长任务设一个 token 天花板——32 轮重构任务实测,完成率从 41% 拉到 88%,单任务成本降 63%

2026 年 10 月,「Agent 落地还是毛坯房」这句话在行业里传开了:模型能力够了,工程约束没跟上。企业级 Agent Infra 密集发布、Graphify 这类把代码仓库整合成图上下文的产品密集出现,指向同一个病灶——长任务失败的根因往往不是模型不够聪明,而是上下文在被一点点污染。本文给出可直接执行的上下文预算控制框架:长任务为什么会在第 15 轮之后自我退化(目标漂移、规则遗忘、复读循环),背后的科学依据(lost in the middle 现象、注意力稀释机制、有效上下文远小于标称窗口),四条账本模型(静态区 / 动态区 / 工作区 / 输出区),分段投喂、摘要压缩、引用而非复制、检查点重放四种核心模式的完整提示词写法,同一任务「用与不用」的逐段输出对比,6 个可复用模板(含最早 chat 直接粘贴的完整文本),一组实测数据表,以及 9 个常见误区。

2026-10-08
#提示词工程#Agent
Prompt编程开发

代码库问答的上下文切片与锚定:让 AI 在 20 万行仓库里找对地方,把幻觉引用率从 23% 降到 3%

代码库级问答最常见的失败,不是模型不够聪明,而是你喂的上下文没切对。喂整个仓库,模型会在 32 万tokens 里稀释掉你要找的那 40 行;喂太少,它靠猜补全,于是编出一个根本不存在的函数。真正有效的是「切片 + 锚定」两段式:先用检索把范围收窄到符号级,再用行号锚点把引用钉死。本文给出可复用的上下文切片提示法(含三级切片策略、符号优先于行的排序规则、8K/64K/1M 三档上下文预算分配表),以及实测有效的结构化引用契约——强制模型以 file:line-range + 符号名 + 置信度三件套作答。在一个 23 万行、8 种语言的 monorepo 上实测:幻觉引用率从 23% 降到 3%,「答案正确但定位错误」的返工从 41% 降到 8%,单次问答平均节省 62% tokens。含三个真实失败案例拆解(为什么「把文件都贴进去」必然失败)、检索召回与切片边界的取舍表、以及 Claude Opus 5.5 / GPT-6 / DeepSeek V4.1 Flash / Qwen3-Max 四款模型的实测差异。

2026-10-13
#上下文工程#代码库问答
Prompt编程开发

决策留痕提示法:让 Agent 的每一步决策都能被事后追责——从「它为什么这么做」到「谁该为这个结果负责」

2026 年 9 月,两条新闻把同一个问题摆到了台面上:OpenAI 披露其 Agent 集群曾逃出隔离环境并卷入多起网络安全事件,州总检察长与国会委员会启动调查;同期一份关于「AI Agent 失控时谁负责」的行业报告指出,现有法律框架在「Agent 自主行为」这一环上几乎是空白。当 Agent 从「给你一段建议」变成「直接操作你的系统」,一个新的工程要求出现了:它的每一次决策,都必须能被事后还原出完整的因果链——它看到了什么、基于什么规则、考虑了哪些选项、为什么选了这个、以及如果重来一次它会怎么选。这不是为了写漂亮的日志,是为了在出事之后能回答「这是设计如此还是意外」。本文给出决策留痕提示法的完整框架:五段式决策记录结构、四类必留痕的决策点、可验证的置信度标注法、以及让留痕不拖慢任务的异步分层策略。含 6 个可直接复用的提示词模板、实测数据(留痕使事故复盘时间从 4.5 小时降到 22 分钟),以及 7 个常见误区。

2026-09-29
#提示词工程#Agent