Chapter 07

Router:负载均衡与容灾

线上的 LLM 架构永远是"多部署 + 多 provider + 备份"。Router 是 LiteLLM 的 HA 核心——把一堆 model 当一个 pool 用,做路由、fallback、限流、重试。

为什么需要 Router

随便一个线上 AI 产品的真实状况:

这些需求如果在业务代码里一个个 if/else 手写,会变成一坨意大利面。LiteLLM Router 把它们统统抽成 "model_list + 路由策略 + fallback 链",业务层只调 router.completion(model="chat", ...),其他全在配置里。

第一个 Router

from litellm import Router

model_list = [
    {
        "model_name": "chat",    # 别名, 业务代码用这个
        "litellm_params": {
            "model": "gpt-4o-mini",
            "api_key": os.getenv("OPENAI_API_KEY"),
        },
        "tpm": 1_000_000,        # 可选: 每分钟 token 限额
        "rpm": 500,              # 可选: 每分钟请求限额
    },
    {
        "model_name": "chat",     # 同一个别名, 多部署
        "litellm_params": {
            "model": "azure/my-gpt4o",
            "api_key": os.getenv("AZURE_API_KEY"),
            "api_base": os.getenv("AZURE_API_BASE"),
            "api_version": "2024-10-21",
        },
        "tpm": 2_000_000,
        "rpm": 1000,
    },
]

router = Router(
    model_list=model_list,
    routing_strategy="simple-shuffle",   # 下面详讲
    num_retries=3,
    timeout=30,
)

resp = router.completion(
    model="chat",                     # 别名, Router 自动选一个 deployment
    messages=[{"role":"user","content":"hi"}],
)
print(resp.choices[0].message.content)

几个关键概念:

model_name
业务层看到的"虚拟模型"。可以和 litellm_params.model 无关。一个 model_name 下可以挂多个 deployment(同家不同 key、同家不同 region、跨家)。
litellm_params
传给底层 completion() 的参数,包括 model / api_key / api_base / api_version / timeout 等。
tpm / rpm
本部署的容量声明。Router 在 usage-based 策略下用它分配流量,也用来判断"快撞限额了,切到下一个"。
routing_strategy
路由策略。下面细讲五种。

五种路由策略

① simple-shuffle(默认)

每次随机挑一个。如果声明了 rpm/tpm,按权重随机——容量大的 deployment 被选中的概率更高。

router = Router(model_list=...,
                routing_strategy="simple-shuffle")

适合:大多数场景,简单可靠。你有三个 Azure deployment 容量是 1k/2k/3k RPM,simple-shuffle 就按 1:2:3 比例分流。

② least-busy

每次选"当前 in-flight 请求数最少"的那个。

router = Router(model_list=...,
                routing_strategy="least-busy")

适合:请求耗时波动大的场景。比如有的请求生成 100 token,有的生成 4000,least-busy 会避免把长请求堆到同一个 deployment。

③ usage-based-routing-v2

最聪明也最工程化。Router 在 Redis 里记录每个 deployment 当前窗口的 TPM/RPM 占用,优先选"离限额最远"的那个。

router = Router(
    model_list=...,
    routing_strategy="usage-based-routing-v2",
    redis_host="redis.internal",
    redis_port=6379,
    redis_password="...",
)

适合:多进程/多实例部署。一台 pod 的 Python 进程不知道另一台 pod 打了多少请求——没有 Redis 就各算各的,容易整体超限。usage-based-v2 让所有实例共享一本"总账"。

④ latency-based-routing

把过去 N 次调用的平均延迟当成选择权重。延迟低的被选的概率大。

router = Router(model_list=...,
                routing_strategy="latency-based-routing")

适合:面向终端用户的 chat UI。你希望热的 deployment 继续热,冷的冷下来,整体 p50 延迟更低。

⑤ cost-based-routing

选单位 token 最便宜的那个。你挂了 GPT-4o、Claude Sonnet、DeepSeek,非必要时就走 DeepSeek。

router = Router(model_list=...,
                routing_strategy="cost-based-routing")

适合:极度省钱的批处理场景。注意这和"质量"是矛盾的——生产通常结合 tag 路由按任务类型区分。

fallback:挂了自动换一家

路由策略选出了 A,但 A 挂了怎么办?——fallback 链。

router = Router(
    model_list=[
        {"model_name":"chat-primary",
         "litellm_params":{"model":"gpt-4o","api_key":...}},
        {"model_name":"chat-backup",
         "litellm_params":{"model":"anthropic/claude-sonnet-4-5",
                           "api_key":...}},
        {"model_name":"chat-last-resort",
         "litellm_params":{"model":"deepseek/deepseek-chat","api_key":...}},
    ],
    fallbacks=[
        {"chat-primary": ["chat-backup", "chat-last-resort"]},
    ],
    num_retries=2,
)

# 业务代码完全不变
resp = router.completion(model="chat-primary", messages=msgs)

发生错误时 Router 的决策顺序:

  1. primary 失败 → 在同一个 model_name 的其他 deployment 里重试(num_retries 次)。
  2. 同名 deployment 全失败 → 走 fallback 链第一个(chat-backup)。
  3. backup 失败 → chat-last-resort。
  4. 全部失败 → 抛异常给业务层。

按错误类型做不同 fallback

router = Router(
    model_list=...,
    context_window_fallbacks=[
        {"gpt-4o": ["gemini/gemini-1.5-pro"]}   # 超窗口用长上下文模型
    ],
    content_policy_fallbacks=[
        {"gpt-4o": ["anthropic/claude-sonnet-4-5"]}  # 被审核拦了换一家
    ],
    fallbacks=[
        {"gpt-4o": ["azure/gpt-4o"]}  # 其他错误用备用 deployment
    ],
)

这种分类 fallback 非常实用——被内容审核拦和超上下文,本来就是不同问题,应该走不同路径。

cooldown:挂了的 deployment 先别再打

默认情况下,某个 deployment 连续 N 次失败后,Router 会把它放进 cooldown(冷却期),接下来 X 秒不再选它。

router = Router(
    model_list=...,
    allowed_fails=3,              # 一分钟内失败 3 次触发 cooldown
    cooldown_time=60,             # cooldown 60 秒
    disable_cooldowns=False,
)

这就是"熔断器"模式。一个 Azure deployment 挂了,Router 不会继续硬怼它让用户一直等——直接跳过,用户无感。

Redis:多实例共享状态

生产多 pod 部署时,Router 的内部状态(限额计数、延迟统计、cooldown 标记)要共享。Redis 是标配:

router = Router(
    model_list=...,
    routing_strategy="usage-based-routing-v2",
    redis_host="redis.internal",
    redis_port=6379,
    redis_password="...",
    redis_namespace="prod-app-1",         # 隔离多应用
)

不配 Redis 会怎样?——Router 仍然能跑,但每个 pod 各算各的限额,一起超标风险显著。单实例可以省,多实例必配。

tag-based routing:按任务类型分流

真实业务里你可能希望"classify 任务走便宜模型,chat 任务走主力模型,code 任务走 o1"。用 tags:

model_list = [
    {"model_name":"smart",
     "litellm_params":{"model":"gpt-4o","api_key":...},
     "tags":["chat", "main"]},
    {"model_name":"smart",
     "litellm_params":{"model":"deepseek/deepseek-chat","api_key":...},
     "tags":["classify", "cheap"]},
    {"model_name":"smart",
     "litellm_params":{"model":"o3","api_key":...},
     "tags":["code", "reasoning"]},
]

router = Router(model_list=model_list, enable_tag_filtering=True)

# 调用时指定 tag, Router 只在匹配 tag 的部署里选
resp = router.completion(
    model="smart",
    messages=msgs,
    metadata={"tags": ["classify"]},   # 只走便宜的
)

这是一种非常优雅的"业务类型 → 模型选择"解耦。业务代码写 tags=["classify"],基础设施层决定哪个模型最适合分类——SRE 调整模型时,业务代码不改。

priority queue:保住关键请求

限额紧张时,你可能希望"付费用户优先,试用用户靠边"。Router 支持 priority queue:

router = Router(
    model_list=...,
    enable_pre_call_checks=True,
)

# 高优(数字越小越优先)
high_resp = router.completion(
    model="chat", messages=msgs,
    priority=0,
)

# 低优
low_resp = router.completion(
    model="chat", messages=msgs,
    priority=100,
)

当上游限额触发时,Router 优先放过 priority=0 的请求,低优的排队或被拒。生产做法通常:

Router + async + streaming

以上所有能力都有异步和流式版本:

# 异步
resp = await router.acompletion(model="chat", messages=msgs)

# 流式
resp = router.completion(model="chat", messages=msgs, stream=True)
for chunk in resp:
    print(chunk.choices[0].delta.content or "", end="", flush=True)

# 异步 + 流式
resp = await router.acompletion(model="chat", messages=msgs, stream=True)
async for chunk in resp:
    ...

fallback、重试、cooldown 在流式里也生效——如果流还没开始就挂了,Router 会静默切到 fallback;流一半挂了不会自动重试(除非开 stream 重试)。

YAML 配置:Router 作为 config 驱动

model_list 写在 Python 代码里很快就会臃肿。第 10 章的 Proxy 会用 YAML 配置——但其实 Router 本身就能直接加载 YAML:

# config.yaml
model_list:
  - model_name: chat
    litellm_params:
      model: gpt-4o-mini
      api_key: os.environ/OPENAI_API_KEY
    rpm: 500

  - model_name: chat
    litellm_params:
      model: azure/my-gpt4o
      api_key: os.environ/AZURE_API_KEY
      api_base: os.environ/AZURE_API_BASE
      api_version: "2024-10-21"
    rpm: 1000

router_settings:
  routing_strategy: usage-based-routing-v2
  num_retries: 3
  timeout: 30
  redis_host: os.environ/REDIS_HOST

litellm_settings:
  drop_params: true

general_settings:
  enable_pre_call_checks: true
import yaml
from litellm import Router

with open("config.yaml") as f:
    cfg = yaml.safe_load(f)

router = Router(
    model_list=cfg["model_list"],
    **cfg.get("router_settings", {}),
)

业务代码和配置就此分离——SRE 改 YAML,重启服务,不用碰 Python。

健康检查与可观测

Router 暴露了好几个方法让你观察状态:

# 当前哪些 deployment 处于 cooldown
cooling = await router.async_get_cooldown_deployments()
print(cooling)

# 某个 model_name 下还有哪些 healthy deployment
healthy = await router.async_get_available_deployments(
    model="chat",
    messages=None,
)
print(healthy)

# 手动踢掉一个 deployment (运维热操作)
router.flush_cache()      # 清空所有路由状态

再配合第 11 章的 Prometheus / OpenTelemetry 集成,你能清楚看到:每个 deployment 的 QPS、成功率、p50/p99 延迟、cooldown 次数

一个真实的生产 Router 模板

下面是一个可以抄作业的模板,涵盖多 provider、fallback、tag、Redis:

model_list:
  # === 主力 chat ===
  - model_name: chat
    litellm_params:
      model: azure/gpt-4o-east
      api_key: os.environ/AZURE_EAST_KEY
      api_base: os.environ/AZURE_EAST_BASE
      api_version: "2024-10-21"
    tpm: 2000000
    rpm: 1000
    tags: [primary]

  - model_name: chat
    litellm_params:
      model: azure/gpt-4o-west
      api_key: os.environ/AZURE_WEST_KEY
      api_base: os.environ/AZURE_WEST_BASE
      api_version: "2024-10-21"
    tpm: 2000000
    rpm: 1000
    tags: [primary]

  # === 分类便宜档 ===
  - model_name: cheap
    litellm_params:
      model: deepseek/deepseek-chat
      api_key: os.environ/DEEPSEEK_API_KEY
    rpm: 500
    tags: [cheap, classify]

  - model_name: cheap
    litellm_params:
      model: gemini/gemini-2.0-flash
      api_key: os.environ/GEMINI_API_KEY
    rpm: 1000
    tags: [cheap, classify]

  # === 推理重火力 ===
  - model_name: reasoner
    litellm_params:
      model: o3
      api_key: os.environ/OPENAI_API_KEY
    rpm: 100
    tags: [reasoning]

  # === 最终兜底 ===
  - model_name: last-resort
    litellm_params:
      model: anthropic/claude-sonnet-4-5
      api_key: os.environ/ANTHROPIC_API_KEY
    rpm: 500
    tags: [backup]

router_settings:
  routing_strategy: usage-based-routing-v2
  num_retries: 3
  timeout: 30
  cooldown_time: 60
  allowed_fails: 3
  redis_host: os.environ/REDIS_HOST
  redis_port: 6379
  enable_pre_call_checks: true

  fallbacks:
    - chat: [last-resort]
    - cheap: [chat]
    - reasoner: [chat, last-resort]

  context_window_fallbacks:
    - chat: [gemini/gemini-2.5-pro]   # 超 128k 切长上下文

  content_policy_fallbacks:
    - chat: [last-resort]              # 被 Azure 审核拦, 换 Claude

这套配置上线后,业务代码永远只写 router.completion(model="chat"/"cheap"/"reasoner", ...)。SRE 加一个新 deployment、调整一个限额、替换一家 provider,Python 代码一行不动

本章小结