多模型智能路由与故障降级架构设计

多模型智能路由与故障降级:LLM 翻译系统的"负载均衡"架构

在 LLM 翻译系统中,不同翻译步骤对模型能力的要求截然不同——术语识别需要领域知识、直译需要忠实度、意译需要文学性。本文深入解析"欧小译"的多模型路由架构,包括领域×步骤二维路由矩阵、三级降级策略和 Protocol 抽象层设计。


一、为什么需要多模型路由?

一个直觉性的认知:没有一个模型能在所有翻译维度上都做到最优

翻译步骤 核心能力要求 最优模型特征
术语识别 领域知识广度 训练数据覆盖多领域
直译 忠实度、精确性 低温度、强指令遵循
自审 批判性思维 结构化输出能力强
意译 文学性、自然度 高温度、创意表达

再叠加领域维度:法律翻译和文学翻译对模型的需求完全相反——前者要求严谨到近乎刻板,后者要求灵动到近乎再创作。

因此我们需要一个二维路由矩阵domain × step → model


二、架构全景

                    ┌─────────────────────────────────────┐
                    │           route_llm()               │
                    │                                     │
                    │  1. 用户指定? ──→ 直接使用          │
                    │  2. 路由表匹配 ──→ 领域+步骤选择     │
                    │  3. 重试+降级 ──→ 自动切换备用模型   │
                    └──────────┬──────────────────────────┘
                               │
              ┌────────────────┼────────────────┐
              │                │                │
        ┌─────▼─────┐   ┌─────▼─────┐   ┌─────▼─────┐
        │ DeepSeek  │   │  百炼     │   │  Claude   │
        │ Provider  │   │ Provider  │   │ Provider  │
        └───────────┘   └───────────┘   └───────────┘
              │                │                │
              └────────────────┼────────────────┘
                               │
                    ┌──────────▼──────────┐
                    │   MODEL_REGISTRY    │
                    │   (全局注册表)       │
                    └─────────────────────┘

三、路由优先级:三级决策链

async def route_llm(state, step_name, prompt, temperature=0.3, 
                    system_prompt=None, max_retries=3) -> str:
    # 第一级:用户手动指定
    user_model = state.get("model")
    if user_model and user_model in MODEL_REGISTRY:
        return await call_with_fallback(user_model, ...)
    
    # 第二级:领域+步骤智能路由
    domain = state.get("domain", "general")
    router = DEFAULT_ROUTER.get(domain, DEFAULT_ROUTER["general"])
    model_name = router.get(step_name, router["default"])
    
    # 第三级:重试+降级
    for attempt in range(max_retries):
        try:
            return await provider.generate(...)
        except Exception:
            model_name = fallback_to_next_model(...)

路由表设计

DEFAULT_ROUTER = {
    "general":    {"default": "deepseek"},
    "tech":       {"default": "deepseek", "terminology": "bailian", "literal": "deepseek"},
    "legal":      {"default": "bailian",  "review": "deepseek", "polish": "claude"},
    "medical":    {"default": "bailian",  "terminology": "bailian", "review": "deepseek"},
    "literature": {"default": "deepseek", "polish": "claude", "review": "claude"},
}

设计思路解读

  • tech 领域:术语识别用百炼(阿里云生态,对中文技术术语覆盖好),直译用 DeepSeek
  • legal 领域:默认百炼(训练数据中法律文书较多),意译用 Claude(长文本表达更自然)
  • literature 领域:审校和意译都用 Claude(创意表达和文学润色是 Claude 的强项)
  • general:全部走 DeepSeek(性价比最优)

路由表采用字典 + fallback 模式:先精确匹配 step_name,匹配不到就用 default。新增领域只需添加一行配置,零代码改动。


四、Protocol 抽象层:零成本接入新模型

class LLMProvider(Protocol):
    """统一 LLM 抽象接口"""
    async def generate(self, prompt: str, temperature: float = 0.3, 
                      system_prompt: str = None) -> str:
        ...

使用 Python 的 Protocol(结构化子类型)而非 ABC 继承。这意味着:

  1. 无需显式继承:任何实现了 generate 方法的类都自动满足 LLMProvider 协议
  2. 类型检查时静态验证:mypy 等工具能在编译期发现接口不匹配
  3. 运行时零开销:Protocol 是纯类型注解,不引入 MRO 查找

每个 Provider 的实现差异主要在于:

  • API 调用方式(SDK vs HTTP)
  • 认证机制(API Key vs OAuth)
  • 模型参数映射(不同厂商的参数命名不同)

模型注册表

MODEL_REGISTRY: Dict[str, object] = {}

def init_model_registry():
    if settings.DEEPSEEK_API_KEY:
        MODEL_REGISTRY["deepseek"] = DeepSeekProvider()
    if settings.BAILIAN_API_KEY:
        MODEL_REGISTRY["bailian"] = BailianProvider()
    if settings.OPENAI_API_KEY:
        MODEL_REGISTRY["openai"] = OpenAIProvider()
    if settings.CLAUDE_API_KEY:
        MODEL_REGISTRY["claude"] = ClaudeProvider()

按需注册:只注册配置了 API Key 的模型。未配置的模型不会出现在注册表中,路由时会自动降级到可用模型。


五、三级降级策略

当模型调用失败时,降级策略按以下优先级执行:

第一级:同模型重试(最多 3 次)
  ↓ 仍失败
第二级:切换到注册表中的其他模型
  ↓ 所有模型都失败
第三级:抛出异常,由上层节点兜底(使用原文/直译结果)

降级过程中的关键行为:

for attempt in range(max_retries):
    try:
        provider = MODEL_REGISTRY.get(model_name)
        result = await provider.generate(prompt, temperature, system_prompt, ...)
        return result
    except Exception as e:
        # 切换到备用模型
        fallback_models = [m for m in MODEL_REGISTRY.keys() if m != model_name]
        if fallback_models and attempt < max_retries - 1:
            model_name = fallback_models[0]  # 取第一个可用备用模型
            # 记录降级事件
            model_fallbacks_total.labels(
                original_model=original_model,
                fallback_model=model_name,
                step_name=step_name
            ).inc()
        else:
            raise

降级顺序的隐含逻辑MODEL_REGISTRY.keys() 的迭代顺序取决于注册顺序(Python 3.7+ dict 保持插入顺序),因此注册顺序实际上定义了降级的优先级链。


六、全链路可观测性

路由层内嵌了 Prometheus 指标采集:

# 步骤耗时
step_duration_seconds.labels(step_name=step_name, model_name=model_name).observe(step_duration)

# 模型重试次数
model_retries_total.labels(model_name=model_name, step_name=step_name).inc()

# 模型降级次数
model_fallbacks_total.labels(
    original_model=original_model, 
    fallback_model=model_name, 
    step_name=step_name
).inc()

# 错误计数(按错误类型、步骤、模型三维度)
translation_errors_total.labels(
    error_type=type(e).__name__,
    step_name=step_name,
    model_name=model_name
).inc()

这些指标能让你在 Grafana 中回答这些关键问题:

  • 哪个模型的故障率最高?→ translation_errors_total by model_name
  • 哪个步骤最常触发降级?→ model_fallbacks_total by step_name
  • 重试后成功的比例是多少?→ model_retries_total / translation_errors_total
  • 不同模型在不同步骤的P99 延迟?→ step_duration_seconds histogram

七、SSE 实时推送降级事件

降级不是静默发生的——每一次降级都会通过 SSE 推送给前端:

# 重试时推送
await push_sse_event(session_id, {
    "type": "step_retry",
    "payload": {
        "step": step_name,
        "model": model_name,
        "message": f"{step_name}失败,正在重试 ({attempt + 1}/{max_retries})...",
        "status": "retrying"
    }
})

# 降级时推送
await push_sse_event(session_id, {
    "type": "model_fallback",
    "payload": {
        "original_model": original_model,
        "fallback_model": model_name,
        "message": f"降级到备用模型: {model_name}"
    }
})

用户在前端能看到"正在重试..."、"已切换到备用模型"等实时反馈,而不是一个无响应的 loading 动画。


八、Temperature 的精细化控制

不同翻译步骤使用不同的 temperature,这是路由层的一个重要细节:

步骤 Temperature 理由
术语识别 0.2 需要确定性输出,术语不应有"创意"
直译 0.3 略高于术语,允许小幅表达变化
自审 0.2 审校需要严谨,不需要创造性
意译 0.5 最高温度,需要文学性和自然表达

temperature 由路由层的调用方(各节点)显式传入,而不是在路由层内部决策。这是因为 temperature 与 Prompt 的设计强耦合——节点最了解自己需要什么样的输出。


九、分治视角:能力分治的工程实现

多模型路由本质上是一种能力分治——将翻译所需的多种认知能力,分配给不同模型独立完成。

从"全能模型"到"专家团队"

一个直觉类比:你不会让一个人同时担任法律顾问、技术专家和文学编辑。你会组建一个专家团队,每人负责一个维度。多模型路由就是这个思路的工程化:

"全能翻译"需求
    │
    ├── 领域知识 ──→ 百炼(训练数据覆盖多领域)
    ├── 精确翻译 ──→ DeepSeek(低温度、强指令遵循)
    ├── 质量审校 ──→ DeepSeek(结构化输出能力强)
    └── 文学润色 ──→ Claude(创意表达、长文本自然度)

分治粒度:步骤级 vs 请求级

路由表的设计粒度是步骤级而非请求级:

"legal": {
    "default": "bailian",      # 法律文本默认走百炼
    "review": "deepseek",      # 但审校步骤切到 DeepSeek
    "polish": "claude"          # 润色步骤切到 Claude
}

这意味着同一个翻译请求中,不同步骤可以用不同模型。这是分治粒度精细化的体现——不是按"请求"分治,而是按"能力需求"分治。

降级链:分治的容错扩展

降级机制是分治在容错维度的延伸。当最优模型失败时,系统不会崩溃,而是沿着降级链寻找次优解:

首选模型(最优匹配)→ 备用模型(次优匹配)→ 兜底模型(能力保底)

每一级降级都是分治的"局部失败不影响全局"原则的体现。


十、总结

多模型路由架构解决了 LLM 翻译系统的三个核心问题:

  1. 能力匹配:让每个翻译步骤使用最擅长的模型
  2. 高可用:三级降级确保单模型故障不影响整体服务
  3. 可扩展:Protocol 抽象 + 注册表模式,接入新模型只需实现一个类

这种"路由矩阵 + 降级链"的设计模式,不仅适用于翻译系统,对于任何需要多 LLM 协作的应用场景(如 RAG pipeline、Agent 编排、多步推理)都有借鉴价值。

posted @ 2026-06-09 23:35  黄忠  阅读(29)  评论(0)    收藏  举报