ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

大模型 API 账单越来越高?用单次成功任务成本反推多花在哪一步

大模型 API 账单越来越高?用单次成功任务成本反推多花在哪一步

账单涨得快于调用量,原因通常是下面四种之一,或几种叠加:输入变长,输出变长,一个任务里的请求变多,或者模型选得偏大。只看调用次数分不出是哪一种。更有用的指标是单次成功任务成本:统计期内全部请求的费用(含失败和重试),除以验收通过的任务数。这个数字变了,就能指出先改哪一环。

一、为什么用成功任务做分母

按调用次数算单价,会掩盖两件事。一是失败和重试的费用被摊平在成功请求里。二是一个任务可能调用好几次模型,调用次数多少不能说明完成了多少工作。把验收通过的任务作分母,失败会直接拉高指标。

单次成功任务成本 = 统计期内全部请求费用(含失败、重试) ÷ 验收通过的任务数

二、先看五个信号

信号包含什么偏高时先查
每次请求的输入 Token历史轮次、检索片段、系统指令历史是否每轮重发,Top-K 是否过大,系统指令是否重复
每次请求的输出 Token回答正文和推理内容是否有长度约束,推理类参数是否一直开着
每个任务的调用次数Agent 循环、重试、校验同一工具、同一参数是否反复出现
按模型拆分的费用占比每个模型承担了多少费用分类、标签这类简单任务是否用了大模型
缓存读取占全部输入的比例前缀是否稳定前缀里是否有时间戳或随机 ID,变化的内容是否放在了前面

三、用请求日志算指标

日志每行记录一次请求,用 JSON Lines 格式保存。字段先归一化,才能跨模型相加:

  • input_tokens:未缓存输入,不含缓存读取和缓存写入。OpenAI 的prompt_tokens包含缓存命中部分,写入前要减去cached_tokens。
  • cache_read_tokens、cache_write_tokens:没有就填 0,不要留空。
  • task_passed:任务是否通过验收。同一任务的每一行填相同的值。

下面的脚本读取这样的日志,输出指标。

importjsonimportsys# 示例单价:元 / 百万 Token,发布前替换为所选型号官方价目表的数字PRICES={"model-large":{"input":10.0,"cache_read":1.0,"cache_write":12.5,"output":40.0},"model-small":{"input":1.0,"cache_read":0.1,"cache_write":1.25,"output":4.0},}defrecord_cost(rec):# 字段已归一:input_tokens 是未缓存输入,不含缓存读取和缓存写入p=PRICES[rec["model"]]return(rec["input_tokens"]*p["input"]+rec["cache_read_tokens"]*p["cache_read"]+rec["cache_write_tokens"]*p["cache_write"]+rec["output_tokens"]*p["output"])/1e6defsummarize(path):passed={}# 任务 ID -> 验收是否通过,同一任务的每条记录填相同值calls=missing=0cost_all=input_all=cache_all=0.0withopen(path,encoding="utf-8")asf:forlineinf:rec=json.loads(line)calls+=1passed[rec["task_id"]]=rec["task_passed"]ifrec["input_tokens"]isNone:missing+=1# 用量缺失时不按 0 计,单独统计continuecost_all+=record_cost(rec)input_all+=rec["input_tokens"]+rec["cache_read_tokens"]+rec["cache_write_tokens"]cache_all+=rec["cache_read_tokens"]ok=sum(passed.values())print(f"任务数{len(passed)},验收通过{ok},调用次数{calls},平均每任务{calls/len(passed):.2f}次")print(f"总费用(含失败与重试):{cost_all:.4f}")ifinput_all:print(f"缓存命中率(缓存读取 / 全部输入):{cache_all/input_all:.1%}")ifok:print(f"单次成功任务成本:{cost_all/ok:.4f}")ifmissing:print(f"警告:{missing}条记录缺少用量,未计入费用,上面的数字偏低,需对账")if__name__=="__main__":summarize(sys.argv[1])

示例日志usage.jsonl的数据为虚构,只用于验证脚本:

{"task_id":"t1","model":"model-large","input_tokens":8000,"cache_read_tokens":6000,"cache_write_tokens":0,"output_tokens":500,"task_passed":true}{"task_id":"t1","model":"model-small","input_tokens":1200,"cache_read_tokens":0,"cache_write_tokens":0,"output_tokens":80,"task_passed":true}{"task_id":"t2","model":"model-large","input_tokens":9000,"cache_read_tokens":0,"cache_write_tokens":0,"output_tokens":700,"task_passed":false}{"task_id":"t2","model":"model-large","input_tokens":null,"cache_read_tokens":0,"cache_write_tokens":0,"output_tokens":null,"task_passed":false}

运行命令是python cps.py usage.jsonl。Windows 控制台出现中文乱码时,先设置PYTHONIOENCODING=utf-8。输出如下:

任务数 2,验收通过 1,调用次数 4,平均每任务 2.00 次 总费用(含失败与重试):0.2255 缓存命中率(缓存读取 / 全部输入):24.8% 单次成功任务成本:0.2255 警告:1 条记录缺少用量,未计入费用,上面的数字偏低,需对账

这个脚本的限制:

  • 单价写死在PRICES里,官方调价后要同步更新,并注明核对日期。
  • 只统计已记录的请求。平台没有返回的请求,要和账单逐条对账。
  • 阶梯价、时段价、批量折扣没有实现,需要时自行扩展。

四、按改动成本从低到高处理

  1. 换模型,不动流程。分类、标签这类任务先试小模型。在同一批样本上比较准确率和完整任务费用,再小范围切换。
  2. 精简输入。删掉重复的系统指令。历史保留关键事实和最近几轮,早期内容改成摘要,但摘要本身也计费。RAG 先去重,再缩小 Top-K,并测试召回质量。时间范围、产品版本、排除项这类限制条件不要删。
  3. 调整缓存前缀。稳定内容(指令、文档、工具定义)放在前面,变化的问题放在后面。缓存命中按各家规则计价:OpenAI 的缓存写入规则随型号变化,Anthropic 区分缓存写入和缓存读取。命中不等于免费,低频复用的前缀,写入成本可能高于节省的部分。
  4. 给流程设边界。Agent 设最大轮次、超时和预算。同一工具、同一参数重复执行时中止并记录原因。能用代码校验的格式(JSON 字段、日期)交给代码,不再让模型复看。重试集中在一处,并设上限。
  5. 约束输出,但别卡死。Anthropic Messages 用max_tokens,OpenAI Responses 用max_output_tokens。OpenAI Chat Completions 的max_tokens已弃用,应核对max_completion_tokens的支持情况。上限过紧会截断答案并触发重试,推理内容也占生成额度。
  6. 比较供应商时算完整任务价格。输入、输出、缓存读写和存储费一起算,不只比最低输入单价。对批量、非实时任务,看厂商是否提供异步批量接口。

每次只改一项,改前改后用同一统计期比较。同时改多项,就无法判断哪一项起了作用。

五、平台侧的护栏与对账

灵眸 AI(渠道入口)的 Token 设置里可以配置总额度、每分钟请求数和模型白名单。给 Agent 用的 Token 设上限,可以给失控的循环设一个开销边界;分类类任务的 Token 只允许调用小模型,能防止配置被改错后误用大模型。超出上限后的具体表现,以控制台说明为准。

对账可以使用用量导出接口GET /api/v1/usage。它返回逐条明细,包括request_id、各类 Token、原价total_cost和实扣actual_cost。使用时注意以下四点:

  • 导出接口用账号登录换取 JWT 鉴权,不接受sk-开头的 API Key。登录密码放在环境变量里,不要写进脚本。
  • 导出数据里没有任务 ID,也没有验收结果。调用时把响应里的请求 ID 和任务 ID 一起记下,导出后按request_id关联。
  • 文档没有写明input_tokens是否包含cache_read_tokens。用一条已知缓存命中的请求核对后,再套用第三章的归一化规则。
  • 平台侧元数据保留 90 天。跨季度对账前要定期导出存档。

核对平台扣费时,直接汇总actual_cost。它是美元金额,已乘渠道倍率,即total_cost × rate_multiplier。第三章的脚本按单价重算,适合比较模型和功能,不能替代账单。

六、方案边界

  • 换小模型、压缩上下文、调整缓存,都不能保证节省。省下的费用如果换来质量下降,单次成功任务成本可能反而上升。
  • 自动重试只能处理部分临时错误。重试和网关都不能消除所有 429,也不能替代上游额度规划。
  • 聚合平台的计费口径要逐项核对:币种、渠道倍率、失败请求是否计费。把这些写进验收清单,不要只比较单价。
  • 缺失的用量不能按 0 计。按 0 计时,总费用和单次成功任务成本都会偏低,容易误判优化已经见效。

七、常见问题

缓存命中率的分母怎么定?

分母取全部输入 Token,包括未缓存输入、缓存读取和缓存写入,不包括输出。只用未缓存输入做分母,比率会偏高。

失败的请求要计入成本吗?

要。失败和重试的请求如果已经在服务端完成,通常会计费。统计时,失败请求的费用计入分子,分母只数验收通过的任务。具体计费规则以厂商文档为准,无法确认时按可能计费处理。

平台导出的用量和自己的日志对不上怎么办?

按顺序核对三项。第一,导出时是否传了timezone,否则日期边界按 UTC 切分,和本地日志会差一段时间。第二,两边的请求 ID 能否对上。第三,Token 字段是否按同一口径归一化,尤其是输入是否包含缓存读取。

八、检查清单

  • 每次请求都写一条日志,包括失败和重试
  • 输入、缓存读取、缓存写入、输出分开记录
  • 日志里有请求 ID 和业务任务 ID,导出后能关联
  • 按任务统计验收通过数,计算单次成功任务成本
  • Agent 设最大轮次和预算,分类类任务限定模型
  • 导出明细时传入时区,并定期存档
  • 每次优化只改一项,用同一统计期比较

账单涨得快,先别急着换模型。先算清每个成功任务花了多少钱,再判断该改哪一步。你们的单任务成本主要花在长上下文、Agent 循环还是失败重试上?欢迎在评论区说说。

参考资料

以下资料的核对日期待发布前补写。发布前需逐个打开当日页面确认。

  • 灵眸 AI 官方文档:导出 Usage 使用明细、如何使用 Token、合规与备案(元数据留存)。核对日期待补。
  • OpenAI API 定价:https://platform.openai.com/docs/pricing
  • OpenAI Prompt Caching:https://platform.openai.com/docs/guides/prompt-caching
  • OpenAI Responses 参数(max_output_tokens):https://platform.openai.com/docs/api-reference/responses/create
  • OpenAI Chat Completions 参数(max_completion_tokens):https://platform.openai.com/docs/api-reference/chat/create
  • Anthropic API 定价:https://docs.anthropic.com/en/docs/about-claude/pricing
  • Anthropic Prompt Caching:https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
  • Anthropic Messages 参数(max_tokens):https://docs.anthropic.com/en/api/messages
  • Google Gemini API 定价:https://ai.google.dev/gemini-api/docs/pricing
  • Google Gemini 上下文缓存:https://ai.google.dev/gemini-api/docs/caching
返回列表