☰
大模型API成本治理实战:缓存、路由与提示词优化指南
2026/10/10 13:05:20 网站建设 项目流程

最近团队在打磨一个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序列化后的值。这样设计的好处是:同样的用户用同样的参数发同样的消息,才能命中缓存,避免“看起来相同但实际不同”的请求污染结果。

缓存存储我们直接用了Redis,Key是哈希值,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、status(pending、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对比。成本治理很容易陷入“以为省了、实际没省”的错觉,只有对比数据是诚实的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询