前面的系列已经介绍了模型调用、上下文、生成参数、错误处理以及成本基础。把这些知识放进真实应用时,还缺一个经常被忽略的边界:一次请求到底允许消耗多少资源。本文只聚焦一个核心问题——在发出模型请求前,用普通 Python 代码实现一个可解释的预算闸门,阻止超出次数、Token 或费用上限的调用。示例不连接任何厂商 API,因此可以离线运行和验证。
为什么要在请求前检查预算 模型调用通常不是普通的本地函数。它可能产生按输入和输出分别计费的 Token 消耗,也可能因为重试、批处理或用户重复点击而快速累积请求量。仅在响应返回后统计,无法阻止已经发生的超预算调用。
预算闸门的职责很单一:接收本次请求的估算值和累计用量,判断是否允许继续。它不负责选择模型、不负责执行网络请求,也不负责决定业务内容。这样的边界便于测试:同样的输入应得到稳定的“允许”或“拒绝”结果。
需要注意,下面的 Token 估算只是工程上的粗略预估,不等同于某个模型服务的官方计费结果。不同模型的分词规则不同;正式计费应以对应服务的官方用量字段和价格文档为准。闸门的价值在于提前设定保守上限,而不是伪造精确账单。
先定义输入和规则 我们把预算拆成三类:单次请求最多估算多少 Token、整个运行最多允许多少 Token、最多允许多少次请求,以及累计费用上限。实际项目也可以只启用其中一部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from dataclasses import dataclass@dataclass(frozen=True ) class Budget : max_requests: int max_input_tokens: int max_total_tokens: int max_cost: float input_price_per_million: float output_price_per_million: float @dataclass class Usage : requests: int = 0 input_tokens: int = 0 output_tokens: int = 0 cost: float = 0.0
Budget 是不可变配置,避免程序运行中悄悄修改上限;Usage 保存已经消耗的资源。费用使用浮点数只适合这个演示。若要做严格财务核算,应改用 decimal.Decimal,并按服务商公布的单位精度处理。
实现一个保守的 Token 估算器 在没有真实 API 响应之前,可以先用字符数做预估。中文、英文、代码和标点的分词比例并不相同,所以这里故意取一个偏保守的上限:每四个字符至少按一个 Token 估算,并向上取整。
1 2 3 4 5 6 7 8 from math import ceildef estimate_tokens (text: str ) -> int : """仅用于发请求前的保守估算,不代表服务商的精确分词结果。""" if not text: return 0 return ceil(len (text) / 4 )
这个函数的优点是透明、无依赖、容易替换。接入具体服务后,可以把它替换为该模型对应的 tokenizer;预算闸门的接口不需要改变。
让闸门返回可解释的决定 拒绝调用时,调用方需要知道原因,而不是只得到一个布尔值。下面的实现按请求次数、单次输入 Token、累计 Token 和预计费用依次检查,并返回第一条失败原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 def estimate_cost (input_tokens: int , output_tokens: int , budget: Budget ) -> float : input_cost = input_tokens / 1_000_000 * budget.input_price_per_million output_cost = output_tokens / 1_000_000 * budget.output_price_per_million return input_cost + output_cost def check_budget ( prompt: str , expected_output_tokens: int , usage: Usage, budget: Budget, ) -> tuple [bool , str , int , float ]: input_tokens = estimate_tokens(prompt) request_cost = estimate_cost(input_tokens, expected_output_tokens, budget) if usage.requests + 1 > budget.max_requests: return False , "超过请求次数上限" , input_tokens, request_cost if input_tokens > budget.max_input_tokens: return False , "单次输入 Token 估算值超限" , input_tokens, request_cost if usage.input_tokens + input_tokens + expected_output_tokens > budget.max_total_tokens: return False , "累计 Token 预算不足" , input_tokens, request_cost if usage.cost + request_cost > budget.max_cost: return False , "累计费用预算不足" , input_tokens, request_cost return True , "允许调用" , input_tokens, request_cost def record_usage ( usage: Usage, input_tokens: int , output_tokens: int , request_cost: float , ) -> None : usage.requests += 1 usage.input_tokens += input_tokens usage.output_tokens += output_tokens usage.cost += request_cost
expected_output_tokens 必须来自应用自己的上限或生成参数,而不能假设模型一定只返回很短的内容。检查通过后,只有在请求真正成功时才调用 record_usage;如果网络超时且无法确认服务端是否已处理,应根据服务商的幂等和计费规则设计更谨慎的记账策略。
运行一个完整的离线示例 下面的程序模拟两次调用。它不会访问网络,也不会伪造模型响应,只演示闸门如何决定和记录用量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 budget = Budget( max_requests=2 , max_input_tokens=80 , max_total_tokens=180 , max_cost=0.01 , input_price_per_million=1.0 , output_price_per_million=2.0 , ) usage = Usage() for prompt in ["请把这段会议记录整理成三个行动项。" , "请再给出一份详细的风险说明。" ]: allowed, reason, input_tokens, request_cost = check_budget( prompt, expected_output_tokens=60 , usage=usage, budget=budget ) print (f"allowed={allowed} reason={reason} input_tokens={input_tokens} " ) if not allowed: continue actual_output_tokens = 45 actual_cost = estimate_cost(input_tokens, actual_output_tokens, budget) record_usage(usage, input_tokens, actual_output_tokens, actual_cost) print (usage)
运行方式是把代码保存为 budget_gate.py 后执行 python budget_gate.py。输出中的具体 Token 数由字符串长度决定,程序会明确显示每次是否放行以及累计 Usage。示例只记录“成功返回”的预设输出 Token;真实代码应使用 API 响应中的实际用量字段更新,而不是继续使用估算值。
常见问题 为什么不直接把预算检查写在调用函数里 可以封装,但不要把检查、网络请求、重试和业务提示词全部揉成一个函数。独立的闸门可以单元测试,也可以在命令行、队列消费者和 Web 服务中复用。调用层只需要遵守“先检查,成功后记账”的协议。
估算太保守会不会浪费预算 会,所以应把估算视为安全边界,而不是最终账单。上线前用实际响应中的 Token 数据校准估算比例;宁可在开发阶段预留余量,也不要把上限设得刚好贴边。
重试应该如何计数 重试不是免费的“同一次调用”。如果服务端可能已经处理请求,重试可能再次消耗 Token。最简单的策略是每一次实际发出的请求都占用一次次数预算;更复杂的系统还应为重试设置独立上限,并记录请求 ID。
多个进程如何共享预算 本文的 Usage 只在单进程内有效。多个 Worker 同时运行时,必须把用量放进具有原子更新能力的共享存储,或者由集中式队列统一发放额度。仅靠进程内变量会导致每个进程都以为自己还有完整预算。
小结 预算闸门不是计费系统,却是 AI 应用的重要安全边界。本文用四步完成了一个可运行的最小实现:用数据类描述预算和用量,用保守规则估算输入 Token,在请求前检查次数、Token 与费用,成功后记录实际用量。下一步接入具体模型服务时,只需替换请求和用量读取部分,同时保留这层独立的约束逻辑,就能让成本控制从“事后统计”变成“请求前决策”。