系列 《生产化与运营:让 AI 应用可靠、可控、可持续地跑》 第 1 / 1 篇 进阶

系列总览 — 为什么这样组织、读它需要什么、读完能做什么

学习地图 — AI 应用工程师学习地图 第 6 / 7 个系列

  1. 模型网关——所有调用经过的一层

上一个系列:评测、可观测与可追溯

事故先说:一个团队有十二个服务各自调模型,每个服务的环境变量里有一份供应商 API key;某个 key 被写进了日志、日志被同步到第三方;月底账单是一个总数,没人知道哪个功能花了多少;供应商限流时十二个服务各自重试,把限流打得更狠;想加一个 fallback 供应商,要改十二处。这些问题的共同解是一层模型网关:所有模型调用经过它,供应商 key 只在它那里,路由、限流、归因、日志在一处。

这一篇讲网关的七项职责、市面上的选项各是什么定位、什么时候自建、以及网关不该做什么。

本篇要回答的核心问题是:

网关做哪七件事,各解决什么事故?1 LiteLLM、OpenRouter、Cloudflare AI Gateway 一类各是什么定位、怎么选?2 什么时候自建,网关的边界在哪?3

一、总览

1. 七项职责

%%{init: {"flowchart": {"wrappingWidth": 260}}}%%
%% 图:模型网关的位置:十二个服务经它调三家供应商,供应商 key 只在网关
flowchart TB
    subgraph APPS["十二个服务"]
        direction LR
        A1["客服"] --- A2["搜索"] --- A3["报告 agent"] --- A4["…"]
    end
    APPS -- "逻辑模型名 chat-default<br/>网关凭据(按服务 / 租户发)" --> GW["<b>模型网关</b><br/>① 认证与密钥 ② 路由与 fallback ③ 限流与配额<br/>④ 成本归因 ⑤ 日志 / trace ⑥ 缓存 ⑦ 内容检查"]
    GW -- "供应商 key 只在这里" --> P1["OpenAI"] & P2["Anthropic"] & P3["自托管 vLLM"]
    GW -. "限流时统一退避,不是十二个服务各自重试" .-> P1

网关的七项职责
职责 解决的事故 关联的层
认证与密钥 key 散落、泄露、轮换要改多处 第四篇密钥管理
路由与 fallback 供应商锁死、故障无退路 《模型作为组件(06):客户端工程——重试、超时、幂等、限流与流式解析》的方向性
限流与配额 一个功能打爆整体配额;账单失控 《模型作为组件(06):客户端工程——重试、超时、幂等、限流与流式解析》令牌桶、第二篇预算
缓存 重复请求重复花钱 《Prompt 与上下文工程(05):prompt caching 与上下文的排列》(供应商侧缓存是另一件事)
成本归因 不知道钱花在哪 第二篇
日志与脱敏 trace 散落、PII 进日志 《评测、可观测与可追溯(05):trace——span、OpenTelemetry GenAI 语义约定与工具选择》
供应商抽象 换模型改每处调用 《模型作为组件(02):API 契约(一)——消息、工具调用、结构化输出与流式,四家 API 的共同骨架》(抽象有边界)

2. 本文的章节安排

第二章逐项讲七项职责;第三章选项与定位;第四章自建;第五章网关的边界;第六章实践建议。

二、七项职责

1. 认证与密钥

应用拿网关的凭据(按服务 / 租户 / 功能发放,可随时吊销),供应商的 key 只在网关。收益:轮换在一处;泄露一个应用凭据只影响它自己的配额;供应商 key 不会出现在应用日志与工作区(PocketOS 的第一环——《工具、Agent 与 harness(05):权限、沙箱与安全边界》)。网关的凭据本身要能表达范围(哪些模型、多大配额、哪些功能)。

2. 路由与 fallback

应用请求一个逻辑模型名(chat-default、reasoning-high、cheap-classifier),网关映射到具体供应商与快照——换模型改一处配置。fallback 链按 《模型作为组件(06):客户端工程——重试、超时、幂等、限流与流式解析》的方向性:同模型重试 → 同家族降档 → 跨供应商(推理状态、加密压缩 item、工具方言不能跟着切——网关要知道哪些请求不可跨供应商,或让应用声明)→ 非模型路径(应用侧)。故障检测:错误率、超时、429 的滑动窗口;熔断与半开(《模型作为组件(06):客户端工程——重试、超时、幂等、限流与流式解析》)。

%% 图:逻辑模型名到供应商的路由表——应用只写 chat-default / reasoning-high / cheap-classifier;网关把它映射到具体快照并挂一条 fallback 链:同模型重试 → 同家族降档 → 跨供应商(只对无状态请求;会话中途带推理状态的请求声明「绑定供应商」不可跨)→ 返回错误让应用走非模型路径;换模型只改这张表
flowchart TB
    APP["应用请求<br/>model = chat-default<br/>标签:租户 · 功能 · prompt v12<br/>会话中途?绑定供应商"] --> GW["网关路由表"]
    GW -->|"chat-default"| M1["claude-sonnet-5-20260630"]
    GW -->|"reasoning-high"| M2["gpt-5.6-sol-2026-08-21 · effort high"]
    GW -->|"cheap-classifier"| M3["gemini-3.5-flash-lite"]
    M1 -->|"① 429 / 5xx:同模型重试"| M1
    M1 -->|"② 容量 / 超时:同家族降档"| F1["claude-haiku-4.5"]
    F1 -->|"③ 整家不可用 · 且请求无状态"| F2["gpt-5.6-terra"]
    F1 -.->|"③ 会话中途带推理状态:不可跨 → 返回错误"| X["应用侧:非模型路径"]
    C["熔断:30 s 内错误率 > 20% 打开<br/>半开探测后恢复"] -.-> M1
    classDef app fill:#eef6ff,stroke:#5b8fd6,color:#222
    classDef gw fill:#fff7e0,stroke:#c98a00,color:#222
    classDef m fill:#eefaf0,stroke:#4d9a5c,color:#222
    classDef bad fill:#fdecea,stroke:#c0392b,color:#222
    class APP app
    class GW,C gw
    class M1,M2,M3,F1,F2 m
    class X bad

3. 限流与配额

三个维度:RPM / TPM(供应商给的总配额,网关按租户 / 功能分配,防止一个功能打爆整体——《模型作为组件(06):客户端工程——重试、超时、幂等、限流与流式解析》的令牌桶集中到这里)、美元预算(按租户 / 功能的日 / 月上限,接近告警、超过降级——第二篇)、并发(agent 的长会话占连接)。发前数 token(《模型作为组件(06):客户端工程——重试、超时、幂等、限流与流式解析》)在网关做一次即可。

4. 缓存

两种要分清。精确缓存(同样的请求体返回同样的响应):对幂等的、非确定性可接受的查询有效(分类、抽取同一文档),对对话与 agent 无效(每步不同);TTL 要短、按租户隔离。语义缓存(相似的问题返回缓存的答案):风险大——”相似”不等于”同答案”(”退货要多久”与”退款要多久”),只在 FAQ 类且能容忍的场景用,并让用户能刷新。供应商侧的 prompt caching(《Prompt 与上下文工程(05):prompt caching 与上下文的排列》)是第三件事:网关不做,但要不破坏它——网关不能改请求体的顺序、不能注入动态内容到前缀、要透传 prompt_cache_key 与断点标记。

%% 图:三种「缓存」要分清——精确缓存:请求体逐字节相同才返回缓存响应,对幂等查询有效、对对话无效;语义缓存:「退货要多久」与「退款要多久」向量相近却是两条不同政策,默认关、只在声明可缓存的 FAQ 上开;供应商侧 prompt caching 是第三件事,网关不做,但不能改前缀、要透传 prompt_cache_key 与断点标记
flowchart TB
    subgraph E["① 精确缓存(网关做,可选)"]
        direction LR
        E1["请求体哈希相同"] --> E2["返回缓存的响应<br/>分类同一文档 ✓ · 对话 / agent ✗(每步不同)"]
    end
    subgraph S["② 语义缓存(网关能做,默认关)"]
        direction LR
        S1["「退货要多久」"] -->|"向量相近 0.91"| S2["命中「退款要多久」的答案<br/>✗ 两条不同的政策"]
    end
    subgraph P["③ 供应商侧 prompt caching(网关不做,不能破坏)"]
        direction LR
        P1["透传 prompt_cache_key · cache_control 断点"] --> P2["不重排消息 · 不在 system 开头注 request_id"]
    end
    E ~~~ S ~~~ P
    classDef ok fill:#eefaf0,stroke:#4d9a5c,color:#222
    classDef bad fill:#fdecea,stroke:#c0392b,color:#222
    classDef n fill:#fff7e0,stroke:#c98a00,color:#222
    class E1,E2,P1,P2 ok
    class S1 n
    class S2 bad

5. 成本归因

每次调用带标签:租户、功能、prompt 版本、模型、会话、环境;响应里的 usage(输入 / 输出 / 缓存读写 / 推理)按标签进账本;按 《模型作为组件(04):成本与延迟的账——一次调用花多少钱、慢在哪一段》的价目算美元(价目表在网关维护一份)。这是第二篇全部工作的基础——没有归因就没有优化。

6. 日志与脱敏

网关是 trace 的天然出口(《评测、可观测与可追溯(05):trace——span、OpenTelemetry GenAI 语义约定与工具选择》):每次调用一个 span,含完整请求响应(或引用)、usage、延迟分段、路由决定、fallback 事件。脱敏在这里做:PII 识别与替换、凭据抹除——应用不必各自实现。采样与保留期按 《评测、可观测与可追溯(05):trace——span、OpenTelemetry GenAI 语义约定与工具选择》。

7. 供应商抽象

统一的请求 / 响应形状让应用不关心供应商——但抽象有边界(《模型作为组件(02):API 契约(一)——消息、工具调用、结构化输出与流式,四家 API 的共同骨架》):推理状态的搬运方式(thinking block / reasoning item / thought signature)、工具调用的方言、结构化输出的实现、缓存断点、流式事件的形状各家不同。网关能统一”普通对话 + 工具调用”的公分母,不能抹平推理与缓存的差异——这些要么由网关按供应商专属逻辑处理,要么由应用声明”这个请求绑定供应商”。过度抽象的网关会让你用不上供应商的新能力。

三、选项与定位

网关选项与定位
选项 形态 定位 适合
LiteLLM
  • 开源
  • 代理服务或 Python SDK
  • 百余供应商
自托管的全功能网关:路由、fallback、预算、密钥、日志、OpenAI 兼容接口 想自托管、多供应商、要预算与归因
OpenRouter 托管;多模型市场 一个 key 用几百个模型、按用量计费、有 fallback 与路由 快速多模型实验、不想管供应商账户
Cloudflare AI Gateway 边缘托管;接自己的供应商 key 缓存、限流、日志、分析、fallback,在边缘 已用 Cloudflare、要边缘缓存与限流
Portkey / Helicone 一类 托管(部分可自托管) 网关 + 可观测(Helicone 改 base URL 即接入) 以可观测与成本监控为主
各云的网关(Bedrock、Vertex、Azure AI Foundry 的路由) 云托管 云内多模型 + 云的 IAM 与合规 已深度用某云、合规要求流量在云内
自建 反向代理 + 七项 完全可控 合规(流量不出域)、特殊路由、规模成本

判据:自托管(数据不出域的要求)、延迟(多一跳几十毫秒;边缘网关就近)、供应商覆盖(含本地模型与自托管推理)、归因粒度(能否按你的标签维度出账)、与 trace 的集成(OTel 输出)、对供应商专属特性的透传(推理状态、缓存断点、Batch)、成本(托管网关按请求或用量收费)。

四、自建

1. 理由

流量不出域的合规要求;路由逻辑特殊(按租户的合同选供应商、按内容分类路由);规模大到托管网关的费用可观;已有成熟的 API 网关基础设施(Envoy / Kong)可以扩展。

2. 最小集

一个反向代理 + 七项的最小版本:凭据映射表(应用凭据 → 允许的模型与配额)、逻辑模型名 → 供应商路由表(带 fallback 链与熔断)、令牌桶(按凭据)、美元账本(usage × 价目表,按标签)、OTel span 输出(含脱敏)、供应商 key 的密钥库(Vault 一类)。精确缓存与语义缓存可选。两三周的工作量;难的不是写,是运维(它是所有 AI 功能的单点——高可用、扩容、供应商 key 的安全)。

3. 混合

常见的做法:自建一层薄网关做凭据、归因、脱敏、trace(合规相关),后面接 LiteLLM 一类做路由与供应商适配(适配器多且更新快,自己维护不划算)。

%% 图:常见的混合架构——自建一层薄网关做合规相关的四件事(凭据映射、归因账本、脱敏、OTel trace 出口,流量不出域),后面接 LiteLLM 一类做路由、fallback、熔断与百余供应商的适配器(更新快,自己维护不划算);供应商 key 只在密钥库里
flowchart LR
    A1["服务 A"] & A2["服务 B"] & A3["agent 运行时"] -->|"网关凭据 · 逻辑模型名 · 标签"| THIN["自建薄网关(域内)<br/>凭据 → 模型白名单 / 配额<br/>归因账本:usage × 价目表<br/>PII / 凭据脱敏<br/>OTel span 出口"]
    THIN --> LL["LiteLLM(自托管)<br/>路由表 · fallback 链 · 熔断<br/>百余供应商适配器 · OpenAI 兼容接口"]
    LL --> V1["OpenAI"] & V2["Anthropic"] & V3["自托管 vLLM"]
    KV["密钥库(Vault)<br/>供应商 key 只在这里"] -.-> LL
    classDef app fill:#eef6ff,stroke:#5b8fd6,color:#222
    classDef own fill:#fff7e0,stroke:#c98a00,color:#222
    classDef ll fill:#eefaf0,stroke:#4d9a5c,color:#222
    classDef v fill:#f0f0f0,stroke:#888,color:#222
    class A1,A2,A3 app
    class THIN,KV own
    class LL ll
    class V1,V2,V3 v

五、网关的边界

1. 不该做

2. 与 harness 的关系

L4 讲的 agent 运行时(会话、工具、权限)在网关之上:运行时组装请求、经网关调模型;网关不参与工具执行与权限判定(那是 《工具、Agent 与 harness(05):权限、沙箱与安全边界》的四层,在运行时里)。托管的 Agents API(《工具、Agent 与 harness(03):agent 运行时——会话、持久化与 durable execution》)把运行时放在供应商侧——此时网关只是它前面的一跳(或不存在)。

%% 图:网关在栈里的位置——上面是应用层(prompt 组装 · 检索 · 上下文管理)与 agent 运行时(会话 · 工具执行 · 权限四层),它们组装好请求经网关调模型;网关只管调用这一跳的七件事,不碰业务逻辑、不改请求体、不参与工具执行与权限判定;下面是供应商与自托管推理
flowchart TB
    APP["应用层(L2 · L3)<br/>prompt 组装 · 检索 · 上下文预算与压缩"]
    RT["agent 运行时(L4)<br/>会话日志 · 工具执行 · 权限档 / 审批 / 执行策略 / 沙箱"]
    GW["模型网关(本篇)<br/>认证与密钥 · 路由与 fallback · 限流与配额 · 缓存 · 归因 · 日志与脱敏 · 抽象"]
    V["供应商 API · 自托管推理(vLLM)"]
    APP --> RT -->|"组装好的请求 + 逻辑模型名 + 标签"| GW --> V
    X1["✗ 不在网关:prompt 组装 · 检索 · 工具执行 · 权限判定 · 改请求体"] -.-> GW
    classDef a fill:#eef6ff,stroke:#5b8fd6,color:#222
    classDef r fill:#eefaf0,stroke:#4d9a5c,color:#222
    classDef g fill:#fff7e0,stroke:#c98a00,color:#222
    classDef v fill:#f0f0f0,stroke:#888,color:#222
    classDef bad fill:#fdecea,stroke:#c0392b,color:#222
    class APP a
    class RT r
    class GW g
    class V v
    class X1 bad

六、实践建议

  1. 收口:所有模型调用改走网关(逻辑模型名),供应商 key 从所有应用的环境变量里移除。
  2. 凭据分发:每个服务 / 功能一个网关凭据,带模型白名单与配额。
  3. 归因标签:每次调用带租户 / 功能 / prompt 版本 / 环境,usage 进账本——第二篇的起点。
  4. fallback 链声明:每个逻辑模型名配 fallback 链,标注哪些请求不可跨供应商。
  5. 脱敏与 trace 在网关:OTel 输出,PII 与凭据抹除。
  6. 不改请求体、不默认语义缓存、透传缓存标记。
  7. 高可用:网关按核心基础设施对待。

七、本文小结

  • 网关是所有模型调用经过的一层,七项职责:认证与密钥(key 只在网关)、路由与 fallback(逻辑模型名、方向性、熔断)、限流与配额(RPM / TPM / 美元 / 并发按租户功能分)、缓存(精确可用、语义慎用、不破坏供应商侧缓存)、成本归因(标签 + usage + 价目表)、日志与脱敏(trace 出口)、供应商抽象(公分母,推理与缓存的差异是边界)。
  • 选项:LiteLLM(自托管全功能)、OpenRouter(托管多模型市场)、Cloudflare AI Gateway(边缘)、Portkey / Helicone(可观测为主)、云网关、自建;按自托管、延迟、覆盖、归因粒度、trace 集成、专属特性透传、成本选。
  • 自建的理由是合规、特殊路由、规模;最小集是反向代理 + 七项;常见混合是薄自建层 + LiteLLM。
  • 边界:不做业务逻辑、不改请求体、不替应用决定 fallback 语义、不默认语义缓存、必须高可用;运行时与权限在网关之上。

八、自测

  1. 一个团队用网关做了语义缓存,把”退货要多久”与”退款要多久”命中同一答案。问题与修法?

    答案

    语义相似不等于同答案——两个问题的政策不同,缓存返回了错误答案。修:语义缓存只在明确声明可缓存的 FAQ 类请求上做、阈值极高、按租户隔离、用户可刷新;默认关闭;精确缓存对幂等查询更安全。详见第二章。

  2. 网关为了统一日志格式,在每个请求的 system 开头注入了 [request_id: …]。会发生什么?

    答案

    前缀每次不同,供应商侧的 prompt caching 全部失效(《Prompt 与上下文工程(05):prompt caching 与上下文的排列》),输入成本涨十倍、TTFT 变长。网关不能改请求体前缀;request_id 放请求头或响应侧的 span 属性。详见第五章。

  3. 网关配置了跨供应商 fallback,一个多轮推理任务在第 8 轮被切到另一家后质量骤降。原因?网关该怎么改?

    答案

    推理状态(thinking block / reasoning item / 加密压缩 item)不能跨供应商搬运,切换后模型丢了推理上下文(《模型作为组件(03):API 契约(二)——推理模型:thinking、effort 与跨轮的推理状态》、第六篇)。网关应让应用声明”此会话绑定供应商”或”跨供应商仅在会话首轮”,会话中途只做同家族降档或重试,跨供应商只对无状态请求。详见第二章、第五章。

  4. 合规要求”请求内容不出本公司网络”,但团队想用托管网关的路由能力。怎么设计?

    答案

    请求内容必然要到供应商(除非自托管模型),”不出网络”指不经过第三方网关。做法:自建薄网关(凭据、归因、脱敏、trace)在自己网络内,路由与适配用自托管的 LiteLLM 一类(开源、部署在自己网络),直连供应商端点或自托管推理。托管网关(OpenRouter、Cloudflare)不满足。详见第三章、第四章。

  5. 为什么归因标签要在网关而不是各应用自己记?

    答案

    网关是唯一经过所有调用的点,能统一强制标签的存在与格式、统一用一份价目表算美元、统一进账本;各应用自记会漏、格式不一、价目表版本不同、无法跨应用聚合。归因是第二篇成本工程的基础,必须完整。详见第二章。

  1. 认证与密钥——应用拿网关凭据(按服务 / 租户 / 功能发放、可吊销、带范围),供应商 key 只在网关,解决 key 散落泄露与轮换要改多处;路由与 fallback——逻辑模型名映射到供应商与快照,fallback 链按方向性(重试 → 同家族降档 → 跨供应商仅对无状态请求 → 非模型路径),熔断与半开,解决供应商锁死与故障无退路;限流与配额——RPM / TPM / 美元 / 并发按租户与功能分配,解决一个功能打爆整体与账单失控;缓存——精确缓存对幂等查询、语义缓存慎用、不破坏供应商侧 prompt caching(不改前缀、透传标记);成本归因——每次调用带租户 / 功能 / prompt 版本 / 模型 / 会话标签,usage 按价目表进账本,解决不知道钱花在哪;日志与脱敏——统一 trace 出口,PII 与凭据在此抹除;供应商抽象——统一公分母(对话 + 工具调用),推理状态、工具方言、缓存断点是边界。详见第二章。 ↩

  2. LiteLLM:开源、自托管、代理或 SDK、百余供应商、路由 / fallback / 预算 / 密钥 / 日志、OpenAI 兼容——想自托管与多供应商。OpenRouter:托管多模型市场,一个 key 几百模型按用量计费——快速实验、不管供应商账户。Cloudflare AI Gateway:边缘托管接自己的 key,缓存限流日志分析 fallback——已用 Cloudflare、要边缘。Portkey / Helicone:网关 + 可观测为主(Helicone 改 base URL 即接入)。云网关(Bedrock / Vertex / Foundry):云内多模型 + IAM 与合规。自建:完全可控。判据:自托管与数据不出域、延迟(多一跳,边缘就近)、供应商覆盖(含本地与自托管推理)、归因粒度、OTel 集成、对推理状态 / 缓存断点 / Batch 等专属特性的透传、托管费用。详见第三章。 ↩

  3. 自建的理由:合规要求流量不经第三方、路由逻辑特殊(按租户合同、按内容分类)、规模大到托管费用可观、已有 Envoy / Kong 可扩展。最小集:反向代理 + 凭据映射表 + 逻辑模型名路由表(fallback 与熔断)+ 令牌桶 + 美元账本 + OTel span 输出含脱敏 + 密钥库;两三周写、难在运维(单点高可用、扩容、key 安全);常见混合是薄自建层做合规相关(凭据、归因、脱敏、trace)+ 自托管 LiteLLM 做路由与适配。边界:不做业务逻辑(prompt 组装、检索、上下文管理在应用层)、不改请求体(不重排、不注入前缀、不改工具定义——毁缓存)、不替应用决定 fallback 语义(跨供应商丢推理状态,应用声明)、不默认语义缓存、必须高可用;运行时与四层权限在网关之上,托管 Agents API 时网关只是前面一跳。详见第四章、第五章。 ↩

这篇对你有用?
系列 《生产化与运营:让 AI 应用可靠、可控、可持续地跑》 第 1 / 1 篇 进阶

系列总览 — 为什么这样组织、读它需要什么、读完能做什么

学习地图 — AI 应用工程师学习地图 第 6 / 7 个系列

  1. 模型网关——所有调用经过的一层

上一个系列:评测、可观测与可追溯

本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/model-gateway-the-layer-every-call-goes-through.html)的前提下,欢迎各种形式的转载、翻译或商业引用。


COMMENTS

评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。

×