省 token 这个事,我问过不少做 AI 应用的朋友,十有八九都拍着胸脯说"我一个月把 token 成本砍了 40%"。但你再追问一句"你失败重试一次会多花多少钱",大多数人会愣住。这个愣住就是问题所在:大家把精力全放在 prompt 压缩上,却忘了重试机制才是账单上的隐形炸弹。我见过一个很典型的案例,某团队把 system prompt 从 800 字砍到 200 字,历史消息从 10 轮压成 3 轮摘要,单次请求输入 token 从 5000 降到 3000,成就感爆棚。结果上线第一天,一次网关超时触发自动重试,同样的请求原封不动又发了一遍,输入 token 直接变 6000,把省下来的那 2000 token 全赔回去还不够。这不是段子,是每天都在发生的账。
这篇文章不聊怎么把 prompt 压得更狠,只聊一件事:你省 token 省得很开心的时候,重试机制是怎么把利润全吞掉的,以及怎么设计一套不会让 token 预算失控的调用层。
1. 一场典型的省 token 翻车现场:先看账怎么算
1.1 一次超时,为什么等于两次完整计费
先还原一个很常见的场景。你做了一个 RAG 问答机器人,用户问了一个超长问题,你的代码把检索到的上下文、聊天历史、工具定义全部拼进 prompt,拼出来 6000 token 的输入请求。请求发出去,网关没有在预期时间内返回,触发了你写好的重试逻辑,于是客户端立刻把同样的 6000 token 又发了一遍。
表面上看,这次交互只是"一次失败 + 一次成功",实际账单呢?至少是两笔输入费用,甚至可能更多。为什么?因为很多 API 的计费规则是"服务端收到请求并开始处理就算钱",而不是"响应成功返回才算钱"。第一次请求如果已经到达模型服务、已经开始解析 prompt,那么哪怕最后超时了、响应没回到你手里,那 6000 token 的输入已经进了账单。重试又发了 6000 token,于是输入侧就是 12000 token 的费用,比正常情况多了整整一倍。
更惨的是输出侧。如果第一次请求其实已经在服务端生成了 800 token 的结果,只是回传超时,你重试后模型又生成了 800 token,那么输出侧也可能被算两遍。我不是在吓唬你,我见过真实的账单里,一次超时重试导致某次调用的总 token 消耗是正常情况的三倍。你的 prompt 优化再猛,也很难抵消这种倍率。
1.2 失败请求不是"没产生费用",而是"生效但没人接收"
很多人心理上默认一个错误的等式:请求失败 = 服务端没干活 = 不扣 token。在本地调用一个函数、写个脚本,这个等式成立;但调云端 LLM API 不是这个逻辑。云端模型的计费判定点通常发生在请求被服务端接受、开始进行推理的时刻,它不会因为你客户端超时就把这笔推理"退款"。
打个比方,你在餐厅点了菜,后厨已经下锅了,但你等不及走了,还去隔壁重新点了一份一样的。厨房不会因为你人走了就不收钱,两桌菜的成本都算在你头上。模型推理这件事同样不可撤销,你收到的超时往往是"结果回传超时",而不是"服务端没算"。真正的"请求根本没到服务端"只发生在连接建立失败、DNS 解析失败、TLS 握手失败这类阶段。但很多重试库没有区分这两类情况,一律无脑重发,这就给账单埋了一颗大雷。
我自己后来养成了一个习惯:看 token 用量报表时,会把"成功请求的 token 消耗"和"失败后重试的 token 消耗"分成两个字段统计。不看不知道,一看才发现,有些项目的重试消耗能占到总 token 的 15% 到 30%。如果这些项目之前还拼命压 prompt,那真的是省小钱、亏大钱。
2. 重试为什么会吞掉这么多 token:三种隐性重复计费
2.1 重发完整 payload:输入 token 原样再扣一遍
这是最直接的重复计费。你的请求体里如果带着完整上下文——历史对话、检索结果、工具定义、system prompt——那么每次重试都会把这整包内容重新发送、重新计费。重试 N 次,输入 token 就是 N+1 份(算上原始请求)。
有人可能想,那我重试的时候把历史对话砍掉一部分,不就省了吗?这恰恰是另一个坑。我在后面第四章会详细讲,重试时的请求体最好是"完全幂等"的,也就是字段、顺序、内容跟原请求保持一致。一旦你在重试时改动了 prompt,比如重新压缩历史消息、重新生成时间戳、临时去掉某个工具定义,后果是缓存命中率下降、响应分布变化,甚至业务逻辑出错。为了省 token 去改重试请求,通常只会让问题更复杂。
正确的省法是:在一开始设计 prompt 的公共部分时,就把 system prompt、工具定义这些"高频固定内容"放到请求体的固定前缀位置,让服务端的上下文缓存机制能够命中。重试时保持原样,靠缓存折扣来降低重复计费的价格,而不是靠删内容。
2.2 超时后服务端已经开跑:输出 token 被算两遍的风险
前面讲到,服务端可能已经完成了部分甚至全部生成,只是响应回传失败。这种场景下,重试会导致输出 token 双算。很多 SDK 的超时时间默认很短,比如 10 秒,而一些复杂的 agent 请求可能需要 60 秒甚至更长才能完成一次完整推理。你设了 10 秒超时,模型第 11 秒生成完了,客户端已经进入重试流程。服务端那笔输出照记,新请求又开始一轮新的生成。
如果你接的是流式接口,情况还要微妙一点。SSE 流可能已经传了一部分内容,客户端因为某一段网络抖动断开了流,这时重试的话,已经收到的部分和重试重新生成的部分都会计算 token。而且大模型的输出没有幂等性,同样的 prompt 重试两次,生成结果可能完全不同,你不能指望重试得到一份更好的答案,只能得到一份更贵的账单。
要避免这种情况,超时阈值必须根据模型的实际推理时长来设计,而不是拍脑袋定一个 5 秒 10 秒。我见过一个项目,调的是比较重的模型,平均完成时间 40 秒,超时却设了 15 秒,导致高峰期几乎每次都触发重试,token 花费直接翻了几倍。这就是典型的"重试设计没跟上模型特性"。
2.3 并发翻倍、限流升级、长上下文二次展开:连锁反应才是大头
比单次双算更可怕的,是重试引发的系统性连锁反应。当你在高并发场景下统一重试时,失败请求会和新请求叠加在一起,形成短暂的并发尖峰。服务端本来是因为过载才给你 429 或 5xx,你的重试又给它踩了一脚油门,结果就是更严重的限流,然后更多请求失败,更多重试,最后触发熔断。这一轮下来,token 的额外消耗不是简单的两倍三倍,可能是整段时间窗口内的全部请求都被迫重试,成本按小时计都在暴涨。
另一个隐藏点是长上下文在重试时的"二次展开"。比如一个 agent 应用,每轮工具调用都会重新拼接完整对话历史,一次任务里调 8 次工具,每次的输入都包含前面所有轮次的输出。如果其中某一轮调用失败触发重试,那么从这一轮往后,所有后续轮次都会在更长的上下文基础上重新计算。这就像滚雪球:重试的不只是一次请求,而是把整条推理链后面的上下文长度都撑大了。有些 optimizer 还喜欢把历史消息每次全量重新编码,不做增量缓存,遇到重试这个缺陷会被放大得非常明显。
顺便说一个不花钱但花时间的版本:本地部署开源模型,比如你在 6GB 显存上跑一个量化过的 27B 模型,表面上 token 免费,但模型加载和 KV cache 的分配都在显存里,一次请求失败后如果你在代码里做了简单重试,往往要先把之前的结果丢弃、重新加载权重、重新处理整个输入。这里赔的不是 token,是延迟和 GPU 利用率。所谓"token 自由"是有代价的,很多人没算这笔账。
3. token 失效、403 和 refresh_token:重试陷阱的另一半
3.1 鉴权 token 和计费 token 是两回事,但它们的坑会叠加
这里要先分清两个"token"。一个是 OAuth/OIDC 体系里的 access_token,用来证明"你有权限调用这个 API";另一个是大模型计费里的 token,是文本长度的计量单位。这两个东西经常在同一段代码里出现,但它们的失效机制完全不同:access_token 会过期,刷新失败会导致 401、403;计费 token 没有"失效"概念,只有"数量高低"。
问题在于,当 access_token 失效时,你的 API 客户端通常会尝试刷新 token 并重放原始请求。这个重放不会因为"只是刷新了凭据"就变便宜,LLM API 请求体里有多少 token,重放照样按多少 token 算。所以你会看到一种很冤枉的账单:请求失败原因是权限过期,失败本身没消耗多少,但自动 refresh 之后重试的请求,把整段 prompt 重新计费了一遍。
我见过一个更隐蔽的案例:某个 agent 框架会把 access_token 存到上下文里,算子调用时把 token 字符串拼到工具参数中,结果 token 刷新后,旧 token 仍然留在历史消息里,重试请求带着一整串过期的凭据信息发出去,既浪费计费 token,又增加了凭据暴露面。这是设计上的双重失误,但很常见。
3.2 403 token exchange failed:这类错误重试一百次也是白烧
热搜词里反复出现sign-in could not be completed token exchange failed: token endpoint returned status 403 forbidden,这类问题在接入企业身份提供商或 SSO 登录时非常典型。token exchange 这一步通常发生在登录阶段,它和 LLM 调用还隔着一层。它的 403 往往意味着认证策略层就拒绝了这次交换,比如国家/地域限制、客户端配置错误、授权码已用、redirect_uri 不匹配。
对于这类错误,最忌讳的就是在用户点击登录时自动无限重试。因为错误根源不在服务端临时故障,而在客户端配置或用户会话状态。重试只会让用户看到持续转圈,让认证服务器多接几次无效请求,如果你在登录成功后才开始调 LLM API,那还谈不上一开始的 token 消耗;但如果你的登录流程里包裹了其他调用,或者用户已登录后 access_token 刷新失败,然后你用"重新登录 + 重放业务请求"的粗暴方式兜底,业务请求的重放就会烧掉 LLM token。
正确做法是分类处理。401 Unauthorized和403 Forbidden必须立刻停止重试,进入重新认证或提示用户流程;只有那些429 Too Many Requests、5xx Server Error、连接超时、临时网络抖动,才值得走重试队列。
3.3 refresh_token 为空字符串这类 Bug:重试多少次都不会成功
再看另一个热搜词:failed to refresh token: 400 bad request: invalid 'refresh_token': empty string. expected a string with minimum length 1, but got an empty string instead.这个错误有意思,它明确告诉你 refresh_token 是个空字符串。这意味着什么?意味着你根本没有拿到有效的 refresh_token,可能是初始授权流程没走完,可能是存储层把空值写进去了,也可能是并发登录时两个请求互相覆盖了凭据。
这种错误和无脑重试是绝配。很多封装库的逻辑是:调用 API 遇到 401 -> 调用刷新逻辑 -> 刷新失败 -> 再刷新 -> 再失败。如果刷新失败的原因是 refresh_token 本身为空,那你重试一千次也拿不到新 token,因为问题出在"凭据状态",不是"网络瞬时故障"。这段期间如果业务逻辑还在不断重发 LLM 请求,每一发都是带完整 prompt 的付费调用,但每一发都会在鉴权层被拦下来。更麻烦的是,有些请求会在提示词里包含用户输入,每次重试都会重新计费输入 token,哪怕模型根本没机会真正生成答案。
所以我在代码里会把错误分成两类,一类是"重试可能有效"的,一类是"重试必定无效"的。重试必定无效的错误,应当直接抛出并进入人工处理或者状态修复流程,而不是放进重试循环。判断依据不复杂:去看看这个错误是不是因为你自己的代码状态坏了。自己状态坏了,修好状态再重放才有意义;自己状态没问题、服务端抖动,才用指数退避重试。
4. 设计一个舍不得让你赔钱的调用层:重试与预算策略
4.1 把"可重试错误"和"不可重试错误"写进代码,而不是写在文档里
第一步不是在代码里加try...except,而是先建立一张错误分类表。不同 API 的错误类型略有差异,但通常可以这样分:
| 错误类别 | 典型场景 | 是否值得重试 | 重试策略 |
|---|---|---|---|
| 429 | 限流、并发超限 | 可以重试,但必须退避 | 指数退避 + 抖动,参考 Retry-After 头 |
| 500/502/503/504 | 服务端瞬时故障、网关错误 | 可以重试 | 指数退避,最大 2~3 次 |
| 连接超时 / 连接重置 | 网络抖动、代理不稳定 | 可以重试 | 短退避,但注意超时阈值要合理 |
| 400 | 请求参数错误、prompt 格式非法 | 不重试 | 检查参数 |
| 401/403 | access_token 失效、权限不足 | 不重试 | 刷新凭据后重放一次,最多一次 |
| 404 | 模型名错、路由不存在 | 不重试 | 检查 API 配置 |
| 409 | 状态冲突、并发写冲突 | 视业务而定 | 一般不上自动重试 |
| 400 + refresh_token 为空 | 会话状态损坏 | 绝对不重试 | 进入重新登录流程 |
我把这套分类直接做成一个should_retry函数,而不是靠每个人记文档:
RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504} CONNECTION_ERRORS = (TimeoutError, ConnectionError, APIConnectionError) def should_retry(error) -> bool: if isinstance(error, (TypeError, ValueError, KeyError)): # 自己代码问题,重试没有意义 return False if isinstance(error, CONNECTION_ERRORS): return True if hasattr(error, "status_code"): return error.status_code in RETRYABLE_STATUS_CODES return False这个函数是整个重试策略的地基。没有这个分类,后续所有手段都是空中楼阁。我见过太多项目直接for attempt in range(3)然后无差别重试,这种代码在 happy path 上没问题,一出事故就变成 token 粉碎机。
4.2 指数退避是重试的底线,而不是可选项
如果你已经确认这个错误值得重试,那下一步就是怎么控制重试节奏。最粗的做法是立刻重试,或者固定 1 秒后重试。在低并发场景下可能没事,但一旦流量上来,立刻重试会撞在服务端故障的枪口上,所有客户端同时重试,打出一波远超原峰值的请求洪峰。
指数退避的核心思想是:每多一次失败,就把等待时间翻倍,并且加上随机抖动,避免重试请求在时间上扎堆:
import random import time def backoff_delay(attempt: int, base_seconds: float = 0.5, max_seconds: float = 8.0) -> float: delay = min(max_seconds, base_seconds * (2 ** attempt)) jitter = random.uniform(0, delay * 0.3) return delay + jitter加上抖动是因为,如果 100 个客户端都严格按照0.5s -> 1s -> 2s退避,它们的重试时间点依然会重叠,抖动就是给这个"共振"加一点噪声。你可以看到,重试策略里最难的不是代码,而是对"何时该放弃"的把握。我的经验是:LLM API 调用通常最多重试 2 次,也就是总请求数最多 3 次。再往上,边际成功概率极低,边际成本却很高,尤其是在你用的是大上下文模型时。3 次请求每次都是完整输入,这个成本多数业务扛不住。
4.3 预算熔断与 token 拨备:给重试留出明确的位置
单次请求的 token 消耗好算,难的是你没法预知今天会不会出故障。所以我习惯在调用层做两道保险。
第一道是"重试拨备"。在做 token 预算规划的时候,不要按理想成功率来计算。如果你平时请求失败率是 5%,那么预算至少要留出 10% 到 15% 的余量给重试和降级。很多团队做成本预估时只用"单次成功请求的 token 数 × 调用次数",然后拿着这个数字去要预算,一旦出现故障周,实际账单超出预期,就只能砍功能或砍模型。按我经验,把重试拨备写进预算模型里,比事后解释超支要省心得多。
第二道是"滑动窗口熔断"。我推荐在调用层维护一个 token 用量计数器,按分钟和小时两个粒度统计。当单位时间的 token 消耗超过阈值时,不再发起新请求,而是走降级逻辑。这比单纯依赖 API 返回 429 再被动退避更主动:
class TokenBudget: def __init__(self, limit_per_hour: int): self.limit = limit_per_hour self.consumed = 0 def can_request(self, estimated_tokens: int) -> bool: return self.consumed + estimated_tokens <= self.limit def record(self, tokens: int): self.consumed += tokens事先估算请求 token 并不难:输入 token 可以提前统计,输出 token 给个上限预估即可。熔断的阈值可以设为预算的 80%,剩下的 20% 留给正在途中的请求和最后的收尾清理。一旦熔断触发,宁可让这次请求失败返回友好提示,也不要让它在重试循环里把预算烧穿。记住一个原则:重试的目的是让"有价值的请求"成功,不是让"失败请求"永远不落地。
4.4 把缓存落在请求结构里:让重试命中缓存而不是全价计费
很多主流 API 对重复的输入前缀有提示词缓存机制,命中缓存后这部分 token 会有折扣价,甚至大幅降低。这里面有一个关键点很容易被人忽略:缓存能否命中,取决于重复请求的前缀是否完全一致。
也就是说,你的 system prompt、工具定义、公共指令这些固定内容,必须放在 prompt 的最前面,中途绝对不要插入随机值。很多人为了调试方便,会把时间戳、随机 request_id 生成一个字符串拼在 prompt 某个位置,或者每次重试时重新压缩历史消息摘要,这都会让缓存前缀失效。重试时如果缓存全部落空,那重试请求就是全价重算,损失更大。
我自己的做法是:
- system prompt 和全局工具定义放在 prompt block 的最前面,内容永远不变量;
- 用户问题、检索结果这类"每次不同"的动态内容,放在固定内容之后;
- 重试时保持请求体与原始请求完全一致,不因为"重试"而重新组织 prompt。
这样安排,即使失败了,重试的公共部分大概率能命中缓存,输入侧的成本从"两份全价"变成"一份全价 + 一份缓存折扣",差距非常可观。
4.5 失败降级优先于无限重试
最后一步,也是很多人最容易忽略的:不是所有请求都值得重试成功。你要给调用层定义一个"降级路径"。当一个请求在第一次失败后,第二、第三次还失败,这时候正确的动作不是继续消耗 token,而是降级。
举几个实际的降级例子:
- 如果调用的是一个昂贵的超大模型,失败后可以降级到一个小模型重新尝试,输出质量略降,但 token 成本可能只有原来的十分之一,而且大概率不触发同样的限流;
- 如果是非核心的摘要、打标签功能,失败后可以直接返回空结果或默认文本,让主流程继续,没必要为一个辅助功能耗尽重试预算;
- 如果是交互式问答,失败后可以给出"服务端繁忙,请稍后重试"的提示,把重试决策交给用户,而不是让系统在后台自动烧 token。
降级和重试是配合使用的:第一次重试用指数退避,第二次重试换小模型,第三次直接放弃走兜底。这套阶梯式策略,既保证了核心请求的成功率,也控制了最坏情况下的 token 消耗。我见过太多团队把降级做成"if error: return None",却忘了返回 None 之后业务逻辑可能产生更诡异的行为。降级路径的返回值设计,也要像主路径一样仔细,保证下游能接收到一个"合理但弱化"的结果。
5. 实测账本:省下的和赔掉的,到底谁说了算
5.1 用真实价格算一笔请求级流水账
光讲道理不够,我拿一个实际的定价模型算一遍。假设你用的是按输入和输出分开计费的 API,输入 token 单价 1 元/百万 token,输出 token 单价 3 元/百万 token。一个典型的业务请求,优化前输入 3500 token,输出 800 token,单次成本:
- 输入:3500 / 1_000_000 × 1 = 0.0035 元
- 输出:800 / 1_000_000 × 3 = 0.0024 元
- 单次合计:0.0059 元
优化后你把 prompt 压到输入 2000 token,输出不变,单次成本:
- 输入:2000 / 1_000_000 × 1 = 0.002 元
- 输出:800 / 1_000_000 × 3 = 0.0024 元
- 单次合计:0.0044 元
看起来省了 25% 左右,对吧?现在我们把重试加进来。假设请求失败率 5%,每次失败后自动重试一次,重试请求完整重发。优化后的实际期望成本是:
- 0.0044 × (1 + 0.05) = 0.00462 元
再考虑一种更常见的情况:超时导致服务端已部分计费,失败的那次请求虽然没返回结果,但输入侧的 2000 token 已经被计费,然后在重试里又产生一次正常成本。单次期望成本变成:
- 0.0044 + 0.05 × (0.002 + 0.0044) = 0.0044 + 0.05 × 0.0064 = 0.00472 元
如果失败率是 10%,重试一次,那么成本变成 0.0044 × 1.1 = 0.00484 元。再看优化前的无重试成本 0.0059 元,好像还是省了。但别急,重试往往会带来更长的输出。失败重试的请求,因为模型重新生成,输出通常不会恰好等于第一次的 800 token,很多情况下会更长,比如 1200 token。以失败率 10% 计算,新增成本就变成 0.05 × (0.002 + 0.0036) = 0.00056 元,单次期望 0.00496 元。如果失败率到 15%,重试一次的期望成本已经到 0.00544 元,逼近你没做任何优化的数字。
5.2 一百次请求的账本对比:重试率比 prompt 长度更致命
我把四种情况放到 100 次请求的规模上看,会更直观。下表按输入 1 元/百万 token、输出 3 元/百万 token、每次输出 800 token 计算:
| 场景 | 单次输入 token | 单次输出 token | 失败重试率 | 100 次请求总成本 |
|---|---|---|---|---|
| 优化前,无重试 | 3500 | 800 | 0% | 0.59 元 |
| 优化后,无重试 | 2000 | 800 | 0% | 0.44 元 |
| 优化后,重试一次 | 2000 | 800 | 5% | 0.462 元 |
| 优化后,重试一次 | 2000 | 800 | 10% | 0.484 元 |
| 优化后,重试一次并叠加失败部分计费 | 2000 | 800 | 10% | 0.516 元 |
| 优化后,重试两轮 | 2000 | 800 | 10% | 0.5324 元 |
你看,当重试率达到 10% 并算上服务端部分计费后,你辛苦压 prompt 省下来的 0.15 元,被重试机制吃掉了一大半。如果重试两轮,基本等于白干。更不用说那些失败输出更长、上下文缓存又没命中的场景,翻车几乎是必然的。
这告诉我们一个反直觉的结论:prompt 优化的空间是有上限的,通常也就 20% 到 40%;但重试机制的蝴蝶效应是没有下限的,可以把单次成本打到 2 倍、3 倍甚至更高。所以在成本治理上,重试策略的优先级应该排在 prompt 压缩前面。先把"不该重试的不重试、该重试的按指数退避、重试有预算熔断、失败有降级路径"这四件事做齐,再去抠 prompt 的字数,才是正确的顺序。
5.3 报表里必须能区分"有效 token"和"灭火 token"
前面算的账,最终都要靠数据来验证。我强烈建议你在所有 LLM 调用里埋点,至少记录这些字段:请求时间、模型名、输入 prompt_tokens、输出 completion_tokens、total_tokens、HTTP 状态码或错误类型、重试次数、request_id、业务标签。有了这些数据,你会发现一个特别有用的分析维度:把 token 消耗按"是否发生在重试路径上"切成两堆。
我习惯给报表加两个指标:一是有效 token 占比 = 成功请求的 token / 总 token,二是重试放大系数 = 总 token / 成功场景下的理论 token。有效 token 占比越接近 100%,说明你的调用层越干净;重试放大系数大于 1.2,说明重试策略已经失控。我见过一个项目,有效 token 占比只有 71%,也就是三个 token 里有一个是浪费在失败重试上的,这个项目再怎么压 prompt 也救不回来,因为病根在调用策略,不在提示词长度。
排查的时候,把命中429、5xx、超时的请求按小时聚合,画个趋势图,再和 token 消耗趋势画在一起,你会发现它们长得几乎一模一样。这就是"灭火 token"的真实形态。看到这个图形之后,任何人都会明白:先治重试,再谈省 token。
6. 最后一点个人体会
写了这么多,其实我最想说的是一个顺序问题:很多人把"省 token"当成一个 prompt 工程问题,整天研究怎么压缩上下文、怎么改写提示词,但刷起账单来看,真正让成本失控的往往不是 prompt 太长,而是重试、超时、鉴权失败这些稳定性问题。prompt 优化省的是"乐观情况下的钱",重试策略省的是"故障情况下的底裤"。
我自己的项目经验是:先建好错误分类和重试策略,再谈压缩 prompt。压缩 prompt 是锦上添花,重试策略是保命底线。另外有一个很土但很有效的办法:别只在脑子里想象重试成本,直接把上游服务改成"每 3 次请求故意挂掉 1 次"的故障模式,跑到一套压测环境里,盯一天 token 用量曲线。你会非常直观地看到,哪部分代码在真正烧钱。这个故障注入习惯,比开一百次会议都有用。
再分享一个最近养成的习惯:每次上报 token 用量的时候,都把重试次数和错误码一并带上。这个改动很小,但它让"省 token"这件事从一个模糊的方向感变成了可以逐条核对的数据。后来我能在一个下午里找出三个隐藏的重试黑洞,靠的就是这种报表。希望这篇内容能帮你在下一次看账单的时候,先想到重试,再想到 prompt 长度。