延迟、用量与费用
延迟、用量与费用 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
读完本课你能:为一次模型调用记下四个真实数字——首字延迟、总耗时、输入 token、输出 token;看懂缓存命中与长上下文怎么影响账单;用数据决定「下一步优化哪里」,而不是凭感觉猜。
问题场景
月底整理支出,发现这个月 AI 调用花了平时的三倍。翻记录:不是回答变长了,而是聊得越多、每一轮的输入越大——聊天应用每一轮都把历史消息整个重发一遍。另一个发现:同一句话有时快有时慢,慢的那次输入 token 却一样。没有测量,这两个现象都解释不了,更谈不上优化。
核心解释
一次流式调用要看两组数。
延迟分两段:首字延迟(TTFT,Time To First Token,从发出请求到第一个字出现)和总耗时。流式界面的体感由首字延迟决定,「20 秒出全文」和「1 秒出首字」是两个产品。本站 ai_call 表记录的 latency_ms 是整次调用的耗时。
用量是计费依据:prompt_tokens(输入)加 completion_tokens(输出)。关键认知:输入同样要钱,而且多轮对话里输入往往是大头——每轮都携带全部历史。本站的真实做法:请求时带 stream_options: { include_usage: true },流结束时在回调里拿到用量:
// lib/ai/adapter.ts(节选):请求声明要用量,结束时带回
body: JSON.stringify({
model: cfg.model,
messages: opts.messages,
stream: true,
stream_options: { include_usage: true },
// ...
}),
// StreamCallbacks 的 onDone: (usage: { prompt_tokens?; completion_tokens? } | null) => void
两个直接影响账单的因素:
- 长上下文线性涨价。历史每多一轮,
prompt_tokens就多一截。本站把历史裁剪到最近 20 条(app/api/ai/chat/route.ts),就是在体感与费用之间取的平衡点。 - 提示缓存。上游对「提示词开头完全一致」的部分按折扣价计费,前提是开头稳定:系统提示放最前面,别在里面拼时间戳、随机数这类每次都变的内容——变一个字,缓存前缀就从那里断掉。
如何验证测量生效:跟 AI 聊一轮,然后查表:
-- 查 data/database/reactlab.db(sqlite3 命令行或任意 SQLite 工具)
SELECT model, prompt_tokens, completion_tokens, latency_ms, created_at
FROM ai_call ORDER BY created_at DESC LIMIT 5;
有记录、数字随对话轮次增长、latency_ms 与体感一致,测量链路就算通了。
示例拆解
用量落库发生在 app/api/ai/chat/route.ts 的 onDone 回调里:
onDone: (usage) => {
if (usage) {
db.prepare(
"INSERT INTO ai_call (id, conversation_id, message_id, model, kind, prompt_tokens, completion_tokens, latency_ms, status, error, created_at) VALUES (?, ?, ?, ?, 'chat', ?, ?, ?, 'ok', NULL, ?)"
).run(
newId("call_"), convId, assistantMsgId, process.env.AI_MODEL ?? "unknown",
usage.prompt_tokens ?? null, usage.completion_tokens ?? null,
Date.now() - started, nowIso()
);
}
},
三个关键点:
- 没有 usage 就不记账:
if (usage)不是可有可无——部分提供方流式不回用量,硬记会写出一堆 null 污染统计(A38 讲迁移时这是核对项)。 - latency 从请求开始计时:
Date.now() - started覆盖整条链路,包括工具调用轮次,不只是模型生成。 - 记在服务端:浏览器拿不到也不该拿计费口径,账本要在唯一可信的位置。
动手任务
- 实验区是一个「用量台账」模拟器:跑一遍,观察多轮对话里每轮输入 token 的增长和缓存命中对费用的影响;把历史裁剪条数改小再跑,对比总费用。
- 本机跟 AI 助手聊 3 轮,用上面的 SQL 查
ai_call,确认三轮的prompt_tokens递增。 - 拿到自己的数据后写一句话结论:「当前最值得优化的点是 X,依据是 Y」。候选:裁剪历史、稳定提示前缀、调低输出上限。
常见错误
- 只盯总耗时:流式产品里首字延迟才是体感大头;总时长长但首字快,用户照样觉得「在动」。
- 以为只有输出计费:多轮对话里输入常占账单大头,优化输入(裁剪、缓存)往往比「让模型少说」有效。
- 系统提示里拼动态内容:把「今天日期」「随机推荐」写进系统提示开头,缓存前缀永远不命中,每轮都按全价算。
- 记账后从不回看:
ai_call写了没人查等于没记,测量只有变成结论才有价值。
检查与复盘
- 能说出延迟要拆成首字延迟和总耗时两段,以及各自影响什么
- 能解释为什么多轮对话的
prompt_tokens线性增长,并给出两个对应手段(裁剪、稳定前缀) - 查过自己站里
ai_call的真实数字,能报出最近一次调用的输入输出 token
参考资料
- OpenAI 文档:Chat Completions API —— usage 字段与 stream_options 的官方定义
- OpenAI 文档:Prompt caching —— 前缀缓存的作用范围与折扣口径
- OpenAI 文档:Pricing —— 输入/输出/缓存输入的分档单价示例
读完这一课了?
标记完成后,工作台会安排接下来的学习。