Skip to content

所在组:Production | 上一组出口:能限制权限、暂停/恢复任务 | 本组出口:能拆解一次请求的成本与延迟,并按优化阶梯选择性价比手段 前置:模型 API 契约(usage 字段)、可观测性(每请求记录) | 下一步:部署与发布(预算告警与熔断落地点)

1. 概述 ​

结论先讲:AI 应用的成本与延迟不是「厂商说了算的黑盒」,而是可分解、可记账、可优化的工程量。成本 = 每请求 token 用量 × 单价结构(输入 / 缓存写 / 缓存命中 / 输出四档);延迟 = TTFT(首 token 时间)+ 生成吞吐 + 工具往返。优化的正确顺序是先记账、再优化——没有可观测性的每请求记录,一切优化都是猜。动态单价一律以厂商定价页为准(本文引用结构不引用数字,见「原理」段的核验表)。

心智模型:一次请求的钱和时间去了哪 ​

决策表:贵模型还是小模型 ​

场景特征选贵模型选小模型先优化别换
任务复杂度多步推理/严格指令遵循分类/抽取/格式转换/日常对话—
失败代价错一次损失大(法律/资金)错了可重试可忽略—
流量结构长尾少量难例高频重复大流量高频且相似 → 先缓存
当前症状质量不达标质量过剩、账单痛上下文爆炸 → 先压缩

路由模式(难例升贵模型、易例走小模型)是两者兼得的形态,前提是有评估阈值判定难度。

何时使用 / 何时不用 ​

  • 用:任何有持续账单或延迟 SLO 的系统;任何「换模型能不能省钱」的决策。
  • 不用:月成本可忽略的个人玩具——记账与优化的工时比账单贵。

历史版本里程碑:本页 2026-09 由旧 engineering/cost-optimization.md(模型路由/语义缓存/压缩/自托管四策略)重写,补充延迟分解与优化阶梯,并把所有单价口径改为「结构引用 + 厂商定价页 + retrievedAt」。

2. 使用 ​

最小实战:15 分钟、零 API key,实现每请求成本追踪 + 预算熔断 + 延迟分解记录。单价以配置注入(示例数字是演示配置,非厂商报价),真实项目从厂商定价页录入并标注日期。

步骤 1:保存 cost-tracker.mjs ​

javascript
// cost-tracker.mjs — 每请求成本记账 + 预算熔断 + 延迟分解(单价为演示配置,非厂商报价)
import assert from 'node:assert/strict';

// 单价结构:真实项目从厂商定价页录入(每 1M token),并记 retrievedAt。
// 结构本身是行业通式:输入 / 缓存写(约 1.25x)/ 缓存命中(约 0.1x)/ 输出 四档
// (OpenAI、Anthropic 同构,2026-09-01 核验)。
const PRICES = {
  'model-large':  { input: 5.0, cacheWrite: 6.25, cacheRead: 0.5, output: 25.0, retrievedAt: 'fixture' },
  'model-small':  { input: 0.5, cacheWrite: 0.625, cacheRead: 0.05, output: 2.0, retrievedAt: 'fixture' },
};

function requestCost(model, usage) {
  const p = PRICES[model];
  if (!p) throw new Error(`unknown model: ${model}`);
  const m = (n) => n / 1e6; // tokens -> 百万 token 单位
  const write = usage.cacheWriteTokens ?? 0; // 首次写入缓存的前缀
  const read = usage.cacheReadTokens ?? 0;   // 命中缓存的前缀
  return p.input * m(usage.inputTokens - write - read)
       + p.cacheWrite * m(write)
       + p.cacheRead * m(read)
       + p.output * m(usage.outputTokens);
}

// ---- 预算熔断:单位时间累计超限即拒绝新请求(防失控循环烧钱)----
function createBudgetGuard(monthlyLimitUsd) {
  let spent = 0;
  return {
    check: () => { if (spent >= monthlyLimitUsd) throw new Error('budget_exhausted'); },
    record: (usd) => { spent += usd; return spent; },
    spent: () => spent,
  };
}

// ---- 延迟分解:一次请求的分段记录 ----
function latencyProfile(segments) {
  const total = segments.reduce((a, s) => a + s.ms, 0);
  const gen = segments.find((s) => s.name === 'generate');
  return {
    totalMs: total,
    ttftMs: gen?.ttftMs ?? null,
    tokensPerSec: gen ? gen.outputTokens / (gen.ms / 1000) : null,
    toolRoundtrips: segments.filter((s) => s.name === 'tool').length,
  };
}

// ---- 演示(确定性):同一前缀的「首笔写缓存」与「重放读缓存」 ----
const guard = createBudgetGuard(1.0); // 演示预算 1 美元
const r1 = requestCost('model-small', { inputTokens: 2000, outputTokens: 200 });
assert.ok(Math.abs(r1 - (0.5 * 0.002 + 2.0 * 0.0002)) < 1e-9);
guard.record(r1);

// 首笔:4 万 token 前缀按「缓存写」档计价(比普通输入贵 25%)
const r2 = requestCost('model-large',
  { inputTokens: 50000, cacheWriteTokens: 40000, outputTokens: 1500 });
assert.ok(Math.abs(r2 - (5.0 * 0.01 + 6.25 * 0.04 + 25.0 * 0.0015)) < 1e-9);
guard.record(r2);

// 重放:同一前缀命中缓存,按「缓存命中」档计价(约为输入价的 1/10)
const r2b = requestCost('model-large',
  { inputTokens: 50000, cacheReadTokens: 40000, outputTokens: 1500 });
assert.ok(Math.abs(r2b - (5.0 * 0.01 + 0.5 * 0.04 + 25.0 * 0.0015)) < 1e-9);
guard.record(r2b);

const profile = latencyProfile([
  { name: 'retrieve', ms: 120 },
  { name: 'generate', ms: 1800, ttftMs: 350, outputTokens: 1500 },
  { name: 'tool', ms: 400 }, { name: 'tool', ms: 300 },
]);
assert.equal(profile.toolRoundtrips, 2);
assert.ok(profile.tokensPerSec > 0 && profile.tokensPerSec < 2000);

console.log('小模型请求:', r1.toFixed(6), 'USD');
console.log('大模型·首笔写缓存:', r2.toFixed(6), 'USD');
console.log('大模型·同前缀重放:', r2b.toFixed(6), 'USD');
console.log('累计:', guard.spent().toFixed(6), 'USD / 限额 1');
console.log('延迟分解:', JSON.stringify(profile));

guard.record(0.9); // 模拟流量继续消耗
assert.throws(() => guard.check(), /budget_exhausted/); // 负例:熔断生效
console.log('预算熔断: budget_exhausted 如预期抛出');

步骤 2:运行 ​

bash
node cost-tracker.mjs

步骤 3:正常输出 ​

text
小模型请求: 0.001400 USD
大模型·首笔写缓存: 0.337500 USD
大模型·同前缀重放: 0.107500 USD
累计: 0.446400 USD / 限额 1
延迟分解: {"totalMs":2620,"ttftMs":350,"tokensPerSec":833.3333333333333,"toolRoundtrips":2}
预算熔断: budget_exhausted 如预期抛出

读法:首笔写缓存(0.3375)比不写缓存直接全按输入计价(0.2875)贵 25%——写缓存是预付;第二笔同前缀请求(0.1075)把 4 万 token 从输入档挪到 0.1x 的命中档,预付即回本。

步骤 4:负例观察 ​

把重放用例的 cacheReadTokens 改为 0(缓存完全未命中,4 万 token 回到普通输入档),成本从 0.1075 涨到 0.2875——这就是「缓存命中率」作为成本杠杆的直接展示;再调低 monthlyLimitUsd 到 0.05,第二笔大额记录后即熔断。

验收与清理 ​

  • 验收:断言全过;能口头回答「缓存命中省的是哪一档的钱、缓存写为什么反而更贵」。
  • 清理:删除文件即可。

3. 原理 ​

token 成本结构(核验于 2026-09-01,单价以厂商定价页为准) ​

结构项OpenAI(developers.openai.com/api/docs/pricing)Anthropic(platform.claude.com/docs/en/build-with-claude/prompt-caching)
计价分档输入 / 缓存命中 / 缓存写 / 输出,每 1M token(长短上下文两档单价)基础输入 / 缓存写(5 分钟档与 1 小时档)/ 缓存读 / 输出
缓存档单价命中为输入价的 10%;缓存写为 1.25x(gpt-5.6 系核验)缓存读为输入价的 0.1x;缓存写为 1.25x(5m)/ 2x(1h)
批处理Batch 与 Flex 档五折(24h 内完成的非时效请求)Batch API 输入输出均五折(24h 内完成)
不可见推理 tokens按输出 token 计费(占上下文但不可见)推理计入输出成本(thinking 计费与显示无关)

三条工程推论:缓存命中的钱接近输入价的十分之一(两家一致),高重复前缀的场景缓存是第一杠杆;缓存写两家首档同为 1.25x——写缓存首笔多付 25%,第二笔同前缀请求即回本,此后每次命中都在收租;看不见的推理 tokens 也按输出计费,「输出看着短」不等于便宜。

缓存的工程前提(前缀匹配) ​

提示缓存是严格前缀匹配:前缀中任何一字节变动都会使其后的缓存失效。因此稳定内容(冻结的系统提示、确定性顺序的工具清单)放前面,易变内容(时间戳、请求 ID、用户问题)放后面。验证手段:读取响应 usage 的缓存字段(Anthropic 为 cache_creation_input_tokens 与 cache_read_input_tokens),重复请求持续为零即存在静默失效源(系统提示里的时间戳、乱序序列化的 JSON、变动的工具集);另一类静默失效是前缀过短——Claude 各模型有 512–4096 token 的最小可缓存长度,低于阈值的请求直接跳过缓存(两个缓存字段均为 0 且不报错),排查命中率时先排除这一条(Anthropic prompt caching 文档,retrievedAt 2026-09-01)。缓存失效的排查与可观测性共用同一份 usage 记录。

延迟分解 ​

分量定义杠杆
TTFT请求发出到首个 token预填充量(上下文长度)、缓存命中(命中段免重算)
生成吞吐首 token 后每秒输出 token 数模型档位、输出长度约束
工具往返每次工具调用的串行等待并行工具调用、工具超时上限、减少循环步数
端到端以上之和(多轮再乘轮数)架构层:改检索、改 prompt、改路由

交互式产品对 TTFT 敏感(体感「快不快」),批处理对吞吐与单价敏感——优化目标先分类再动手。

优化阶梯(按投入产出排序,从低到高) ​

  1. 缓存:稳定前缀 + 命中验证。改动最小,收益直接(见上)。
  2. prompt 压缩:砍冗余指令、检索结果摘要、历史裁剪——输入 token 直接下降。
  3. 模型路由:易例走小模型、难例升贵模型,路由依据用评估阈值而非直觉。
  4. 批处理:非时效任务走 Batch 档(两厂商均有折扣结构)。
  5. 蒸馏/微调:把高频任务的贵模型行为固化到小模型——投入大,需 Learn LLM 的训练知识与本仓评估的质量门。
  6. 架构:改检索策略减少注入量、改 agent 循环减少步数——回到检索组/行动组重新设计。

先记账再上阶梯:每一级都要用每请求成本数据验证收益,否则优化本身成为新的成本。

规范要求 vs 本地实测 ​

本页无协议规范可实现测;「规范」侧为两家厂商定价页的结构口径(上表,retrievedAt 2026-09-01),「实测」侧为本页记账器对三档结构的计算(断言验证)。单价数字刻意不入正文——引用结构,不引用价格。

4. 开发 ​

症状 → 证据 → 处理 → 完成标准 ​

症状:月账单环比翻倍,不知道从哪涨的。 证据:每请求 usage 记录按「功能 × 模型 × 分档」聚合(→可观测性);找出贡献增量的维度。 处理:输入涨 → 查上下文膨胀与缓存命中率;输出涨 → 查推理类模型用量与循环步数;请求量涨 → 查是否有失控重试。 完成标准:能指认主要增长项并给出对应阶梯动作;下个周期同维度对账回落。

症状 → 证据 → 处理 → 完成标准 ​

症状:缓存命中率持续为零,缓存形同虚设。 证据:响应 usage 的缓存命中字段在重复请求下仍为 0,且前缀长度已超过该模型的最小可缓存阈值(排除过短前缀);diff 两次请求的序列化前缀。 处理:消除前缀里的变动源——时间戳/请求 ID 移到后段、JSON 键排序固定、工具清单冻结顺序。 完成标准:同前缀重复请求的缓存命中字段大于 0 且稳定;命中率进入常规看板。

症状 → 证据 → 处理 → 完成标准 ​

症状:P95 延迟劣化,用户抱怨变慢。 证据:延迟分解看板定位分量——TTFT 涨(上下文变长/缓存失效)、吞吐降(输出变长)、工具往返涨(循环步数增加)。 处理:按分量对症:压缩上下文、限制输出长度、并行化工具、给工具设超时上限。 完成标准:劣化分量回到基线区间;延迟分解成为发布前后的对账项(→部署与发布)。

反模式清单 ​

  • 先优化后记账:没拆账就上微调/换模型,收益无法归因。
  • 只看总价不看分档:缓存命中与推理 token 两档的杠杆完全不同。
  • 把演示单价当事实传播:正文写死厂商价格必然过期——结构引用 + 厂商定价页 + retrievedAt 是唯一合规写法。
  • 预算无熔断:失控循环一夜烧穿预算(OWASP 2026 将 Unbounded Consumption 升至 LLM06)。

5. 资料库 ​

四级阅读路线:

  • Beginner:跑通本页记账器;理解三档单价与 TTFT/吞吐/往返。
  • Builder:给自己的系统接每请求 usage 记账与预算熔断;验证缓存前缀稳定。
  • Operator:建立「功能 × 模型 × 分档」成本看板与延迟分解看板;按优化阶梯逐级验证。
  • Researcher:读两家厂商定价与缓存文档全文;对照推理引擎侧的性能机理(→ Learn LLM)。

资源表 ​

名称证据层级canonical URL用途支持的断言下一步
OpenAI API 定价页L0(厂商官方)https://developers.openai.com/api/docs/pricing单价与分档结构输入/缓存命中/缓存写/输出四档;Batch 与 Flex 五折;推理 tokens 按输出计费(retrievedAt 2026-09-01)录入最新单价到配置
Anthropic 定价与缓存文档L0(厂商官方)https://platform.claude.com/docs/en/build-with-claude/prompt-caching单价与缓存结构缓存写 1.25x(5m)/2x(1h)、读 0.1x;最小可缓存长度 512–4096 token;Batch 五折(retrievedAt 2026-09-01)读其 batch-processing 文档
OpenTelemetry GenAI 语义约定L0(官方规范)https://github.com/open-telemetry/semantic-conventions-genaitoken/延迟属性的统一命名gen_ai.usage.* 与 TTFT 类指标(Development 级,retrievedAt 2026-09-01)可观测性
OWASP GenAI LLM Top 10 2026L0(官方清单)https://genai.owasp.org/resource/owasp-genai-llm-top-10-2026/无限制消耗风险LLM06 Unbounded Consumption(retrievedAt 2026-09-01)安全

主动证伪与未决问题 ​

  • 证伪入口:若你的流量里前缀几乎无重复(每次上下文都全新),缓存这一级应跳过——优化阶梯按流量形态裁剪,不是逐级打卡。
  • 未决:路由器(难易分流)的难度判定本身引入额外成本与错误率,何时「路由不划算」需要用评估数据逐案例定,本仓暂无量化门槛。

learn-ai 到此为止 / 继续去哪 ​

为前端工程师打造 · 基于 VitePress 构建