每个月对完账单都会有一种“token都去哪儿了”的错觉。明明用户量没暴涨功能没加多少API费用却总能稳稳超出预算。做API接入和大模型应用的同行应该都有同感token优化方案听上去是个省钱话题实际上是个成本治理、性能调优和体验保障的综合问题。这个方案并不复杂核心就是盯住每一次请求里到底有多少token是在干正事有多少只是在陪跑。下面从一个真实做过的问答机器人项目出发把我在Token优化上的拆解思路、执行步骤、效果数据和踩坑记录全部摊开讲适合正在做大模型应用、负责API成本控制、或者第一次把应用推到线上开始为账单发愁的人。1. Token到底在计什么费先把账算明白1.1 四条费用管道和一条漏水管绝大多数商用大模型服务费用是按token计量的一个token大致相当于一个汉字或半个到三个英文单词。很多人只盯着“输入输出”这个最粗的计费块但实际上一个普通请求的开销会分散在几个不同管道里输入token是每次提问都会带上的系统提示词、历史消息、工具定义、检索片段都会被算进去输出token是模型生成的回答内容单价通常比输入token更高原因在于输出无法并行预计算只能一个一个地生成缓存token是服务商在启用缓存后的一种优惠计费项命中缓存时输入部分价格会低很多还有一类隐蔽开销是重试、超时和无效输出这类token不产生任何业务价值却是账单里最常被忽视的漏水管。我见过一个典型的浪费场景某团队要把一份3万字的内部规则文档接入问答机器人图省事直接全文塞进系统提示词所有对话都背着这3万字去计算。单看一次请求输入token确实只收一次费用但每次对话都要重复携带这部分内容一个用户一天问30个问题就是30倍的重复消耗。我算过一次账假设每次额外携带2万token的重复信息日活500用户、人均25次请求一天就是2.5亿token的无谓消耗。这种浪费不是技术问题是方案设计问题。所以在谈任何优化手段之前第一步永远是先把账单拆开看清消耗的大头到底在哪一条管道上。我一般会在代码里给每个API请求记录完整的usage信息包括输入、输出、缓存命中数再按维度汇总。从实际数据来看大部分应用的token浪费集中在三块系统提示词冗余、历史消息无限累积、输出内容过度冗长。这三块就是后续优化的主战场。1.2 上下文窗口不是垃圾桶装得下不等于用得好有些开发者看到模型上下文窗口很大就产生了一个错觉既然能装下那就都塞进去。这个想法在成本上已经够危险了在效果层面还有另一个隐患——长上下文并不等于高质量理解。我对接过某主流大模型时做过对比测试同样一道推理题把它埋在4万token的文档末尾和单独提问前者的回答准确率明显下降。这不是个例很多模型在极长上下文中会出现所谓的“中间遗忘”即对输入序列中前段和后段的内容捕捉较好但中段内容容易被忽略。原因一方面来自注意力机制在长序列上的分布问题模型天然倾向于关注输入末尾的信息另一方面也来自KV Cache的内存和计算开销长度翻倍时这部分资源消耗也随之上涨响应时间变长几乎是必然的。打个比方上下文窗口就像一张工作台不是东西装得下就都能被好好利用工具堆得越多、翻找越难。实际做优化的时候我给自己定了一条原则只把“本次回答真正需要的信息”放进上下文其余一切通过检索、外置记忆和摘要压缩来解决。这样既省钱又省延迟还能保证回答质量。从这个角度看Token优化方案本质上是在控制模型工作的“噪音负载”。2. 从输入到输出的全链路瘦身打法2.1 系统提示词与固定模板的极限压缩系统提示词是每个请求都要携带的一组固定token它的问题在于“再小也是乘法”。假设系统提示词只有500token日请求1万次一天下来就是500万token的固定开销一个月就是1.5亿。如果能压到200token每月直接省下9000万token。所以压缩系统提示词不是抠字眼是实打实的降本动作。压缩的具体做法我总结成三个步骤。第一步把自然语言描述改写成结构化指令用最少的话说清角色、任务、输出格式、边界。第二步把所有固定不变的内容从系统提示词里挪出去要么放文档检索要么做离线模板而不是让每个请求都背着。第三步砍掉多余的示例说实话三个示例能解决的问题不需要放七个示例每个示例都在增加请求的固定负担。我当时接手的一个模拟项目X系统提示词原本是这样的“你是一个智能客服助手你需要根据公司内部的规章制度回答用户的问题回答的时候要语气友好、条理清晰如果遇到不确定的问题要如实说明不要编造虚假信息同时要给出具体的条款依据对于复杂问题要先拆解再回答……”这一整段算下来接近200token还包含了大量可以从回答风格推导出的信息。压缩后我用一段结构化文本替代核心只有角色、任务、输出三行压到不到80token。这里有个细节要注意压缩提示词不能靠硬删要同步观察线上回答质量的变化。我习惯的做法是一次只改一类内容做AB对比而不是一次把所有说明都删掉否则出了问题根本不知道是哪段话起了作用。2.2 多轮历史消息滑动窗口加分段摘要如果说系统提示词是乘法级的浪费历史消息就是指数级的浪费。一个用户连续提问10轮每轮用户消息加模型回复平均1200token第10轮请求的对话历史就有1.2万token第20轮就是2.4万。更麻烦的是很多历史消息在后续对话中已经没有任何参考价值了全部带上只会稀释模型对当前问题的注意力。早期我给某个跨平台系统接客服机器人时直接把最近20轮对话全部塞进上下文结果有两个问题非常明显一是对话一长模型偶尔开始答非所问把前面几轮已经解决过的问题又重新拿出来讲二是首字延迟越来越慢用户体感上能明显感觉到“越聊越卡”。后来我改用了一套组合策略核心就是“滑动窗口分段摘要”。具体做法是把历史分成两层最近4轮对话保留原文因为这是用户当前问题的直接背景4轮之前的消息不再逐条携带而是由程序定期生成一段摘要把已经确认的需求、已答复的结论、未解决的问题提炼成200token以内的概要。每次请求只携带“最近4轮原文历史摘要”而不是完整的20轮原文。这样一来有效上下文大幅缩短而且关键信息反而更集中了因为摘要帮模型过滤掉了大量寒暄和重复。这套做法在文本类任务上的效果尤其好回答质量和响应速度都有提升。需要提醒的是摘要本身也是调用模型生成的也会消耗token所以摘要不要每轮都做。我的策略是每隔5轮更新一次会话摘要新增内容量少单次摘要调用成本很低。如果连摘要费用都想省也可以抽取关键字段比如订单号、地址、用户意图做成结构化记忆配合规则拼装回去比自然语言摘要更省、更稳。2.3 输出侧优化限制自由发挥的三种手段很多人优化token只盯着输入侧但输出侧的浪费往往更隐蔽也更离谱。因为多数场景下输出token的计费单价高于输入token而且模型天生有“话痨”倾向同一个问题它可以给你写3个字也可以给你写300个字。如果应用场景是工具调用、智能客服、信息抽取输出长度失控会让成本成倍增长。第一个手段是设置合理的max_tokens上限。这不是拍脑袋设一个很小的数而是要按业务场景去估算合理的输出长度。比如一个FAQ问答机器人正常回答不超过200字也就是大约150到250个token那就把上限设在300个token既给了模型余量又堵住了长篇大论的口子。但注意这个值不能设得太小不然模型会在回答中途被硬生生截断反而产生不完整输出进而诱发用户的二次追问成本又反弹回去了。第二个手段是强制结构化输出。如果应用只需要从文本里抽取出实体、分类结果或评分就不要让模型自由发挥文本而是要求它输出JSON格式的结果。很多服务商提供了结构化输出或JSON模式模型会严格按照字段约束生成长度可控、解析稳定不会出现“好的根据您的描述我认为……”这种前缀废话。第三个手段是给系统提示词里明确加上“只输出结论不输出过程解释、不重复问题、不表达礼貌社交用语”。有人觉得这种提示很怪但实际效果立竿见影。我测试过同样一个分类任务加这一句之后平均输出token从320降到85而且准确率没有任何变化。输出侧优化做到位单次请求成本能从“基准线”再降30%左右这部分白捡的钱没道理不拿。3. 实操记录给某问答机器人做一次Token优化3.1 基线体检先搞清楚钱烧在哪纸上谈兵没意思我拿去年做的某问答机器人项目当样板完整跑了一遍Token优化全流程。先说项目背景这是个面向内部员工的知识问答机器人知识库是一堆规章制度、操作手册和常见问题文档用户通过企业IM发起提问机器人负责检索资料并生成回答。项目上线后月度API账单稳定在偏高区间业务方觉得不对劲找到我这边做成本治理。接手后第一件事不是动手改代码而是做基线体检。我在调用层给每个请求写了一份日志记录输入token、输出token、缓存命中情况和上下文构成。统计了一周长数据发现单次请求的平均token构成是这样的系统提示词约400token固定知识库前缀约5000token历史消息约3000token用户输入约80token模型输出约450token合计接近9000token。日均请求量约8000次一天的总消耗在7200万token左右。最扎眼的是那个固定知识库前缀5000token里绝大部分内容是文档库目录、权限说明和少量高频FAQ原文。这些内容本可以在需要时检索而不是每个请求都让模型从头到尾读一遍。历史消息3000token也不算小因为当时采用“近10轮全量携带”策略很多“谢谢”“知道了”“稍等”之类的寒暄在反复计费。系统提示词400token虽然只占少数但因为它是固定乘法项压缩收益同样可观。体检结论一句话单请求9000token里真正对回答有帮助的输入大约只有2000token浪费率接近60%。说明这一版方案有非常大的优化空间而且优化的路径很清晰就是逐项瘦身不必上什么黑科技。3.2 分步落地每个动作都要可量化优化操作我拆成了五个步骤每个步骤都带上可量化的预期收益不做那种“感觉上省了但不知道省了多少”的改动。第一步压缩系统提示词。把400token的提示词压到了150token方式正是前面讲的“自然语言改结构化”保留角色、任务边界、输出要求、引用格式说明。这一步只花了半小时但固定成本直接降了六成。第二步引入文档切块检索。把固定5000token的文档前缀彻底拿掉改成先做检索从知识库中召回和当前问题最相关的3个片段每个片段控制在500token以内按需拼进用户消息里这里的相关度用的是语义相似度计算而不是关键词匹配。第三步历史消息改造。近10轮全量携带改成“近4轮原文历史摘要”摘要每5轮更新一次单条摘要上限200token。第四步输出限流。设置了max_tokens上限并在系统提示词里加入了“只输出结论”的约束。第五步加缓存。做法放到下一节讲。优化后的单次请求token构成变成了这样系统提示词150token检索片段平均600token历史摘要加近4轮原文约1000token用户输入80token模型输出压缩后约250token合计约2100token。对比优化前的9000token单次请求消耗下降了约76%。我按日均8000次请求换算了一下优化前每天约7200万token优化后约1680万token。如果按月30天算每个自然月的消耗从21.6亿token降到了5亿token左右成本下降的幅度远超最初预期。整个过程没有更换更便宜的模型也没有牺牲回答质量纯粹是把每次请求的负载捋顺了。3.3 再进一步缓存和模型分级路由优化到这一步大头已经被砍干净了但还有一个能被继续压缩的区间重复性请求。在企业IM问答场景里很多员工会反复问相似的问题比如“年假能休几天”“报销流程是什么”这些常见问题完全可以命中缓存不再调用模型生成。我引入了两层设计。第一层是精确缓存以归一化后的用户消息作为key比如去掉首尾空格、全半角统一、繁体转简体命中后直接把上次的回答取出并拼接本次请求时间戳返回。第二层是语义缓存对用户消息做向量化计算与已有缓存条目的相似度超过0.92的阈值直接复用缓存结果。设置这个阈值需要注意太容易命中会误伤相似但不同的业务场景里返回旧答案可能造成严重错误太严格又会导致命中率上不去。0.92作为一个经验值对不同业务可以微调。实测下来这一类问答机器人的精确加语义缓存综合命中率大约在20%到25%。因为每个命中的请求省下的是一次完整的大模型调用等于直接省掉了该请求的全部输入输出token这部分的收益是乘法级的。而且缓存不改变回答质量命中时返回的是历史已验证的正确答案稳定性反而更好。另一个值得尝试的优化是模型分级路由。我在这个项目里把请求按复杂度分了两档简单问题比如FAQ匹配、日程查询走轻量级模型这类问题上下文短、输出固定用大模型完全是杀鸡用牛刀复杂问题比如规章制度推理、流程判断走全局能力更强的高规格模型。分级路由的判断逻辑可以很朴素先根据服务端规则筛选已知的简单问法再用一个轻量的意图分类模型兜底没有命中简单意图的默认走重模型。这套方案可以让约35%的请求流量跑到低成本档位对总账单的下降贡献明显。4. 常见问题与排查技巧实录4.1 为什么账单token总是比本地预估的多很多开发者在做Token优化时都会遇到这个困惑自己在代码里用字符串长度或者网上找的估算公式算出来的token数和后台账单里的usage对不上而且总是账单更多。这不是服务商算错了而是token的切分方式远比想象的复杂。中英文混合文本、特殊字符、换行符、空格这些都会影响切分结果。一个汉字大概是1到2个token但英文单词可能一个词拆成好几段标点符号和空格也各自占用token。用“字符串长度除以2”来估算只能做到数量级正确做不了精确预算。我自己吃过亏某一版检索逻辑会把用户消息里的换行符替换成逗号我本地预估输入2000token实际账单显示2400token多出来的400全是换行符和标点的切分结果。正确的做法是使用服务商提供的tokenizer工具进行校准如果服务商没有开源工具也要在日志里记录每次API响应的usage字段用真实数据建立本地的估算基准。另一个容易忽略的点是开发框架的底层可能自动注入了一些内容比如指令模板、安全提示、工具描述这部分不在你的业务代码里显式体现但会计入输入token。遇到账单和预估对不上的情况我建议先排查系统提示词是否被框架二次包装、历史消息是否多带了一轮空会话、重试逻辑是否把失败请求的费用也算进来了。4.2 缓存命中率低的五个隐蔽原因缓存方案上线初期命中率凉凉是常态。我排查过不少项目发现低命中率往往不是缓存本身没用而是key设计或者前置处理出了问题。第一个隐蔽原因是key里带上了时间戳、会话ID这类每次都会变的字段导致即便用户问的是一模一样的问题缓存也在每次请求都失配。正确做法是把请求中可变但无关影响的字段排除在key之外。第二个原因是文本没有归一化用户输入带着首尾空格、全角半角差异、多余换行同一个问题就被当成了不同问题。第三个原因是相似度阈值设得太高0.98以上的阈值在语义缓存里基本等于不命中因为自然语言很少有两个表达能一模一样到那种程度。第四个原因是只做精确缓存而没有语义扩展面对“年假怎么休”“年假几天”“年假政策”这类同义表达精确缓存毫无办法。第五个原因是业务本身长尾属性强用户问题分布太散缓存天生就没有用武之地。我把这五个原因按排查优先级排了个序做成了一张速查表症状可能原因解决办法完全相同的请求没有命中key中包含会话ID或时间戳清洗key字段只留业务核心信息相似表达没有命中只有精确缓存没有语义缓存加向量化检索和相似度阈值判断阈值类命中率极低相似度阈值过高从0.90起步按业务场景标定命中结果时好时坏文本没有归一化统一空格、全半角、大小写、换行命中率稳定但不降本长尾查询过多放弃缓存改用模型分级和检索优化4.3 上下文越长响应越慢性能质检怎么兜底Token优化推进过程中还有一个很常见的问题输入变短之后响应确实快了但回答质量是否下降了这个“快而差”的陷阱是必须防的因为省token不能以牺牲效果为代价否则业务方一票否决方案就白做了。我在这套问答机器人上线新优化版本后做了一套三层质检。第一层是自动规则质检检查回答是否包含空内容、是否截断、是否包含了提问中明确要求不允许提供的敏感词。第二层是抽样人工评估从线上日志里按用户满意度和会话长度分层抽样每个版本抽300条对照检索片段、模型输出和提问内容人工打分判断答案是否相关、准确和完整。第三层是离线测试集回归准备一套固定的200条测试题目包含简单FAQ、复杂推理、边界情况三类每次改动后跑一遍对比新旧版本的正确率变化。我实际遇到的坑在一次“压缩历史消息”的改动之后。当时把“近10轮全量”改成“近4轮原文摘要”自动规则质检全部通过人工抽样的整体分数也没明显下降但等看分层数据时发现涉及“很久之前提过的某个订单编号”这一类的长程问题回答准确率下降了12个百分点。原因很清晰摘要虽然保留了结论但把具体的订单号给弄丢了。这属于典型的“信息压缩损失”补救方法是在摘要模板里增加“关键实体必须保留”的强制要求并单独用一个字段保存订单号等关键实体而不是让它和自然语言混在一起。改完之后长程问题的得分恢复了。我的经验是Token优化必须是持续观测的过程任何一次改动都要有对比数据跟着不能改完就撒手。就算优化效果很好每月也要重新审一遍系统提示词和检索策略因为业务文档在变、用户问题分布在变、模型能力也在变没有一劳永逸的优化方案。最后再分享一个我一直在用的小技巧给每一类请求在日志里打一个“有效token占比”的指标用业务实际使用到的上下文量除以总输入token。比如检索问答场景里我只看“检索片段本轮用户提问”的token数占比。这个指标能直观反映优化有没有跑偏。我见过不少团队费用是降下来了但为了降费把关键上下文都删了回答质量跟着就崩了。Token优化方案的核心始终是“把钱花在刀刃上”而不是“一分钱都不花”。成本控制是手段回答质量和用户体验才是目标这一点想清楚了后面所有动作都不会跑偏。