“便宜模型先试,失败再换贵模型”看起来节省成本,却经常把失败定义交给模型自评:错误答案可能非常自信,结构合法也不代表事实正确,第一次调用还已经消耗了延迟和 token。真正的路由不是价格排序,而是一份经过评测的执行政策。
本文面向已经有多种 AI 任务、需要控制质量、延迟与预算的工程团队。你需要能读取 API usage,并拥有代表性评测集。我们会依次决定能力与风险、同步或异步、缓存布局、实际用量记账与安全降级;不复制会快速过期的价格表,也不把未经实测的成本数字写成结论。
视觉资产记录:封面(1600 × 900)与文中决策图(1400 × 800)均为 WEB/SUN 于 2026-07-16 创作的程序化 SVG;来源/许可为本项目原创自有资产,未使用第三方图片。
图 1(1400 × 800):路由政策先满足硬约束,再在通过离线评测的候选中优化执行路径;真实 usage 与失败结果回流下一版政策。原创程序化 SVG,WEB/SUN,2026-07-16。
路由先写硬约束
请求进入系统时,先生成与模型无关的 envelope:任务类型、输入模态、是否需要工具或严格 Schema、风险等级、用户租户、数据区域、同步截止时间、最大输出、可否异步和政策版本。缺少必填事实就拒绝或要求补充,不能让模型“猜一个路由”。
候选模型必须先满足上下文、模态、结构化输出、工具与部署政策等能力。随后在固定评测集上比较任务成功率、关键切片与失败严重度。Model optimization把 evals 放在提示优化和模型选择的反馈循环中;因此“更小模型”只有在当前任务与风险切片通过门槛后,才是候选,不是默认答案。
路由条件应是确定性的、带版本的。不要依据一次回答的“confidence: 0.98”自动放行;若采用级联,触发升级的信号应来自可检查事实,例如 Schema 失败、引用缺失、检索证据冲突、规则校验不通过或人工复核,而不是让同一个生成器评价自己。
type Job = {
kind: 'extract' | 'answer' | 'code-review';
risk: 'low' | 'high';
needsTools: boolean;
deadline: 'interactive' | 'deferred';
};
type QualifiedRoute = {
model: string;
supportsTools: boolean;
passedSlices: ReadonlySet<string>;
};
function chooseRoute(job: Job, routes: QualifiedRoute[]) {
const slice = `${job.kind}:${job.risk}`;
const qualified = routes.filter(
(route) => (!job.needsTools || route.supportsTools) && route.passedSlices.has(slice),
);
if (qualified.length === 0) throw new Error(`No eval-qualified route for ${slice}`);
return { route: qualified[0], execution: job.deadline === 'deferred' ? 'batch' : 'online' };
}
示例刻意不写模型名字和价格。生产配置应保存模型快照/别名解析结果、eval 版本、提示版本、价格表生效时间和批准人;升级稳定版或试用 beta 时创建新候选,跑完同一门禁再改变流量。这段纯路由逻辑已映射到 examples/ai-web/src/ai-routing.ts,通过 TypeScript 类型检查与 Vitest;本文仍未执行付费请求,因此价格、线上路由和真实模型表现的结论仍是 source-reviewed。
把时效变成执行等级
交互式问答需要立即返回;夜间分类、离线抽取、评测和批量嵌入通常不需要。OpenAI 当前 Batch API 指南说明 Batch 使用独立速率限制池,面向无需即时结果的异步任务,并给出相对同步 API 低 50% 的成本与 24 小时完成窗口。这个服务合同应在产品里表现为“最晚完成时间”,不能伪装成实时队列。
Batch 输入 JSONL 的每行都需要唯一 custom_id;输出行顺序可能与输入不同,必须按该 ID 回填。一个输入文件只能包含同一模型的请求,因此先路由、再分桶、最后生成文件。提交前保存输入 hash 与业务幂等键;下载结果后逐条核对 HTTP 状态和 error,不能因为 batch 状态 completed 就把所有行标为成功。
批次到期、部分失败或结果文件暂不可取时,重试未完成的业务键,而不是整个文件。重复结果用幂等键去重。需要用户此刻等待的任务禁止偷偷塞进 Batch;反过来,离线工作也不该为了代码简单全部走在线端点。
| 工作负载 | 合适路径 | 不可牺牲的门禁 |
|---|---|---|
| 对话中的短回答 | 在线流式 | 质量切片、超时、取消 |
| 可延后的批量抽取 | Batch | custom_id、逐行错误、截止时间 |
| 延迟可波动的非关键任务 | 可评估 flex/队列 | 产品能接受等待与资源不可用 |
| 高风险外部动作 | 在线两阶段 | 权限、确认、幂等、审计 |
缓存优化的是相同前缀
Prompt Caching要求精确前缀匹配。稳定的 developer 指令、示例、工具定义和 Schema 放在前面,用户问题、检索片段与本次变量放在后面;图片 detail、工具列表或前部空格变化都可能破坏复用。缓存不是把答案保存下来,而是复用相同输入前缀的处理。
当前指南说明,满足支持条件且达到 1024 tokens 的提示会自动参与缓存;小于该长度的请求也会返回缓存明细,但命中为零。Responses API 从 usage.input_tokens_details.cached_tokens 读取缓存读取量,Chat Completions 对应 usage.prompt_tokens_details.cached_tokens;较新模型家族还可能报告 cache_write_tokens。因此命中率必须从响应 usage 聚合,不能用“提示看起来一样”估计。
不要为了命中缓存把租户秘密、临时授权或过期检索结果移动到长期静态前缀。prompt_cache_key 可以影响路由与命中,但不是访问控制,也不能跨越数据保留政策。缓存命中不会改善答案正确性;提示、工具或知识变更时应接受必要的 miss,并用版本化前缀避免错误复用。
成本账本只相信响应事实
每次调用记录 request ID、业务键、路由政策、解析后的模型、执行等级、输入/输出 token、缓存读写 token、工具次数、状态、重试原因和价格版本。金额在内部账本用整数最小货币单位计算,价格从带生效时间的配置读取;账单对账前标记 estimated,不能把客户端 tokenizer 估算当最终费用。
成本按“成功业务结果”而非“单次调用”观察:一次请求如果重试三次、两次输出被验证器丢弃,它的真实成本属于同一业务键。看板同时显示成功率、严重失败率、尾延迟、每成功项成本和缓存读写比例,否则压低 token 很容易掩盖质量退化。
Cost optimization建议减少请求、缩短输入/输出、在保持准确性的条件下选择更小模型,并把 Batch 或 flex 用于合适工作负载。工程顺序也应如此:先删除无价值调用和重复上下文,再验证输出上限与缓存,最后在通过 eval 的模型间优化价格。
降级不能暗中降低权限
主模型超时后切换备用模型时,仍沿用相同工具 allowlist、Schema、数据区域和人工审批。备用模型若不支持严格输出或某种模态,正确结果是降级为“只读回答”“请求文字输入”或进入人工队列,而不是放宽验证。Batch 不可用时也不应自动把海量离线任务冲入在线速率池。
典型失败包括:按价格硬编码模型而没有评测;模型自报低信心才升级;混合模型写进同一个 Batch 文件;按输出行号回填;把 completed 当逐行成功;动态内容放在提示开头;用预计 token 代替 usage;价格更新后重算旧账;备用路径跳过审批。
每次政策变更先 shadow 记录候选决策,不立即执行;再对代表性切片比较质量、延迟和实际 usage。只有差异解释清楚且门禁通过才提升版本,保留一键回到上一政策的能力。成本优化的回滚单位应是“路由政策 + 提示 + 模型解析 + 价格版本”,而不只是一个模型字符串。
结论
可靠的成本工程有固定次序:能力和安全先筛掉不合格路径,离线 eval 决定哪些模型有资格,业务截止时间选择在线或 Batch,相同前缀布局争取缓存,实际 usage 建立可对账账本,失败时只降低功能与时效,不降低权限和验证。
当团队能解释每个请求为何走这条路、它通过了哪版评测、结果如何按业务键计费与重试,模型路由才从一组价格 if/else 变成可运营的生产系统。
Sources
- Batch API — OpenAI,访问于 2026-07-16。
- Prompt caching — OpenAI,访问于 2026-07-16。
- Cost optimization — OpenAI,访问于 2026-07-16。
- Model optimization — OpenAI,访问于 2026-07-16。