自托管 AI gateway — LiteLLM/Bifrost/Portkey 与统一接入层
自托管 AI gateway — LiteLLM/Bifrost/Portkey 与统一接入层
日期: 2026-07-27 阶段: Phase 2 - AI-native 参考架构 标签: #ai-gateway #litellm #cost-control
核心问题
agent-v2 现在直接在 src/agent/config/providers.ts 里硬编码了 5 个 provider(xiaomi/anthropic/openai/deepseek/custom),每个 sub-agent 各自 generateText() 直连 LLM API。这套「应用直连」模式在 demo 期没问题,但放到合规产品里有四个致命洞:成本不可控(没有跨 agent 的预算闸门,orchestrator 一次广度检索能烧 15× 普通 chat 的 token,Anthropic multi-agent 系统已实测过,2025-06)、故障不韧性(一个 provider 429 整条链就断)、密钥裸奔(API key 散在前端配置里)、可观测割裂(Day 22-25 建的 OTel 链路在「调 LLM」这一跳是黑盒)。
今天回答一个架构问题:该不该在应用和 LLM 之间插一层 AI gateway? 答案是「该」,但关键是想清楚 gateway 的五层职责边界、三个主流产品(LiteLLM / Bifrost / Portkey)的取舍,以及 agent-v2 统一走 gateway 后哪些代码该删、哪些该留。
关键内容
A. AI gateway 的五层职责:从「代理」到「控制平面」
把 gateway 当成「一个转发 OpenAI 格式请求的反向代理」是低估它。它的本质是 LLM 调用的控制平面(control plane),职责自下而上分五层,每层解决「应用直连」的一个洞:
① 接入层(normalization):用 OpenAI 兼容格式统一 100+ provider。LiteLLM 把 Bedrock/Vertex/Anthropic/本地 vLLM 全收敛成同一个 /chat/completions 端点(LiteLLM docs,2026-06)。价值不是「省得学多个 SDK」,而是让上游应用对 provider 无感——切换底模不改业务代码。
② 缓存层(caching):精确缓存(OpenAI/Anthropic prompt caching 透传)+ 语义缓存(Day 45 详谈)。命中直接返回,不进 LLM。
③ 路由层(routing):跨 deployment 的负载均衡 + 按成本/延迟/语义选模型(Day 46 详谈)。
④ 韧性层(resilience):fallback 链、retry、cooldown。某 provider 429 或超时,自动降级到 fallback 模型,而不是把错误抛回 agent。
⑤ 治理层(governance):virtual keys(每个 team/project 一把虚拟密钥,真密钥只在 gateway 持有)、per-key budget(日/周/月/年预算,超支直接拒请求)、rate limit、spend tracking、guardrails(PII/越狱拦截)、统一 logging。
关键洞察——这五层必须在一个进程内串成一条请求流水线,因为它们共享同一份「请求上下文」(谁的 key、命中没命中缓存、走了哪个 fallback、花了多少钱)。LiteLLM 的实现是一套 hook 链(LiteLLM docs,2026-06):
client ──► [gateway proxy]
│
① auth: 校验 virtual key、查 budget(超支 → 402 拒绝)
│
② async_pre_call_hook: 改写/拒绝请求(PII mask、注入 system)
│
③ cache lookup: 精确 hash 命中? ──是──► 直接返回(不计费、不进 LLM)
│ 否
④ router.pick(): 按策略选 deployment(成本/延迟/健康度)
│
⑤ 调 LLM ──► 失败? ──是──► fallback 链下一个 / retry(带 cooldown)
│ 成功
⑥ async_post_call_hook: 流式审计、guardrail(streaming 仅 audit,不可拦)
│
⑦ spend tracking + logging: 写 cost、写 OTel span
│
response ──► client
反直觉洞察①(gateway 的价值不在「省事」而在「把横切关注点从 N 个 agent 里抽出来」):很多人觉得 gateway 只是「少写几个 SDK adapter」,所以「我才 5 个 provider,不值得引入」。真正的价值是:预算、重试、密钥轮换、成本归因这些横切关注点,在「应用直连」模式下会被复制到每一个 sub-agent(knowledge/research/portfolio 各写一遍 retry),而 gateway 让它们收敛到一处。判断要不要上 gateway 的标准不是 provider 数量,而是 agent 数量 × 横切关注点数量——agent-v2 有 4 个 agent(1 orchestrator + 3 sub),横切关注点 5 个,等于 20 处潜在重复。
post_call guardrail 在流式下只能审计、不能拦截(LiteLLM docs,2026-06):因为 chunk 已经逐个发给客户端了,等整个响应组装完再跑 guardrail,内容早就吐出去了。这意味着「输出侧合规拦截」对流式 SAR 草稿根本拦不住——合规拦截必须放在输入侧 pre_call(拒绝危险请求)+ 应用侧 HITL(人审后才提交),不能指望 gateway 输出 guardrail 兜底。
B. LiteLLM vs Bifrost vs Portkey:三产品定位对比
三个产品看似都是「AI gateway」,但定位差异巨大,选错会在生产期付出代价。核心分歧在语言/性能和自托管 vs 托管两个轴:
| 维度 | LiteLLM | Bifrost | Portkey |
|---|---|---|---|
| 实现语言 | Python | Go | Node/TS |
| 延迟开销 | 10-50ms(Py 解释器) | ~11µs @ 5k RPS | 低(边缘部署) |
| 吞吐拐点 | ~300-500 RPS 开始劣化 | 5k+ RPS 稳定 | 高(managed 弹性) |
| provider 数 | 100+(2500+ 模型) | 1000+ 模型 | 250+ 模型 |
| 语义缓存 | Redis 精确为主 | 内置双层语义缓存(降本 60-85%) | 模糊匹配语义缓存 |
| virtual key/budget | ✅ 成熟 | ✅ | ✅ |
| 自托管 | ✅ 开源 Docker | ✅ 开源 | ✅(2026-03 核心 Apache 2.0 开源) |
| MCP gateway | 通过插件 | — | 原生支持 |
| 最佳场景 | 自托管团队默认首选 | 高 RPS、对延迟敏感 | 要 managed + 内置 guardrail/PII |
来源:TECHSY / FloTorch / getmaxim LLM gateway 对比(均 2026),Bifrost README(«50x faster than LiteLLM, <100µs @ 5k RPS»,2026-06)。
关键决策逻辑:
- agent-v2 选 LiteLLM。理由:本项目是单租户教学+作品集 demo,RPS 个位数,根本碰不到 Python 的吞吐拐点(300 RPS);而 LiteLLM 的「100+ provider 统一 + virtual key + budget + Docker 自托管 + 开源免费」恰好覆盖五层全部职责,是「自托管团队 2026 默认首选」(FloTorch,2026)。
- Bifrost 的 11µs 在我的场景是过度优化。它解决的是「5k RPS 下 Python gateway 崩了」——那是高并发推理服务的问题,不是教学 demo 的问题。
反直觉洞察②(性能数字要按「你的 RPS 区间」读,不能按「绝对值大小」读):「Bifrost 比 LiteLLM 快 50×」是真的,但这个差距只在 300+ RPS 才显现——低于这个量,两者对用户都是不可感知的毫秒级。被「快 50×」吓得不敢用 LiteLLM 是典型的用别人的瓶颈选自己的工具。先问「我的峰值 RPS 是多少」,再看延迟开销曲线在哪一段——本项目 RPS<10,LiteLLM 的 10-50ms 开销对一次 2-5s 的 LLM 调用是 <2% 的尾巴。
C. agent-v2 统一走 gateway 的改造:删什么、留什么
改造目标:让 4 个 agent 不再各自直连 provider,全部把 baseURL 指向一个 LiteLLM 网关地址,gateway 内部再扇出到真 provider。改造的本质是把 providers.ts 从「provider 注册表」降级为「gateway 端点配置」:
改造前: knowledgeAgent ─┐
researchAgent ─┼─► providers.ts(5个真URL+真key) ─► OpenAI/Anthropic/...
portfolioAgent ─┘ (key 散落、预算各管各、retry 各写各)
改造后: 4个 agent ──► 单一 baseURL=http://gateway/v1 ──► [LiteLLM]
(前端只持 virtual key) ├─ 真 key 只在网关
├─ budget 统一闸门
├─ fallback 链统一
└─ OTel span 统一
改造清单(计划,非已实现):
| 动作 | 文件 | 说明 |
|---|---|---|
| 留 | providers.ts 的 ProviderDef 结构 | 仍需 label/默认模型供 UI 选 |
| 改 | defaultBaseURL 全部指向 gateway | 5 个真 URL → 1 个 GATEWAY_URL |
| 改 | 真 API key 配置 | 从前端 useAgentConfig 移除,迁到 gateway 的 config.yaml,前端只填 virtual key |
| 删 | sub-agent 里任何手写 retry/backoff | 韧性下沉到 gateway fallback 链 |
| 加 | Budget 与 gateway budget 的对账 | orchestrator/budget.ts 现做应用层步数/成本闸门,gateway 做账本层硬闸门,两者双闸(应用闸先触发给好的 UX 报错,gateway 闸是兜底防超支) |
设计要点/决策表
| 要点 | 决策 | 理由 |
|---|---|---|
| 是否引入 gateway | 引入(P2 规划) | 横切关注点(预算/韧性/密钥/可观测)收敛,agent 数×关注点数 = 20 处重复 |
| 选哪个 | LiteLLM 自托管 | RPS<10 碰不到 Py 拐点;五层职责全覆盖;开源 Docker |
| 不选 Bifrost | 11µs 是高 RPS 优化 | 本项目无 5k RPS 场景,过度优化 |
| 真密钥位置 | 只在 gateway config.yaml | 前端持 virtual key,密钥不下发浏览器 |
| 预算闸门 | 应用 Budget + gateway budget 双闸 | 应用闸给 UX、gateway 闸防兜底超支 |
| 输出合规拦截 | 不靠 gateway post_call | 流式下只能审计;改用 pre_call + HITL |
对本项目的落地
- 新建
infra/litellm/config.yaml(规划):声明model_list(把现PROVIDERS的 5 项搬进去)、router_settings.fallbacks(如claude-sonnet-4-6 → gpt-5 → mi-large)、general_settings里挂 budget。这个文件是「provider 真相」的新单一来源,替代散落在前端的配置。 - 改
src/agent/config/providers.ts:新增一个gatewayprovider(defaultBaseURL: process.env.GATEWAY_URL),并在useAgentConfig.ts默认选它;真 provider 定义降级为「gateway 内部路由目标」的文档注释。ProviderDef结构保留不动,UI 无需改。 - 对接
orchestrator/budget.ts:现有Budget.assertCostOk()是应用层估算闸;在 gateway 落地后,把 gateway 返回的x-litellm-response-costheader 回填进Budget的真实花费,让应用层闸从「估算」升级为「网关回传的真实成本」,消除 Day 1-7 prototype 里成本估算偏差。 - 对接 Day 22-25 OTel:LiteLLM 的 logging 层产 OTel span,把它接到现有
useTraceStore.ts的 trace 树上,让「调 LLM」这一跳从黑盒变成带 token/cost/provider/fallback 命中的可观测 span,补齐 Day 24「全链路埋点」在 LLM 跳的缺口。 - 诚实标注:gateway 接入为 P2 规划动作,W?? 落地时须重新确认 LiteLLM 当周版本与
config.yamlschema(schema 跨大版本会变);当前 agent-v2 仍是应用直连,本笔记描述的是目标架构。
参考资料
- LiteLLM — AI Gateway (LLM Proxy) docs:load balancing/routing/fallbacks、virtual keys、budgets+rate limits、spend tracking、guardrails(8项)、logging/alerting/metrics、traffic mirroring、Docker 自托管 (2026-06)
- LiteLLM — Modify/Reject Incoming Requests (call hooks) + Custom Guardrail docs:
async_pre_call_hook改写/拒绝输入、post_call流式仅 audit 不可拦、guardrail_information 写入 logging payload (2026-06) - FloTorch — LLM Gateway Comparison 2026: Enterprise Buyer's Guide:LiteLLM 自托管默认首选、Python 300-500 RPS 拐点 (2026)
- maximhq/bifrost README + getmaxim 5 Best AI Gateways 2026:Bifrost Go 实现 ~11µs @ 5k RPS、«50x faster than LiteLLM»、语义缓存降本 60-85%;Portkey 2026-03 核心 Apache 2.0 开源、原生 MCP gateway、250+ 模型 (2026-06)
- 本仓库
src/agent/config/providers.ts、src/agent/orchestrator/budget.ts、src/agent/orchestrator/orchestratorAgent.ts、src/agent/trace/useTraceStore.ts(2026-06)
SOTA 检查 (2026-06-11)
- 「自托管 AI gateway」在 2026-06 是 agent 生产化的标配层:LiteLLM(自托管开源默认)、Bifrost(高性能 Go)、Portkey(managed+guardrail,2026-03 核心开源)三分天下,对比口径在 FloTorch/getmaxim/TECHSY 多份 2026 报告里一致。
- 语义缓存是 gateway 之间的主要差异化战场:Bifrost 主打内置双层语义缓存(降本 60-85%),Portkey 模糊匹配,LiteLLM 以 Redis 精确缓存为主、语义需外接——这条决定 Day 45「语义缓存实测」该不该换 gateway,留待 Day 45 深挖。
- 过时认知警示:「应用直连足够、不需要 gateway」在多 agent + 合规场景已过时——预算硬闸、密钥隔离、统一成本归因是合规产品的 table-stakes,2026 工程口径普遍把 gateway 当默认层而非可选项。
- 待跟踪:LiteLLM 大版本
config.yamlschema 演进、Portkey 开源核心是否补齐自托管语义缓存;执行落地当周须按顶层时效硬规则重新 WebSearch 确认 LiteLLM 最新稳定版本号。