模型路由 - 让正确的模型做正确的事 / Model Routing for Matching Models to Tasks
📅 创建时间:2026-05-08 🏷️ 标签:#ModelRouting #CostOptimization #Haiku #Sonnet #模型选择 📚 前置知识:[[13-可观测性与调试]]
📋 本章目标
- 理解为什么 Agent 需要模型路由
- 掌握模型路由的决策策略
- 理解 Claude Code 的模型分级策略
- 能够为 Agent 设计合适的模型路由方案
- 掌握成本与质量的平衡方法
第1部分:为什么需要模型路由?
1.1 模型能力与成本的不对称
┌─────────────────────────────────────────────────────────────┐
│ 模型能力 vs 成本对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Claude 3.5 Sonnet(主力模型) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 能力:★★★★★ │ │
│ │ 速度:★★★★☆ │ │
│ │ 价格:$$$($3/M 输入,$15/M 输出) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Claude 3.5 Haiku(轻量模型) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 能力:★★★☆☆ │ │
│ │ 速度:★★★★★ │ │
│ │ 价格:$($0.8/M 输入,$4/M 输出) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 差距:Haiku 比 Sonnet 便宜约 20 倍 │
│ 但 Haiku 能完成 Sonnet 80% 的任务 │
│ │
└─────────────────────────────────────────────────────────────┘1.2 Agent 中不同任务的难度差异
┌─────────────────────────────────────────────────────────────┐
│ Agent 任务难度分布 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 简单任务(80%的调用)← 应该用便宜模型 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 分类判断(是/否) │ │
│ │ • 权限检查(是否危险操作) │ │
│ │ • 格式验证(JSON 是否有效) │ │
│ │ • 意图识别(用户想做什么) │ │
│ │ • 简单总结(100字以内) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 复杂任务(20%的调用)← 必须用强模型 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 复杂代码生成 │ │
│ │ • 多步推理 │ │
│ │ • 架构设计 │ │
│ │ • Bug 诊断与修复 │ │
│ │ • 长文总结 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 如果所有任务都用 Sonnet → 80% 的钱花在了不需要的地方 │
│ │
└─────────────────────────────────────────────────────────────┘第2部分:模型路由策略
2.1 Claude Code 的模型分级
┌─────────────────────────────────────────────────────────────┐
│ Claude Code 模型分级 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Haiku(最便宜) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 用途:分类、路由、权限检查 │ │
│ │ 特点:快速、低成本 │ │
│ │ 适用:yoloClassifier.ts 中的权限判断 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Sonnet(中档主力) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 用途:标准会话执行 │ │
│ │ 特点:能力与成本的平衡 │ │
│ │ 适用:大部分日常任务 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Opus(最强但最贵) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 用途:ULTRAPLAN(深度规划)、复杂推理 │ │
│ │ 特点:极致能力,但成本高 │ │
│ │ 适用:需要 30 分钟深度思考的复杂任务 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 核心原则:用最便宜的模型完成能完成的任务 │
│ │
└─────────────────────────────────────────────────────────────┘2.2 路由决策框架
python
from dataclasses import dataclass
from enum import Enum
from typing import Callable
class TaskComplexity(Enum):
LOW = "low" # 分类、路由、验证
MEDIUM = "medium" # 标准对话、代码修改
HIGH = "high" # 复杂推理、架构设计
@dataclass
class RouteDecision:
model: str
reasoning: str
estimated_cost_input: float # 每 1M tokens 的价格
estimated_cost_output: float
class ModelRouter:
"""模型路由器"""
MODEL_CATALOG = {
"haiku": {
"name": "claude-3-5-haiku",
"strengths": ["快速分类", "意图识别", "简单验证"],
"cost_input": 0.8,
"cost_output": 4.0,
"max_tokens": 4096,
},
"sonnet": {
"name": "claude-3-5-sonnet",
"strengths": ["代码生成", "日常对话", "分析总结"],
"cost_input": 3.0,
"cost_output": 15.0,
"max_tokens": 8192,
},
"opus": {
"name": "claude-3-7-sonnet",
"strengths": ["复杂推理", "深度规划", "架构设计"],
"cost_input": 15.0,
"cost_output": 75.0,
"max_tokens": 8192,
},
}
def route(self, task: str, context: dict = None) -> RouteDecision:
"""根据任务特征决定使用哪个模型"""
# 策略1:基于任务类型路由
task_type = self._classify_task(task, context)
if task_type == TaskComplexity.LOW:
return RouteDecision(
model="haiku",
reasoning=f"任务类型 '{task_type.value}' 适合轻量模型",
estimated_cost_input=self.MODEL_CATALOG["haiku"]["cost_input"],
estimated_cost_output=self.MODEL_CATALOG["haiku"]["cost_output"],
)
# 策略2:基于复杂度评估
complexity = self._estimate_complexity(task, context)
if complexity < 0.3:
return RouteDecision(
model="haiku",
reasoning="复杂度评分 0.3,使用轻量模型",
estimated_cost_input=self.MODEL_CATALOG["haiku"]["cost_input"],
estimated_cost_output=self.MODEL_CATALOG["haiku"]["cost_output"],
)
elif complexity < 0.7:
return RouteDecision(
model="sonnet",
reasoning="复杂度评分 0.7,使用主力模型",
estimated_cost_input=self.MODEL_CATALOG["sonnet"]["cost_input"],
estimated_cost_output=self.MODEL_CATALOG["sonnet"]["cost_output"],
)
else:
return RouteDecision(
model="opus",
reasoning="复杂度评分 > 0.7,使用最强模型",
estimated_cost_input=self.MODEL_CATALOG["opus"]["cost_input"],
estimated_cost_output=self.MODEL_CATALOG["opus"]["cost_output"],
)
def _classify_task(self, task: str, context: dict) -> TaskComplexity:
"""分类任务类型"""
# 基于关键词的简单分类
low_keywords = ["分类", "判断", "检查", "验证", "识别", "是否"]
high_keywords = ["设计", "架构", "优化性能", "深度分析", "复杂", "多个"]
if any(kw in task for kw in low_keywords):
return TaskComplexity.LOW
if any(kw in task for kw in high_keywords):
return TaskComplexity.HIGH
return TaskComplexity.MEDIUM
def _estimate_complexity(self, task: str, context: dict) -> float:
"""评估任务复杂度,返回 0-1 的分数"""
score = 0.0
# 长度因素
if len(task) > 500:
score += 0.2
# 代码量因素(如果有)
if context and "code_length" in context:
if context["code_length"] > 500:
score += 0.2
if context["code_length"] > 2000:
score += 0.3
# 多步骤因素
if context and "expected_steps" in context:
if context["expected_steps"] > 3:
score += 0.2
if context["expected_steps"] > 5:
score += 0.2
# 模糊度因素
ambiguous_words = ["大概", "可能", "也许", "似乎", "怎样"]
if any(w in task for w in ambiguous_words):
score += 0.1
return min(score, 1.0)第3部分:实战路由策略
3.1 工具级别的路由
python
class ToolRouter:
"""工具级别的模型路由"""
# 每个工具最适合的模型
TOOL_MODEL_MAP = {
# 简单验证类:用 Haiku
"classify_intent": "haiku",
"check_permission": "haiku",
"validate_format": "haiku",
"detect_injection": "haiku",
"detect_secrets": "haiku",
"parse_json": "haiku",
# 标准工具:用 Sonnet
"read": "sonnet",
"write": "sonnet",
"edit": "sonnet",
"grep": "sonnet",
"bash": "sonnet",
"search": "sonnet",
"explain_code": "sonnet",
"debug": "sonnet",
# 复杂工具:用 Opus
"architect_design": "opus",
"complex_refactor": "opus",
"multi_file_analysis": "opus",
"security_review": "opus",
}
def get_model_for_tool(self, tool_name: str) -> str:
"""获取工具对应的模型"""
return self.TOOL_MODEL_MAP.get(tool_name, "sonnet") # 默认 Sonnet
def execute_with_routing(self, tool_name: str, task: str, context: dict):
"""根据路由执行工具"""
model = self.get_model_for_tool(tool_name)
return self.call_model(model, task, context)3.2 成本感知的批量路由
python
class CostAwareRouter:
"""成本感知的路由器"""
def __init__(self, monthly_budget: float):
self.monthly_budget = monthly_budget
self.spent = 0.0
self.daily_quota = monthly_budget / 30
def can_afford(self, estimated_cost: float, model: str) -> bool:
"""检查是否能负担这次调用"""
daily_spent = self.get_daily_spent()
if model == "opus" and estimated_cost > 0.50:
# Opus 昂贵,严格控制
return daily_spent < self.daily_quota * 0.3 # Opus 不超过日预算的 30%
if model == "sonnet" and estimated_cost > 0.10:
return daily_spent < self.daily_quota * 0.7
return True # Haiku 便宜,不限制
def route_with_budget(self, task: str) -> RouteDecision:
"""结合预算的路由"""
base_decision = ModelRouter().route(task)
# 如果超出预算,降级模型
if not self.can_afford(
base_decision.estimated_cost_input,
base_decision.model
):
if base_decision.model == "opus":
return RouteDecision(
model="sonnet",
reasoning=f" Opus 超出预算,降级为 Sonnet",
estimated_cost_input=self.MODEL_CATALOG["sonnet"]["cost_input"],
estimated_cost_output=self.MODEL_CATALOG["sonnet"]["cost_output"],
)
elif base_decision.model == "sonnet":
return RouteDecision(
model="haiku",
reasoning=f"Sonnet 今日配额用尽,降级为 Haiku",
estimated_cost_input=self.MODEL_CATALOG["haiku"]["cost_input"],
estimated_cost_output=self.MODEL_CATALOG["haiku"]["cost_output"],
)
return base_decision第4部分:动态路由与学习
4.1 基于历史的路由优化
python
class AdaptiveRouter:
"""自适应路由器——根据历史成功率调整路由"""
def __init__(self):
self.task_results = {} # task_pattern -> success_rate
def record_result(self, task_pattern: str, model: str, success: bool):
"""记录任务执行结果"""
key = f"{model}:{task_pattern}"
if key not in self.task_results:
self.task_results[key] = {"success": 0, "total": 0}
self.task_results[key]["total"] += 1
if success:
self.task_results[key]["success"] += 1
def get_success_rate(self, task_pattern: str, model: str) -> float:
"""获取特定任务在特定模型上的成功率"""
key = f"{model}:{task_pattern}"
if key not in self.task_results:
return None
data = self.task_results[key]
return data["success"] / data["total"] if data["total"] > 0 else None
def route_adaptive(self, task: str) -> str:
"""自适应路由——选择历史上成功率最高的模型"""
task_pattern = self._extract_pattern(task)
best_model = "sonnet" # 默认
best_rate = 0.0
for model in ["haiku", "sonnet", "opus"]:
rate = self.get_success_rate(task_pattern, model)
if rate is not None and rate > best_rate:
best_rate = rate
best_model = model
return best_model第5部分:路由的工程实践
5.1 模型降级策略
┌─────────────────────────────────────────────────────────────┐
│ 模型降级策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 主模型不可用时: │
│ Sonnet 限流 → 降级到 Haiku + 重试(简单任务) │
│ Sonnet 失败 → 降级到 Opus(复杂任务) │
│ │
│ 降级触发条件: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • API 返回 429 Rate Limit │ │
│ │ • API 返回 500/503 服务器错误 │ │
│ │ • 响应超时(> 60s) │ │
│ │ • 输出为空或格式错误 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 降级重试次数: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Haiku → Sonnet:重试 1 次 │ │
│ │ Sonnet → Opus:重试 2 次(复杂任务重试更重要) │ │
│ │ Opus → Sonnet:重试 3 次(终极手段) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘5.2 A/B 测试框架
python
class ABTestRouter:
"""A/B 测试路由——用于实验不同路由策略"""
def __init__(self, experiment_name: str):
self.experiment_name = experiment_name
self.variants = {
"control": lambda task: "sonnet", # 对照组
"cheap": self._route_cheap, # 实验组:尽量用便宜的
"fast": self._route_by_speed, # 实验组:按速度选择
}
self.results = {v: {"success": 0, "cost": 0.0, "count": 0}
for v in self.variants}
def route(self, task: str, user_id: str) -> str:
"""根据用户 ID 分配实验组"""
variant = "control" if hash(user_id) % 2 == 0 else "cheap"
model = self.variants[variant](task)
self.current_variant = variant
return model
def record(self, task: str, model: str, success: bool, cost: float):
"""记录实验结果"""
variant = getattr(self, "current_variant", "control")
self.results[variant]["count"] += 1
if success:
self.results[variant]["success"] += 1
self.results[variant]["cost"] += cost
def get_experiment_report(self) -> dict:
"""输出实验报告"""
report = {}
for variant, data in self.results.items():
count = data["count"]
if count > 0:
report[variant] = {
"samples": count,
"success_rate": data["success"] / count,
"avg_cost": data["cost"] / count,
"cost_efficiency": data["success"] / data["cost"] if data["cost"] > 0 else 0,
}
return report核心总结
总结1:路由的核心思想
用 Haiku 做分类和路由
用 Sonnet 做标准任务
用 Opus 做深度推理
总原则:最便宜完成任务的模型就是最好的模型总结2:路由策略
基于规则:关键词、任务类型、复杂度评分
基于成本:月度预算、日配额、成本阈值
基于历史:成功率统计、自适应学习
基于实验:A/B 测试验证假设总结3:降级策略
Rate Limit → 降级到便宜模型重试
Server Error → 降级到更可靠模型
超时 → 降级 + 增加超时时间章节测试
测试1:路由原则
模型路由的核心原则是什么?
测试2:Haiku 适用场景
以下哪个任务最适合用 Haiku? A. 写一个完整的 REST API B. 判断用户输入是否包含敏感词 C. 分析一个复杂的并发 bug D. 设计微服务架构
测试3:成本节省
如果 80% 的简单任务用 Haiku($0.8/M),20% 的复杂任务用 Sonnet($3/M),相比全部用 Sonnet 能节省多少成本?
测试4:降级策略
什么时候应该触发模型降级?
测试5:自适应路由
自适应路由根据什么来优化模型选择?
参考答案
测试1答案
答案:用最便宜的模型完成能完成的任务。不同任务难度不同,应该匹配最适合的模型——简单任务用 Haiku,标准任务用 Sonnet,深度推理用 Opus。
测试2答案
答案:B(判断用户输入是否包含敏感词)
解析:敏感词判断是简单的分类任务,不需要复杂推理,Haiku 完全能胜任且成本最低。其他选项都需要深度推理或代码生成,需要 Sonnet 或 Opus。
测试3答案
答案:约节省 70% 的成本
解析:简单计算——假设 100 个任务,Haiku 处理 80 个,Sonnet 处理 20 个:
- 混合方案:80 × $0.8 + 20 × $3 = $64 + $60 = $124
- 全部 Sonnet:100 × $3 = $300
- 节省:(300 - 124) / 300 ≈ 59%
实际场景中简单任务 Token 量通常更少,节省比例可达 70-80%。
测试4答案
答案:以下情况应触发降级:
- API 返回 429 Rate Limit
- API 返回 500/503 错误
- 响应超时(超过设定阈值)
- 输出为空或格式错误
测试5答案
答案:基于历史成功率统计
解析:自适应路由器记录每个任务类型在每个模型上的历史成功率,然后选择历史上成功率最高的模型。随着数据积累,路由越来越精准。
相关笔记
- [[13-可观测性与调试]] - 成本监控是路由优化基础
- [[08-real-world-applications]] - 不同场景的模型选择
- [[03-rag-basics]] - RAG 中的检索可以用轻量模型
下一步学习
- [ ] 阅读 24 - OpenClaw 设计分析
- [ ] 阅读 25 - Claude Code 泄露源码分析
学习状态:🟡 开始学习