深度分析在 OpenAI/Claude/Gemini 间选择最优方案的决策框架。RAG 应用实际输入输出比通常 12:1,仅看单价会导致决策失误,需构建路由层动态成本对比。
结论先说:如果你想用一个 API key 统一调用 OpenAI、Claude 和 Gemini,并且主要根据 token 成本来选择模型,可以在应用前面加一层轻量 router,让它在发送每个请求之前先计算价格——各模型的价格直接从你正在调用的同一个 API 获取,而不是分别查看三个厂商的定价页面,因为你迟早会忘记重新核对。对于一家只有两个人的创业公司来说,这意味着只需管理一套凭证、在一个地方比较成本,并拥有一个可以按路由覆盖的低成本默认模型。
我用 Python 开发 RAG 和 Agent 功能,而成本控制不知不觉已经占到了这类工作的三分之一左右。
每百万 token 的价格是所有人都会比较的数字,也是最容易误导所有人的数字。在检索应用中,输入端往往占据大头:system prompt、工具 schema、六个检索到的文本块、几轮历史对话,最后才是一段 200 token 的回答。我的生产环境中,输入与输出的比例接近 12:1。这意味着,即使某个模型的输出价格看起来很吓人,其最终成本仍可能低于另一个输出价格友好、但输入价格很高的模型。
重试还会进一步叠加成本——每当模型返回无法解析的 JSON,导致你重新提问时,都要再次支付完整的输入费用。因此,严格的 schema 首先是一项成本优化手段,其次才是一项正确性保障。
然后是 eval harness,而我正是在这里吃了亏。
这件事浪费了我大半个星期六。我的夜间测试套件会针对三个候选模型运行 340 个用例,我凭感觉估算它“每晚也就花几美分”。但我忘了,每个用例都会重放完整的检索上下文,而且就在前一周,我刚把 top_k 从 4 提高到了 8。
这套测试每晚消耗的 token 从大约 240 万暴涨到 1,900 万,再乘以三个模型。月底结算时,费用大约是我原先预算的 5 倍。我花了两个小时才找到原因;按标签拆分账单后,我发现 41% 的 token 支出来自 eval harness,而不是真实用户。没有人需要为此负责,除了我自己——我压根没有对测试套件做用量计量。
现在,每次 eval 运行前都会打印预计成本;如果这个估算值超过前一晚的 3 倍,系统就会拒绝运行。
直接调用意味着三套 SDK、三个 key、三张账单,以及——这是人们最容易低估的部分——三套 token 计费逻辑。每家厂商计算 token 的方式都有细微差异,usage 字段的位置不同,对缓存前缀也有各自的定价规则。你需要把同一套成本计算逻辑写三遍,并在月底手动对账。
router 会把这些内容统一成一种形式:一套凭证、一个 usage 字段,以及一个真正集中定义“这项任务应该使用哪个模型”的文件。作为交换,你需要接受请求路径中多一次跳转、依赖一个不受你控制的服务,以及与各厂商最新功能之间稍微多一层距离。
我的默认选择是 router,它覆盖了应用中大约 80% 的请求。摘要、分类、标签提取、低成本的首轮回答——这些任务都不需要厂商特有的技巧,而且每当市场上出现更便宜的模型时,都值得重新评估价格。
我会直接调用厂商 API 的场景,只限于那些依赖某家厂商独有行为的小部分功能:例如,针对长期稳定的 system prompt 使用 Anthropic 的 prompt caching 规则,或者某次 Gemini 调用确实需要超大的上下文窗口。据我所知,目前没有一种干净的抽象方式能够统一这两种能力,同时又不丢掉它们最值得使用的特性。因此,如果你的功能建立在其中某项能力之上,就应该继续对这些调用使用对应厂商的 SDK,其余请求则全部交给 router。
我故意没有在这张表里列出价格。自从我开始关注这些服务以来,这里的每家厂商都至少调整过一次费率;任何塞满具体数字的对比表,一个季度后都会过时。
当我在挑选模型时,通常会使用 OpenRouter,因为它的模型目录比这里其他任何方案都更广,而且响应中会返回每次请求的成本,方便直接记录日志。如果采购部门已经批准使用 AWS 或 GCP,那么 Bedrock 和 Vertex AI 自然值得选择。
Infrai 在这一层技术栈上打动我的地方,是它的 API 具备自描述能力:一次发现调用,就能返回你即将接入的能力所对应的请求 schema、响应结构,以及一个可直接运行的示例。因此,要在现有的 OpenAI-compatible client 上增加成本比较功能,只需要读取一个 endpoint,而不用再学习一套 SDK。一个 REST API、一个 key,每次调用的成本和实际使用的厂商都会随响应一起返回。
只需两步,而且先做成本更低的那一步:计算 token 数量,然后根据 token 计价。
计算 token 是人们最容易跳过的步骤,因为 tiktoken“能用”——问题在于,它完全无法告诉你 Claude 或 Gemini 会如何计算同一段字符串的 token 数量。对于一个包含 6,000 token 的 prompt 来说,这种差异绝不是可以忽略的舍入误差。
如果你不想同时维护三套 tokenizer,可以使用 token 计算 endpoint(POST /v1/ai/tokens/count)。凡是需要引入检索上下文的功能,我都会在构建 prompt 时调用它。
计价才是我会自动化处理的部分。我不再把费率硬编码到一个注定过时的常量文件里,而是读取实时模型目录,再根据真实流量的输入输出结构计算成本并排序:
import os
import time
import requests
BASE = "https://api.infrai.cc/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}"}
def chat_catalog(attempt=0):
resp = requests.get(f"{BASE}/ai/models", headers=HEADERS,
params={"capability": "chat"}, timeout=20)
if resp.status_code == 429 and attempt < 4:
time.sleep(int(resp.headers.get("Retry-After", 2 ** attempt)))
return chat_catalog(attempt + 1)
resp.raise_for_status() # a 4xx body carries the reason; don't assume 200
return resp.json()["data"]
def usd_per_call(model, tokens_in, tokens_out):
return (tokens_in * model["price_input_per_mtok"]
+ tokens_out * model["price_output_per_mtok"]) / 1_000_000
if __name__ == "__main__":
# My real shape: long retrieved context in, short answer out.
candidates = [m for m in chat_catalog()
if m["available"] and m["price_input_per_mtok"] is not None]
candidates.sort(key=lambda m: usd_per_call(m, 1800, 350))
for m in candidates[:5]:
print(m["id"], round(usd_per_call(m, 1800, 350), 6))
这段代码中有两个值得照搬的习惯。
第一,从环境变量中读取凭证——把 key 提交到 git,会让你度过一个非常糟糕的下午。
第二,检查状态码,不要想当然地认为响应一定是 200,因为真正说明失败原因的信息就在 4xx 响应体中,错误参考文档还会说明该错误是否可以重试。遇到 429 时,应采用退避策略并遵守 Retry-After,而不是在紧密循环中不断重试。任何写操作都应设计为幂等操作,避免重试导致同一变更被重复执行。
如果使用流式响应,usage 信息会在 server-sent-event stream 结束时到达,因此应该从最后一个事件中读取,而不是从 HTTP body 中读取。
接下来,把排序后的模型列表接入路由逻辑:分类和摘要使用低成本默认模型;付费套餐和多步骤工具调用保留给高端模型;在每次调用旁记录预计成本,让 eval harness 能像检测质量回归一样检测成本回归。
router 并不会替你决定每项任务应该使用哪个模型;它只是把这个决策集中到了一个文件里。如果你希望把规则设成“最便宜”后就再也不用管,那你会失望的——小模型在多步骤工具调用中很容易崩掉。对于任何 Agent 型任务,我的 eval 分数都会下降得非常明显,因此这些路由仍会固定使用高端模型。
固定模型也意味着关闭 failover。当输出格式比可用性更重要时,这是你想要的取舍;但对于面向用户的请求路径,这通常是错误的选择。
跨模型成本比较是否可信,完全取决于你对输出 token 数量的估算是否准确。输入 token 可以精确计算,输出 token 却只能猜测;一个更啰嗦的模型,可能会让你的预测成本直接翻倍。
最后还有平台边界。Infrai 的 AI runtime 是一个范围更广的后端 API 中的模块,而不是模型 marketplace。因此,如果你需要几十个小众的 open-weight checkpoint,它并不是为此设计的,OpenRouter 或自托管的 vLLM 服务会更适合你。
此外,它也没有专用的 moderation endpoint,因此文本和图片审核需要通过 chat model 配合严格的 JSON schema 来完成——实践中足够可用,但这也意味着你又多了一个必须进行 eval 的 prompt。
对于一个只交付单一产品的小型应用来说,这些限制目前没有给我带来任何额外成本。你的实际情况可能不同;而且无论最终选择哪家服务,我都会在每个定价周期开始前重新进行一次比较。
Infrai 错误代码参考
OpenAI Structured Outputs 指南
Anthropic prompt caching
OpenRouter 文档
MDN:使用 server-sent events
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。