各自管 Key 的日子
2024 年初,公司内部好几条业务线都想接大模型:客服要做智能问答,研发要做代码助手,数据团队要做自然语言查数。各团队自己调 OpenAI 或本地部署的模型,结果就是 Key 管理混乱,提示词和知识库没法复用,鉴权和审计更谈不上。
所以我们做了一个企业级的知识库问答服务:对上提供统一 API,对下屏蔽不同模型供应商的差异,RAG 检索增强、多轮对话、工具调用、流式输出都收进来。
六层,各管一摊
服务用 Go(Gin)做网关层,从上到下分六层:
- 接入层:统一鉴权(复用统一认证中心的 JWT)、限流、SSE 流式输出;
- 会话层:多轮对话历史管理、Token 预算控制、对话摘要;
- 检索层:混合检索(BM25 + 向量)、light_rag 轻量检索、知识图谱增强;
- 模型层:统一 LLM 适配层,封装 OpenAI、Azure、VLLM、HuggingFace 等,支持多模型路由和降级;
- 工具层:MCP 工具调用、Function Calling、外部 API 编排;
- 数据层:PostgreSQL 存对话和元数据,Milvus/Qdrant 存向量,Redis 做缓存和会话状态。文档解析和向量化不在本服务里,交给数据集管理服务做(eino + pond 高并发),问答服务只做检索和生成,职责清楚。
网关入口
路由注册和中间件链长这样:
| |
对话主流程
编排长这样(伪代码,展示核心链路):
| |
四个权衡
SSE 的错误处理是个坑:Header 一旦写出去,就回不去标准 JSON 错误了。所以先同步等模型连接成功(拿到第一个 chunk 或错误),再切换到 SSE 流;生成中途出错,用 event: error 事件推给客户端,客户端统一按事件处理。
检索不能绑架主流程。向量库偶尔抖动超时,不能让整个问答跟着挂。检索设了 800ms 超时,超时就走纯对话模式,并在响应头标注 X-Retrieval-Mode: fallback,前端可以提示用户:这条答案没过知识库。
对话历史不全塞 Redis。长对话的内存占用大,最后是 PostgreSQL 持久化 + Redis 缓存最近 N 轮的混合策略,超出窗口的历史通过摘要压缩。
模型路由先规则后智能。简单问题给便宜模型,复杂问题路由到强模型。路由策略初期基于规则(关键词 + 长度),后续计划加一个小模型做意图分类。
后来
回头看,这个服务的价值不在封装了多少个模型,而在把鉴权、检索、上下文、工具调用这些共性能力沉淀成平台,让业务方只需要关心自己的知识库和提示词。统一接入后,Key 管理、成本统计、审计日志都有了着落,新业务接入 LLM 的周期从周级降到了天级。
封面图:robert.claypool / Flickr · CC BY 2.0
