尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI应用Token成本失控?从网关优化到上下文减负的实战指南

AI应用Token成本失控?从网关优化到上下文减负的实战指南 Openlaw 平台上线一年多我一直负责网关这一层。过去几个月遇到了这几年最头疼的事token 消耗像开了闸一样涨月末账单直接超标运维群里炸了锅。当时所有人的第一反应都是是不是被刷了或者模型服务商计价出错了但查到最后发现问题全出在我们自己身上。这篇文章把我从排查、止血、再到系统性优化的全过程整理出来了涉及网关层怎么做 token 计量、请求入口怎么限流、上下文怎么减负、模型路由怎么省钱以及优化过程中踩过的各种坑。不管你是做 AI 应用开发的还是负责网关基础设施的只要你用的是 token 计费的模型服务这些思路应该都直接用得上。1. 先把问题说清楚Openlaw 网关到底在干什么1.1 网关在 AI 应用里的定位Openlaw 是一个面向法律场景的 AI 应用平台用户会做法律咨询、合同审查、文书生成这类事情。在技术架构上前端服务后面挂着一层网关所有的模型请求都会经过这里由网关负责把请求转发给不同的模型服务商OpenAI、Claude、智谱 GLM 等同时做鉴权、计量、限流和日志记录。我当时把网关的职责归纳成了四件事路由根据业务类型把请求发到合适的模型避免所有流量都打到同一个后端计量统计每次请求消耗的 token按用户、按功能模块记账这是成本核算的基础保护限流、熔断防止异常流量打垮下游模型服务也防止成本失控观测输出完整的调用链日志方便事后回溯问题这四件事里计量是最容易被忽视但出事时最要命的。因为 token 计量直接关系到成本核算如果这里的数据不准你根本不知道钱烧在哪里。我们的网关其实一直都有日志但平时只看接口成功率、延迟这些常规指标token 消耗没有纳入每日监控所以出问题时才发现数据是一片空白。1.2 Token 成本超标的直接表现事情是从一份月度账单开始的。运营同事把账单截图发到群里当月模型 API 的费用是上个月的 2.8 倍直接超了预算 80%。我当时第一反应是不可能吧因为业务量并没有明显增长DAU 数据也就比平时多了 10% 左右按理说成本不应该有这么大的变化。为了快速止血我干的第一件事是去网关日志里拉出 token 消耗的时序数据。不看不知道一看吓一跳日均 token 消耗从平时的 1200 万左右两周内飙到了 3800 万翻了 3 倍多。而且不是缓慢上升是某次发布之后突然跳上去的。这个特征很关键——不是流量自然增长导致的八成是某个版本改动引入了问题。这里也提醒大家一个经验AI 应用的成本监控不能只看账单必须看实时指标。等月末账单出来再发现异常早就来不及了。我在这次事故之后把token 消耗和单请求平均成本这两项加到了每日巡检列表里一旦出现超过 30% 的环比波动就立即告警。2. 排查过程Token 到底烧在哪里2.1 从账单和日志反推消耗分布排查的第一步是建立成本消耗的分布视图。我把网关里的调用日志按这几个维度做了聚合按功能模块分组法律咨询、合同审查、文书生成、法条检索等按模型分组GPT-4、Claude、GLM 等按请求类型分组新对话、多轮对话、文档处理按用户分组找出异常消耗的大户这些聚合结果一出来问题基本就有眉目了。下面是当时整理的一张简化表按日均 token 占比排序功能模块平时占比出问题时占比变化法律咨询多轮40%35%持平合同审查文档解析20%45%突增法条检索25%12%下降文书生成15%8%下降合同审查模块的 token 消耗涨幅最夸张占比从 20% 直接跳到 45%。这个信号说明问题出在文档处理这条链路上而不是常规对话链路上。顺着这个线索往下挖事情的真相才开始浮出水面。2.2 揪出三类典型的 Token 浪费场景顺着合同审查链路继续查最终锁定了三个问题每一个单独拿出来都够让人头疼的。第一类是系统提示词膨胀。某个版本更新后为了提升合同审查的准确率团队往系统提示词里塞入了一份很长的审查规则里面包含了各种法律条文和行业规范总共约一万八千字。这份规则在每次请求时都会作为前置 token 发送。按中文 token 换算一万八千字大概是 1.2 万到 1.5 万 token这个开销在每次合同审查请求中都固定发生。哪怕真实的合同正文只有三四千字系统提示词比用户内容还多好几倍大部分成本就这么白白浪费了。第二类是文档重复注入。合同审查的流程是用户先上传合同系统把合同内容送到模型分析然后进入人机多轮问答。最初的设计是每一轮问答都把合同全文重新发送一遍。一次合同审查平均 5-8 轮问答一份三万字的长合同就会被重复发送 5-8 次。按中文 token 估算三万字大概是 2 万到 2.5 万 token重复 6 次就是 12-15 万 token单次合同审查的成本直接高了一个数量级。第三类是重试风暴。网关对模型服务设置的是 3 秒超时模型响应一旦超过 3 秒就判定超时并重新发送请求。遇到模型服务端负载高的时候一个请求可能在短时间内连续重试 3-4 次每次重试都是全量的 token 支出。当时日志里有一个典型 case同一个请求在 12 秒内重试了 4 次最后一次成功时总共烧掉了 5 倍多的 token。2.3 定位这些问题用到了什么工具说实话定位这些问题并不需要什么特别高深的工具关键是日志要全、聚合要快。我当时用到的几个方法论分享一下网关日志要带着 model、prompt_tokens、completion_tokens、total_tokens、request_id、user_id、module、error_code 这些字段完整落库用 ClickHouse 存调用日志查询快聚合方便亿级数据量查起来也就几秒每个请求的系统提示词版本号记录下来方便对比不同版本之间的 token 消耗差异对长文本请求单独记录输入字符数和估算 token 数用于做异常断言和告警这里我想重点强调 prompt 版本号这个点。之前我们一直没给提示词做版本管理改一次就是直接改线上配置导致出问题时根本不知道是哪个版本引入的。后来我们建了一个简单的提示词版本表每次改动都会记录版本号、变更内容、操作人网关日志里也会带上当前版本号。就这么一个简单的动作后面排查类似问题的时间缩短了一大半。3. 优化方向一请求入口的节流设计3.1 请求级别的 Token 预算控制排查清楚之后我先做了最紧急的止血动作——在网关层加请求级别的 token 预算控制。这个方案是最快能落地的因为不需要改业务代码只改网关的转发逻辑。具体逻辑是这样的网关收到一个请求时先估算这个请求的输入 token 数如果超过预设的上限就直接拒绝或者走降级流程不发给模型服务商。估算方法很简单中文按字数 × 1.3粗算英文按字符数 ÷ 4粗算不追求精确但能挡住最离谱的请求。对于 Openlaw 的场景我设置的初始阈值是输入 token 上限6000单次请求输出 token 上限2000单次响应单用户单日 token 上限5 万单功能模块单日 token 上限100 万这样设置之后那几类最严重的文档重复注入请求在第一道关口就会被拦下来。当然实际业务中不能光拦截拦截之后要给出友好提示比如该请求上下文过长请缩短文档或重新上传保证用户体验不至于断崖式下跌。3.2 用户级别的配额与熔断请求级别控制只是第一层用户级别的配额和熔断同样重要尤其是对于自动化脚本、异常客户端这一类无底洞消耗来源。我们遇到过一个真实 case某个企业客户的集成脚本出了 bug在循环里反复调用合同审查接口一个晚上跑了 300 多次消耗了 800 多万 token直接烧掉了几千块。如果没有用户级配额这种事故会反复出现。在网关里我为每个租户和用户维护一个 token 日配额计数器。请求经过网关时把本次请求的 token 数累加到该用户的当日计数中。当计数超过配额的一定比例比如 80%时返回一个告警头超过 100% 时直接拒绝请求并返回配额超限的错误。同时给运营后台加了配额调整入口方便销售或客服针对重点客户临时调整上限避免误伤正常业务。3.3 超时与重试策略调整超时和重试是 token 浪费的重灾区。我之前的默认超时是 3 秒这对流式响应来说太短了因为模型的首字延迟TTFT经常在 1-5 秒之间浮动。一个响应可能模型已经生成了一部分但网关因为超时就把整个请求重发了那已经生成的 token 等于白烧。流式请求的超时判断不能只看整个请求是否完成要区分首 token 到达时间和相邻 token 间隔时间。正确做法是首 token 超时设为 20 秒具体看模型服务的 P95 延迟相邻 token 的最大间隔设为 30 秒重试次数限制为 1 次重试采用指数退避初始间隔 2 秒最大 30 秒关键点是不是所有错误都值得重试。如果模型服务返回的是 429限流或 5xx服务端错误重试有意义如果是 400参数错误或 401鉴权失败重试是没用的应该直接返回错误。我们的网关在重试前会判断错误码只对可重试的错误码做补偿。提示流式响应场景下超时判断的粒度要细化到两个 token 之间的间隔。一开始我们只设置了整体超时结果首 token 到达很快但中途卡住了整体超时又没到客户端就一直挂着模型还在后台继续生成最后白花了一堆输出 token。4. 优化方向二上下文管理的减负策略4.1 系统提示词的压缩与蒸馏止血之后就要开始做长远的优化了。系统提示词是最容易被忽视的成本点之一因为它在每次请求中都会重复消耗积少成多金额相当可观。我们的做法分三步把系统提示词拆成基础指令和领域指令两层基础指令每次都发领域指令只在特定场景触发时才发对领域指令做蒸馏把冗长的规则描述改成结构化的、简洁的条目删掉重复和修饰性语言把大段的法条原文从提示词中移除改为按需检索后注入到用户输入部分以合同审查的系统提示词为例改造前它大概是这样的示意你是一个专业的合同审查专家。请对以下合同进行审查重点关注 1. 合同主体资格审查包括签约方的资质、信用状况、经营范围等…… 2. 合同条款合法性审查包括价款条款、履行期限、违约责任等…… 3. 违约责任条款审查包括违约金比例、赔偿范围、争议解决方式等…… 长度约 18000 字包含大量法条原文和解释性内容改造后变成角色合同审查专家 任务识别合同中的高风险条款并提出修改建议 关注点主体资格、合法性、违约责任、争议解决 输出格式问题条款 - 风险等级 - 修改建议 长度约 800 字法条原文全部移除改为内部知识库按需检索这个动作的效果非常明显合同审查模块的单次输入 token 从平均 2 万降到了 6000 左右成本直接降了 60% 以上而且因为干扰信息少了审查意见的质量反而更稳定了。4.2 对话历史的滑动窗口与滚动摘要多轮对话是另一个 token 消耗大户。用户在咨询一个法律问题时往往会问十几轮甚至几十轮每次请求都把全部历史对话发送给模型上下文就会越长越大直到撑爆模型上限。我们实现的方案叫滑动窗口 滚动摘要当对话历史的总 token 数超过阈值比如 8000时把最早的一部分对话压缩成一段摘要用摘要替换原始对话内容同时保留最近 N 轮完整对话。这样既不会丢失关键信息又能把上下文控制在合理范围内。具体做法上摘要由模型在对话进行中异步生成。当某一次对话结束后如果历史 token 数超过阈值后台任务会调用一次摘要模型把旧的对话压缩成一段 300-500 字的中文摘要存入会话状态。下一次请求时网关用摘要 最近 N 轮完整对话作为上下文而不是把全部历史都塞进去。这里有一个经验总结摘要必须做 token 预算控制建议摘要输出控制在历史 token 总量的 10% 以内。否则摘要本身可能比原始对话还贵得不偿失。另外摘要生成要放到异步任务里不能卡在主请求链路上否则会增加用户可见的延迟。4.3 法律条文与案例的检索式输入Openlaw 这类法律场景有一个特点模型并不需要知道全部法条只需要针对当前案件引用相关的法条和案例。如果直接把整部法律文本塞给模型成本高且准确率未必高因为模型的注意力会被无关内容稀释。我们把这条链路改成了 RAG检索增强生成模式先用 embedding 模型对用户的问题做向量化从法条库和案例库中检索 top 5-10 条相关内容把检索结果组装成参考资料段注入到系统提示词后面模型只根据注入的参考资料来回答这个改动后法条检索模块的单次请求 token 从 8000 左右降到了 2000-3000而且答案的准确率反而提升了因为模型不再被大段无关法条干扰。很多人做 RAG 容易犯一个错误把检索到的内容原封不动地全部塞进去。实际上检索结果里往往有大量冗余信息应该再做一步压缩和去重只保留与问题高度相关的段落。我们在检索后加了一个轻量级的剪枝逻辑对每个检索段落算一个相关度得分低于阈值的段落直接丢弃保证注入的参考资料不超过 1500 token。5. 优化方向三响应侧的成本优化5.1 模型路由与分级调用不同模型的价格差异非常大动辄差一个数量级。对于 Openlaw 这种有大量简单问答场景的平台统一使用高端模型是巨大的浪费。我们做了一个模型路由层根据请求的复杂度把请求分发到不同价位的模型。简单的判断维度包括功能模块类型法条检索、简单问答用低价模型深度分析、合同审查用高端模型用户身份免费用户走低价模型付费会员走高端模型输入长度短文本用低价模型长文本、复杂文档用高端模型意图分类用一个便宜的分类模型先做意图判断再决定路由这里的成本收益要算清楚。假设一个简单问答用高端模型需要 0.05 元用低价模型只需要 0.005 元一天 10 万次简单问答每天就能省 4500 元。这个优化在一个月内就能把之前的成本超支全部追回来。5.2 缓存层的设计与落地缓存是 Token 成本优化的另一个大头。Openlaw 的一些问题在法律场景中非常典型、高频比如合同违约怎么处理离职补偿怎么算。这些问题每天被问上千遍每次都要调用模型白算一遍非常浪费。我们实现了两级缓存精确缓存请求的 token 序列完全一致时直接命中缓存返回历史响应语义缓存请求与历史请求的语义相似度超过阈值余弦相似度 0.9时返回历史响应语义缓存的核心是 embedding 向量。每次请求到达网关时先用一个轻量 embedding 模型算出请求的向量然后在缓存中做相似度搜索。命中缓存时网关直接返回缓存的响应不再调用下游模型。实际运行中语义缓存的命中率在 15%-25% 之间具体取决于业务分布。对于高频重复问题较多的业务命中率会更高。虽然语义缓存有一定的误判风险相似问题可能答案不同但我们通过设置较高的相似度阈值0.92-0.95和增加业务模块维度约束把误判率控制在了可接受范围内。5.3 限制输出 Token 的指令与参数输出 token 的控制往往比输入更难因为输出是模型自己生成的不好精确控制。但通过参数和指令仍然可以显著压缩。在网关层我们固定了每个功能模块的 max_tokens 参数简单问答max_tokens 300合同审查意见max_tokens 800深度法律分析max_tokens 1500同时在 prompt 中明确要求回答简洁控制在 200 字以内。模型虽然不一定严格遵循字数限制但实际效果是响应长度平均下降了 40% 左右。还有一个容易被忽略的点是停止符的使用。对于结构化输出比如 JSON 格式的审查意见我们在调用时设置 stop 参数让模型在输出完必要的结构化内容后立刻停止生成避免额外输出解释性文字。这里可以省下大约 20%-30% 的输出 token。6. 常见问题与排查技巧实录6.1 日志中的典型异常特征在优化过程中我们积累了一套快速识别的特征模式整理成了一张速查表异常特征可能的根因快速处理方式某功能模块 token 占比突然上升提示词改动、文档重复注入对比最近一次发布的内容改动单请求平均 token 持续上涨对话历史未清理、上下文无限增长限制最大轮数或启用滑动窗口摘要重试率超过 5%超时设置过短、模型服务端波动调整超时参数、限制重试次数某用户 token 消耗异常高脚本 bug、自动化批量调用用户级配额熔断输出 token 远超预期缺少 max_tokens 限制、prompt 未约束设置响应长度上限、添加停止符6.2 优化上线后的效果观测优化不是一锤子买卖上线之后必须持续观测效果。我们建立了两个核心监控指标单请求平均 token 消耗成本效率指标和单请求平均成本折合成人民币。这两个指标如果出现异常波动就会触发告警。在优化方案全部上线后的第二周我们做了完整的数据对比指标优化前峰值优化后降幅日均 token 消耗3800 万780 万79.5%月度成本超标 80%预算的 70%显著改善p95 响应延迟4.8s2.6s提升 45%6.3 容易忽略的四个成本陷阱最后分享几个在这次优化中发现的、特别容易忽略的成本陷阱都是我踩过的坑部分评测流量绕过网关直接调用模型服务导致这部分成本完全不在网关的计量范围内。排查成本账单时这部分流量查不到很迷惑人。解决办法是统一所有模型调用都经过网关禁止任何裸调用这个约束要在代码评审里卡死。定时任务没做错峰。Openlaw 有一些后台定时任务比如定时汇总用户案件信息、生成周报等。这些任务如果在下班高峰期集中执行因为模型服务端的负载和限流成本会更高、成功率也会下降。建议把这类任务的执行时间错开安排在凌晨低峰期。流式输出与客户端断开。流式输出时如果客户端中途断开了连接模型可能还会继续生成一段输出这部分 token 是白花的。网关层应该在检测到客户端断开时主动向模型服务端发送终止信号不能让模型继续生成。中英混排的 token 开销。法律场景经常出现中英混排文本比如合同包含英文条款混合文本的 token 数会比纯中文高不少。如果可能尽量要求模型用单一语言输出或者在预处理阶段做语言分割把不同语言的段落分开处理。7. 写在最后的一点体会这次优化做完我的一个核心体会是Token 成本优化不是一次性项目而是需要持续经营的系统工程。它需要网关层有完整的数据业务层有合理的 prompt 设计模型层有聪明的路由策略三者缺一不可。还有一个比较颠覆我认知的地方是省 token 和提升质量很多时候并不矛盾。我们以为把上下文压缩了、把法条改成检索式输入模型质量会下降。但实际上因为输入信息更聚焦、干扰更少模型回答的准确率和用户满意度反而提升了。这也让我更坚定了一个原则给模型的信息应该是少而精的精选集而不是大而全的资料库。踩过这么多坑之后我现在做任何 AI 应用的改动都会先问一句这次改动的 token 消耗影响是什么如果说不清楚那就说明还没有为成本做过设计。希望这篇分享能让大家少走一些弯路把省下来的钱花在真正有价值的地方。
返回列表