1. 模型路由的重新思考:从一次旗舰更替说起
1.1 一个信号:旗舰模型迭代速度远超预期
做 AI 应用开发的人最近应该都有一种感受,就是模型迭代的速度已经快到让人麻木。前脚刚把某个模型集成进生产环境,调完 prompt、跑完评测、写好 fallback 逻辑,后脚就发现榜单上冒出来一个新版本,分数高了一截,价格还便宜了。这种节奏下,如果还把模型当成一个"选定就不动"的基础设施,迟早会出问题。
标题里提到的"7 天换掉自家旗舰",本质上反映的是一个行业现实:头部厂商的旗舰模型生命周期正在急剧缩短。以前一个大版本能撑一年半载,现在可能几周就有一次重要更新。而"智能只差 1 分"这个说法更有意思,它暗示的是——当两个模型的能力差距小到评测误差范围内时,继续死守某一个模型就不再是技术决策,而是惯性决策。
这就引出了本文要聊的核心:模型路由(Model Routing)。简单说,就是不再把请求固定发给某一个模型,而是根据任务类型、成本预算、延迟要求、输出质量等维度,动态选择最合适的模型来处理。这件事在 2024 年还属于"进阶玩法",到 2025 年已经逐渐变成 Agent 类应用的标配架构。
1.2 谁需要关心模型路由
如果你只是偶尔用 API 跑个 demo,那路由对你意义不大,手动切一下就行。但如果你在做以下几类事情,路由就是绕不过去的:
- Agent 类产品:一个 Agent 在一次任务里可能要调用几十次模型,有的步骤需要强推理,有的步骤只是格式化输出,全部用旗舰模型是纯烧钱。
- 成本敏感型应用:面向 C 端的大规模调用,每百万 token 的成本差异会被放大到非常可观的程度。
- 对延迟敏感的场景:实时对话、代码补全这类场景,用户等不了三秒,这时候小模型的速度优势就体现出来了。
- 需要高可用的生产系统:单一模型服务出问题时,路由层可以自动切换到备用模型,保证服务不中断。
我自己的项目里,路由层上线之后,月度 API 成本下降了大约 60%,而端到端的任务成功率几乎没有变化。这个数字不是靠"用便宜模型"换来的,而是靠"把合适的任务分给合适的模型"实现的。
1.3 本文会讲什么
接下来我会从架构设计、核心实现、参数调优、问题排查几个角度,把模型路由这件事拆开讲清楚。涉及到的技术点包括:路由策略的设计、评测驱动的模型选择、Agents API 的集成方式、Codex Cloud 这类代码场景的特殊处理,以及实际落地时踩过的坑。内容会尽量贴近实操,能直接抄的部分我会给出可运行的代码和配置。
2. 路由架构的整体设计与选型考量
2.1 三种主流路由架构的取舍
模型路由不是只有一种做法,根据业务复杂度不同,大致可以分成三个层次:
| 架构层次 | 核心思路 | 适用场景 | 实现复杂度 |
|---|---|---|---|
| 静态路由 | 按任务类型硬编码映射到固定模型 | 任务类型少、变化不频繁 | 低 |
| 规则路由 | 基于规则引擎动态判断,支持降级和重试 | 中等规模、有成本优化需求 | 中 |
| 智能路由 | 用分类模型或打分模型预测最优模型 | 大规模、多模型、动态变化 | 高 |
我建议大多数团队从规则路由起步。原因很直接:静态路由太死,业务一变就要改代码;智能路由听起来很美,但你需要先有足够的数据积累才能训练出靠谱的分类器,否则就是过早优化。规则路由是一个很好的中间态,既能覆盖大部分场景,又不会引入太多复杂度。
2.2 为什么选择"评测驱动"而不是"榜单驱动"
很多人选模型是看榜单的,哪个分数高用哪个。但榜单分数和你的实际业务表现之间,往往有巨大的鸿沟。榜单考的是通用能力,你的业务可能是"从合同里抽取特定条款"或者"把用户口语转成 SQL",这两件事对模型的要求完全不同。
我的做法是建立一套业务专属评测集。具体来说:
- 从生产日志里采样 200-500 条真实请求,覆盖主要任务类型。
- 为每条请求标注"期望输出"或"可接受的输出范围"。
- 用候选模型分别跑一遍,记录准确率、延迟、成本三个指标。
- 按任务类型分别统计,找出每个任务类型下的最优模型。
这套评测集不需要很大,但一定要真实。我见过太多团队用公开数据集选模型,上线后发现效果完全对不上,就是因为评测集和业务分布不一致。
2.3 路由层应该放在哪里
路由层的位置选择会影响整个系统的耦合度。常见的有三种:
- 客户端路由:在调用方直接判断,简单但逻辑分散,多端接入时难以统一。
- 网关层路由:在 API 网关做统一分发,集中管理,但网关可能成为瓶颈。
- 独立路由服务:单独部署一个轻量服务,专门负责模型选择,灵活但多一跳网络开销。
我倾向于网关层路由,因为大多数团队已经有网关基础设施,加一层路由逻辑的边际成本最低。如果业务规模很大,再考虑拆成独立服务。客户端路由只适合原型阶段,生产环境不建议。
提示:路由层一定要做成无状态的,所有决策依据(配置、评测结果、模型状态)都从外部存储读取。这样扩容和灰度都会简单很多。
3. 核心实现:从任务分类到模型选择
3.1 任务分类器的设计
路由的第一步是搞清楚"这个请求是什么类型的任务"。任务分类不需要很复杂,一个基于关键词和轻量模型的混合分类器就够了。我的实现是这样的:
import re from enum import Enum class TaskType(Enum): CODE_GEN = "code_generation" REASONING = "complex_reasoning" EXTRACTION = "structured_extraction" CHAT = "casual_chat" SUMMARIZE = "summarization" # 关键词规则,覆盖高频场景 KEYWORD_RULES = { TaskType.CODE_GEN: [r"```", r"def ", r"function", r"class ", r"import "], TaskType.EXTRACTION: [r"提取", r"抽取", r"返回JSON", r"结构化"], TaskType.SUMMARIZE: [r"总结", r"概括", r"摘要", r"summarize"], } def classify_by_rules(text: str) -> TaskType: for task_type, patterns in KEYWORD_RULES.items(): for pattern in patterns: if re.search(pattern, text, re.IGNORECASE): return task_type return TaskType.CHAT # 默认兜底规则分类的准确率大概在 70% 左右,剩下的 30% 用一个小模型兜底。这里的关键是:分类器本身不能用旗舰模型,否则路由的成本优势就被吃掉了。用一个小模型或者甚至一个微调过的 embedding + 分类头就够了。
3.2 模型能力矩阵的维护
路由的第二步是知道"每个模型擅长什么"。这需要一个模型能力矩阵,我一般用 YAML 维护,方便热更新:
models: flagship-a: provider: openai strengths: [reasoning, code_generation] cost_per_1k_input: 0.015 cost_per_1k_output: 0.06 avg_latency_ms: 2400 max_context: 128000 status: active fast-b: provider: openai strengths: [chat, summarization, extraction] cost_per_1k_input: 0.0005 cost_per_1k_output: 0.0015 avg_latency_ms: 600 max_context: 16000 status: active code-specialist: provider: openai strengths: [code_generation, code_review] cost_per_1k_input: 0.003 cost_per_1k_output: 0.012 avg_latency_ms: 1200 max_context: 64000 status: active这个矩阵里的数据不是拍脑袋写的,而是从评测结果和线上监控里持续更新的。我一般每周跑一次评测,把最新的准确率和延迟数据回写到矩阵里。
3.3 路由决策的核心逻辑
有了任务类型和模型矩阵,路由决策就是一个打分问题。我的打分公式大致是这样的:
score = w1 * quality_score + w2 * (1 / normalized_cost) + w3 * (1 / normalized_latency)三个权重的默认值是w1=0.6, w2=0.25, w3=0.15,但会根据任务类型动态调整。比如代码生成任务,质量权重会提到 0.8;而闲聊场景,延迟权重会提到 0.4。
def route(task_type: TaskType, context_length: int, budget_mode: str = "balanced"): candidates = [] for model_name, info in MODEL_MATRIX.items(): if info["status"] != "active": continue if context_length > info["max_context"]: continue if task_type.value not in info["strengths"]: continue candidates.append((model_name, info)) if not candidates: return DEFAULT_FALLBACK_MODEL weights = get_weights(task_type, budget_mode) scored = [] for name, info in candidates: quality = QUALITY_SCORES.get((name, task_type.value), 0.7) cost = info["cost_per_1k_input"] + info["cost_per_1k_output"] latency = info["avg_latency_ms"] score = (weights["quality"] * quality - weights["cost"] * cost - weights["latency"] * latency / 10000) scored.append((name, score)) scored.sort(key=lambda x: x[1], reverse=True) return scored[0][0]这段代码看起来简单,但里面有几个关键点值得展开说。
3.4 参数计算:成本权重怎么定
成本权重的设定不能拍脑袋,要结合你的实际预算。假设你的月度预算是 1000 美元,日均请求 10 万次,平均每次请求 2000 token(输入+输出),那么单次请求的成本上限是:
1000 / (30 * 100000) = 0.00033 美元/次换算成每千 token 的成本,大约是 0.165 美元/千 token。这个数字决定了你能用多贵的模型。如果旗舰模型的价格是 0.075 美元/千 token,那理论上全用旗舰也扛得住;但如果价格是 0.3 美元/千 token,就必须做路由分流。
我一般会算一个"成本红线",超过红线的模型只在关键任务上用。这个红线不是固定的,会随着业务增长和预算调整动态变化。
3.5 降级与重试策略
路由层必须处理模型不可用的情况。我的策略是三级降级:
- 同能力降级:首选模型超时或报错,切换到同能力等级的其他模型。
- 跨能力降级:同能力模型都不可用,降级到能力稍弱但更稳定的模型。
- 兜底降级:所有模型都不可用,返回缓存结果或友好错误提示。
重试次数我一般设为 2 次,且第二次重试会换模型。因为同一个模型连续失败,大概率是服务端问题,重试同一个模型没意义。
注意:降级一定要记录日志,包括原始模型、降级模型、降级原因。这些数据是后续优化路由策略的重要依据。
4. 实操过程:从零搭建一个可用的路由层
4.1 环境准备与依赖安装
假设你用 Python 做后端,核心依赖其实很少:
pip install fastapi uvicorn httpx pyyaml pydantic如果你要用 OpenAI 的官方 SDK,还需要:
pip install openai这里有个小坑:OpenAI 的 SDK 在不同平台上的可选依赖不一样。比如在 Windows 上,有时候会遇到missing optional dependency @openai/codex-win32-x64这类提示,这通常是因为某些平台特定的包没有正确安装。解决办法是重新安装对应的包:
npm install -g @openai/codex或者如果你用的是 Node 环境,直接npm install重新拉一遍依赖。这类问题不影响核心功能,但会让人困惑,提前知道能省不少时间。
4.2 路由服务的骨架搭建
我用 FastAPI 搭一个最小可用的路由服务,核心就三个文件:配置、路由逻辑、API 入口。
# config.py import yaml from pathlib import Path def load_model_matrix(path: str = "models.yaml"): with open(path, "r", encoding="utf-8") as f: return yaml.safe_load(f)["models"] MODEL_MATRIX = load_model_matrix()# router.py from config import MODEL_MATRIX from classifier import classify_by_rules, TaskType QUALITY_SCORES = { ("flagship-a", "complex_reasoning"): 0.95, ("flagship-a", "code_generation"): 0.93, ("fast-b", "casual_chat"): 0.88, ("fast-b", "summarization"): 0.85, ("code-specialist", "code_generation"): 0.91, } def get_weights(task_type, budget_mode): base = {"quality": 0.6, "cost": 0.25, "latency": 0.15} if task_type == TaskType.CODE_GEN: base["quality"] = 0.8 base["cost"] = 0.1 base["latency"] = 0.1 if budget_mode == "economy": base["cost"] = 0.5 base["quality"] = 0.4 return base# main.py from fastapi import FastAPI from pydantic import BaseModel from router import route from classifier import classify_by_rules app = FastAPI() class RouteRequest(BaseModel): prompt: str context_length: int = 0 budget_mode: str = "balanced" @app.post("/route") def route_request(req: RouteRequest): task_type = classify_by_rules(req.prompt) model = route(task_type, req.context_length, req.budget_mode) return {"task_type": task_type.value, "model": model}这个骨架跑起来之后,你就可以在调用方先请求/route拿到模型名,再去调用对应的模型 API。当然,更优雅的做法是把路由和调用合并成一个服务,调用方只发一次请求。
4.3 与 Agents API 的集成
Agent 类应用的路由会更复杂一些,因为一个 Agent 任务里包含多个步骤,每个步骤的任务类型可能不同。我的做法是在 Agent 的每一步都做一次路由决策,而不是整个任务用一个模型。
具体来说,Agent 的 prompt 里会包含当前步骤的描述,路由层根据这个描述判断任务类型。比如:
- 规划步骤 → 复杂推理 → 旗舰模型
- 工具调用参数生成 → 结构化抽取 → 快速模型
- 结果总结 → 摘要 → 快速模型
- 代码生成 → 代码生成 → 代码专用模型
这样下来,一个 Agent 任务里可能用到 2-3 个不同的模型,整体成本和延迟都能优化。实测下来,一个原本全用旗舰模型的 Agent 任务,成本能降到原来的 35% 左右。
4.4 Codex Cloud 场景的特殊处理
代码场景对路由有特殊要求。代码生成和代码审查对模型能力要求高,但代码补全这种场景又要求低延迟。我的处理方式是:
- 代码生成(从零写一个函数):用代码专用模型,质量优先。
- 代码补全(补全一行):用快速模型,延迟优先。
- 代码审查(检查 bug):用旗舰模型,质量优先。
- 代码解释(解释一段代码):用快速模型,成本优先。
Codex Cloud 这类服务的好处是它把代码场景的模型选择封装好了,你不需要自己维护代码专用模型。但如果你要自己路由,就需要在任务分类里把代码场景细分得更细。
4.5 灰度上线与效果验证
路由层上线不能一刀切,我一般分三步走:
- 影子模式:路由层只记录决策,不实际改变调用。对比"路由决策的模型"和"实际使用的模型"的效果差异。
- 小流量灰度:5% 的流量走路由,观察错误率、延迟、成本三个指标。
- 逐步放量:每周增加 10-20% 流量,直到全量。
影子模式这一步很多人会跳过,但它其实最重要。因为路由决策的质量只有通过对比才能验证,直接灰度的话,出了问题你都不知道是路由的问题还是模型的问题。
5. 常见问题与排查技巧实录
5.1 路由决策"看起来对但效果差"
这是最常见的问题。路由决策逻辑没问题,但实际效果就是不如全用旗舰模型。原因通常有三个:
- 评测集不具代表性:评测集里的任务和线上真实任务分布不一致。
- 质量分数过时:模型更新了,但质量分数没更新。
- 任务分类错误:分类器把复杂任务误判成了简单任务。
排查方法:抽样 100 条路由决策,人工检查分类是否正确、模型选择是否合理。我一般会发现 10-15% 的决策是可以优化的,调整之后效果会明显改善。
5.2 成本没有明显下降
如果路由上线后成本没降,先检查这几个点:
| 可能原因 | 检查方法 | 解决方式 |
|---|---|---|
| 流量集中在旗舰模型 | 统计各模型调用占比 | 调整权重,提高成本权重 |
| 分类器偏向复杂任务 | 统计任务类型分布 | 优化分类规则 |
| 重试次数过多 | 查看重试日志 | 减少重试或换更稳定的模型 |
| 缓存命中率低 | 统计缓存命中率 | 增加缓存层 |
我遇到过一次成本没降的情况,最后发现是分类器把所有带"分析"两个字的请求都判成了复杂推理,导致大量请求走了旗舰模型。改掉这个规则之后,成本立刻降下来了。
5.3 延迟反而变高了
路由层本身会引入额外延迟,如果路由逻辑复杂或者路由服务响应慢,整体延迟可能不降反升。解决办法:
- 路由决策做成内存计算,不要查数据库。
- 模型矩阵和权重配置缓存在本地,定期刷新。
- 路由服务单独部署,避免和其他服务抢资源。
我的路由服务 P99 延迟控制在 5ms 以内,基本可以忽略不计。
5.4 模型更新导致路由失效
模型更新是常态,每次更新都可能影响路由效果。我的做法是:
- 订阅模型厂商的更新公告。
- 每次更新后跑一遍评测集。
- 如果质量分数变化超过 5%,更新模型矩阵。
- 如果变化超过 15%,触发路由策略重新评估。
这套流程听起来麻烦,但比"模型悄悄更新了,线上效果突然变差"要好得多。
5.5 常见问题速查表
| 现象 | 可能原因 | 快速排查 |
|---|---|---|
| 路由后效果下降 | 分类错误/分数过时 | 抽样检查决策 |
| 成本没降 | 流量集中/重试多 | 看调用占比 |
| 延迟升高 | 路由服务慢 | 看路由 P99 |
| 某模型频繁失败 | 服务不稳定 | 看错误日志 |
| 配置不生效 | 缓存未刷新 | 检查刷新机制 |
提示:路由层的日志一定要详细,至少记录:请求 ID、任务类型、候选模型、最终选择、决策耗时、实际调用结果。这些数据是后续优化的基础。
6. 一些实操心得与后续扩展方向
路由这件事,我踩过的坑比想象中多。最开始我以为只要把任务分类做好就行,后来发现模型能力矩阵的维护才是长期成本最高的部分。因为模型更新太频繁,你不可能每次都手动更新,必须有一套半自动的流程。
我现在的做法是:每周跑一次自动化评测,评测结果自动写入模型矩阵,但权重调整还是人工确认。这样既保证了数据的时效性,又避免了自动调整带来的风险。
另一个心得是:不要追求路由的完美。路由的本质是在质量、成本、延迟之间做权衡,没有最优解,只有最适合当前业务的解。我见过一些团队为了追求"最优路由"投入了大量工程资源,最后收益还不如简单规则路由。路由的价值在于持续优化,而不是一次做到位。
后续如果要扩展,我会考虑两个方向:一是引入在线学习,根据实际调用结果动态调整权重;二是做多模型投票,对关键任务用多个模型同时生成,取最优结果。这两个方向都有价值,但都需要先有稳定的基础路由层。
最后分享一个小技巧:路由层的配置一定要支持热更新,不要重启服务。我一般用文件监听或者配置中心来实现,改完配置几秒内生效,调试起来非常方便。这个细节看起来小,但实际用起来能省很多时间。