AI API 实践三:为什么要关注 Token,而不只是请求次数?
前两篇主要聊了 AI API 的统一管理、Key 设计、用量统计和异常排查。
这篇继续往下讲一个更容易被忽略,但实际很关键的问题:Token 管理。
很多人在刚接入 AI API 时,最开始关注的是:
接口能不能调通? 响应速度快不快? 模型效果好不好? 请求有没有报错?这些当然重要。
但项目一旦进入长期运行阶段,就会发现另一个问题更现实:
Token 到底消耗在哪里? 为什么请求次数不多,成本却很高? 为什么某些任务突然消耗暴涨? 不同项目之间应该怎么分配 Token 额度?AI API 的成本和稳定性,很多时候不是由“请求次数”决定的,而是由Token 使用方式决定的。
一、请求次数和 Token 消耗不是一回事
很多接口服务按请求次数统计比较直观。
比如:
今天调用了 1000 次接口 昨天调用了 800 次接口 请求量上涨了 25%但 AI API 不一样。
一次请求可能很便宜,也可能很贵。
例如下面两个请求:
请求 A: 用户输入:帮我生成 5 个标题 请求 B: 用户输入:请阅读下面 2 万字文档,并输出详细总结它们都是“一次请求”,但 Token 消耗完全不是一个量级。
所以在 AI API 管理里,只看请求次数是不够的。
更应该关注:
输入 Token 输出 Token 总 Token 平均每次请求 Token 每个 Key 的 Token 消耗 每个项目的 Token 消耗 每种任务的 Token 消耗这也是我后来开始专门记录 Token 数据的原因。
二、Token 消耗主要来自哪里?
从实际项目看,Token 消耗一般来自两个部分:
输入 Token:传给模型的内容 输出 Token:模型生成的内容很多人刚开始只关注输出,觉得“模型生成了多少字”才是成本来源。
但实际上,输入内容也非常重要。
比如知识库问答场景,用户只问了一句话:
这个产品支持私有化部署吗?但系统可能会拼接大量上下文:
系统提示词 用户问题 检索出来的 8 段文档 历史对话记录 格式要求 角色设定 返回结构说明最后真正发给模型的内容可能非常长。
也就是说,用户看到的只是一个简单问题,但后端实际发送的 prompt 可能已经很大。
三、一个典型的 Token 消耗场景
假设有一个知识库问答系统。
用户问题是:
如何配置 API Key?后端可能会构造这样的 prompt:
你是一个技术文档助手,请基于下面资料回答用户问题。 资料 1: 这里是一段 1000 字的文档…… 资料 2: 这里是一段 1200 字的文档…… 资料 3: 这里是一段 900 字的文档…… 资料 4: 这里是一段 1500 字的文档…… 用户问题: 如何配置 API Key? 请用中文回答,并给出步骤说明。用户只输入了几个字,但系统拼接的上下文可能已经几千字。
如果每次问答都这样处理,Token 消耗会很快上升。
所以在知识库问答里,真正需要优化的往往不是用户问题,而是:
检索片段数量 每个片段长度 历史对话轮数 系统提示词长度 输出内容长度四、历史对话也是隐藏成本
聊天类应用里,还有一个非常容易忽略的成本来源:历史对话。
很多聊天应用会把多轮上下文一起传给模型。
例如:
[ { "role": "system", "content": "你是一个 AI 助手" }, { "role": "user", "content": "第一轮问题..." }, { "role": "assistant", "content": "第一轮回答..." }, { "role": "user", "content": "第二轮问题..." }, { "role": "assistant", "content": "第二轮回答..." }, { "role": "user", "content": "第三轮问题..." } ]前几轮对话短的时候没什么问题。
但如果用户连续聊了几十轮,每次请求都带上完整历史记录,Token 消耗会越来越高。
尤其是下面几种场景:
代码调试对话 长文改写对话 论文阅读对话 合同分析对话 知识库连续追问上下文会越堆越长。
所以聊天应用里,一般需要做历史消息控制,比如:
只保留最近 N 轮对话 超过长度后做摘要 重要信息单独存储 无关上下文不再传入模型 按任务类型决定上下文长度简单一点的做法是只保留最近几轮:
function getRecentMessages(messages, maxRounds = 6) { return messages.slice(-maxRounds * 2); }更好的方式是把旧对话压缩成摘要,再和最近几轮一起传给模型。
五、Prompt 模板也会影响 Token
很多项目里,系统提示词会越写越长。
一开始可能是:
你是一个专业助手,请回答用户问题。后来为了控制输出效果,会不断加规则:
你是一个专业助手,请回答用户问题。 回答必须准确。 不能编造。 如果不知道请说明不知道。 请使用 Markdown。 请分点回答。 请先总结再解释。 请给出示例。 请避免输出无关内容。 请保持语气自然。再后来为了适配业务,又继续加:
你需要遵守以下业务规则…… 你需要按照以下 JSON 格式输出…… 你需要参考以下字段说明…… 你需要避免以下表达……最后系统提示词可能变成一大段。
这不一定是坏事,因为好的 prompt 可以提升结果稳定性。
但需要意识到:系统提示词每次请求都会消耗 Token。
如果一个接口每天调用几万次,系统 prompt 多 500 个 Token,长期下来消耗就很明显。
所以 prompt 模板也要定期整理:
删除重复规则 合并相似表达 减少无效解释 不同任务使用不同模板 不要所有接口共用一个超长系统提示词六、输出长度也要控制
除了输入,输出也需要限制。
有些任务不需要模型长篇大论。
比如:
生成标题 文本分类 提取关键词 判断是否违规 生成摘要 识别意图这些任务如果不限制输出,模型可能会额外解释很多内容。
例如你只是想要分类结果:
用户输入:这句话属于什么类型?结果模型输出:
这句话属于咨询类问题。原因是用户表达了对某个功能的疑问,语气上没有明显投诉情绪,因此可以归类为普通咨询。如果业务只需要一个标签,那么更合适的输出是:
咨询这时候可以在 prompt 里明确要求:
只输出分类结果,不要解释。 可选分类:咨询、投诉、建议、其他。或者要求 JSON:
{ "type": "咨询" }这样可以减少输出 Token,也能降低解析成本。
七、不同任务要设计不同 Token 策略
不是所有 AI 调用都应该使用同样的 Token 策略。
我一般会把任务分成几类。
1. 短文本任务
例如:
标题生成 标签生成 文本分类 意图识别 关键词提取这类任务输入短、输出短,应该严格限制输出长度。
可以设定:
最多输出 50~200 字 不需要解释 不需要长格式2. 中等文本任务
例如:
普通问答 短文改写 客服回复 评论总结 邮件润色这类任务可以允许适中长度输出,但不需要无限展开。
可以设定:
输出 300~800 字 保留必要解释 避免重复3. 长文本任务
例如:
文档总结 合同分析 论文阅读 代码审查 知识库问答这类任务输入本来就长,需要重点控制上下文。
可以设定:
限制检索片段数量 限制单段文本长度 先摘要再分析 分段处理 必要时异步执行4. 批量任务
例如:
批量翻译 批量摘要 批量生成标题 批量清洗数据 批量分析评论这类任务最容易出现 Token 暴涨。
建议:
单独 Key 单独额度 单独限速 单独日志 失败可恢复 支持中断 不要和线上业务共用额度八、为每个 Key 设置 Token 预算
上一篇聊过 Key 隔离,这篇可以继续往下拆。
Key 不应该只是“身份标识”,也可以对应 Token 预算。
例如:
prod_kb_qa:每天 200 万 tokens prod_writer:每天 100 万 tokens batch_summary:每天 50 万 tokens dev_local:每天 5 万 tokens test_env:每天 10 万 tokens这样做的好处是,每个项目都有自己的使用边界。
如果某个批量脚本异常,不会直接影响线上业务。
比如:
batch_summary 今天达到 50 万 tokens 上限 自动暂停这个 Key 线上 prod_kb_qa 不受影响这比所有项目共用一个总额度安全得多。
九、Token 预算可以按环境区分
环境不同,Token 预算也应该不同。
例如:
开发环境:低额度 测试环境:中低额度 生产环境:正常额度 批量任务:独立额度 临时测试:短期额度示例:
dev_kb_qa:每日 20,000 tokens test_kb_qa:每日 100,000 tokens prod_kb_qa:每日 2,000,000 tokens batch_kb_import:每日 500,000 tokens这样可以避免本地调试或测试任务误消耗大量资源。
尤其是开发环境,不建议给太高额度。
开发阶段经常会出现循环调用、重复测试、日志重放等情况。
十、记录 Token 日志时要记录哪些字段?
只记录总消耗不够,最好能记录到请求维度。
一个比较实用的日志结构可以是:
{ "request_id": "req_001", "api_key": "prod_kb_qa", "project": "knowledge_base", "task_type": "qa", "model": "fast-chat", "prompt_tokens": 1800, "completion_tokens": 420, "total_tokens": 2220, "latency_ms": 2100, "status_code": 200, "created_at": "2025-01-01 10:00:00" }后面就可以做很多分析:
哪个 Key 消耗最高? 哪个项目消耗最高? 哪个任务平均 Token 最高? 哪个模型输出最长? 哪类请求最慢? 错误请求是否也消耗了 Token?这些信息对优化很有帮助。
十一、一个 Token 异常排查案例
假设某天发现总 Token 消耗突然上涨 80%。
第一步,不要先看请求次数,而是按 Key 聚合:
prod_kb_qa:+5% prod_writer:+8% batch_summary:+320% dev_local:+2%很明显,问题在batch_summary。
第二步,看请求次数:
batch_summary 请求次数从 500 次增加到 1800 次第三步,看平均 Token:
平均 total_tokens 从 1200 增加到 3600这说明不只是请求变多了,每次请求也变长了。
第四步,看输入和输出拆分:
prompt_tokens 增长明显 completion_tokens 变化不大这说明问题主要在输入内容。
继续排查,可能发现:
原来只传摘要字段 现在把全文字段也传进去了或者:
检索片段从 3 段变成了 10 段这种问题如果没有 Token 日志,很难快速定位。
十二、前端也要注意 Token 成本
有些 Token 浪费不是后端造成的,而是前端交互设计造成的。
比如:
用户每输入一个字就触发 AI 补全 用户频繁点击重新生成 页面刷新后重复提交 按钮没有 loading 状态导致多次点击 自动保存时触发 AI 分析这些都可能导致额外请求。
前端至少要做一些基础保护:
按钮防重复点击 请求中显示 loading 失败后再允许重试 自动触发类功能加防抖 长文本任务提示用户确认例如:
let loading = false; async function handleGenerate() { if (loading) return; loading = true; try { await callAI(); } finally { loading = false; } }这种简单处理可以避免很多重复调用。
十三、后端要做输入长度校验
不要完全相信前端限制。
后端也应该做输入长度校验。
例如:
const MAX_INPUT_LENGTH = 8000; function validateInput(text) { if (!text || text.trim().length === 0) { throw new Error("输入内容不能为空"); } if (text.length > MAX_INPUT_LENGTH) { throw new Error("输入内容过长,请缩短后再提交"); } }对于长文档任务,也可以先拆分:
function splitText(text, chunkSize = 3000) { const chunks = []; for (let i = 0; i < text.length; i += chunkSize) { chunks.push(text.slice(i, i + chunkSize)); } return chunks; }不要把任何长度的文本都直接塞给模型。
十四、缓存可以减少重复 Token 消耗
有些 AI 请求其实是重复的。
例如:
同一篇文章多次生成摘要 同一个商品多次生成标题 同一个文档多次提取关键词 同一个问题多次命中相同知识库答案这类场景可以考虑缓存。
缓存 Key 可以根据输入内容生成 hash:
import crypto from "crypto"; function createCacheKey(input) { return crypto .createHash("sha256") .update(input) .digest("hex"); }当输入内容完全一致时,直接返回缓存结果。
当然,缓存不适合所有场景。
比如强实时、强个性化、多轮对话场景就要谨慎。
但对于批量处理、文档摘要、固定模板生成来说,缓存很有价值。
十五、不要忽略失败请求的成本
很多人以为请求失败就没有成本,但实际情况不一定。
有些请求可能已经被上游模型处理了一部分,只是最后返回失败、超时或解析异常。
所以统计时最好区分:
成功请求 Token 失败请求 Token 超时请求 Token 重试请求 Token特别是重试机制要小心。
例如:
第一次请求超时 系统自动重试一次 第二次又超时 用户再次点击重试最后可能同一个任务消耗了多次 Token。
所以重试策略要有边界:
限制最大重试次数 长文本任务谨慎重试 429 限流不要立刻重试 认证错误不要重试 重复提交要去重十六、Token 管理的几个实用规则
总结一下,我现在会遵循这些规则:
不要只看请求次数,要看 Token 区分输入 Token 和输出 Token 每个项目单独 Key 每个环境单独 Key 批量任务单独 Key 开发环境低额度 生产环境重点监控 长文本任务控制上下文 聊天任务控制历史轮数 Prompt 模板定期精简 输出长度要限制 重复任务可以缓存 失败和重试也要统计 异常消耗要能定位到 Key这些规则看起来比较细,但对长期运行的 AI 项目很有帮助。
十七、实践总结
AI API 接入早期,大家通常关注“能不能调用成功”。
但项目跑久之后,真正影响成本和稳定性的,往往是 Token 管理。
一个项目请求次数不高,并不代表消耗低。
一个接口响应正常,也不代表成本可控。
一个 Key 能用,也不代表适合多人共享。
比较合理的方式是:
用 Key 区分项目和环境 用日志记录 Token 明细 用额度限制控制风险 用上下文管理减少浪费 用缓存减少重复调用 用监控发现异常增长对 AI 项目来说,Token 管理不是后期优化项,而是从一开始就应该设计进去的基础能力。
最后补充
最近几篇文章一直在记录 AI API 管理相关实践,包括 Key 设计、用量统计、异常排查和 Token 控制。
我目前体验的是斑马 API 这类统一入口工具,比较适合 AI 产品、自动化脚本、团队共享接口和多模型管理场景。现在新用户有体验活动,注册后可以获得一个月会员,邀请新用户也有额外体验时长。
https://bmapi.020212.xyz/register?aff=YU55ECFS8AF2
