别再把聊天界面当作 AI 网关:下一代 Agent 系统的入口正在重构
别再把聊天界面当作 AI 网关:下一代 Agent 系统的入口正在重构
买方以为在采购聊天体验,但生产环境真正稀缺的,是按团队、按美元计费的控制面。
中世纪的修道士自己酿造啤酒,大多是现酿现喝,作为日常饮用水的补充。当时的酒里并没有啤酒花,因为那会让酒变苦。直到后来,人们需要把啤酒长途运输、大规模商业化销售时,才必须加入啤酒花——不为口感,只为防腐,防止整桶酒在半路坏掉。
今天,许多团队在内部落地 AI 时,做的第一件事就是用 Docker 跑起一个漂亮的聊天界面(比如 Open WebUI)。这就像修道士的现酿啤酒,好看、见效快。但当你真正要把这杯“酒”端给整个企业,当几千名员工的请求洪流涌入时,系统最先崩溃的往往不是大模型,而是财务预算和合规红线。
读完这篇针对某大型机构真实部署架构的拆解,你会明白为什么把 UI 和网关焊死是架构灾难,以及在企业级应用中,如何正确建立 AI 系统的计费与路由代理。
1. 虚假的入口与真实的跳板
当你打开这个名为 ai-gateway 的生产仓库,README 里的第一张表列满了入口链接:各个环境的 Open WebUI 聊天地址。人类的直觉会引导你先点开那个聊天框。
那是错的入口。
真正把产品骨架钉死的,是 Open WebUI 容器配置里的这两行代码:
{ name: 'OPENAI_API_BASE_URL', value: 'http://${litellmContainerName}.internal.${containerAppEnv.properties.defaultDomain}' }
{ name: 'OPENAI_API_KEY', secretRef: 'openai-api-key' }
聊天界面根本不直连 GPT。 它连的是环境内部的一台 LiteLLM 代理。是由 LiteLLM 在接管请求后,再决定这次对话是路由给云厂商 A 的区域 PTU,还是走云厂商 B 的跨区推理。
在这个架构里,Open WebUI 只是漂亮的车轮,而中间那个负责调度和承重的“空毂”,才是让系统转起来的 LiteLLM。
一次完整的发送,真正经过的跳板是这样的: 用户通过企业级 SSO 登录,会话状态存在 Redis,用户画像和历史记录写进前端自带的 Postgres。随后,消息被转发给 LiteLLM。在返回的路上,LiteLLM 会异步打两处账:一份明细日志送进 Log Analytics(保留 60 天),一份长期用量元数据写进后端的代理库。最后,前端再把对话渲染到浏览器。
如果你上传了一份 PDF,原文件会进入云存储,文本经 LiteLLM 调用 text-embedding-ada-002 转化为向量,最后存入前端的 pgvector 数据库。下一次提问,系统会先检索再拼装 Prompt。
🩸 血泪提醒:配置里有一行关键的 STORE_PROMPTS_IN_SPEND_LOGS: "false"。这意味着代理的开销日志里,对话内容会被强制抹成 "{}"。当法务团队来问你“用户的 Prompt 存在哪”时,如果你回答“都在 LiteLLM 的明细表里”,这就已经是一次合规事故了——正文实际上分布在 Log Analytics 和前端库里。
聊天是体验,代理才是账本。而账本比体验更难事后补救。
2. 对称性破缺:买方到底在买什么
在这份架构决策记录(ADR)中,决策者们面临着一个核心矛盾:既要避免费用爆炸,又要开放模型能力给其他系统调用。他们比对的根本不是“哪个聊天框更好看”,而是成本控制、跨应用计费、非单一厂商模型支持以及未来的迁云能力。
这里发生了一次典型的架构“对称性破缺”。在 Demo 阶段,UI 和网关可以糊在一个 docker-compose 里。但一进生产,两者的变更频率就彻底劈叉了:模型列表和计费策略每周都可能变,而聊天产品的任何破坏性更新都必须慎之又慎。把它们焊死在同一个部署单元里,两边只会互相锁死。
不妨看看买方诉求在落地时的真实位移:
| 买方角色 | 他们口头要的(体验层) | 他们签字时真正要的(控制层) |
|---|---|---|
| 师生/终端用户 | 能聊天、能传 PDF | 别把敏感数据漂到国外,别突然提示没额度 |
| 财务/业务部门 | “弄个 AI 给我们用用” | 能按团队、按虚拟 Key 算美元,能强行切断超支 |
| 安全/法务合规 | SSO 登录、数据加密 | 数据驻留、60 天强制审计日志、双人授权的 Break-glass 流程 |
| 其他内部应用 | 别强迫我们用这个 UI | 给一条 OpenAI SDK 完全兼容的内部 API URL |
即便拿掉前端的 Open WebUI,这套系统依然是一个完整的 AI 网关。研究项目、内部脚本、其他微服务都可以拿着 LiteLLM 签发的虚拟 Key,去打同一个 /chat/completions 接口。但反过来,如果拿掉 LiteLLM,让 UI 直连大模型,你立刻就会失去统一的路由表、基于 Key 的预算上限,以及跨应用的审计能力。
3. 为什么是 LiteLLM,而不是企业 API 网关?
至于为何不直接使用云厂商现成的企业级 API Management(APIM),ADR 里算了一笔账:某云厂商的 APIM Premium 版本指示性平台成本大约是每月数千美元。但这还不是最致命的。APIM 在做 RESTful 限流时天下无敌,但它无法原生把大模型的 Token 换算成美元,除非你自己外挂一层复杂的计算逻辑。
没有任何按团队算钱和切断超支能力的系统,不配叫做企业级 AI 网关。
LiteLLM 赢在这个系统最看重的几个维度上:
- LLM 经济学:能按 Key、用户、团队做 Token 或美元预算,响应头自带剩余额度。
- 多供应商控制面:一个 OpenAI 兼容入口,向下打通各家云厂商。配置里明明白白写着:A 厂商走托管身份,B 厂商走 Access Key。
- 策略集中:内容安全拦截、SIEM 系统日志推送,全挂在这一层代理上。
但作为工程师,我们必须把它的代价(那点“苦味”)也摆在台面上:
- 你拥有了这台代理的完整运维权。 容器升级、Postgres 维护、Redis 状态,全是你的值班面;而 APIM 是 PaaS,能省掉一整块应用运维。
- 单点控制面风险。 代理一挂,所有模型入口一起瘫痪。
- 高危的运维物件。
LITELLM_SALT_KEY是重中之重,一旦丢失,库里加密的所有虚拟 Key 全部作废,相当于系统脑死亡。
4. 目录即边界:信息隐藏的工程实践
这个仓库并非应用源码仓,而是部署的真相源。它被严格切分成了四个栈,这种严格的切分直接体现了 Parnas 意义上的“信息隐藏”原则:
src/network/:已有网络、子网、NSG。几乎不该动,代码只是为了文档化复刻。src/ai-platform/:日志、监控、存储、密钥库。承载的是共享底座和告警接收人。src/litellm/:代理、用量库、跨云路由。承载的是高频变更的模型列表和计费策略。src/openwebui/:聊天、向量库、SSO。承载的是 UI 发版和用户体验。
在 openwebui 的配置里,有一行不会说谎的注释:
activeRevisionsMode: 'Single' // Change this from 'Multiple' to support sticky sessions
为了支持 WebSocket 和粘性会话,他们主动放弃了多修订版本的灰度切流。发版就意味着新旧替换,容器会先拉出流量,睡 90 秒,再部署,防的就是双写。引入有状态聊天产品后,系统必须付出放弃无缝灰度发布的架构代价。
此外,系统里还藏着一个每天 17:00 UTC(当地时间凌晨)准时运行的 Cron Job,专门清理 90 天以上的闲置数据和孤儿文件。知道这件事,比会背云资源的名字更像一个真正的系统 SME(领域专家),因为总会有人在周一早上大喊:“我上周五上传的知识库怎么没了!”
📌 举一反三:变更频率决定架构边界
当两个模块的变更频率和变更驱动力(业务体验 vs 财务风控)完全不同时,把它们强行耦合在一个部署单元里,就是给未来的线上事故埋雷。拆分目录,是为了让两种时钟不要共用同一个发条。 边界:如果你的系统处于验证期(MVP),且团队规模在十人以内,单体部署的迭代速度红利远大于拆分带来的风控价值。此时强行拆分反而会拖垮交付节奏。
5. 结语:检查你的“啤酒花”
工业级的 AI 系统,靠的从来不是表面雕花的杯子,而是那点不起眼、甚至带点运维“苦味”的防腐啤酒花。
如果你正准备,或者已经拉起了一套内部的 AI 聊天系统,今天下班前可以做一件事:打开你的部署配置,搜索 OPENAI_API_BASE 或等价的指向变量。
如果它直接指向了某家云厂商的公网 Endpoint,那么你现在拥有的只是一个脆弱的客户端,而不是一个网关。接着你可以去问问财务:能否按人或团队,准确报出上个月的 LLM 美元开销?
如果答案是不能,那么你们的财务报表,正在裸奔的路上。