大模型网关Token成本飙升?四板斧压缩路由缓存限额实战
2026/9/7 12:16:23 网站建设 项目流程

1. 收到账单那一刻:Token 曲线背后的成本失控信号

上个月打开Openlaw网关的成本账单时,我第一反应是怀疑统计口径出了问题。月度Token消耗量环比涨了187%,费用直接超掉预算线的六成;同期业务调用量只增长了不到25%。翻译成大白话就是:每处理一条请求,花的钱都变贵了。

Openlaw是我们团队内部搭的统一大模型网关——所有业务应用、智能体流程、后台任务要调用各家模型服务商,都会先打到这一层,由网关来做鉴权、路由、配额和Token计量。Token激增如果发生在业务服务器上,大概率是用户量涨了;发生在网关上,根源八成在架构设计或调用治理。这篇文章记录我从看到账单到定位根因、再到落地优化的全过程,东西不复杂,但基本上都是真金白银换来的经验。

1.1 我不是先看金额,而是先看曲线形态

收到账单邮件后,我的习惯不是急着去压财务要明细,而是先打开网关自带的监控面板,把过去三十天的Token消耗曲线拉出来看。原因很简单:账单金额是多因素叠加后的结果——实际请求量、输入输出Token的比例、缓存命中率、供应商单价差异——直接看钱,你根本分不清大头在哪儿。曲线形态反而能快速告诉你事故的类型。

我看到的第一件事是:曲线既不是均匀上坡,也不是随机毛刺,而是两个非常清晰的“台阶”。第一个台阶出现在月初第3天,日消耗从6000万Token直接跳到1.2亿,之后稳定了整整一周;第二个台阶在第11天,又跳到1.8亿,后面继续缓慢爬升。这种台阶式上涨说明不是流量突发,而是某个常驻调用模式变了:新增了定时任务、某块业务改成聊长上下文,或者某个智能体的循环逻辑被放开。如果是毛刺型曲线,基本是瞬时并发被顶上去了,处理思路完全不同。

把曲线按小时粒度展开后还有第二个发现:凌晨2点到4点之间也有一批稳定消耗,但那个时段业务侧根本没有真实用户。这说明有凌晨调度任务在跑,或者是一些无效请求在反复打模型。这类消耗最隐蔽,它不产生业务价值,却每天稳定烧掉几百万Token,是典型的“沉默成本”。

1.2 把三类可疑流量打上标签

光看总曲线不够,我第二步是把所有流量按来源标签拆开。Openlaw网关本身支持为每个接入方分配AppKey,我按照真实使用场景把它们归成三类:C端交互型会话,包括客服问答、助手类应用;后台任务型,包括文档批量总结、定时抓取、报表生成;第三方集成,指外部系统通过Webhook回调触发模型调用。

拆分结果非常直接:C端交互型会话占总消耗约80%,后台任务占15%,第三方集成单看量不大,但单次请求消耗异常高。于是排查重心落到“一个普通C端会话为什么能吃掉这么多Token”上。

这里我踩过第一个坑:不能只按AppKey聚合,还得在同一类流量里再按会话长度分桶。同样是客服问答,有的会话聊2轮就结束,有的一路聊到30轮。混在一起算平均Token,你会得到一个看起来很正常的数字,真正烧钱的那批超长会话反而被平均值稀释了。后来我在网关里加了会话级Token累积字段,一眼就看到:消耗排名前10%的会话,吃掉了这一类近60%的Token。排查范围立刻从“所有流量”缩小到“超长会话”和“高频循环调用”两块。这一步是整个排查的转折点,也是我后面决定做上下文压缩的直接依据。

2. 顺着网关日志挖出来的四类 Token 黑洞

2.1 多轮会话的上下文滚雪球

第一类原因是所有做对话产品的团队都会遇到的经典问题:多轮会话把历史消息全部塞进请求里,一轮一轮滚下去,上下文越来越长。Openlaw网关的日志里,有些会话到了第40轮,单次请求携带的上下文已经超过3万Token。就算后端的模型上下文窗口够大,这3万Token也是实打实计费的。

成本增长是平方级的。假设每一轮问答平均新增800个Token,第N轮时,单次请求的输入成本大约就是“固定系统提示词加N轮增量”。用户的第1次调用可能只要几百Token,第20次调用可能已经上万;如果这个会话继续挂机由定时任务自动续聊,每一轮都是全新计费。更扎心的是,很多团队调试功能时只测前三轮,根本不会触发超长会话,问题就被藏到了生产环境。

我们在抽样日志里捞了一个典型会话:一个用户断断续续问了5天问题,累计48轮,最后那几次请求,每条的输入Token都在2.5万以上。这个会话在最后一周烧掉的Token,比开头三周加起来还多。也就是说,会话生命周期的后半程,每花出去的一块钱里,至少有七毛钱在重复传历史。

2.2 工具调用循环导致“越干越费”

第二类原因比第一类更隐蔽:智能体的工具调用循环。Openlaw网关上接了不少智能体应用,这些应用在执行任务时会反复调用搜索、查数据库、分析文档等工具。日志里我翻到过几次很夸张的调用链,智能体为了回答“某条规定的有效期到什么时候”,连续调了15次工具,每次工具返回结果都长达1000多Token,然后这个结果又被完整拼进下一次请求。最后那一条请求的输入超过2万Token,而用户实际想要的答案,第一轮工具结果里早就有了。

问题出在智能体的“循环终止条件”上。很多智能体框架的默认逻辑是:只要工具结果和当前问题还沾一点边,就继续推理、继续调工具,直到达到最大迭代次数。每一步都在往上下文里追加几千Token,而这些追加内容大多是同一批数据的不同排列。

网站在这个场景里的角色很尴尬:它看得到Token消耗,但看不到智能体内部状态机。所以我只能在网关侧做防御性限制——给单会话的工具调用次数加总量控制,超过阈值强制返回,把损失限制在可控范围内。这个限制不优雅,但确实有效。

2.3 超时重试引发的重复计费

第三类原因要从网关本身的架构说起。Openlaw在设计之初负责对上游模型调用做超时控制和重试,初衷没问题:某家模型服务商偶尔变慢,客户端等着急,网关就自动换备选通道重试一次。但问题是,我们只实现了“重试”,却没有在重试之前先确认上一次请求到底有没有成功送达。

有一次上游通道的响应时间超过了我们的重试阈值,Openlaw判断请求失败,自动重发了同一条请求;实际上游已经在处理第一条请求了,只是响应慢了几秒。于是同一条业务请求被执行了两次,Token账单也记了两次,业务侧却只收到一个结果。这类重复调用占总体消耗的比例不高,大概5%左右,但它是零价值消耗,优先级必须排在高位。

排查这类问题有个技巧:重点看同一会话ID、同一时间窗口内有没有两条请求内容完全相同。网关日志里如果出现这种“双胞胎”请求,多半就是重试逻辑在捣鬼。修复的方式很简单,给每条业务请求加全局唯一请求ID,重试时携带同样的ID,在网关层做幂等过滤。

2.4 模型路由失配:杀鸡频繁用牛刀

第四类原因在账单里看得最直接:模型单价失配。Openlaw网关的默认配置是把所有请求都扔给能力最强的旗舰模型,因为一开始团队想保证效果,没有做分级路由。运行了一段时间之后我做了抽样分析,发现大约35%的请求属于简单任务——提取一个日期、把一段话转成JSON、打一个情感标签。这类任务用轻量级模型完全能胜任,价格却可能差3到5倍。

举个例子,同样是一万Token输入、五百Token输出,旗舰模型的费用可能是轻量模型的五倍。如果能让这35%的简单请求走轻量模型,总费用直接打六折。这个账算下来之后我才真正意识到:网关不能只做“统一出口”,还得做“分级出口”;否则统一接入的便利性,迟早会变成成本黑洞的温床。

3. 为什么同样的调用量,成本却差了三倍:计价与缓存细节

把四类黑洞都定位出来之后,我开始做成本模拟,结果发现:即便把请求量压回到正常水平,成本仍然比预想高不少。这时候我才醒悟,问题不只是“调用太多”,更是“每次调用太贵”。

3.1 Prompt 缓存是个大金矿,但默认没被利用

现在很多主流模型服务商都提供Prompt缓存能力:同一个请求如果前缀完全一致,后续命中的部分会便宜很多,有些甚至只有原价的十分之一。这个机制对大模型网关来说就是天然的省钱利器,因为它最适合“多条请求共享系统提示词、共享同一批历史上下文”的场景。

但Openlaw网关一开始并没有把这个机制用好。问题出在网关每次请求的Prompt头部都拼接了时间戳、请求ID、业务标识符这类动态字段。按缓存机制的要求,只有前缀完全一致才能命中,头部一旦变化,整段前缀缓存全部失效。从服务商视角看,我们几乎每一条请求都是“全新输入”,按全价计费。后来我把这些动态字段统一挪到了Prompt尾部,缓存命中率才从几乎为0提升到接近50%。

这里要特别提醒:Prompt缓存是按前缀匹配的,所以任何带随机性或实时性的内容都不要放在头部。还有一种是团队内常见的操作失误——每周调整系统提示词版本,版本一变,旧缓存直接作废。为了兼顾迭代和缓存,我在网关里给提示词做了版本化,只有确认要发布新版本时才切换前缀,避免把缓存周期切得太碎。

3.2 输入输出差价与“隐藏的补全长度”

第二个让我意外的细节是输入和输出的计价差异。绝大多数模型服务商对输出Token的单价要远高于输入Token,有些甚至差到三倍以上。这意味着,就算我们辛辛苦苦压缩了输入,如果输出特别长,成本依然下不来。

于是我开始盯一个指标:补全膨胀率,也就是一次调用里输出Token数除以输入Token数。正常问答场景这个比值通常在0.1到0.3之间;但在网关日志里,有些智能体场景的比值竟然超过了1,输出比输入还长。

我顺着日志去翻,发现是几个智能体在“思考过程”里把中间结果原样输出成最终回复,有些甚至会把一段调试信息、工具日志也拼在里面发给终端用户。不光浪费Token,体验也很差。这种输出膨胀的根因,往往是提示词里要求“一步步详细推理”而没有限制输出格式。我把典型场景做了一个修剪,在提示词里明确“只输出最终结论和必要依据”,膨胀率立刻从0.8降到0.2左右。

3.3 供应商计价模型的差异与网关记账误差

第三个细节更偏向工程治理:不同模型服务商对Token的计算方式并不完全一致。同一个中文字符,在甲家的模型里可能算1个Token,在乙家的模型里可能被切分成2到3个Token。网关侧做Token计量时只能按自己的方式预估,于是记账误差就出现了。

我对比过Openlaw网关记录的Token数和各家服务商账单上的Token数,发现部分月份误差能到10%以上。这个问题不影响实际成本,但会严重影响优化决策——如果网关记录的基线就是错的,我们在压Token消耗时,等于拿一把不准的尺子反复量长度。

修复不复杂:网关在收到每次调用后的usage回传字段时,直接用服务商返回的真实Token数覆盖本地估算值,再按应用和会话维度做累加。这样至少保证了后续分析的记账口径一致。这里我特别想强调一个经验:网关这类中间层系统,做计量一定要“以服务商回传为准”,不要自己另起炉灶算一套,两套数据并行的结果一定是两边都对不上。

任务类型输入Token输出Token补全膨胀率优化手段
客服问答(短会话)12002400.2保持现状
客服问答(40轮长会话)300003000.01摘要压缩
智能体工具调用16000120000.75循环限制、输出约束
实体抽取(简单任务)5001200.24轻量模型路由

4. 我落地的四板斧:压缩、路由、缓存、限额

定位问题和理解计价规律之后,就进入动手优化阶段了。我并没有一上来就重写网关,而是先做风险最低、见效最快的四件事,每一件都能单独评估收益。

4.1 上下文压缩:把历史对话折叠成概要

针对上下文滚雪球的问题,我在Openlaw网关里新增了一个会话级上下文压缩器。核心逻辑不复杂:当会话累计Token超过8000之后,不再把完整历史追加进下一次请求,而是先把超出窗口部分的历史交给轻量模型总结成概要,然后只保留“最近两轮完整消息+概要”装入请求。

可能有朋友会问:做总结本身不是也要花钱吗?确实要,但总结是一次性成本。拿一个40轮会话举例:如果不压缩,第41轮请求光是上下文就要3万Token;压缩之后只需要3000 Token概要加最近两轮的1600 Token,单次请求直接省了2.5万Token。就算把总结时花掉的2000 Token均摊进去,长期看也是划算的。

压缩器里有一个关键参数需要反复调:历史保留窗口。窗口太大,压缩省下的钱不够多;窗口太小,模型容易丢失早期的重要信息。我在客服场景里试过2轮、4轮、8轮三档,最后选了4轮,因为客服问答的有效信息高度集中在最近几轮,更早的内容基本可以通过概要覆盖。

4.2 基于任务难度的模型路由

针对模型路由失配,我在网关上实现了一个两层路由。第一层是规则路由,根据请求里的业务类型关键词和接入方使用的接口名,把明确属于简单任务的请求直接分发到轻量模型;第二层是动态路由,轻量模型返回时如果带上了低置信度标记,网关自动把同一请求升级到旗舰模型再跑一次。

规则路由我用的是非常朴素的实现:一张任务类型到模型通道的映射表,由各业务线自己上报。“实体抽取”“格式转换”“关键词分类”走轻量模型;“复杂推理”“长文档分析”“多轮规划”走旗舰模型。拿不准的任务宁可先走旗舰模型,也要保证质量不被优化动作拖垮。

这一版路由上线后的收益很直观:旗舰模型的调用量占比从100%降到62%,但业务反馈并没有变差。原因是那38%的请求本来就只做简单转化,轻量模型在单点任务上的表现并不差,差的只是复杂推理和多步规划能力。

4.3 语义缓存:让相似请求只花一次钱

针对重复问题,我在网关上加了语义缓存。原理是:每次请求进来,先对Prompt做Embedding,再用向量相似度去缓存库里找有没有高相似的请求;如果余弦相似度超过0.95,直接复用上一次的响应,不再调用模型。

这套方案特别适合问答场景里“用户换个说法问同一个问题”的情况。我们线上大概有15%的请求能命中语义缓存,这15%是纯省下来的成本。不过这里有几个注意事项:

  • 不是所有请求都适合缓存。凡是涉及实时数据、用户身份、当前时间的请求,都必须绕开缓存。
  • 缓存键不能只用原始文本,要带上业务线和会话ID,避免跨用户的隐私内容泄露。
  • 必须有TTL过期策略,我默认设了24小时,避免旧数据被无脑复用。

实现时踩过一个小坑:Embedding模型本身也有Token消耗,而且可能不算便宜。我一开始把每条请求都做Embedding,结果缓存成本几乎吃掉了节省的钱。后来优化成“先做请求文本的简单哈希,命中哈希白名单再做语义匹配”,只有前几次完全相同的请求才进入语义流程,成本才算降下来。

4.4 网关侧配额与熔断:把成本锁在预算内

最后一道防线是配额与熔断。Openlaw网关开始支持按应用维度配置每日Token预算——比如客服应用每天上限2000万Token,达到上限后网关直接返回429错误,由应用方申请提升预算再放开,而不是默默烧钱。

实现上用Redis存计数器,核心逻辑不长:

import redis r = redis.Redis(host="redis.internal", port=6379, decode_responses=False) def consume_quota(app_key: str, used_tokens: int, daily_limit: int) -> bool: key = f"gateway:quota:{app_key}:{date_today()}" pipe = r.pipeline() pipe.incrby(key, used_tokens) pipe.expire(key, 86400) current, _ = pipe.execute() return current <= daily_limit

方案技术上很简单,但它的治理意义远大于代码量本身:把成本控制从“事后看账单”变成了“事前干预”,给业务方一个明确预算上限,超限不再是无感支出。跟配额配套,我还加了每分钟调用数监控和告警,正常波动阈值内不打扰,一旦超过阈值立刻把消息推到负责人群里。这一板斧不替你省钱,但能保证你不会在某天早上醒来突然发现账单爆炸。

5. 下一步想做的研究方向

优化上线到现在三周,成本已经稳定在预算线以内,四板斧解决的是“过去积累的问题”。运营过程中我又观察到几个新问题,这也是接下来准备深入研究的方向。

5.1 按业务线拆分的 Token 预算矩阵

现在的配额是单应用单维度,但实际业务是立体的:同一个业务线可能同时跑三个应用,三个应用共享一个总预算;有些应用白天量小、凌晨有批处理任务,如果只按“应用”配额度,夜间时段容易出现额度空置。

我准备做一张“业务线×应用×时段”的预算矩阵,用历史数据自动算出每个格子的基准额度,并支持月度滚动调整。比如客服线工作日白天给大头,文档线凌晨批处理时段给大头,额度如果没用完可以在月末结算时跨格子转移。这样能把每一分预算花在正确的时间窗口,而不是每天一大笔额度在低峰期空转。

5.2 Token 利用率(UTL)作为核心指标

Token利用率是我最近特别关心的指标。它衡量的是:模型输出的Token里,有多少真正被下游任务采纳了。比如模型生成了500字总结,但业务只用其中一句核心结论,那另外480字就是浪费。

顺着这个思路,我准备在智能体场景里加一个“输出采纳率”埋点。可以在网关的日志里记录模型完整输出,再对比业务端实际使用的文本长度,自动算出每条请求的UTL。定位到UTL特别低的任务环节后,优先优化那些环节,效果可能比单纯压缩输入更好。

5.3 全链路成本归因:从请求到 Prompt 片段

现在的监控只能看到“一次请求花了多少Token”,看不到“这些Token花在系统提示词、历史消息、工具结果、当前输入各占多少”。我计划在网关日志里按Prompt片段做标记,然后输出成本构成报告。

比如一份报告会告诉业务方:“你的历史消息占了60%的成本,建议开压缩”“你的工具结果平均每次占3000 Token,建议做结果截断”。这类建议如果能自动生成,业务方就不需要理解网关内部实现,照着建议改就行。这个方向本质上是把优化能力产品化,从“网管帮你调”变成“网关告诉你哪里该调”。

5.4 结构化输出约束与 Token 省流

最后一个方向是优化输出端。现在很多调用都是让模型自由发挥文本,业务端再做结构化解析,这个过程的Token浪费很大。我准备针对高频接口强制注入JSON Schema或函数调用规范,让模型直接输出结构化结果,既省Token又减少解析失败。

顺着这个方向,我还想研究“输出预算”机制:给每次调用设置最大输出Token限制,超过就截断并触发一次二次压缩,防止输出膨胀。很多模型支持在请求参数里设置max_tokens,但有没有一种更智能的动态机制,让它根据任务类型自动决定输出上限,而不是所有请求都统一给一个最大值,这需要收集更多数据才能做准。

优化成本这件事,我现在的体会是:它不是一次性工程,永远会有新场景、新模型、新业务逻辑冒出来。但只要网关这个“统一出入口”的观测和治理能力在,每一笔钱花到哪里都是可以追溯的。下一步我最想跑通的还是Token利用率这个指标——它可能会暴露很多我们目前没意识到的问题。等有结论了,我再写一篇跟各位分享。

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

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

立即咨询