ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Token账单失控?AI应用成本控制与优化实战

Token账单失控?AI应用成本控制与优化实战 AI 应用里Token 正在成为最容易被忽略的成本单元。最近关于“一人每月烧掉数千美元 Token”的讨论在开发者社区传播得很广核心不是模型选得好不好而是用量本身失控了。聊天、Agent、批量任务每执行一次都会按 Token 计费当调用次数和上下文长度同时膨胀时账单就会从“可以忽略”变成“必须限额”。当提供 AI 服务的企业也开始提醒工程师注意“狂刷 Token”时说明成本控制已经不是一个可有可无的优化项。下面按五个环节展开先理解计费单位再搭监控做配额最后优化并顺带把身份认证 Token 和 AI 计费 Token 的区别讲清楚。目标是让一套 AI 应用的 Token 消耗变得可见、可控、可优化。1. 为什么 Token 会变成账单上的钱1.1 Token 不是字数是模型处理文本的切分单位Token 是模型处理文本的最小单元。它不是字母也不是完整单词而是模型分词器把一串文本切分后得到的片段。一个 Token 可能是一个英文单词的一部分例如unbelievable可能被切成un、believ、able一个完整的短单词例如AI、the一个标点符号例如,、.一个汉字或者一个汉字的片段。不同模型使用不同分词器所以同一个句子的 Token 数量没有统一公式。常见情况是英文文本中一个单词大约对应 1 到 1.5 个 Token中文文本中一个汉字通常对应 1 到 2 个 Token。比如一句话里有 100 个汉字模型可能切成 120 到 180 个 Token。这就是“狂刷 Token”的第一个来源很多开发者以为一条消息只算一次调用成本实际上系统提示词、历史消息、用户输入、工具返回结果都会被切分成大量 Token并且每一轮请求都会重新计算一遍。1.2 输入 Token 和输出 Token 价格为什么不同大模型服务的计费通常不是按“请求次数”而是按 Token 数。不同服务商对输入和输出采用不同计价方式常见模式是计费项含义典型特点输入 Token发给模型的所有文本包括系统提示词、用户消息、历史上下文、工具结果通常按百万 Token 计价输出 Token模型生成的回复内容通常比输入 Token 更贵缓存 Token命中服务商上下文缓存时输入价格会降低仍会产生费用但低于未命中Embedding Token调用向量化模型时按文本片段计费常用于检索和语义缓存注意输入和输出价格差异很重要。一个短请求可能只发送几百个输入 Token但如果max_tokens设置得很大模型生成了几千甚至上万 Token 的输出账单很可能比一个长上下文请求还要高。实际价格会随模型版本、服务商和市场活动变化不要凭记忆写死到代码里。生产环境应该把价格配置模型单独管理按服务商官方价格页维护。1.3 Agent 循环和长上下文是“狂刷 Token”的两大元凶单次调用即使消耗几百 Token成本也不会太高。真正让账单失控的是循环调用和上下文膨胀。一个典型的 Agent 任务会经历规划、调用工具、读取结果、判断下一步、重试、总结。每一步都有可能调用一次模型而每一步都要携带之前积累的上下文。假设每个步骤需要 4000 Token执行 10 步就是 40000 Token执行 30 步就是 120000 Token。如果中间有失败重试实际用量还会继续翻倍。更隐蔽的是长对话场景。用户和 AI 连续聊 20 轮如果系统把所有历史消息原封不动地拼接进每一次请求那么第 20 轮请求所消耗的 Token 不是第 20 轮新增的那部分而是前 19 轮全部历史加第 20 轮输入。这个成本会随着轮数增长近似线性上升但实际感受是“聊得越久越贵”。所以标题里“一人每月烧掉数千美元 Token”的场景通常是以下几个特征叠加高频率调用、长上下文不清洗、Agent 多步循环、失败后重试、调试时反复重放完整 Prompt。2. 先给系统装上一块“油表”Token 计量与监控2.1 在请求层统一记录 usage成本失控之前必须先能看到每个请求消耗了多少 Token。几乎所有主流大模型 SDK 都会在响应中返回 usage 字段里面包含prompt_tokens、completion_tokens和total_tokens。建议不要在每个业务逻辑里单独记录而是在调用大模型的公共入口统一处理。下面是一个 Python 示例def chat_with_accounting(user_id, project_id, model, messages): response client.chat.completions.create( modelmodel, messagesmessages, max_tokens512, ) usage response.usage record_token_usage( user_iduser_id, project_idproject_id, modelmodel, prompt_tokensusage.prompt_tokens, completion_tokensusage.completion_tokens, total_tokensusage.total_tokens, estimated_costestimate_cost( model, usage.prompt_tokens, usage.completion_tokens, ), ) return response def record_token_usage(user_id, project_id, model, prompt_tokens, completion_tokens, total_tokens, estimated_cost): # 写入日志系统或用量数据库字段按团队规范调整 print({ event: token_usage, user_id: user_id, project_id: project_id, model: model, prompt_tokens: prompt_tokens, completion_tokens: completion_tokens, total_tokens: total_tokens, estimated_cost: estimated_cost, })关键点在于必须在拿到response.usage后立刻记录而不是猜用量。实际计费以服务商返回的 usage 为准自己用分词器估算的结果只能作为参考和预判。2.2 用 tiktoken 做请求前预估算有些场景需要在请求发出前就知道 Token 数量比如检查上下文是否超长、判断用户今天的预算是否足够。tiktoken 是常见的文本切分工具可以用于 OpenAI 系模型的大致估算import tiktoken def count_tokens(text: str, model: str gpt-4o) - int: try: encoder tiktoken.encoding_for_model(model) except KeyError: encoder tiktoken.get_encoding(cl100k_base) return len(encoder.encode(text)) if __name__ __main__: sample AI Token cost control print(count_tokens(sample))tiktoken 只覆盖 OpenAI 的部分模型编码规则。Claude、通义、智谱、DeepSeek 等模型通常有自己的分词器或近似估算方式。用 open-source 库估算得到的结果不等于服务商计费结果但用来做“请求是否过大”的预检是有价值的。2.3 建立多维度的 Token 用量明细表只有日志还不够成本分析需要按用户、项目、模型、时间维度聚合。推荐在数据库中建一张用量明细表最简化结构如下CREATE TABLE ai_token_usage ( id BIGINT PRIMARY KEY AUTO_INCREMENT, request_id VARCHAR(64) NOT NULL, project_code VARCHAR(64) NOT NULL, user_id VARCHAR(64) NOT NULL, model_name VARCHAR(64) NOT NULL, action_type VARCHAR(32), prompt_tokens INT NOT NULL, completion_tokens INT NOT NULL, total_tokens INT NOT NULL, estimated_cost DECIMAL(12, 6), created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_project_time (project_code, created_at), KEY idx_user_time (user_id, created_at) );后面所有报表、告警、配额判断都可以基于这张表。实际生产环境如果数据量很大建议按天分表或者把明细写入 ClickHouse、Elasticsearch 等分析型存储避免占满业务数据库。estimated_cost建议用配置表里的单价计算不要硬编码在业务代码中。单价调整时只需要修改配置表历史数据仍可保留。2.4 设置成本告警而不是只看月底账单月底账单只能说明“已经超了”不能告诉你是哪个用户、哪个项目、哪个模型导致的。所以要把告警前移。常见告警指标包括告警级别指标示例阈值建议动作Warning单用户日消耗 Token超过目标预算的 70%通知项目负责人Warning单次请求输出 Token超过 2000检查 max_tokens 配置Critical项目小时消耗 Token超过正常均值数倍临时熔断或切小模型Critical失败重试次数每分钟超过 20 次检查模型连接、参数错误阈值没有统一标准需要根据团队预算和业务量调整。重要的是先有计量再有告警否则“限额”只能是空话。3. 限额不是拍脑袋配额、并发和熔断3.1 事前配额请求入口先查预算限额不能只写在产品文档里要在技术入口真正拦截。最常用的是基于 Redis 的固定窗口计数器因为 Redis 支持原子自增和过期时间适合在分布式服务中做跨进程计数。import time import redis r redis.Redis(hostlocalhost, port6379, db1, decode_responsesTrue) class TokenQuota: def __init__(self, redis_client, limit: int, window_seconds: int, prefix: str quota): self.r redis_client self.limit limit self.window window_seconds self.prefix prefix def allow(self, key: str) - bool: cache_key f{self.prefix}:{key}:{int(time.time()) // self.window} current self.r.get(cache_key) if current is not None and int(current) self.limit: return False pipe self.r.pipeline() pipe.incr(cache_key) pipe.expire(cache_key, self.window 1) pipe.execute() return True quota TokenQuota(redis_clientr, limit10000, window_seconds3600) if not quota.allow(user:10086): raise Exception(quota exceeded, please wait)固定窗口实现简单但窗口边界可能出现突发流量。如果想更平滑可以用令牌桶或滑动窗口。学习环境可以先从固定窗口开始生产环境再根据流量特征升级。这里要注意请求发出后实际 Token 消耗无法中途收回。即使限制每天 100 万 Token某一瞬间仍然可能同时发起多个大请求并超出一点。因此预警阈值要低于硬额度比如预算 100 万 Token在消耗到 80 万时就触发告警。3.2 事中控制限制并发、上下文长度与重试配额之外还需要限制并发和请求体积否则多个请求同时进入额度会被瞬间打满。常用控制参数如下参数作用设置过大设置过小max_tokens限制单次输出长度可能导致高额输出费用回复被截断体验差max_concurrency限制同时请求模型的数量并发过高成本飙升吞吐下降context_limit限制单次请求上下文 Token上下文过长费用高重要信息被截断retry_count限制失败重试次数重试放大成本单次失败就放弃可用性低建议在调用大模型时显式传max_tokens不要依赖服务商默认值。默认值有时会很大一旦模型开始“废话式”生成输出 Token 就会快速累积。重试更要谨慎。网络抖动导致重试可以接受但如果因为 Prompt 配置错误导致每次都失败重试只是在重复烧钱。重试前要确认错误类型参数错误、权限错误、内容审核拒绝等不需要重试。3.3 事后熔断超预算自动降级事前配额和事中控制仍然挡不住突发情况比如某个新功能上线后瞬间被大量调用。这时候需要熔断和降级机制。可以定义一个成本控制配置cost_guard: daily_token_budget: 2000000 monthly_cost_limit: 5000 action_when_exceeded: - block_chat - switch_model: cheap notify_channels: - email - dingtalk当当日消耗超过预算时不是直接停止所有 AI 功能而是按优先级降级先用规则匹配替代简单分类再切换小模型处理常规请求最后才是拒绝非核心功能的调用。def before_llm_call(user, action): daily get_daily_usage_for_user(user) if daily.estimated_cost settings.daily_user_cost_limit: if settings.exceeded_action degrade: return call_cheap_model(action) raise QuotaExceededException(daily cost limit exceeded)关键是要保证降级路径可回退。熔断不是永久关闭而是当成本回落到安全水位后自动恢复主模型调用。3.4 把成本反馈到工程师和业务方限额如果只是后台静默执行工程师仍然会对成本无感。最好在每次模型调用的日志、内部管理后台或开发调试工具里展示 Token 和费用估算。一个可落地的做法是在联调环境中增加--debug-token-cost开关每次调用模型后在控制台输出[token] modelgpt-4o prompt1200 completion800 total2000 est_cost0.015 usd让工程师在开发阶段就能看到“这一行代码花了多少钱”。很多“狂刷 Token”的问题并不是恶意行为而是开发者在调试时没有感知反复重放同一段超长上下文。成本反馈本身就能改变开发习惯。4. 降低 Token 消耗的工程手段4.1 Prompt 瘦身模板、样例和指令要克制Prompt 越长每次请求越贵这是最直接的线性关系。优化 Prompt 时不要只盯着效果还要关注 Token 数。看一个简化对比# 优化前 你是一个资深文案编辑请根据以下要求帮我修改一段产品文案。 要求包括语气要专业、不夸张、突出核心卖点、字数控制在200字以内 同时保留品牌原有风格。产品资料如下... # 优化后 修改下面文案200字内专业语气保留品牌风格突出核心卖点...优化后语义没有减少但 Token 少了很多。常见 Prompt 模板里有一些无意义的固定寒暄、重复角色设定、过多示例都可以压缩。Few-shot 示例要克制。一个示例可能是 100 到 200 Token放 10 个就是 1000 到 2000 Token。如果模型本身已经能理解任务只放 1 到 2 个正例或反例即可。模板还需要做版本管理不能每个人随意修改后让 Prompt 越变越长。4.2 上下文管理摘要、窗口与外部记忆长对话和 Agent 场景中上下文管理是省 Token 的关键。常见方案有三种滑动窗口只保留最近 N 轮消息历史摘要每 N 轮把旧历史总结成摘要替换完整文本外部记忆把知识、档案、工具结果放到向量数据库按需检索再拼进 Prompt。滑动窗口实现最简单def build_context(history, max_turns6, max_tokens1200): recent history[-max_turns:] messages [] used 0 for item in reversed(recent): item_tokens count_tokens(item[content]) if used item_tokens max_tokens: break messages.insert(0, item) used item_tokens return messages工具返回结果也要截断。很多 Agent 会调用搜索或数据库接口返回几百 KB 的 JSON。如果全部塞给模型Token 会爆炸。正确做法是只保留关键字段或者对超长文本做摘要并标记原始长度供模型判断是否需要继续查看。4.3 缓存与语义缓存让模型少算完全一样的请求不必重复调用模型。可以在 Redis 中保存“模型名 消息内容 参数”的哈希结果。import hashlib import json def chat_with_cache(messages, model, cache_client): key_body { model: model, messages: messages, } key llm: hashlib.sha256( json.dumps(key_body, sort_keysTrue, ensure_asciiFalse).encode() ).hexdigest() if cached : cache_client.get(key): return cached response call_llm(messages, model) cache_client.set(key, response, ex3600) return response这种精确缓存适合客服问答、固定知识库问答、系统提示词完全一致的场景。如果用户问题表达不同精确缓存命中率不高可以考虑语义缓存先用向量检索相似度命中后再返回历史答案。语义缓存本身也要消耗向量模型的 Token而且有误命中风险。建议先做精确缓存再逐步评估语义缓存的收益和准确率。4.4 模型分级路由简单任务不需要最强模型不同模型价格差距可能非常明显。如果所有任务都走同一个最强模型成本会很高。实践中可以按任务类型做分级路由任务类型推荐方式理由分类、抽取、格式化小模型或规则任务简单大模型提升有限短文本摘要中小模型可控输出长度成本低代码生成、复杂推理大模型需要更强能力常见客服问答缓存 小模型 大模型兜底大部分问题可被缓存或小模型覆盖def route_task(task_type, content): if task_type in (classify, extract, short_summary): return call_small_model(content) if task_type in (code_debug, complex_reasoning): return call_large_model(content) return call_default_model(content)分级路由还需要有开关和监控。切换小模型后如果业务指标下降要能快速回滚到大模型。4.5 调试习惯别在一次调试里反复烧完整上下文“狂刷 Token”很多发生在调试阶段。一个开发者反复修改 Prompt 并重新运行完整测试脚本一次可能只处理 3 条数据但每次运行都会把几千 Token 的上下文发送给模型。一天重复几十遍成本就上来了。建议调试时先用最小样例比如 1 到 2 条数据不要全量跑在调用入口打印 Token 数每次运行都能看到增量调试日志保存到本地文件而不是每次都请求模型修改 Prompt 后先对比变化片段不要从头到尾全部重放。提高成本可见性胜过一百次口头提醒。5. 别把身份认证 Token 和 AI 计费 Token 混在一起5.1 两种 Token 差异速查搜索引擎里关于 Token 的讨论经常混杂着两类完全不同的问题AI 计费 Token 和身份认证 Token。前者是模型处理文本的切分单位后者是用户登录后拿到的凭证。维度AI 计费 Token身份认证 Token含义模型文本处理单元用户会话凭证产生方式模型分词器切分认证服务器签发消耗每次请求被消耗并计费每次请求携带不按数量计费失效请求结束即计费完成到期或主动吊销常见问题用量超预算401、403、token expired如果遇到“token exchange failed”或“token 失效”这类报错先确认它来自模型 API 还是登录认证服务再决定排查方向。5.2 JWT 续签的基本流程和常见坑JWT 常用于 Web 登录场景。由于 access token 有效期通常较短客户端需要用 refresh token 换取新的 access token。def refresh_access_token(refresh_token): response auth_client.request_token({ grant_type: refresh_token, refresh_token: refresh_token, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }) if response.ok: store_tokens( response[access_token], response[refresh_token], ) else: log_auth_error(response.status_code, response.text) raise TokenRefreshException(refresh failed)常见坑包括refresh token 有效期也过期续签失败需要重新登录refresh token 存储在 localStorage存在被脚本读取的风险access token 放在 URL 参数中容易出现在访问日志里多端登录时 refresh token 被轮换旧的 token 失效后导致体验异常。正确的做法是refresh token 存储在服务端或安全的 HttpOnly Cookie 中前端只保存必要的会话标识不要自己发明复杂的续签算法。5.3 token exchange failed 类认证报错排查实践中经常遇到这类报错sign-in could not be completed token exchange failed: token endpoint returned status 403 forbidden这通常是 OAuth 或 OIDC 登录流程中客户端向认证服务器的 token endpoint 发起请求时被拒绝。排查顺序建议如下现象常见原因检查路径token endpoint 返回 403client_id / client_secret 配置错误核对认证服务器后台配置token endpoint 返回 403scope 不一致或权限不足检查授权请求和 token 请求的 scope返回 400 invalid_grant授权码过期或已被使用确认授权码一次性使用和有效期sign-in completed 失败redirect_uri 不一致检查回调地址是否在服务商白名单报错包含所在区域限制服务商策略限制确认客户端出口区域是否符合服务商条款不要尝试绕过限制看到 403 时不要只盯着“forbidden”这个单词要去看认证服务器返回的具体错误码和描述。很多问题出在配置不一致而不是网络或账号。先检查请求参数再检查服务商配置最后看服务器日志。5.4 免费 Token 额度只适合学习不适合长期生产很多模型服务商会提供免费额度适合学习和小流量验证。但免费额度通常有时间限制、速率限制和模型限制并不适合作为生产环境的长期成本方案。生产环境接入模型时要把 Token 费用当作硬成本评估并按照前文方式接入计量、配额和告警。如果依赖免费额度一旦额度用尽或策略调整服务可能会直接不可用比账单超支更难处理。6. 从成本失控到可控落地清单和常见坑6.1 第一阶段先把计量建起来没有计量就没有限额。第一阶段只做一件事让每次模型调用的耗时、请求 ID、模型名、Token 用量、费用估算都进入日志和数据库。完成标准是在内部系统里能回答这几个问题今天消耗了多少 Token哪个用户消耗最多哪个项目消耗最多哪个模型占了大头最贵的一次请求是什么6.2 第二阶段再设配额和告警计量稳定后开始设置配额、告警和熔断。推荐按用户、项目、模型三个维度分别设置预算。检查项完成标准涉及工具请求入口有额度校验超额度返回 429 或降级Redis 计数器单次输出限制 max_tokens没有一条超长输出模型 API 参数并发调用有上限高峰期不会无限创建调用并发控制组件失败重试有次数限制错误不会放大费用重试策略超预算能自动降级主模型切换或功能降级配置开关告警能通知到负责人阈值触发后有人跟进邮件、IM Webhook6.3 第三阶段持续优化与复盘成本优化不是一两次就能完成的。建议每两周做一次 Token 消耗复盘看哪些 Prompt 占用了最多 Token、哪些场景可以缓存、哪些任务可以降级到小模型。把模型 API 的服务商价格做成配置每月核对一次防止价格调整后成本估算失真。6.4 常见坑汇总错误做法为什么错推荐方案不设置 max_tokens模型可能生成长文本输出 Token 更贵显式设置 max_tokens 和截断逻辑每次请求携带全部历史消息上下文无限膨胀成本非线性增长滑动窗口、摘要、外部记忆失败后无脑重试重复请求放大费用区分可重试错误和不可重试错误只在客户端做限额用户可以绕过前端直接调 API在服务端入口做配额校验看到 token 报错就往模型费用上想身份认证 Token 和 AI 计费 Token 两套链路先区分错误来源再排查6.5 生产环境落地时还需要补什么生产环境除了本文的计量、限额和优化之外还要考虑配置外置化模型名、价格、额度放到配置中心不写死在代码里日志脱敏用户输入和模型输出中可能包含敏感信息日志打点前要过滤成本分摊按项目、用户、部门打标签方便财务拆分灰度验证新 Prompt 和模型路由先小流量测试再逐步扩大回滚方案模型接口出现问题时能快速切到备份模型或降级服务定期复盘把 Token 成本指标纳入项目周会避免月底突击。Token 成本失控的根因很少是某一个模型太贵而是用量黑洞太多上下文无限长、重试无上限、调试反复烧、缺少监控。先用计量表把每个请求记录下来再叠加配额和告警最后做 Prompt 和上下文的减法短期内就能看到账单趋于稳定。如果还遇到认证 Token 报错记住先区分是模型计费 Token 还是身份认证 Token再沿着对应链路查方向对了问题才会好解决。
返回列表