首页 › cost-hacks › 结构化输出在偷偷加价:三道暗税

当你的 agent 从"会聊天"升级到"会干活"——会调函数、会返回 JSON、会按 schema 填表——账单往往在这个拐点悄悄翻倍。绝大多数人会归咎于"用得多了",但真正的元凶藏在三个你几乎不会盯的地方:每一轮都被重发一遍的工具定义、被动态内容挤爆的 prompt 缓存、以及格式校验失败触发的重试雪崩。本文拆解这三笔"结构化输出暗税"的具体机制、真实成本放大倍数,以及立刻能上手的对冲手法。读完你会知道:一次逻辑调用,为什么实际烧了三遍的钱。

先看一个反直觉的现象

很多团队在做完 function calling / JSON 输出改造后,发现一个奇怪的趋势:输入 token 涨得比输出还猛。你以为只是"功能多了",其实是结构化输出这件事本身在持续吃 input——而 input 是大多数计费模型里最稳、最容易被忽略的固定成本。

下面这张图把一个典型 agent 轮次里"钱花在哪"标了出来,三个加粗节点就是本篇要拆的三道暗税。

flowchart TD
    A[用户一次请求] --> B[组装 system prompt + 工具定义]
    B --> C{暗税一: 工具定义重复过路费<br/>每轮重发 3k–6k token}
    C --> D{暗税二: 缓存命中检查<br/>动态内容插入 → 缓存命中 0.1x<br/>否则 1.0x}
    D --> E[模型生成结构化输出 JSON]
    E --> F{格式/Schema 校验}
    F -->|通过| G[✅ 返回结果<br/>本次成本 = 1x]
    F -->|不通过 JSON 非法| H[暗税三: 重试雪崩<br/>整段重生成]
    H --> E
    F -->|Schema 违反字段| H
    style C fill:#ffd6d6
    style D fill:#ffe8cc
    style H fill:#ffd6d6

三处标红/标黄的节点,任何一处失控都能让你的单次逻辑调用成本从 1x 飙到 2–5x。下面逐个拆。

暗税一:工具定义的"重复过路费"

机制

OpenAI 和 Anthropic 的 function calling,本质是把"工具说明书"当作 input 塞进请求。一个稍微正经的 agent——带 read_file、write_file、list_dir、grep、run_command、web_search、browser_* 等十几个工具——光 JSON schema 描述就能轻松到 3,000–6,000 token。

关键点:这套定义每一轮都要原样发一遍。如果一次任务跑 50 轮,就是 15 万–30 万 token 的 schema 在反复过路。而这些 token 大部分时候是死的、不变的、本可以缓存。

真实成本感

以 Claude Sonnet 级别的输入单价(参考官方定价区间,约 $3 / 百万 input token,请以官网实时价为准)粗算:

同一个工具块,缓存与不缓存差近 10 倍。 工具越多、轮次越深,差距越大。

对冲手法

  1. 给工具块打缓存标记。Anthropic SDK 里在 tools 数组所在的消息块上挂 cache_control: {type: "ephemeral"},让工具定义进入前缀缓存。OpenAI 的前缀缓存是自动的,但前提是你的前缀必须稳定——把 system + tools 放在最前面、保持顺序不变。
  2. 懒加载工具,按任务裁剪。不要每次都把 20 个工具全塞进去。先让一个廉价模型做意图识别,只把本次可能用到的 3–5 个工具传下去。社区里不少 coding agent 框架(如 LangGraph 的动态工具节点)已经在做这件事。
  3. 精简 schema。把 description 写短,去掉用不到的 enum 冗余项和嵌套层级。schema 不是文档,是计费的 input。

暗税二:JSON 模式让 prompt 缓存失效

机制

这一道最隐蔽。前缀缓存的命门是:缓存从请求最前面开始,按字面匹配,一旦中间插入了哪怕一个动态字符,后面的部分全部失效。

很多团队结构化输出改造的"标准姿势"是这样的——每条请求里塞入了大量动态内容:当前时间戳、用户原始 query、检索回来的文档片段、session id……如果这些动态内容被放在 system prompt 和工具定义之前或之间,缓存断点就被顶到后面,原本能命中 0.1x 的静态前缀,全变成 1.0x 全价。

关键比例

跨厂商多年稳定的架构性比例(这是真正值得记的"信息差",比具体几分钱更耐用):

计费项 相对倍数(以基础 input=1x 计)
普通 input token 1.0x
Prompt 缓存写(5 分钟窗口) 1.25x(Anthropic;OpenAI 自动缓存写入另有规则)
Prompt 缓存读(命中) 0.1x
Batch API(离线批) 0.5x
Output token 通常为 input 的 4–5x

读懂这张表,你就懂了整套成本优化的底层逻辑:命中缓存 = 省 10 倍;写缓存 = 多花 25% 但只付一次;批处理 = 打五折。 三者叠加,就是社区里流传的"批处理叠缓存砍九成账单"的数学根基。

对冲手法

  1. 静态前缀沉底,动态内容浮顶。请求结构固定为:[system prompt] → [工具定义] → [few-shot 示例] → [动态:用户输入/检索片段]。把所有不变的东西压在最前面,缓存断点放在动态内容之前。
  2. 单一断点纪律。不要到处乱打 cache_control。一个清晰的断点(静态部分末尾)比三个零散断点命中率高得多。Anthropic 支持最多 4 个断点,但"够用即可"。
  3. 别在缓存段里塞时间戳和随机 id。这是最常见的自毁——你以为"加点上下文更准",其实每次都把缓存写崩。需要时间信息?放在最后那段动态里。

暗税三:格式校验失败的"重试雪崩"

机制

这一道是真正的"暗税"杀手。流程是:模型吐了一段 JSON → 你的代码用 Pydantic / jsonschema 校验 → 字段缺失或类型错 → 整段重新生成。

问题在于:重试是全量重跑的。一次逻辑请求,如果重试 3 次才过,你付的是 3 份 input + 3 份 output,但只得到 1 个结果。这就是开头说的"一次调用烧三遍"。

更糟的是,不少校验库(比如流行的 instructor)默认重试次数偏高、且失败时常常顺手把 max_tokens 和 temperature 调大,等于把单次重试的成本也往上推。

真实成本感

假设某 extraction 接口原始失败率 15%:

校验失败率每升 10 个百分点,账单就肉眼可见地涨。这是最容易被忽略的"质量税"。

对冲手法

  1. 上 strict 结构化输出。OpenAI 的 response_format: {type: "json_schema", strict: true}、以及各家对应的"受约束解码"模式,能把 JSON 非法率压到接近 0。这是治本——失败率从 15% 降到 <1%,重试税基本消失。
  2. 流式部分校验。instructor 支持 partial JSON 流式解析,边收边校验,能在模型还没说完时就发现格式问题并定向重试,而不是傻等完整输出再重来。
  3. 设硬性重试上限。max_retries=2 或 3,超过就降级(返回结构化错误、或转人工),别让一个坏 schema 把成本拖进无底洞。
  4. extraction 别用旗舰。这是和暗税一呼应的认知差:从一段文本里抽 5 个字段、填一张表,Haiku / Flash / Mini 级别的小模型完全够用。把 Opus、GPT-5.6 这类旗舰留给真正需要推理的环节。旗舰只该当指挥,不该当抄表员。

一张图看懂三道暗税的对冲闭环

把上面三招串起来,就是一个完整的"结构化输出成本卫生"流水线:

flowchart LR
    subgraph 请求组装
      S1[system prompt + 工具定义<br/>打 cache_control] --> S2[动态内容放最后]
    end
    subgraph 模型选择
      R1{是抽取/填表?} -->|是| R2[小模型 Haiku/Flash/Mini]
      R1 -->|否,需推理| R3[旗舰 Opus/GPT-5.6]
    end
    subgraph 生成校验
      G1[strict JSON schema<br/>受约束解码] --> G2[流式部分校验]
      G2 --> G3{max_retries=2}
    end
    请求组装 --> 模型选择 --> 生成校验

按这条链路改造,结构化输出的单次逻辑调用成本普遍能压回接近"理论最低值"——即只为你真正需要的这一次生成付费。

信息差总结

维度 你以为的(90% 的人) 实际上(信息差)
工具定义成本 "工具就是功能,不花钱" 每轮重发,是死扛的 input 固定税,可缓存省 10 倍
Prompt 缓存 "开了就自动省钱" 动态内容插错位置 → 缓存全失效,命中 0.1x 变 1.0x
JSON 校验失败 "重试一次而已" 整段重跑,单次调用成本乘以重试次数
缓存写入 "写入还要收费?不划算" 写 1.25x 只付一次,读 0.1x,深对话里稳赚
抽取任务选模型 "用最强模型最稳" 抽取是小模型主场,旗舰留给推理

行动指南(今晚就能做)

  1. 审计你的工具 token。用 LiteLLM 的 cost_calculator 或自己在请求里 dump 一次 system + tools 的 token 数。如果超过 2,000,第一步就是裁剪 + 缓存。
  2. 加一行 cache_control。把 system prompt 和工具块整体打上缓存标记,观察第二天的 input 成本曲线——通常会有一个明显的下台阶。
  3. 核对缓存命中率。在响应里读 usage.cache_read_input_tokens / cache_creation_input_tokens(Anthropic)或对应字段。命中率低于 50%?多半是动态内容插错位置了。
  4. 给重试上紧箍咒。全局把 max_retries 降到 2–3,并打日志统计 retry 率。retry 率 >10% 说明 schema 设计有问题,回头修 schema 比加钱更值。
  5. 把 extraction 路由到小模型。用路由层(LiteLLM Router / 自建 classifier)把"从文本抽字段"这类活分流给 Haiku / Flash / Mini,旗舰只接推理重活。

参考来源

一句话带走:结构化输出的钱,不是花在"输出"上,而是花在每一轮重发的工具定义、被动态内容挤爆的缓存、以及校验失败的重试上。盯住这三处,你的 agent 才是真的"会干活",而不是"会烧钱"。 本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护。 聚焦5G/6G核心网、3GPP标准、AI工具实战。知识星球「51学通信」提供深度技术答疑和行业洞察。

1. 完成摘要(What was done)

2. 下一步计划(What's next)

3. 注意事项(If applicable)

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入