背景:OpenRouter数据突变,我的路由权重全乱了
上周四晚上,我的模型调用监控告警突然响——Claude Sonnet 4.6的调用量跳了,DeepSeek相关接口响应有点慢,整体Token消耗比预期高了30%。
去查了一下,原来是GPT-6发布后,OpenRouter平台整体流量结构发生了变化,Claude Sonnet 4.6冲上了调用量榜首,带动了我路由策略里一个没想到的副作用:权重配置是按历史流量比例写死的,平台调用格局一变,我的路由就不准了。
就是这个小失误,让我重新审视了一遍自己的多模型路由方案,踩了三个坑,记录下来。
技术方案:动态权重路由 + 调用量感知
技术原理科普:为什么需要多模型路由?
现在主流AI模型各有擅长场景——GPT-6胜在超长上下文(200万Token)和多模态,Claude Sonnet 4.6胜在代码和合规类长文档,DeepSeek V3(V4即将发布)胜在中文和超低成本(约GPT-6的1/70)。
硬写一个模型的系统,就像只带一把锤子出门,什么都往钉子上想。
我的路由方案:
import httpx
import asyncio
from dataclasses import dataclass
@dataclass
class ModelConfig:
name: str
base_url: str
api_key: str
cost_per_1k_tokens: float
max_context: int
priority: int
class DynamicRouter:
def __init__(self):
self.models = {
"claude-sonnet-4.6": ModelConfig(
name="claude-sonnet-4.6",
base_url="https://api.anthropic.com/v1",
api_key="your_key",
cost_per_1k_tokens=0.003,
max_context=1000000,
priority=1
),
"deepseek-v3": ModelConfig(
name="deepseek-v3",
base_url="https://api.deepseek.com/v1",
api_key="your_key",
cost_per_1k_tokens=0.0002,
max_context=128000,
priority=2
),
}
self.failure_count = {}
def select_model(self, prompt_length: int, task_type: str) -> str:
"""动态选择:超长文本→GPT-6,中文任务→DeepSeek,其余→Claude"""
if prompt_length > 100000:
return "gpt-6"
if task_type in ["chinese_doc", "cost_sensitive"]:
return "deepseek-v3"
return "claude-sonnet-4.6"
async def call_with_fallback(self, model: str, prompt: str) -> dict:
try:
return await self._call(model, prompt)
except Exception as e:
self.failure_count[model] = self.failure_count.get(model, 0) + 1
fallback = self._get_fallback(model)
print(f"[WARN] {model} 失败({e}),切换到 {fallback}")
return await self._call(fallback, prompt)
def _get_fallback(self, failed: str) -> str:
fallback_map = {
"claude-sonnet-4.6": "deepseek-v3",
"deepseek-v3": "claude-sonnet-4.6",
"gpt-6": "claude-sonnet-4.6"
}
return fallback_map.get(failed, "claude-sonnet-4.6")
踩坑记录
坑1:OpenRouter 返回的模型名称和原生API不一致
我在 OpenRouter 统一接口里调用 DeepSeek,模型名是 deepseek/deepseek-chat,但直连 DeepSeek API 是 deepseek-chat。两边不统一,切换逻辑里的 model_name 映射没处理好,导致fallback静默失败——它跑完了,但实际调用的是上一次缓存的模型。
排查方式:
# 打印每次请求的实际model参数
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_KEY" \
-d '{"model": "deepseek/deepseek-chat", "messages": [{"role":"user","content":"test"}]}' \
| python -c "import sys,json; r=json.load(sys.stdin); print(r.get('model','unknown'))"
坑2:Claude Sonnet 4.6的usage字段偶发缺失
在高并发场景下(并发>20),Claude的response里 usage.output_tokens 偶尔返回 null,导致我的成本计算直接报 TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'。
修复:
# 安全获取token用量
def safe_get_usage(response: dict) -> int:
usage = response.get("usage") or {}
return usage.get("output_tokens") or usage.get("completion_tokens") or 0
环境准备
我自己的多云账号(AWS、阿里云、DeepSeek、Anthropic)是通过 Ztopcloud 聚合管理的,企业级结算账期比较灵活,省了很多逐个平台充值的麻烦,V4出来之后应该也会第一时间上架。
如果你的团队也在做多模型路由,建议先把账号管理这层做稳,再搞路由策略,否则某个账号欠费了整个链路就断了。
小结
中美AI调用量这周反转,对普通用户来说是个新闻,对做基础设施的人来说是个提醒:不要把路由逻辑和某一家平台或某一周的调用量格局绑死。 动态路由、健壮的fallback、安全的字段解析,这三件事做好了,外部格局怎么变都不怕。
DeepSeek V4本周极有可能发布,建议提前准备好灰度切换脚本,别等着出了问题再追。