1. 生产级知识库与 Agent 网关的整体设计思路1.1 为什么要把知识库和 Agent 网关放在一起做单独做一个 RAG 知识库或者单独做一个 Agent 网关这两件事在 Demo 阶段都不难。难的是把它们放到生产环境里让它们协同工作还要保证延迟、成本、可观测性都在可控范围内。我最近这段时间主要就是在干这件事踩了不少坑也积累了一些还算靠谱的经验。先说清楚这两个东西各自是什么。知识库负责把非结构化的文档、网页、PDF、Markdown 等资料经过切块、向量化、索引之后变成可以被检索的知识源。Agent 网关则是所有大模型调用的统一入口它要处理路由、鉴权、限流、重试、缓存、日志这些事情。把两者放在一起是因为 Agent 在回答用户问题时往往需要先检索知识库再基于检索结果生成回答这个链路如果各自为政排查问题会非常痛苦。我见过太多团队的做法是知识库用一套服务Agent 调用用另一套服务两边日志格式不一样trace id 对不上出了问题只能靠猜。所以我的核心设计思路是——把知识库检索也当成网关的一个上游能力来对待统一入口、统一日志、统一超时控制。1.2 核心链路的拆解一条完整的请求链路从用户提问到最终回答大致经过这几个环节用户请求进入 Agent 网关网关做鉴权和限流网关判断这次请求是否需要检索知识库如果需要调用知识库的检索接口拿到候选文档片段对候选片段做重排序rerank筛掉不相关的把筛选后的片段拼进 prompt调用大模型大模型返回结果网关做后处理和日志记录返回给用户这个链路里第 3 步和第 4 步是最容易出问题的地方。检索召回不准后面再好的模型也救不回来重排序太慢整体延迟就上去了。所以我在设计时把这两步单独拎出来做了性能预算。1.3 方案选型背后的考量关于检索方案我最终选择了BM25 向量检索的混合方案而不是纯向量检索。原因很直接纯向量检索在语义相似度上表现好但对精确匹配的关键词、专有名词、编号这类内容经常翻车。比如用户问XX-2024-001 这个工单的处理流程纯向量检索很可能召回一堆语义相近但编号不对的文档。BM25 在这种场景下就是补位选手它对词频和逆文档频率敏感精确匹配能力强。混合检索的常见做法是两路召回后做融合融合算法我用的是 RRFReciprocal Rank Fusion因为它不需要两路分数的量纲对齐实现简单效果稳定。这个后面会详细讲。Agent 网关这边我选择自己写一层薄薄的网关而不是直接用现成的 API 网关。原因是现成网关对 LLM 场景的特殊需求支持不够比如 token 计费、流式响应的中断处理、多模型路由这些自己写反而更灵活。2. 知识库构建的核心细节与实操要点2.1 文档切块策略切块大小不是拍脑袋定的切块chunking是知识库构建里最容易被忽视、但影响最大的环节。我见过有人直接把整篇文档塞进去也见过有人按固定 512 字符硬切。这两种做法在生产环境都会出问题。我的切块策略是这样的按语义边界切优先按段落、标题、列表项切而不是按字符数硬切控制块大小在 300-800 token 之间太小会丢失上下文太大会稀释语义保留重叠相邻块之间保留 10%-15% 的重叠避免边界处的信息被切断给每个块加上元数据来源文档、章节标题、页码、更新时间为什么是 300-800 token这是实测下来的经验值。低于 300 token 的块往往只包含半句话检索出来也没法用高于 800 token 的块向量化之后语义被平均掉了检索精度反而下降。当然这个值跟你的文档类型有关技术文档可以偏小叙述性文档可以偏大。元数据这块我要特别强调。没有元数据的知识库在生产环境基本没法用。因为用户问的问题往往带有时间、来源、版本这些约束没有元数据你就没法做过滤。比如用户问最新的部署流程是什么你得能按更新时间过滤。2.2 向量化模型的选择向量化模型的选择我主要看三个维度中文效果、维度大小、推理成本。维度大小直接影响存储和检索速度。768 维和 1536 维在百万级文档下存储和检索延迟差距是肉眼可见的。我的建议是如果中文场景为主优先选针对中文优化过的模型不要盲目追求维度高。推理成本这块如果文档量大建议做批量向量化并且把向量化任务做成异步的。我一开始是同步做的结果一次全量重建索引把服务拖垮了后来改成队列 批量处理才稳定下来。注意向量化模型一旦选定后续换模型的成本极高因为所有文档都要重新向量化。所以选型时一定要留足评估时间别急着上线。2.3 BM25 索引的构建与参数调优BM25 的实现我用的是常见的开源库核心参数是两个k1 和 b。k1控制词频饱和度默认 1.2-2.0。值越大词频的影响越强b控制文档长度归一化默认 0.75。值越大长文档的惩罚越重我的调参方法是先固定 b0.75在验证集上网格搜索 k1然后固定最优 k1再搜 b。验证集用真实的用户查询和标注的相关文档构建规模不用大几百条就够看出趋势。中文场景下BM25 还需要处理分词问题。我用的分词方案是结合词典的因为通用分词器对专有名词的切分经常不准。词典里我会把业务相关的术语、产品名、编号规则都加进去这样 BM25 的召回质量会明显提升。2.4 混合检索的融合策略两路召回之后怎么融合是个关键问题。我试过几种方案融合方案优点缺点适用场景加权求和实现简单需要分数归一化量纲难对齐两路分数分布接近时RRF无需归一化稳定丢失分数绝对值信息通用场景推荐学习排序效果最好需要标注数据成本高有充足标注时我最终用的是 RRF公式很简单每个文档的得分是1/(k rank)的累加k 一般取 60。这个方案的好处是不管两路召回的分数是什么量纲只看排名融合结果都很稳定。RRF 之后我会再取 Top-N 做重排序。重排序模型比向量检索慢但精度高所以只对少量候选做。这个先粗排后精排的两阶段思路是生产环境的标配。3. Agent 网关的实现与关键环节3.1 网关的核心职责划分Agent 网关不是一个简单的反向代理它要承担这些职责统一鉴权所有模型调用都从这里走API Key 统一管理模型路由根据请求特征路由到不同模型比如简单问题走小模型复杂问题走大模型限流与配额按用户、按租户做限流防止单个用户打爆服务重试与降级上游模型超时或报错时自动重试或降级到备用模型缓存相同或相似的请求直接返回缓存结果日志与追踪记录完整的请求链路方便排查这六件事里模型路由和缓存是省钱的两大杀器。我实测下来加上缓存之后重复请求的模型调用成本能降 30% 以上。3.2 模型路由的实现细节模型路由的规则我分了三层按请求类型路由检索类请求走便宜模型生成类请求走强模型按复杂度路由先用一个轻量分类器判断问题复杂度再决定用哪个模型按降级策略路由主模型不可用时自动切到备用模型复杂度分类器我用的是一个小模型输入是用户问题输出是简单/中等/复杂三档。这个分类器不需要很准只要能把明显简单的问题分流出去就行。实测下来简单问题占比通常在 40% 左右这部分走小模型能省不少钱。提示路由规则一定要可配置不要硬编码在代码里。因为模型的价格和能力变化很快硬编码会导致每次调整都要发版。3.3 流式响应的处理LLM 的流式响应streaming在生产环境处理起来比想象中麻烦。主要问题有三个中断处理用户关闭页面时要能及时中断上游请求避免浪费超时控制流式响应没有明确的结束时间需要设置首字节超时和总超时错误恢复流到一半上游报错要能优雅地告诉前端我的做法是网关层维护一个请求上下文记录每个流式请求的状态。用户断开连接时通过上下文取消上游请求。首字节超时设 10 秒总超时按模型的最大输出长度估算。3.4 缓存策略的设计缓存这块我分了三级精确缓存请求参数完全一致时命中用哈希做 key语义缓存请求语义相似时命中用向量相似度判断片段缓存知识库检索结果缓存因为检索比生成便宜但也不免费精确缓存最简单命中率也最高但只对完全重复的请求有效。语义缓存命中率更高但有误判风险需要设置较高的相似度阈值。片段缓存是我觉得性价比最高的因为很多问题的检索结果是重叠的。缓存的失效策略也要考虑。知识库更新后相关的缓存要失效。我的做法是给缓存加上知识库版本号版本变了就整体失效。4. 常见问题与排查技巧实录4.1 检索召回不准的排查思路检索召回不准是最常见的问题排查时我一般按这个顺序来先看原始文档有没有被正确切块切块错了后面全错再看向量化有没有问题拿一个已知相关的查询看它的向量和文档向量的相似度然后看 BM25 分词专有名词有没有被切碎最后看融合和重排序是不是融合把好的结果排下去了我遇到过一个典型案例用户问某个产品型号的问题检索总是召回不相关的内容。排查后发现是分词器把这个型号切成了几段BM25 完全匹配不上。把型号加进自定义词典后问题就解决了。4.2 延迟过高的优化路径延迟高的时候先做链路拆解看时间花在哪一段。我一般会打点记录这几个时间网关接收请求到发出检索请求检索耗时向量检索 BM25 融合 重排序模型调用耗时首字节 总时长后处理耗时实测下来延迟大头通常在模型调用和重排序。模型调用能优化的空间有限主要是选更快的模型或者做流式。重排序可以优化比如减少候选数量、用更轻量的重排序模型、或者对简单查询跳过重排序。4.3 常见问题速查表问题现象可能原因排查方法解决方向检索结果不相关切块不合理/分词错误检查切块边界和分词结果调整切块策略补充词典响应延迟高重排序慢/模型慢分段打点减少候选换轻量模型缓存命中率低请求参数不稳定看缓存 key 分布归一化请求参数流式响应中断超时设置不当看超时日志调整首字节和总超时成本超预算路由规则太粗看模型调用分布细化路由加缓存4.4 几个踩过的坑坑一向量维度和索引不匹配。换向量化模型时忘了重建索引导致检索结果全是乱的。这个坑很隐蔽因为服务不报错只是结果不对。坑二BM25 索引没更新。知识库更新了文档但 BM25 索引没重建导致新文档检索不到。后来我把索引更新做成了文档更新的触发动作。坑三网关重试导致重复计费。上游超时后网关自动重试但上游其实已经处理了结果计费了两次。后来加了幂等 key 才解决。坑四缓存穿透。大量不存在的查询打到后端缓存完全没起作用。加了空结果缓存和布隆过滤器之后缓解了。5. 生产环境的可观测性建设5.1 日志与追踪的设计生产环境没有可观测性等于闭着眼睛开车。我的日志设计遵循几个原则每个请求一个 trace id贯穿网关、检索、模型调用全链路结构化日志用 JSON 格式方便后续分析关键节点打点记录耗时、token 数、命中缓存等trace id 的传递我用的是标准的 header 透传这样跨服务也能串起来。日志里我会记录请求的摘要不记录完整内容避免隐私问题、模型名称、token 消耗、耗时这些。5.2 关键指标的监控我重点监控这几个指标QPS 和错误率基础健康指标P50/P95/P99 延迟看长尾P99 往往暴露问题缓存命中率直接影响成本和延迟token 消耗按模型、按租户统计控制成本检索召回率需要标注数据定期评估这些指标我会做成看板设置告警阈值。比如 P99 延迟超过 5 秒就告警缓存命中率低于 20% 也告警。5.3 成本控制的实操经验成本控制这块我总结了几个有效的手段缓存优先能缓存的都缓存尤其是检索结果路由分流简单问题走小模型实测能省 30%-40%prompt 精简检索片段不要全塞进去重排序后只留最相关的输出长度限制设置 max_tokens避免模型啰嗦批量处理非实时任务批量调用有些模型批量有折扣这几个手段叠加下来我的整体成本比最初降了大概一半。当然前提是效果不能降所以每次优化都要做效果回归。6. 后续可以继续深挖的方向这套东西跑起来之后我还在继续优化几个方向。一个是Agentic RAG就是让 Agent 自己决定要不要检索、检索几次、怎么改写查询而不是固定的一次检索。这个方向效果提升空间大但控制复杂度也高需要仔细设计。另一个是知识库的自动更新和版本管理。现在文档更新还是半自动的理想状态是文档一变索引自动重建缓存自动失效全程不用人工干预。还有就是多租户隔离。现在租户之间的数据和配额隔离做得还不够细后续要加上更严格的隔离机制。这些方向我还在摸索有新的进展再分享。这套架构不是一蹴而就的是一点点迭代出来的每个环节都踩过坑。如果你也在做类似的事情希望这些经验能帮你少走点弯路。
