
最近团队在打磨一个AI原生应用前两个月光是调用大模型API就烧掉了小十万。老板盯着账单问“这钱到底花哪了”的时候我意识到一个问题大部分做AI应用开发的团队根本没把API费用当成一个需要专门设计的技术指标来对待。我们后来花了两周时间做成本治理把单次会话的平均调用成本降到了原来的四分之一整体月度API支出压缩了接近60%效果显著到让我想把这套实战过程完整复盘一遍。这篇文章就想聊聊我们具体怎么做的怎么拆解费用构成、怎么用缓存和路由策略“削峰填谷”、怎么从提示词和上下文管理上抠成本以及那些常规文档里不会告诉你的坑和心得。内容偏工程落地适合正在做AI应用开发、或者已经开始被API账单追着跑的团队参考。1. 成本失控的根源先把账算明白1.1 费用不是“用得多”那么简单很多人一说降本第一反应是“少调用几次”。但把账单拉出来看会发现费用失控通常不是单纯调用次数多而是单次调用的成本远超预期。大模型API的计费基本都按照token数量计算分为输入token和输出token两类。输入token包括系统提示词、历史对话记录、用户当前输入、检索回来的上下文片段输出token则是模型生成的内容。一个容易被忽视的事实是大模型的定价通常输出token比输入token贵2到5倍。像那些需要模型生成较长回答、代码或结构化JSON的任务如果提示词设计不合理、让模型反复思考或重复内容输出token会飞速膨胀。我们的账单里一度出现过单次请求输出token超过6000的情况折算成费用一次调用的成本能顶过去二十次。1.2 我们踩过的“隐性浪费”场景复盘时发现钱的流失主要集中在这几个场景我列出来给大家对照多轮对话中的历史消息全量重发每轮都携带完整对话历史会话越长上下文越大系统提示词加上20轮历史可能就有四五千token一天几万个会话累计起来非常多。简单任务用了大模型很多请求只是关键词提取、文本分类、格式化输出这类简单任务却调用了最贵的大参数模型。提示词冗余严重一个简单的意图识别任务提示词写了两千多字还要求模型“一步一步思考”输出自然又臭又长。相同的请求反复计算同一份合同内容在不同时段被反复提交分析模型每次都是从头开始计算完全没有复用。实时响应需求被高估不少场景本来能接受秒级甚至分钟级延迟却全部走了同步调用导致峰值并发高、配额费用也高。1.3 成本治理的四个发力点把账算清楚之后我们的成本治理就围绕四个方向展开这也是这篇文章后续几个章节的主线缓存策略针对可复用的请求做精确缓存和语义缓存让相同问题不再重复花钱。模型分级路由把简单任务和复杂任务分流小模型能干的绝不上大模型动态降级来平抑成本。提示词与上下文压缩精简系统提示词压缩历史对话让输入token瘦身。异步化与批处理把可延迟的任务从同步链路中拆出来用低峰时段批处理降低并发峰值和成本波动。这四个方向不是割裂的落地时互相配合。比如模型分级路由之后缓存命中率和上下文压缩的效果都会变好。先有全局视角再逐项突破比零散地“省一点是一点”有效得多。2. 缓存层实践让相同的钱只花一次2.1 精确缓存最简单也最容易漏掉的一环先聊精确缓存。思路很朴素如果同一个请求之前已经问过并且结果能被复用就直接返回缓存不再调用模型。我们实现时用一个请求指纹来标识唯一性。指纹由三部分拼接后取哈希用户ID或租户ID、模型参数列表、消息数组。模型参数列表里包含温度、top_p、max_tokens这些推理参数的稳定版本消息数组是完整的消息序列按顺序JSON序列化后的值。这样设计的好处是同样的用户用同样的参数发同样的消息才能命中缓存避免“看起来相同但实际不同”的请求污染结果。缓存存储我们直接用了RedisKey是哈希值Value除了模型返回的正文还额外记录了几项元信息模型名、token消耗、生成耗时、缓存时间戳。token消耗记录是额外加上去的因为后面做成本统计时需要知道“这条结果如果没命中缓存本该花多少钱”这样才能算出真实节省量。TTL策略上普通对话类缓存只保留30分钟避免用户改了某个条件但结果还是旧数据。那些确定性的处理任务比如合同条款摘要、商品描述规范化TTL可以放到24小时甚至更久。TTL要按业务场景区分统一用同一个值很容易出问题。2.2 语义缓存解决“换个说法就要重算”的痛点精确缓存的问题显而易见。用户问“这个产品的退货政策是什么”过一会儿又换成“你们退货怎么处理”字面不同但意思几乎一样。如果只有精确缓存这两次都得重新花钱。我们干脆上了语义缓存。语义缓存的实现是给每个请求生成一个向量存到向量数据库里。新请求进来时先用相同的Embedding模型生成向量然后做相似度检索。相似度超过预设阈值就认为是同一个意图直接返回已缓存的答案。阈值一般设在0.9到0.95之间太松容易误命中太紧又起不到复用效果。这里有三个细节需要特别留意Embedding模型和主模型不要混为一谈。Embedding模型的选择要看语义区分度且这部分调用本身也有成本所以不能每次都重新Embedding。我们的做法是先做精确缓存查重没命中再算向量命中就直接返回尽量减少额外调用。缓存结果要带“过期验证”机制。语义相同的请求如果答案是会变化的比如库存查询、天气查询缓存的结果会误导用户。我们把这些场景标记为“不可语义缓存”只对确定性内容启用。语义缓存的Key要加入业务域隔离。不同业务线的请求向量可以很接近但答案可能完全不同。入库时给每条缓存记录加上业务域标签检索时先按域过滤再算相似度能明显降误命中率。从实际账单看语义缓存加上精确缓存让我们的模型调用总量直接减少了约22%这笔节省是非常可观的。2.3 缓存落地时的工程细节工程上要尽量让缓存层对业务逻辑透明。我们封装了一个统一调用入口叫LLMService业务代码只能通过这个入口访问模型。缓存逻辑全部收敛在LLMService内部业务方感知不到也不需要在各自的代码里写缓存逻辑。这个封装层是后续所有降本策略的落脚点没有它很多优化都只能在各个业务里各做一遍非常零散。缓存读取失败时必须有降级策略。Redis抖动或者向量库超时不能影响主流程catch到异常后直接放行到真实模型调用。成本优化不能以牺牲可用性为代价这个原则要一票否决。还有一个小坑缓存命中后响应时间变快了但你也得保证返回内容风格和格式是一致的。有些业务在模型回复后会做后处理比如格式化JSON、去Markdown缓存层存的不只是模型原文而是后处理完的最终结果否则下游解析时容易踩格式不一致的坑。3. 模型分级路由让合适的模型干合适的活3.1 大模型不是万能的也不是所有任务都要用大模型我们最初整条链路只接了一个大参数模型理由很简单效果最好省心。但账单出来后发现很多任务压根用不到这种级别的能力。比如“从这段文本里提取出所有手机号”这种任务大参数模型和小参数模型的效果几乎没有差别价格却差着一个量级。后来我们引入了模型分级路由核心思路是建立一个任务复杂度评估器对每个请求先做一次预判再决定让它走到哪一级模型。分级大体按三层来做轻量层负责关键词提取、情感粗分类、格式清洗、正则辅助类的任务。用小参数模型价格大约是旗舰模型的十分之一甚至更低。标准层负责需要一定理解能力的任务表格抽取、文档摘要生成、FAQ问答匹配。用中档模型。旗舰层负责复杂推理、代码生成、长文档综合理解、需要严格遵循复杂指令的任务。只有这部分才用最贵的大参数模型。3.2 怎么做任务路由规则先跑模型后上很多人一听“分级路由”就觉得要用一个智能分类模型来判断任务复杂度成本又上去了。我们的做法正好相反先用规则规则不够再用小模型轻易不动大模型来判断。规则路由是最便宜的一层我们内部维护了一张“任务类型-模型档位”的映射表。业务方在接入时会被要求声明任务类型比如intent_detection、extract_contact_info、code_review、long_doc_summary。系统先查映射表直接分配模型档位。更智能一点的路由会结合输入特征消息长度超长必须走支持长上下文的旗舰模型用户输入包含明确的代码块标记时走代码能力强的档位这是对规则层的补充。对于规则无法覆盖的情况我们让轻量层模型做“复杂度三分类”分类标签如果是{简单}直接用轻量层模型处理。标签是{中等}升级到标准层。标签是{复杂}升级到旗舰层。这套分级上线后我们的旗舰模型调用占比从接近100%降到了30%左右。总费用下降的很大一块就是从这里省出来的。3.3 动态降级与超时考虑模型分级还有一个容易被忽略的好处是动态降级。当某个模型服务压力大、响应变慢或配额快用完时路由层可以把一些非核心任务自动降级到低一档模型牺牲一点质量换稳定性和成本。我们实现了一个简单的熔断逻辑如果高成本模型的调用连续失败率超过阈值就把任务降到标准层避免雪崩也避免了用宝贵配额去试探一个不稳定的服务。在路由层需要补偿的是模型能力差异带来的格式不稳定。同一个输出约束大模型能稳稳按JSON Schema给结果小模型偶尔会多给几个字段或写错类型。我们的方案是在提示词里放非常明确的输出示例并在下游做一次结构校验校验失败的请求自动升级到高一层模型重跑。这个“校验失败升级”机制非常关键否则分级省下的钱会变成联调排查的加班时间。3.4 分级路由的评估体系上了分级路由之后不能只看省钱还得盯效果不然很容易为了成本牺牲体验。我们建了一套评估指标每周自动跑一批评测集任务完成率目标字段是否成功提取JSON是否完整可解析。结果可接受率由抽样人工打标看回答质量是否达到业务要求。平均延迟分级后如果延迟明显上升说明模型能力不够需要调高级别。单次调用成本分模型档位统计看消费分布是否合理。如果某个任务类型在低档位上及格率低于80%我们就把该任务类型升级一档哪怕多花点钱稳定性优先。成本优化永远是在效果约束下进行的这个原则需要明确。4. 提示词与上下文压缩把自己逼成话痨压缩大师4.1 系统提示词的“词字值千金”输入token里面系统提示词是每天都在消耗却最容易膨胀的部分。我们早期的系统提示词动辄2000字写得特别全各种边界条件、各种示例、各种few-shot。后来做了一次严格精简原则是能不放的就不放提示词只保留当前任务必需的行为约束和输出格式其他背景知识全部挪到知识库检索。示例只留最典型的一个任务一个示例即可最多两个。示例的价值在于给模型划定输出的形态边界而不是让模型学习业务规则。指令要短而明确与其写“请在回答前分析用户意图并考虑多种可能情况”不如写“只输出JSON对象”。约束越收敛输出token消耗就越低。我们有一个详细的任务原来提示词是2600字精简后大约700字。单次输入token直接少了近2000。如果一个应用每天有10万次调用这一项每月能省下来的钱相当可观。4.2 多轮对话的历史窗口管理多轮对话是上下文消耗的大户。用户和机器人聊了20轮之后如果每次请求都把20轮历史全部发给模型输入token会急剧膨胀。我们遇到一个典型案例客服机器人平均会话长度30轮每轮平均500字。如果全量带上历史输入token经常突破6000相当于每次调用都在为一个会话的高额历史和当前问题买单。后来我们做了三层压缩策略历史截断只保留最近N轮对话超过的就丢弃。N按业务需要设置常见是10到20轮。如果早期对话里有关键信息靠摘要补偿而不是全量保留原文。消息摘要化当一个会话进行到一定长度后把已有的历史消息异步做一次摘要用一段200字的摘要替换掉原有的历史明细。后续请求携带的是摘要而非原始消息。关键信息结构化在对话过程中实时抽取用户关键信息比如客户编号、问题类型、订单号存成结构化字段。后续请求只携带这些结构化字段不携带所有原始的来回追问。比如用户在第3轮报了订单号第18轮再问时不需要重发第3轮那段对话只要把订单号字段放进去就足够。这套组合打下来平均输入token减少了约35%。多轮会话等待时间也变短了用户体验反而更流畅。4.3 检索上下文也要控制给模型喂检索结果时同样存在“宁多勿少”的毛病。早期我们想把所有相关检索片段都给模型生怕漏了信息。结果是输入token暴涨模型还会被不相关内容干扰。调整的要点是给每个检索片段打一个相关度分只取Top K个片段并且每个片段长度做截断。K一般取3到5超过5个片段时模型注意力容易被稀释。检索片段的小标题和来源信息保留但正文只截取关键段落。另外对检索片段做去重防止同一信息以多种相似表述重复消耗token。在需要引用来源的场景里我们还让模型按引用编号输出这些编号对应的完整引用内容在应用层拼接给用户并不全部塞进模型上下文。这个思路很多人没想到模型只需要知道“第几号引用”不需要看到引用全文。4.4 输出上限max_tokens并不是越大越好输出token直接跟价格挂钩但很多业务代码里max_tokens配置得非常宽松。比如任务只是做一个“是/否”判断max_tokens却设成1024模型有时会多发一段理由费用当场翻倍。我们统一梳理了所有任务的max_tokens按任务类型设定合理上限分类打标类上限设为50。短文本生成类上限设为256。摘要类按摘要长度目标值乘以1.5。代码生成类按估算行数设上限。max_tokens不仅是为了防止模型“跑飞”更是一个成本保险丝。把上限设到合理值即使模型真的出现异常也不会产生天价账单。5. 异步化与批处理把高峰削平把闲时利用起来5.1 实时场景真的需要实时吗不少业务默认走同步调用用户点一下按钮接口要等模型返回。这个模式的问题在于用户侧体验确实好但成本上模型服务是按峰值并发和token用量定价的同步调用会让所有请求都挤在同一时间段打出去极易触发限流和高价配额。我们的策略是把请求按实时性需求拆成三类实时必达用户明确在等待一个即时反馈比如对话框里的直接问答。必须同步但可以配合缓存和路由优化。短延迟可接受比如上传一个文档后等待几秒生成摘要。用户能接受进度反馈可以先返回“处理中”再在后台异步跑模型。完全异步比如批量数据分析、定时报表、夜间数据清洗。这些任务根本没有必要在请求时刻同步推理。分类之后真正需要走昂贵同步链路的请求量小了很多成本自然下来。5.2 任务队列与批量化封装我们引入了消息队列和一批批处理的框架。一个大任务被拆成多个子任务后进入队列调度器会攒一批子任务一起调模型接口降低调用次数。批量推理还有个额外好处很多模型API按批次计费有折扣。鉴于有些模型API不支持真正的batch模式我们退而求其次做了请求合并。把多个逻辑独立的短请求合并到一个系统消息里让模型一起生成再按输出的分隔标记拆回给各业务方。这个操作需要谨慎只在任务类型相近时才能合并否则输出结构互相干扰。批处理的时间窗口也要设计好。深夜到凌晨模型API调用量低把那些跑批任务排到这个时段配额充足、延迟低、偶尔还能利用优惠时段。我们内部管这个叫“错峰省钱”。5.3 异步化带来的新问题状态管理与失败重试异步化不是把请求扔到队列里就完事了。用户需要知道任务进度于是要有任务状态机制。我们简单实现了一张任务表字段包括task_id、statuspending、running、success、failed、result、error_msg、created_at、finished_at。前端轮询或通过WebSocket接收任务状态变更。失败重试也要设计好。模型调用偶发超时或者返回空值不能无限重试否则费用翻倍。我们给每个任务设了最多3次重试每次重试之间指数退避。只有队列消费端的重试没有业务发起端的无限重传。5.4 成本稳定的另一面削峰填谷带来的预算可预测异步化带来的一个重要副产品是成本曲线的平滑化。以前账单像过山车白天高峰呼呼烧钱夜里几乎为零。现在大部分高成本任务被搬到了相对平稳的时段执行每日成本曲线变得平缓。到了月底汇报时预算预测也准了很多不会再出现月中一看已经花了80%预算的窘况。还有一点经验异步任务的token消耗也要统一走监控不能因为它是后台任务就放任不管。我们有过一个数据清洗任务因为正则表达式写错了导致模型反复处理同一批畸形数据白白烧掉一堆费用。后台任务必须和在线任务一样有告警、有日志、有成本统计。6. 监控大盘与计费归因没有度量就没有治理6.1 请求级日志是一切分析的基础成本治理的前提是知道每一分钱花在了哪里。我们早期的日志只有状态码和耗时没有记录token消耗导致费用异常时完全无法排查。后来在统一调用入口LLMService里接入了请求级日志每条日志记录以下字段时间戳精确到秒业务线和任务类型请求的模型名和档位输入token数、输出token数缓存是否命中命中则记节省了多少token路由决策结果是否升级、降级、走规则还是走分类模型响应延迟和状态码消息指纹和哈希值这份日志是全链路归因的基石。有了它才能回答老板那句“钱花哪了”。6.2 费用归因的层级设计日志采上来之后我们按三层做聚合层层下钻第一层业务线维度。看哪个业务线消耗最大是否符合预算结构。第二层任务类型维度。看哪些任务类型是费用洼地重点优化资源应该投向哪些地方。第三层模型档位和调用量维度。看是不是有简单任务混进了旗舰模型或者某个任务的调用量异常飙升。有一个场景能说明归因的价值优化后第一周的账单突然回升怎么查都找不到原因。后来按任务类型下钻发现是某个活动页上线后一个“商品卖点生成”任务的调用量蹭蹭涨单次成本不高但总次数巨大。没有归因能力这种问题很难定位。6.3 成本告警怎么设才不“狼来了”告警阈值太灵敏也没用天天告警大家就麻木了。我们的经验是分两级日级环比告警当日累计费用对比前7天同时间段的均值如果超过1.5倍就告警。这个能抓突发异常。单任务成本突增告警某个任务类型的平均单次调用成本如果连续1小时上涨30%以上说明可能有提示词改动或者模型路由异常立刻告警。告警不能只发到群里不看必须带上下钻链接能直接看到异常的调用日志和归因结果。省去“收到告警再花半小时查日志”这个过程处理效率能提高不少。6.4 每周成本复盘一项必须养成的习惯我们团队每周固定有一个15分钟的“成本复盘会”不是形式主义而是实实在在过一遍数据本周总费用和上周对比。各业务线费用分布有没有结构性问题。Top N个高消耗任务逐个过一遍看有没有优化空间。缓存命中率、分档调用占比、平均单次调用成本这些核心指标。就是这个简单习惯让我们能在成本刚有上涨苗头时就及时介入而不是等到月底账单出来才发现问题。7. 常见问题与避坑实录7.1 缓存命中率很高但费用没怎么降为什么这是最容易踩的坑。缓存命中率高但费用降得不明显原因多半是缓存主要命中在低价值请求上。比如一个每天调用几百万次的健康检查类请求本来单次费用就低命中再多也省不了多少。真正高价值的请求比如长文档分析、复杂生成反而因为变化多导致命中率很低。解决的办法是反过来设计先按费用贡献排序任务优先给费用高的任务做缓存。语义缓存里也要为高价值任务单独调高相似度阈值宁可模糊匹配也别错过复用机会。7.2 语义缓存误命中导致用户拿到牛头不对马嘴的答案这个问题出现过一次印象非常深刻。有一次用户问“退款多久到账”缓存里有一条“退货政策”的答案因为向量相似度高直接返回了结果答非所问。语义缓存不是银弹必须结合业务约束。我们的补救措施是把那些“答案强依赖时间/库存/订单状态”的领域直接排除出语义缓存范围只对静态知识类任务启用。7.3 模型降级后格式不稳定下游直接报错分级路由省钱归省钱小模型的输出稳定性确实差一些。有一次把某个信息抽取任务降级到轻量层结果小模型返回的JSON里多了一个字段下游解析直接崩了。现在的规范是所有分级任务必须带输出格式校验校验失败的请求自动升级重跑。这条规则在路由配置里是硬约束不允许绕过。7.4 提示词压缩过头模型开始“听不懂人话”精简提示词也要有度。我们把某些任务的说明删得太狠结果模型频繁返回格式错误重试率反而上升费用并没省下多少。后来学到的经验是提示词里的“行为约束”和“输出格式”两条底线不能压缩能压缩的是冗余的示例、背景知识、不必要的边界条件罗列。每次压缩都要配套跑一遍评测集确认效果没有回退再上线。7.5 异步任务的重试风暴有一次消息队列里的任务因为模型API返回异常进入了疯狂重试的状态。每重试一次都是一笔真实的token费用。但因为任务是后台运行没有加告警等到我们发现时已经白烧了不少钱。异步任务一定要单独加监控和重试上限不能让它默默地在后台烧钱。7.6 成本治理是持续过程不是一次性改造最后想强调一点成本治理不是一次性改造完就结束的事。我们的经验是每次提示词调整、每个模型升级、每新增一个任务类型都可能对成本结构产生影响。所以现在新功能上线前我们都会问一句这功能预计单次调用成本是多少日调用量预计多少是否有缓存策略路由档位怎么设置这已经变成一个常规开发流程的固定环节了。8. 复盘总结与下一步规划这轮成本治理下来我们的核心指标变化很明显缓存命中率从0拉到了25%以上旗舰模型调用占比从近100%降到了30%左右平均单次调用成本下降了约55%整体月度API费用相比治理前下降了近60%。而且这个过程中用户侧的响应体验几乎没有回退部分场景因为缓存命中反而更快了。下一步我们计划做的两件事一是把成本治理能力产品化。目前很多配置还散落在代码里比如各任务的模型档位映射、缓存TTL、压缩策略都是硬编码的。后续准备做一个轻量的成本配置中心让业务同学能通过后台页面调整路由策略和缓存参数不用每次发版才能改。二是探索更细粒度的量化分析。现在的归因还停留在任务类型和模型档位层面下一步希望能做到“提示词片段级别”的成本洞察通过更细粒度的日志逐步建立起更好的效果指导体系。我个人在实际操作中的一个体会是成本治理最难的往往不是技术方案本身而是让团队每个人都建立起“成本也是技术指标”的意识。当每个开发者在写提示词、调参数时都能多问一句“这样会有多少token消耗”效果比任何集中式的优化工具都来得持久。最后分享一个小技巧每次改动都要留一个可对比的样本记录。我们每次优化后会保留一份前一次的请求日志和响应结果方便后面做A/B对比。成本治理很容易陷入“以为省了、实际没省”的错觉只有对比数据是诚实的。