☰
大模型网关实战:从零搭建鉴权、路由、限流与观测体系
2026/10/2 3:26:52 网站建设 项目流程

1. 大模型网关到底解决什么问题:从“每个应用自己接模型”说起

很多团队一开始接大模型的方式都很朴素:业务代码里直接写一段 HTTP 请求,把 API Key 塞进环境变量,然后调用某个模型服务。第一个应用这么干没问题,第二个应用复制一遍也能跑,等到第五个、第十个应用出现时,问题就集中爆发了——Key 散落在各个仓库里,谁在用哪个模型说不清,某个应用把额度跑爆了其他应用跟着遭殃,想换个模型要改十几个地方,出了故障连日志都拼不齐。

大模型网关(LLM Gateway)就是在这个背景下出现的。它本质上是一个位于业务应用和模型服务之间的中间层,所有对模型的请求都先经过它,再由它统一转发、鉴权、限流、记录和路由。你可以把它理解成公司内部的“模型调用总机”:业务方不需要知道后端到底接的是哪家模型、哪个版本,只需要按约定格式把请求交给网关,剩下的由网关处理。

这个定位带来的直接价值有几个层面。统一入口意味着 Key 只存在于网关一处,业务侧拿到的是一套内部凭证,泄露风险大幅下降。统一计量意味着每个团队、每个应用消耗了多少 token 一目了然,成本可以按部门分摊。统一路由意味着当某个模型服务不稳定时,网关可以自动切换到备用模型,业务侧无感知。统一观测意味着所有请求的延迟、错误率、token 消耗都汇聚到一个地方,排查问题不用再挨个应用翻日志。

1.1 网关和普通反向代理的区别在哪

有人会问,这不就是个 Nginx 反向代理吗?表面上看确实像,但差异在于网关要理解“大模型请求”的语义。普通反向代理只看 HTTP 层,它不知道请求体里是什么,也不知道响应是不是流式的。而大模型网关需要解析请求体里的model字段、messages数组、stream标志,需要处理 SSE(Server-Sent Events)流式响应,需要在流式过程中统计 token,需要在多个上游之间做基于模型名的路由。

举个具体例子:业务请求里写的是model: "gpt-4o",但网关配置里可能把gpt-4o映射到三个不同的上游账号做负载均衡,同时设置当主账号返回 429 时自动重试到备用账号。这种逻辑普通反向代理做不了,必须由理解模型协议的应用层网关来实现。

1.2 什么规模的团队真的需要网关

不是所有团队都需要一上来就搭网关。我的经验判断标准是这样的:如果只有一两个应用调模型,Key 管理靠人工也能盯住,那直接调就行,别过度设计。但一旦出现下面任意一种情况,就该考虑上网关:

  • 调用模型的应用超过三个,且分属不同团队维护
  • 需要在多个模型供应商之间做切换或对比
  • 有明确的成本核算需求,要按团队或项目分摊费用
  • 需要对模型调用做审计,记录谁在什么时候调了什么
  • 出现过 Key 泄露或额度被单一应用跑爆的情况

满足两条以上,网关的投入就是值得的。反过来,如果只是个人项目或者小团队内部工具,硬上网关只会增加维护负担。

2. 网关的核心模块拆解:鉴权、路由、限流、观测怎么落地

把网关拆开看,核心就是四个模块。每个模块都有它的设计取舍,我逐个说清楚。

2.1 鉴权层:内部凭证怎么设计才安全

网关对外暴露的凭证和上游模型的真实 Key 必须完全隔离。业务侧持有的应该是网关自己签发的内部 Token,这个 Token 里可以携带团队标识、权限范围、有效期等信息。网关收到请求后先校验内部 Token,通过后再用自己保管的上游 Key 去调模型。

内部 Token 的设计有两种常见做法。一种是静态 Token,就是给每个应用分配一个固定字符串,配置在网关的白名单里。这种方式简单,但轮换麻烦,一旦泄露只能手动吊销。另一种是JWT 签名 Token,网关用私钥签发,业务侧携带,网关用公钥验证。JWT 的好处是可以携带过期时间和自定义声明,不需要网关维护状态,适合应用数量多的场景。

提示:无论用哪种方式,上游模型的真实 Key 绝对不能出现在业务代码、日志或前端。我见过有团队把 Key 写在前端请求里,等于把钥匙挂在门上。

2.2 路由层:按模型名、按权重、按故障切换

路由是网关最有价值的部分。最基本的按模型名路由很好理解:请求里写model: "claude-3-5-sonnet",网关就转发到对应的上游。但实际生产环境需要更复杂的策略。

权重路由用于灰度发布。比如新接了一个模型供应商,先给它 10% 的流量,观察一周稳定性再逐步放大。故障切换用于容灾,当主上游连续返回 5xx 或超时,网关自动把后续请求打到备用上游,同时发告警。成本路由用于省钱,同样的任务优先走便宜的模型,只有复杂请求才走贵的。

这里有个容易踩的坑:故障切换要考虑幂等性。如果请求已经发出去、上游已经开始生成内容了才失败,直接重试可能导致重复计费或重复输出。稳妥的做法是只在“连接建立阶段”失败时切换,一旦开始接收流式响应就不再切换。

2.3 限流层:按 token 限还是按请求数限

限流有两个维度:请求数和token 数。按请求数限流实现简单,但不够精确,因为一个请求可能只消耗几十 token,也可能消耗几万 token。按 token 限流更贴近成本,但需要先估算或后统计,实现复杂。

我的建议是两层都做。第一层按请求数做粗粒度保护,防止某个应用疯狂发请求把网关打挂;第二层按 token 做细粒度配额,控制成本。token 配额可以按天或按月重置,超额后返回明确的错误码,让业务侧知道是配额问题而不是模型故障。

限流的粒度也要想清楚:是按应用限、按团队限还是按模型限?通常按应用限最实用,因为应用是成本归属的最小单位。

2.4 观测层:日志里必须记录哪些字段

观测做得好不好,直接决定出问题时能不能快速定位。一条合格的模型调用日志至少应该包含:请求时间、应用标识、请求的模型名、实际路由到的上游、请求 token 数、响应 token 数、首 token 延迟、总延迟、HTTP 状态码、是否命中缓存、是否发生重试。

其中首 token 延迟和总延迟要分开记。流式响应下,用户感知到的是首 token 延迟,而总延迟反映的是完整生成时间。这两个指标优化方向完全不同:首 token 延迟高通常是网络或上游排队问题,总延迟高可能是生成内容太长。

把这些字段结构化输出到日志系统后,就可以做看板了。我常用的几个看板指标是:各应用 token 消耗趋势、各上游错误率对比、P95 首 token 延迟、限流触发次数。这几个指标基本能覆盖日常运维需求。

3. 自动化编程 Agent 的接入方式:CLI 工具怎么和网关配合

自动化编程 Agent 是这两年很热的方向,典型代表就是各种 CLI 形态的编程助手。这类工具的工作模式是:在终端里接收自然语言指令,理解当前代码库上下文,然后调用大模型生成代码或执行命令。它和网关的关系在于——Agent 调用的模型请求同样应该走网关,而不是每个开发者的机器上各自配置 Key。

3.1 CLI 工具的典型工作流

以常见的编程 CLI 为例,它的工作流大致是:读取当前目录的代码文件作为上下文,把用户指令和上下文拼成 prompt,调用模型,拿到返回后解析成代码修改或命令,再应用到本地。整个过程里,模型调用是最关键的一环,也是最适合走网关的一环。

如果每个开发者都在本地配置自己的 Key,会带来三个问题:成本无法归集、Key 容易泄露、模型版本不统一导致行为不一致。走网关后,开发者只需要配置网关地址和内部 Token,模型选择、版本控制、成本统计都由网关统一管理。

3.2 让 CLI 指向网关的配置思路

大多数 CLI 工具都支持自定义 API Base URL,这就是接入网关的入口。配置时通常需要设置三个东西:Base URL 指向网关地址、API Key 填内部 Token、模型名填网关支持的模型标识。

# 以环境变量方式配置(具体变量名以工具文档为准) export OPENAI_BASE_URL="https://gateway.internal/v1" export OPENAI_API_KEY="内部Token"

配置完成后,CLI 发出的请求就会先到网关,由网关转发到真实模型。这里要注意一点:有些 CLI 工具会做请求格式校验,如果网关的响应格式和官方不完全一致,可能会报错。所以网关在转发时最好保持响应格式的透明,不要随意增删字段。

3.3 Agent 场景下网关的特殊要求

编程 Agent 和普通聊天应用对网关的要求不太一样。Agent 通常会有多轮工具调用,一次任务可能产生几十次模型请求,每次请求都携带大量上下文。这对网关提出了几个额外要求。

第一是上下文长度处理。Agent 的请求体可能非常大,网关要能处理大 body,不能有默认的大小限制。第二是并发控制。一个 Agent 任务可能并发发起多个子请求,网关的限流策略要能识别这种模式,避免误伤。第三是会话关联。把同一个 Agent 任务的多次请求关联起来,便于排查问题时还原完整链路。

注意:Agent 场景下 token 消耗增长很快,一个复杂任务跑下来可能消耗几十万 token。限流配额要提前和业务方对齐,否则很容易在任务中途被限流打断。

4. 从零搭一个最小可用网关:技术选型和关键代码

理论说完了,落到实操。搭一个最小可用网关,不需要一上来就追求大而全,先把鉴权、路由、日志三件事做扎实,限流和高级路由可以后续迭代。

4.1 技术选型:为什么我倾向用成熟框架而不是自己写

自己从零写一个 HTTP 服务转发请求当然可以,但网关涉及流式转发、超时控制、连接池管理、优雅关闭等一堆细节,自己写容易在边界情况上翻车。我倾向用成熟的语言生态里的 Web 框架来做,比如 Python 的 FastAPI、Node 的 Express 或 Go 的 Gin。

选型的核心考量是流式转发支持。大模型响应大多是 SSE 流式返回,框架必须能一边接收上游的流一边转发给下游,不能等整个响应收完再发。FastAPI 配合 httpx 的流式接口、Go 的 io.Copy 都能做到这一点。

数据库方面,如果只是记录日志和配额,PostgreSQL 或 MySQL 都够用。如果日志量很大,可以考虑先写消息队列再异步落库,避免日志写入拖慢请求转发。

4.2 一个最小转发逻辑的骨架

下面是一个简化的转发逻辑示意,重点看流式处理部分:

from fastapi import FastAPI, Request, HTTPException from fastapi.responses import StreamingResponse import httpx app = FastAPI() UPSTREAM_MAP = { "gpt-4o": {"url": "https://upstream-a/v1/chat/completions", "key": "..."}, "claude-3-5-sonnet": {"url": "https://upstream-b/v1/chat/completions", "key": "..."}, } @app.post("/v1/chat/completions") async def chat(request: Request): body = await request.json() model = body.get("model") upstream = UPSTREAM_MAP.get(model) if not upstream: raise HTTPException(status_code=400, detail="unsupported model") headers = {"Authorization": f"Bearer {upstream['key']}"} async def stream(): async with httpx.AsyncClient(timeout=120) as client: async with client.stream("POST", upstream["url"], json=body, headers=headers) as resp: async for chunk in resp.aiter_bytes(): yield chunk return StreamingResponse(stream(), media_type="text/event-stream")

这段代码只做了最基础的路由和流式转发,没有鉴权、没有限流、没有日志。但它展示了核心思路:接收请求、查路由表、流式转发、流式返回。在这个骨架上逐步加模块,比一次性设计一个大系统要稳。

4.3 加上鉴权和日志后的完整度

在转发之前插入鉴权中间件,校验内部 Token;在转发前后记录时间戳和 token 数;在响应结束后异步写日志。这三步加上去,一个最小可用网关就成型了。

token 数的统计有个细节:请求 token 可以在转发前用分词器估算,响应 token 需要在流式过程中累加。如果不想引入分词器依赖,也可以直接读取上游响应里的 usage 字段(如果上游返回的话)。两种方式各有取舍,估算不准确但通用,读 usage 准确但依赖上游支持。

5. 上线后才会暴露的坑:并发、超时、流式中断的真实处理

网关在测试环境跑得好好的,一上线各种问题就来了。我把自己踩过的几个坑整理出来,都是文档里不会写的。

5.1 并发上来后连接池被打满

测试时几个请求并发没问题,生产环境几十个 Agent 同时跑,很快就出现连接超时。原因是 httpx 或类似客户端的默认连接池大小有限,并发一高就排队。解决办法是显式配置连接池上限,并且根据上游的承载能力调整。

limits = httpx.Limits(max_connections=200, max_keepalive_connections=50) client = httpx.AsyncClient(limits=limits, timeout=120)

但连接池也不是越大越好。上游模型服务本身有并发限制,网关开太大反而会把上游打挂。合理的做法是网关连接池略大于上游限制,多出来的请求在网关层排队而不是直接失败。

5.2 超时设置:连接超时和读取超时要分开

很多人只设一个总超时,结果流式响应场景下频繁误杀。流式响应的特点是首 token 可能等很久,但一旦开始返回就持续有数据。如果总超时设成 30 秒,一个生成 60 秒的长回答就会被中途切断。

正确的做法是分开设置:连接超时设短一点(比如 10 秒),因为建立连接很快;读取超时设长一点(比如 120 秒),给长回答留足时间。有些客户端还支持“读取间隔超时”,即两次数据之间超过一定时间才算超时,这个更精确。

5.3 流式中断后的资源清理

客户端提前断开连接(用户关掉终端、网络抖动)时,网关到上游的连接如果不及时关闭,会一直占用资源直到上游自己超时。高并发下这会迅速耗尽连接池。

处理方式是监听客户端的断开事件,一旦发现下游断了,立即取消上游请求。在 FastAPI 里可以通过request.is_disconnected()检测,在 Go 里可以通过 context 取消。这个细节不做,网关跑一段时间就会因为连接泄漏而变慢。

5.4 重试策略要区分错误类型

不是所有错误都值得重试。429(限流)和 5xx(服务端错误)可以重试,400(请求格式错误)重试多少次都一样。而且重试要设上限,通常 2 到 3 次足够,再多只会放大上游压力。

还有一个隐蔽的坑:流式请求重试会导致内容重复。如果已经转发了一部分内容给客户端才失败,重试会让客户端收到两段拼接的内容。所以流式请求的重试只能在“还没开始返回内容”时进行,一旦开始返回就不能重试。

6. 成本与安全:网关层的配额管理和 Key 保护实践

网关除了做技术转发,还承担着成本和安全的管理职责。这两件事做不好,网关的价值就少了一半。

6.1 配额管理:按应用、按天、按模型三个维度

配额管理最实用的粒度是按应用按天。每个应用每天分配一个 token 上限,用完就返回 429,第二天重置。这个粒度既能防止单个应用失控,又不会因为太细而难以维护。

如果团队有多个模型可选,还可以做按模型分档。比如便宜模型配额给大一点,贵模型配额给小一点,引导业务方在合适场景用合适模型。这个策略配合路由层的成本路由,能显著降低整体开销。

配额数据要持久化,不能只放内存,否则网关重启就清零了。用 Redis 做计数器是常见做法,配合定时任务每天重置。

6.2 Key 保护:轮换、加密、最小权限

上游 Key 的保护有三个层面。存储加密是基础,Key 不能明文存在配置文件里,要用密钥管理服务或至少做加密存储。定期轮换是习惯,即使没泄露,也建议每季度换一次。最小权限是原则,如果上游支持按项目分配 Key,就给网关分配独立的 Key,不要和其他系统共用。

网关自身的内部 Token 也要管理。给每个应用分配独立 Token,某个应用 Token 泄露时只吊销它自己的,不影响其他应用。Token 里带上应用标识,方便日志追踪。

6.3 审计日志:谁在什么时候调了什么

审计日志和性能日志要分开。性能日志关注延迟和 token 数,审计日志关注“谁调了什么”。审计日志至少记录:时间、应用标识、模型名、请求摘要(不要记完整内容,涉及隐私)、是否成功。

审计日志的保留周期根据合规要求定,通常至少保留 90 天。如果涉及敏感业务,还要考虑日志本身的访问控制,不是所有人都能看。

7. 我实际落地时的一些体会

网关这个东西,搭起来不难,难的是持续运营。我见过不少团队花两周搭了个网关,上线后没人维护,配置半年不更新,最后变成摆设。

我的体会是,网关的价值在于持续被使用和被观测。只要所有模型调用都走网关,日志和配额数据就有意义;一旦有应用绕过网关直连,数据就失真了。所以推行网关时,一定要把“所有调用必须走网关”作为硬性要求,配合代码审查和网络策略来保证。

另一个体会是不要过度设计。一开始就上复杂的多级路由、智能调度、缓存策略,往往维护成本超过收益。先把鉴权、转发、日志三件事做扎实,跑顺了再逐步加功能。我自己的网关迭代了半年才加上成本路由,前面几个月就是老老实实做转发和统计。

最后说一个具体的技巧:网关的配置变更一定要有版本管理和回滚机制。路由表改错一个字段,可能导致所有请求打到错误的上游。用配置中心或者至少用 Git 管理配置文件,每次变更走审查,出问题能快速回滚。这个习惯能省掉很多半夜被叫起来处理故障的麻烦。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询