多模型路由怎么落地:把任务分流写进项目的最小实现
多模型路由的目标不是"永远选出最强模型",而是根据任务风险、成本预算、响应时延和输出形式,把请求稳定地送到合适的处理路径。
一个可落地的路由器,最小只需要解决四件事:
- 给任务分类;
- 用明确规则选择模型档位;
- 记录选择理由与执行结果;
- 在失败时安全降级或转人工。
模型能力、上下文长度、价格、接口可用性和工具支持都会变化。上线前应以当前官方文档、合同约束和实际回归测试为准,不要把历史经验写成永久规则。
一、先定义"任务",再讨论"模型"
路由失败最常见的原因,是把所有输入都当成同一种聊天请求。
更实用的做法是先给业务请求补齐结构化字段:
| 字段 | 示例 | 用途 |
|---|---|---|
task_type |
classify、extract、code_review |
决定基础路由 |
risk_level |
low、medium、high |
决定是否允许自动处理 |
latency_budget_ms |
1500 |
限制可选路径 |
max_cost_level |
economy、standard |
限制成本 |
requires_json |
true |
要求结构化输出 |
contains_sensitive_data |
true |
触发数据处理约束 |
fallback_allowed |
false |
防止关键任务静默降级 |
例如:
- 文本标签分类:低风险、格式固定,可优先走低成本路径。
- 合同条款提取:要求 JSON,但涉及敏感数据,需要先判断数据是否允许发送到目标服务。
- 生产事故分析:高风险任务不应只依赖自动回答,应该要求证据、人工复核和完整审计记录。
- 代码重构建议:可以先用较低成本路径生成候选方案,再对高风险改动使用更严格的校验。
二、不要从"模型名称"开始设计
把路由规则直接写成"任务 A 用模型 X、任务 B 用模型 Y",短期看简单,长期会让配置难以维护。
更稳妥的抽象是"能力档位":
fast:低延迟、低成本,适合分类、改写、固定格式提取。balanced:适合一般问答、摘要、常规代码辅助。reasoning:适合复杂推理、跨文件分析、需要多轮校验的任务。human_review:高风险、低置信度或涉及不可逆操作时转人工。
业务代码只依赖档位;具体档位映射到哪个可用模型,由部署配置管理。这样在供应商接口、可用模型或计费变化时,不必重写业务逻辑。
实际接入时,模型入口也应从业务代码中抽离出来。若要比较常用工具,可在 moli 查看当前支持工具与计费说明,并把选中的入口写入环境配置;moli 是独立第三方服务,路由规则仍应基于你的测试数据维护。
三、最小可运行实现:先把决策做成纯函数
下面示例不调用任何外部接口,只实现可测试的路由决策。保存为 router.py 后可直接运行。
python
from __future__ import annotations
from dataclasses import asdict, dataclass
from typing import Literal
import json
TaskType = Literal["classify", "extract", "chat", "code_review", "incident_analysis"]
RiskLevel = Literal["low", "medium", "high"]
Route = Literal["fast", "balanced", "reasoning", "human_review"]
@dataclass(frozen=True)
class Request:
task_type: TaskType
risk_level: RiskLevel
latency_budget_ms: int
max_cost_level: Literal["economy", "standard"]
requires_json: bool
contains_sensitive_data: bool
fallback_allowed: bool
@dataclass(frozen=True)
class Decision:
route: Route
reason: str
fallback_allowed: bool
def choose_route(request: Request) -> Decision:
if request.contains_sensitive_data:
return Decision(
route="human_review",
reason="请求包含敏感数据,需先按组织的数据处理规则审核",
fallback_allowed=False,
)
if request.risk_level == "high":
return Decision(
route="human_review",
reason="高风险任务不允许仅依赖自动结果",
fallback_allowed=False,
)
if request.task_type in {"classify", "extract"}:
if request.latency_budget_ms < 800:
return Decision(
route="fast",
reason="任务格式固定且延迟预算较紧",
fallback_allowed=request.fallback_allowed,
)
return Decision(
route="balanced",
reason="任务可使用通用档位以提高输出稳定性",
fallback_allowed=request.fallback_allowed,
)
if request.task_type in {"code_review", "incident_analysis"}:
return Decision(
route="reasoning",
reason="任务需要较多上下文分析和结果校验",
fallback_allowed=False,
)
if request.max_cost_level == "economy":
return Decision(
route="fast",
reason="一般对话任务受成本预算约束",
fallback_allowed=request.fallback_allowed,
)
return Decision(
route="balanced",
reason="一般对话任务使用通用档位",
fallback_allowed=request.fallback_allowed,
)
if __name__ == "__main__":
sample = Request(
task_type="extract",
risk_level="low",
latency_budget_ms=600,
max_cost_level="economy",
requires_json=True,
contains_sensitive_data=False,
fallback_allowed=True,
)
decision = choose_route(sample)
print(json.dumps(asdict(decision), ensure_ascii=False, indent=2))
运行:
bash
python router.py
预期输出包含路由档位、选择理由和是否允许降级。这里最重要的不是规则是否复杂,而是决策可以被独立测试、审查和修改。
四、把"路由"和"调用"分开
项目中建议拆成三层:
Router:只负责输入校验和路径选择。ModelAdapter:只负责调用具体模型接口、超时控制和响应解析。Policy:只负责数据合规、预算、重试和人工复核规则。
这样做能避免两个问题:
- 为了替换模型而修改业务判断;
- 为了调整预算而改动提示词或接口调用代码。
调用层可以维护一个档位到实现的映射:
text
fast -> 低延迟模型适配器
balanced -> 通用模型适配器
reasoning -> 复杂任务模型适配器
human_review -> 工单、审批流或人工队列
Router 不需要知道具体模型名称,也不应该持有访问密钥。
五、结构化输出必须有"验证器"
如果下游要消费 JSON、SQL、配置文件或代码补丁,不能只看模型是否"看起来回答正确"。
至少增加三道检查:
- 语法检查:JSON 能否解析,代码能否编译,SQL 是否可被解析器接受。
- 模式检查:字段是否齐全、类型是否正确、枚举值是否越界。
- 业务检查:例如金额不能为负、日期范围合理、删除操作必须带明确条件。
对于 JSON 任务,可以要求模型只输出 JSON,但仍要在程序侧验证。提示词约束不是安全边界,验证器才是。
六、失败时如何降级
降级不是"调用失败就换一个模型再试一次"。不同失败类型需要不同处理:
| 失败类型 | 推荐处理 |
|---|---|
| 超时 | 在预算允许时切换到更快路径,或返回异步任务状态 |
| 限流或服务不可用 | 按服务条款和系统策略退避重试;超过阈值进入备用路径 |
| JSON 解析失败 | 进行一次受限修复请求,仍失败则标记为失败,不要猜测字段 |
| 内容置信度不足 | 转更高能力档位或人工复核 |
| 高风险任务失败 | 不自动降级到不受控路径,应保留证据并转人工 |
| 敏感数据请求 | 先阻断并走合规流程,不应为了完成任务而换渠道发送 |
尤其要避免"静默降级":用户以为系统完成了高质量分析,实际却拿到了低能力路径的结果。对外响应中至少应保留任务状态和可追踪的请求标识。
七、路由规则如何验证
上线前不要只测"能不能调用成功",而要准备覆盖规则边界的测试表。
建议至少包含:
- 低风险分类任务是否进入
fast; - 高风险任务是否总是进入
human_review; - 敏感数据标记是否能阻断自动调用;
- 延迟预算临界值是否按预期分流;
- 不允许降级的任务在主路径失败后是否确实停止;
- 结构化输出失败时是否被验证器拦截;
- 每条决策日志是否包含规则版本、路由理由和请求标识。
还应做一次"反向测试":故意传入缺失字段、未知任务类型、负数预算和非法枚举值,确认系统拒绝请求,而不是默认走成本最低的模型。
八、日志与观测指标
路由器的日志至少应记录:
- 请求标识;
- 任务类型和风险等级;
- 选择的能力档位;
- 规则版本;
- 耗时、重试次数和最终状态;
- 输出验证是否通过。
不要把原始敏感输入、访问密钥、完整提示词或用户隐私数据直接写入日志。需要排障时,可记录经过脱敏的摘要、哈希或字段统计信息。
观察一段时间后,再根据真实业务数据调整规则。优先看失败率、格式校验通过率、人工退回率、端到端时延和单位任务成本,而不是只看单次回答是否流畅。
九、适合从哪里开始
如果项目还没有多模型需求,也可以先落地一个"单模型 + 路由接口"的版本:
- 定义统一请求对象。
- 用
choose_route()输出能力档位和理由。 - 暂时让所有档位指向同一个适配器。
- 补齐日志、验证器和失败处理。
- 之后再按实际瓶颈拆分档位。
这样未来增加模型、替换接口或引入人工审核时,改动会集中在配置和适配器层,而不是散落到每个业务接口中。