生产级知识库与Agent网关优化实践:从检索质量到成本控制 最近这轮优化做的是生产级知识库和 Agent 网关这两条线的加固。所谓生产级拆开看就是三个字稳、快、省。稳是检索质量不飘省是模型调用成本可控快是用户从提问到拿到答案的延迟能压进可接受区间。这三件事单独做都不难难的是同时做好而且是在业务流量已经跑起来之后再做改造。项目背景一句话就能说清内部业务线有大量文档沉淀散在 wiki、工单、产品手册和离线文档里原来的检索方案是纯粹的 Elasticsearch 关键词匹配召回质量和可用性都差一截。后来上了 RAG 知识库问答又用 Agent 网关把多个模型服务、工具调用、会话上下文统一收口。这一轮优化的目标很直接重构知识库的离线索引与在线检索链路同时把 Agent 网关从“转发 API”升级成真正能扛生产压力的流量调度层。这篇文章不写教科书式的架构图就把我当时踩过的坑、做过的取舍、最后留下来的配置和决策逻辑原原本本说一遍。不管是正在搭知识库的还是准备做 Agent 网关的应该都能从里面找到点能直接用的东西。1. 这次优化要解决什么问题1.1 生产环境暴露出来的三类核心痛点先说痛点。知识库问答和 Agent 网关在生产环境跑了大半年问题其实是分批暴露的。第一类是检索质量问题。旧链路是关键词召回加简单的向量召回再把两路结果合并。表面看召回数量够实际用起来词不达意的情况很频繁。用户问“本月报表为什么导出失败”召回回来的却是“报表功能修改记录”因为关键词匹配到了“报表”语义上完全跑偏。还有一些常见问题是长文档被一刀切切成固定大小片段切碎了上下文答案读起来像拼接的残片。第二类是网关层能力太薄。最早网关就是一层 API 转发把请求按固定路由打到某一个模型服务上。一旦某个上游模型接口限流或者超时所有 Agent 任务跟着失败。限流是各业务线各自设的配额网关完全没有统一管控能力经常出现 A 业务把配额耗尽、B 业务请求被无辜阻塞的情况。日志也是各记各的一个 Agent 会话跨了三个服务排查问题要在三个系统间来回切。第三类是成本不可控。模型按 Token 计费但当时对“一次问答到底花多少 Token”完全没数。偶尔用户反馈一次回答特别慢查日志发现是检索回来的上下文太大把 8K 上下文全塞满了一次请求烧掉几万 Token成本和延迟双高。这三类问题叠加在一起结论很明确不能靠修修补补必须动架构。1.2 优化目标拆解把“稳定”和“质量”变成可量化指标优化的目标和手段必须绑在一起。我列了几个硬性指标后面所有改动都围绕这几个数字展开。检索侧的目标是召回质量能量化。当时引入了一组带标注的评测集模拟真实业务提问大约 300 条对每条问题的标准答案做了可用性标注。优化前的基线是命中率 68%我的目标是把上下文命中率做到 85% 以上并且让答案引用到的文档片段相对集中不出现答非所问。网关侧的目标是稳定性。具体拆成三个指标请求成功率达到 99.5% 以上上游限流导致的失败不允许超过总请求量的 0.5%单个请求 P95 延迟控制在 5 秒以内留出模型推理的时间预算。成本目标则是不允许出现单次问答消耗超过预设 Token 阈值的情况超过阈值的请求必须触发截断或降级。指标定下来之后整个优化工作就有了验收标准。后面做的每一个策略调整都能拿这套指标去衡量不会凭感觉说“好像变好了”。2. 整体架构设计与方案选型2.1 网关层方案选型自研轻量网关还是引入重型网关框架Agent 网关这一步当时有两种主流选择一是直接用开源网关框架二是在现有 API 网关基础上扩展一层 Agent 语义。我最终选了后者理由很实际。开源网关框架在协议兼容和流量治理上确实做得全比如支持流式响应、多模型路由、租户隔离。但对我们的场景来说这些框架往往重量级偏高部署复杂度和运维成本直接翻倍。而且我们的业务对网关有定制需求比如某些对话要走知识库增强检索某些工具调用要经过审批流程这种业务逻辑嵌在开源框架里反而不好维护。我的做法是在原有内部统一网关服务层上扩展了模型路由、语义缓存、限流配额和上下文组装四个模块。网关不直接管理 Agent 里的业务逻辑只负责把请求调度到正确的后端同时把状态、指标、配额统一收口。这样 Agent 业务代码不用改底层能力却被大幅增强改造风险小很多。这次优化是带着业务线一起做很多人会关心 Dify 这类现成平台能不能直接用。我的结论是知识库问答原型阶段当然可以用快速验证价值巨大。但一旦要接入企业级多业务线、多模型网关场景Dify 更适合充当编排层流量治理还是需要一个真正的网关侧来兜底。2.2 知识库链路的分层设计离线索引与在线检索分离知识库系统本身分为离线索引和在线检索两条链路这是整轮优化的骨架。离线索引链路是文档进库时的流水线。输入是各种格式的原始文档Markdown、PDF、Word、HTML输出是结构化切片和对应的向量索引。我把它拆成文档解析、清洗、分块、向量化、写入索引库五个步骤。每个步骤做成独立模块可以单独重跑比如某个文档更新了格式不需要全量重建索引。在线检索链路是用户提问时的路径。输入是用户问题输出是召回的相关文档片段。链路分为查询改写、混合召回、重排序、上下文组装四步。查询改写负责把口语化问题变成更适合检索的形式混合召回负责同时走关键词和向量两路重排序负责把两路结果合并后精排上下文组装负责把最终相关片段拼装成送给大模型的上下文。这两条链路独立部署、独立扩缩容。离线索引跑批任务在线检索走实时接口它们唯一的交集是共享索引存储。这样一个好处是索引更新不影响在线服务在线服务的性能问题也不会拖累索引任务。3. 知识库检索链路优化实录3.1 分块策略与嵌入模型选择从固定大小到语义分块知识库优化里最容易被低估的是分块策略。很多人觉得分块不就是按字数切一刀吗实际效果差距巨大。最早实现就是固定 512 字符切块相邻块加 128 字符重叠。结果非常不稳定有的文档切完上下文依然太长有的文档一个关键段落被切到两块检索时哪块都匹配不到完整的语义。后来改成按文档结构分块优先识别标题层级、段落边界和代码块边界把同一主题的内容聚成一个块再根据块的文本长度决定是否二次拆分。块的最大长度设成接近上下文窗口的十分之一控制在 1000 个字符以内既能保留足够上下文又不至于把检索结果撑得太胖。嵌入模型的选择也做了替换。原来是基于通用语料的嵌入模型在内部文档场景下向量区分度不够。换成针对垂直领域做过微调的嵌入模型之后同一组评测集上命中率提升了大约 7 个百分点。我的经验是如果领域专业性比较强通用嵌入模型可能成为检索效果的天花板建议在自己的领域数据集上做对比评测不要只看公开榜单。3.2 混合检索与会话式重排序多元召回的必要性多路召回的引入彻底改变了旧链路“一条路走到黑”的问题。现在线上同时跑 BM25 关键词召回和向量召回两路再各自取 Top 50 候选合并后交给重排序模型精排。为什么必须多路因为用户提问风格差异很大。有的人提问关键词非常精准比如“Nginx 502 错误排查”BM25 召回效果很好有的人提问是长句描述比如“上周五上传的文件为什么在系统里看不到”这种得靠向量语义匹配。关键词和向量互补缺一路就会漏掉大量应该被召回的内容。重排序阶段一开始用的是普通加权融合效果不理想。后来换了基于交叉编码器的重排序模型把 Query 和候选文档拼接后打分效果立刻上来了。注意这里不要在线的模型上选太大的否则延迟撑不住。我们用了一个轻量级的交叉编码器实测在 50 个候选中精排一轮的耗时在 80 到 120 毫秒之间完全在预算内。这里有一个容易被忽略的点重排序模型最好和检索模型同源或者是同一领域微调的。我用过跨领域的重排序模型测试效果反而不如简单的加权融合因为模型对内部术语的敏感性差异太大提前在评测集上做一轮验证很关键。3.3 上下文组装与 Token 预算控制检索链路最后一步是上下文组装这步对成本和回答质量影响非常大。核心原则是只把高相关性的片段送进模型并控制总 Token 量。具体做法是重排序之后的 Top K 片段先做一次冗余去除如果多个片段内容高度重叠只保留信息量最大的一个。然后按 Token 预算组装默认给模型上下文留出 4K Token 的空间其中知识库片段最多占 3K剩下的留给对话历史和系统指令。这里还用到了一个技巧给片段加引用标签。组装上下文时每个片段前加上类似 【引用文档名/章节名】 的标记让模型知道这段内容的来源。这么做不仅让引用更准确还在后续做答案溯源时提供了极大的便利用户点开引用就能直达原始文档。4. Agent 网关的架构与关键节点优化4.1 路由与限流策略让请求走对门、不打架Agent 网关承接的上游服务通常不止一个可能有多个模型服务、工具调用服务、数据库服务。网关节点的核心职责就是路由和限流。路由规则我设计成多级匹配先按业务线分租户再按模型能力分组最后按请求特征路由。比如一个 Agent 任务是做文档总结命中“长文本处理”组网关自动选择一个支持长上下文且性价比更高的模型如果是实时聊天问答则路由到低延迟模型组。规则单独定义成配置文件支持热更新不用重启网关服务。限流是网关改造里见效最明显的一块。旧方案是每个业务线自己守着配额没有统一视图。新方案做成一个集中的配额中心每个租户按模型实例和 Rate Limit 双维度限流。用令牌桶算法做基础限流超过速率上限的请求直接进入排队队列排队超过 3 秒则返回明确的限流错误。同时支持优先级调度重要业务的请求可以抢占更高配额不至于被突发流量挤垮。4.2 语义缓存省钱和降延迟一起解决网关改造最受欢迎的一个模块可能就是语义缓存了。实现逻辑不复杂用户请求进来先做一次向量化语义缓存模块查询历史请求的向量库如果匹配度超过 0.92直接返回缓存结果不再调用底层大模型。缓存命中时请求基本毫秒级返回体验提升是质变的。缓存的设计有几个关键点。一是缓存的键不能只用原始文本要加入业务线和模型参数的维度避免不同场景相互污染。二是缓存命中必须做响应合并如果同一个问题被多个用户同时问到网关只向上游发起一次模型调用其余请求共享结果。三是缓存要有过期策略知识类问题缓存时间可以长一些比如 24 小时涉及实时状态的问题直接跳过缓存。实测下来缓存命中率大概在 15% 到 20% 之间但对整体成本下降的效果非常明显。4.3 网关的失败处理与回退机制生产环境不可能一切顺遂所以网关必须对失败有兜底。我在这里做了两层回退。第一层是上游实例失败。同一个模型服务背后通常挂多实例网关侧做健康检查某个实例连续报错就摘除流量不健康实例恢复后再自动加回。摘除期间请求分发到健康实例这是最基本的 HA 思路。第二层是模型供应商层面的回退。比如主模型服务因为配额耗尽返回 429网关自动切换到备用模型服务同时拼接一个重试请求。切换过程对用户完全透明只是日志里会记录下来。还有一个细节是超时控制一次模型调用最长等待 45 秒超过就返回中间态提示避免用户无限制等下去。这条规则不是随便设的我测过我们主要调用的几个模型服务最长正常响应基本在 30 秒上下45 秒给了缓冲又不会让网关柱子堆积。5. 可观测性与稳定性建设5.1 全链路追踪与成本归属Agent 网关和知识库检索都是分布式链路的一部分没有全链路追踪根本没法排障。我们最终基于 OpenTelemetry 统一了埋点每个请求生成一个 Trace ID贯穿日志、慢查询、模型调用和缓存命中点。排查问题的方式从“登录三台服务器同时 grep 日志”变成了“输入 Trace ID 拉出整条链路”。哪个环节耗时超了、模型调用花了多少 Token、缓存的 key 命中了没全在一个视图里呈现。这个提升对运维效率是决定性的。成本归属也是基于追踪数据做的。每个请求按业务线、按 Agent、按模型服务标签打计量点每天的 Token 消耗和费用自动汇总结算。谁家的 Agent 烧钱最多、哪个模型单价异常一目了然。优化之前这事只能人工拉账单估算根本做不到精细化管理。5.2 知识库检索质量巡检与索引漂移防护知识库不是建好就不动了是一个持续演进的东西。文档更新了索引要跟着更新文档删除遗漏了模型还是会引用旧内容这种情况我管它叫索引漂移。当时的做法是建了一个定时巡检任务加上一条自检逻辑。巡检任务每天扫描一遍知识库文档和索引表的对应关系发现文档已变更但索引未更新的标记为待更新发现已删除文档但索引还存在的直接清掉向量记录并告警。为了降低索引漂移的影响知识库里的文档也加了版本号每次更新写一条版本记录索引内容和版本记录严格绑定。这样即使某次更新失败也可以快速找到应该回滚的历史版本。5.3 降级预案把可用率从理论落到实际稳定性建设的最后一块是降级预案。我设计了三档降级网关降级、知识库降级、模型降级。网关降级发生在网关自身压力过大时触发的动作是关闭语义缓存写入、减少排队等待时间、优先保证存量会话存活。知识库降级发生在检索服务异常时自动跳过知识库检索直接用模型和固定提示词回答通用问题虽然没有文档支撑但至少不会全线雪崩。模型降级就是 4.3 里提到的回退机制主模型不可用时切备用模型备选模型不可用时切到一个更基础的小模型。每档降级都有对应的开关和告警线上开关是预先配置好的出事时运维只需要一键触发不用现场写代码。我做过两次故障演练从发现问题到降级生效的时间目标控制在 10 分钟以内演练实测结果在 6 分钟左右基本达到预期。6. 踩坑记录与排查速查表6.1 典型案例复盘这五个坑基本人人会踩第一个坑是分块时忽略文档结构。固定长度分块省事但会让检索结果“语义断头”。踩过这次之后所有新接入的文档类型必须过结构解析器然后才能决定分块策略。第二个坑是嵌入模型选型贪大。最早试了一个参数非常大的向量模型检索效果确实好但推理延迟和内存占用直接拖垮了索引重建任务。后来换成小一号但领域微调过的模型效果几乎持平延迟降了一个量级。选型不是越大越好得结合索引构建频率和在线检索延迟两个指标综合看。第三个坑是缓存键设计太粗。早期语义缓存只用了用户问题做键结果不同业务线问同一个词缓存互相串答案。后来把业务线、模型版本、参数温度全塞进缓存键问题才解决。第四个坑是限流的粒度太单一。只按请求数限流不按 Token 限流结果有一次模型服务被判正常但 Token 消耗已经接近配额上限批量任务把配额烧完实时问答全部失败。新方案做了请求数和 Token 双维度限流两边都设阈值谁先超谁触发。第五个坑是日志链路断点。网关、知识库、模型调用三个服务各自记录的请求上下文不一致排查一个超时请求时要靠时间戳慢慢对。引入全链路追踪后发现至少有三类请求是查了日志也还原不出完整路径的这类问题现在已经不存在了。6.2 快速排查清单遇到问题按顺序查整理了一张排查清单生产环境出问题时按顺序走一般能在几分钟内定位问一句“是否所有请求都失败”如果是网关配置或上游服务的健康状态优先排查。查看网关监控面板确认限流和排队指标是否异常。用 Trace ID 定位到具体环节看耗时主要耗在检索还是模型调用。缓存命中率如果突然从 40% 掉到 5%检查缓存键是否被某个新业务线污染。如果模型返回内容明显偏题拉出上下文快照看是不是重排序阶段把无关文档排到了前面。Token 消耗激增时检查上下文组装阶段是否出现了超长片段绕过 Token 预算的边界情况。这张清单配合全链路追踪面板基本是降级处理之外最主要的排障工具。7. 最后分享几个我做优化的真实体会做这轮优化最大的体会是知识库和 Agent 网关是两个不同层面的系统但它们的优化节奏必须同步。知识库追求的是召回质量和答案准确率网关追求的是稳定性和成本效率单独优化某一层很快会暴露另一层的瓶颈。我在优化检索效果时一度把召回做得极其充分上下文塞得满结果网关延迟和 Token 成本直接爆了。后来把两层放在一起调才找到那个真正可用的平衡点。另一个体会是评测集一定要在开工之前建好。没有评测集优化过程就是盲人摸象改了一个参数感觉“好像准确了一点”但你说不出到底是多少。有评测集之后每次改动跑一遍离线评测量化指标摆在面前该不该上、该不该回退决策非常果断。还有一个小技巧想分享上下文组装阶段的引用标记虽然只是一个格式上的小细节却对后来排查“答案错误”起了非常大的作用。没有它时用户说答案哪里不对你得去猜是哪段文档导致的有了它答案错误可以直接定位到具体文档片段排查时间省了一大半。这批优化上线之后知识库检索命中率从 68% 提到了 87%Agent 网关的请求成功率从 93% 提到了 99.6%单次问答的 Token 消耗下降了差不多 35%。数字好看是结果过程才是这篇文章想表达的做生产级系统优化没有捷径靠的是把每一个环节拆开、吃透、再拼回去。希望我踩过的这些坑能帮后来者走一段相对平坦的路。