前段时间在整理自家 GenAI 平台的模型调度方案,我又把 Microsoft Foundry 的 Model Router 翻出来仔细看了一遍。以前"这个请求该走哪个模型"是塞在应用代码里的一堆 if-else,现在它能被挪到平台层统一管起来。这篇就把我看下来的心得和踩过的点记一记,原文在这儿:Architecting Cost-Aware LLM Workloads with Model Router in Microsoft Foundry

我最在意的其实就一件事:把多模型路由收进一个能被治理的部署里。故障转移它自己扛,数据驻留边界它帮你守住,18 个底层 LLM 之间该怎么在成本和质量之间权衡,也是按每一条 prompt 现算的。

架构上的真问题

GenAI 平台只要一复杂,你手里迟早会同时养着一堆模型。分类和闲聊用便宜的,多步任务交给推理模型,真正的硬骨头才上前沿模型,另外还有一票专门啃代码、图像、长上下文的。

所以难点压根不是"哪个模型最强"。是怎么在上了规模之后,还能给每个请求都派对模型,同时治理和可观测性一样都不丢。

常见的几种做法我大多试过,各有各的坑:

模式 权衡
单模型部署 简单 prompt 上花冤枉钱,复杂 prompt 上又力不从心
应用层路由(规则/分类器) 脆弱,模型一迭代就得重新调
LLM 当路由 多一次调用跳转,治理更复杂,还多了自己那份失败模式
按用例分别部署 部署面爆炸,配额和成本报表被切得七零八落

Microsoft Foundry 的 Model Router 给的是一个平台级答案:一个训练好的路由模型,以单一端点部署,按每条 prompt 在最多 18 个底层 LLM 之间做分派。

概念上的架构

有个设计细节得先讲明白:做路由决策的是一个训练出来的模型,不是写死的规则引擎。它看的是 prompt 本身,也就是复杂度、任务类型、要不要推理这些,而且 Microsoft 每接入一批新的底层模型,它也会跟着更新。

哪些归平台管,哪些归你管

对做架构的人来说,这个职责划分才是关键的心智模型。

平台负责的

  • 实时 prompt 分析和路由决策

  • 子集内的自动故障转移

  • 数据驻留边界的强制

  • 向支持的模型透传 prompt 缓存

  • 底层模型的版本管理(通过路由器版本控制)

你负责的

  • 路由模式:Balanced(默认)、Quality 还是 Cost

  • 模型子集,也就是允许路由器碰的那批底层模型

  • 部署类型:Global Standard 或 Data Zone Standard

  • 区域,眼下就 East US 2 和 Sweden Central 两个可选

  • 可观测性挂钩,把 response.model 记下来,方便后面按请求做归因

把路由模式当成设计杠杆

模式 质量带 什么时候用
Balanced(默认) 顶级模型的 ~1–2% 以内 通用对话和 agent 场景
Quality 始终用顶级模型 受监管的输出、复杂推理、关键文档的 RAG
Cost ~5–6% 带宽 大批量分类、草稿生成、低风险对话

我更愿意把路由模式看成一个部署级的 SLO 杠杆。不同的产品面可以指向不同的 Model Router 部署,各自配不同的模式和子集。

模型子集:你真正的治理面

这个特性我觉得最值得花心思。一份子集列表,其实一口气定死了好几件事:

  • 合规:你的 prompt 到底能落到哪些厂商、哪些区域

  • 上下文窗口:有效上下文等于子集里最小的那个模型,所以别乱塞

  • 成本上限:最坏情况下单次调用能烧多少钱,被这份名单框住了

  • 故障转移池:每个子集至少留两个模型,好歹有个备份

  • 缓存命中率:子集越窄越确定,连续重叠的 prompt 越容易落回同一个底层模型

还有一点,以后路由器版本里新加的模型不会自动塞进你的子集。这是故意留的一道闸,想用就得自己动手改部署。

代码:用自定义子集部署

Model Router 的部署方式和 Foundry 里任何模型一样。下面是一段示意性的 ARM/Bicep 风格部署片段,设成 Balanced 模式,并把路由限制在一个精心挑选的子集里——省掉 subset 就等于接受完整的默认池。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
resource modelRouter 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = {
  name: 'model-router-prod'
  parent: foundryAccount
  sku: {
    name: 'GlobalStandard'
    capacity: 250
  }
  properties: {
    model: {
      format: 'OpenAI'
      name: 'model-router'
      version: '2025-11-18'
    }
    routingConfiguration: {
      mode: 'Balanced' // Balanced | Quality | Cost
      modelSubset: [
        'gpt-5-mini'
        'gpt-5'
        'gpt-5.2'
        'claude-sonnet-4-5'
        'claude-opus-4-6'
        'o4-mini'
      ]
    }
  }
}

具体 schema 建议对着当前的 Foundry 部署 API 再核一遍——参数名在不同 API 版本之间是会变的。

通过 Foundry 门户部署

如果你比起 IaC 更喜欢用门户,流程也很短:

  • 登录 Microsoft Foundry,确认 New Foundry 开关是开着的。

  • 打开模型目录,找到 model-router 并选中它。

  • 选 Default settings 就是对所有支持的模型走 Balanced 模式;选 Custom settings 则可以挑路由模式和模型子集。

  • 在 model router 这个部署层面套一个内容过滤器——它会覆盖所有底层模型。别去给单个模型单独设内容过滤器。

  • TPM 速率限制也设在 model router 层面——它对进出路由器的所有活动生效。别给单个底层模型单独设速率限制。

  • (仅 Claude)在把 Claude 模型加进子集之前,先单独从目录里部署它们。其它厂商的模型是透明调用的。

传播延迟提醒一下:路由模式或模型子集的改动,最多可能要五分钟才生效。做灰度和测试的时候要把这个算进去。

代码:调用端点(Python)

部署完成后,Model Router 就是一个标准的 chat-completions 端点。记得每次都抓一下 response.model——这是你做成本分析和路由验证时,每个请求的归因依据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from openai import AzureOpenAI

client = AzureOpenAI(
    azure_endpoint="https://<your-resource>.openai.azure.com/",
    api_key="<your-key>",
    api_version="2025-11-18",
)

response = client.chat.completions.create(
    model="model-router-prod",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Summarize the trade-offs of event sourcing at scale."},
    ],
)

print(response.choices[0].message.content)
print("Served by:", response.model)  # e.g. "gpt-5-mini-2025-08-07"

代码:流式响应

流式和任何 Azure OpenAI chat 部署完全一样。路由决策发生在第一个 token 之前;一旦选定,底层模型就直接开始流式输出。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
stream = client.chat.completions.create(
    model="model-router-prod",
    messages=[
        {"role": "user", "content": "Walk me through CAP theorem with a concrete example."},
    ],
    stream=True,
)

for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

代码:工具调用(agentic 场景)

2025-11-18 这个版本加入了工具调用支持,这样 Model Router 就能用在 Foundry Agent Service 里。路由器会按每一轮挑合适的模型——琐碎的轮次用便宜的,多步的轮次上推理级的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
tools = [{
    "type": "function",
    "function": {
        "name": "get_order_status",
        "description": "Retrieve the current status of a customer order.",
        "parameters": {
            "type": "object",
            "properties": {
                "order_id": {"type": "string", "description": "The order ID."},
            },
            "required": ["order_id"],
        },
    },
}]

response = client.chat.completions.create(
    model="model-router-prod",
    messages=[
        {"role": "system", "content": "You help customers track orders."},
        {"role": "user", "content": "Where is order A-4571?"},
    ],
    tools=tools,
    tool_choice="auto",
)

choice = response.choices[0]
if choice.message.tool_calls:
    call = choice.message.tool_calls[0]
    print("Tool requested:", call.function.name, call.function.arguments)
print("Served by:", response.model)

这里有个 Agent Service 的注意点:如果你的 agent 流程用到了 Foundry Agent Service 的工具,路由就只会限制在 OpenAI 模型里。当路由器坐在依赖这些工具的 agent 流程后面时,子集要照着这个规则来规划。

代码:另一条路——Foundry Responses SDK

如果你打算标准化到 Microsoft Foundry SDK 而不是 OpenAI Python SDK,Responses API 提供了一条等价的路径。安装:pip install azure-ai-projects>=2.0.0 azure-identity

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient

with (
    DefaultAzureCredential() as credential,
    AIProjectClient(endpoint=project_endpoint, credential=credential) as project_client,
    project_client.get_openai_client() as openai_client,
):
    response = openai_client.responses.create(
        model="model-router-prod",
        input="In one sentence, name the most popular tourist destination in Seattle.",
    )
    print(response.output_text)

选中推理模型时的参数处理

因为 Model Router 既可能派到 chat 模型,也可能派到推理(o 系列)模型,参数的行为会随实际选中的模型而变。我的建议是,把应用围绕两种行为的并集来写。

  • Temperature、Top_P:一旦派到 o 系列推理模型就被忽略,其它模型照常生效。

  • stop、presence_penalty、frequency_penalty、logit_bias、logprobs:o 系列会直接丢掉,其它模型照常生效。

  • reasoning_effort:从 2025-11-18 这个路由器版本才开始支持。派到推理模型时,你传的值会被原样透传下去。

我自己遵守的一条实操规则:在路由器前置的部署里,别指望靠 temperature/top-p 来拿确定性;把 reasoning_effort 当成唯一一个在推理和非推理路径上都有一致含义的旋钮。

响应体长什么样

JSON 结构和标准的 chat completion 一模一样。model 字段是关键信号——它告诉你实际是哪个底层模型服务了这次请求。usage 块里还能看到 cached_tokens(prompt 缓存命中)和 reasoning_tokens(o 系列模型处理 prompt 时)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "id": "xxxx-yyyy-zzzz",
  "object": "chat.completion",
  "model": "gpt-5-mini-2025-08-07",
  "choices": [
    {
      "index": 0,
      "finish_reason": "stop",
      "message": {
        "role": "assistant",
        "content": "Charismatic and bold—combining brash showmanship..."
      },
      "content_filter_results": { "hate": { "filtered": false, "severity": "safe" } }
    }
  ],
  "usage": {
    "prompt_tokens": 3254,
    "completion_tokens": 163,
    "total_tokens": 3417,
    "prompt_tokens_details": { "cached_tokens": 3200, "audio_tokens": 0 },
    "completion_tokens_details": { "reasoning_tokens": 128, "audio_tokens": 0 }
  }
}

在 Azure 门户里做监控

性能指标:

  • 打开 Azure 门户,进到你的 Azure OpenAI / Foundry 资源的 Monitoring → Metrics。

  • 按你的 model router 部署名筛选。

  • 按底层模型拆分指标,就能看到流量实际是怎么在被路由的模型之间分布的。

成本归因:

  • 打开 Azure 门户里的 Resource Management → Cost analysis。

  • 按 Tag 筛选,把 tag 类型设为 Deployment,选你的 model router 部署名。

  • 总成本 = 命中这个部署的请求所对应的底层模型费用之和。

我总结了三条实操建议:

  • 每次调用都记 response.model。这是你在应用侧看路由分布和做每个请求归因的首要信号。

  • 别指望账单是单一模型的。Model Router 是按服务该请求的底层模型的费率计费的。拿 Azure Cost analysis 和你的应用日志对一对。

  • 盯着每个底层模型的缓存命中率。缓存收益只在连续重叠的 prompt 落到同一个模型时才有。子集设得太宽松,会悄悄拖垮缓存效率。

需要提前设计防御的失败模式

  • 上下文窗口溢出。有效上下文是子集里最小的那个模型。一条超大 prompt 进来,除非被路由到上下文更大的模型,否则就会失败。防御办法是精挑子集,或者在上游做摘要/截断。

  • Claude 模型没走路由。Claude 需要先单独从目录部署。可以暴露一个部署健康检查。

  • 区域/部署类型不匹配。目前只有 East US 2 和 Sweden Central,也只有 Global Standard 和 Data Zone Standard。灾备要照这个来规划。

  • 速率限制。Global Standard 默认 250 RPM / 250K TPM;Enterprise/MCA-E 更高。要早点把背压做进去。

  • 不支持音频。图片可以接受,但路由决策只看文本。

常见问题速查

问题 可能原因 处理
触发速率限制 对路由器部署的请求太多 提高 TPM 配额,或用指数退避重试
模型选择出乎意料 路由逻辑选了跟预期不同的模型 检查路由模式;用模型子集去约束
高延迟 路由器开销加上底层模型处理 对延迟敏感的负载用 Cost 模式;小模型响应更快
Claude 模型没走路由 Claude 需要单独从目录部署 加进子集前先从目录部署 Claude 模型
上下文超限 有效上下文 = 子集里最小的模型 把子集调整为上下文更大的模型,或在上游摘要/截断

什么时候 Model Router 是对的架构选择

比较契合的场景:

  • 异构流量,prompt 复杂度跨度很大

  • 想把多厂商 LLM 策略(OpenAI + Anthropic + 开源模型)收敛到一个受治理的单一端点后面

  • agent 平台,任务从琐碎一路跨到复杂推理

不太契合的场景:

  • 均质负载,选一个合适的单模型反而更省事

  • 以大上下文 prompt 为主的负载(除非子集是专门为它挑的)

  • 需要每个请求都确定、可复现地落到同一个模型的场景,因为路由器本来就是设计成自适应的

我推荐的落地路径

  • 先打基线。用 Balanced 模式配上完整池把 Model Router 跑起来,拿有代表性的真实流量,把 response.model 完整记上一段时间。

  • 再上治理。等你摸清了合规、上下文、成本这几条线,再引入模型子集,记得每个子集至少留两个模型兜底。

  • 然后调优。基线分布会告诉你该往成本还是质量偏,这时候再切 Cost 或 Quality,或者干脆按产品面拆成两个画像不同的部署。

  • 最后做集成。把路由器接到 Foundry Agent Service 后面,扛起 agentic 那摊事。

一点收尾的想法

说到底,Model Router 把"多模型分派"这件本来堆在应用层的糟心事,挪成了平台层的事。它给你的那几个旋钮(模式、子集、区域),又刚好对上架构师平时最头疼要来回权衡的几样:成本、质量、合规,还有扛不扛得住。生产环境里这块的复杂度常常超出预期,能这么被摁下去,我觉得是实打实有价值的。

示例仓库

Microsoft 在 foundry-samples 这个 GitHub 组织里放了几个开源示例,拿来上手评估很合适:

  • Model Router Capabilities Interactive Demo(Python)。对着你自己的 prompt 集比较 Balanced、Cost、Quality 三种路由模式;能看到成本节省、延迟和路由分布的实时基准数据。

  • Routed Models Distribution Analysis(Python)。在不同路由画像和模型子集下跑 prompt 批次,检查路由器选了哪些模型、各自占比多少——在敲定路由策略之前很有用。

  • Multi-team Quality & Cost Benchmarking(Python workshop)。部署 Model Router,对着固定模型部署做基准,在多团队企业场景里分析成本/延迟权衡。

  • On-Call Copilot Multi-Agent Demo(Python)。看 agent 流程里每一步的模型选择——分类用快而便宜的模型,根因分析上推理模型。

这些示例是给学习和实验用的。在把任何一部分改造进生产之前,先对照你所在组织的安全、合规和负责任 AI 政策审一遍。

延伸阅读