ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

生产级RAG知识库与Agent网关优化实战:从Demo到高并发

生产级RAG知识库与Agent网关优化实战:从Demo到高并发 最近我把一套原本跑在Demo阶段的知识库和Agent网关往前推了一大步目标是让它扛住生产流量。以前我觉得RAG能回答对问题就算成功等真上了生产才发现回答对一次和回答对一万次是两码事。这篇文章就记录我在这个过程中对知识库检索链路和Agent网关做的具体优化以及踩过的那些文档里不会写的问题希望能给正在做同样事情的人一点参考。1. 生产级和Demo级的差距先认清问题再谈优化1.1 Demo跑得通不代表生产扛得住很多团队做RAG知识库都是从Demo起步的切几篇文档扔进向量库用LangChain或者LlamaIndex串一条链子问几个问题能答上来就认为项目成了。但Demo场景和生产场景有三个根本性差异不把这三件事想清楚后面的优化全是空中楼阁。第一个差异是数据。Demo用的通常是几篇精心挑过的干净文档没有表格、没有扫描件、没有页眉页脚也没有PDF里那种两栏排版。生产环境的数据是脏的、乱的、格式五花八门的而且存量可能是几百G甚至几个T。第二个差异是流量模式。Demo是单用户提问生产是多用户并发每天可能几万次请求延迟从能接受变成P99必须控制在某条线以内。第三个差异是容错成本。Demo答错了你可以说模型能力有限生产环境答错了就是事故尤其在医疗、金融、政务、企业内部知识库这些场景里一个错误答案可能带来实打实的业务损失。我当时接手第一件事不是写代码而是拉了一份清单逐条问业务方知识库覆盖多少文档更新频率多高用户提问集中在哪些类型答错了有什么后果这些问题看起来基础但直接决定了后面所有技术选型的方向。比如我发现业务方最在意的是答案必须基于文档不能瞎编那我就得在检索和生成两层都下功夫而不是简单调一调prompt。1.2 生产级知识库的四个硬指标结合这次优化经验我总结出生产级知识库必须满足的四条硬指标供你对照自检指标Demo要求生产要求检索准确性大概能捞出相关文档头部命中率Top-5稳定在85%以上响应延迟10秒能接受P95 3秒P99 5秒数据更新每次全量重建支持增量更新分钟级生效可观测性打印日志看结果全链路Trace每次请求可回溯这四条是我这次优化的牵引线。不管中间做多少细活最终都要落到这几个数字上。2. 知识库优化把检索质量从能用拉到好用2.1 文档解析别想当然这一步决定天花板很多人做知识库把大量精力花在选模型、调prompt上却忽略了最底层的文档解析。但实际经验告诉我解析质量基本决定了整个知识库的天花板——解析出来就是垃圾后面检索和生成做得再好也白搭。我拿到手的生产文档有不少PDF是扫描件还有一部分是那种多栏排版的论文和带复杂表格的Excel。最开始我用现成的PDF解析库直接抽文本结果抽出来全是乱的两栏文字交叠在一起表格行列对不上页眉页脚混进了正文。后来我改用分层解析策略扫描件先走OCR选一个对中文支持好的OCR引擎输出带坐标的文本层带排版信息的PDF用版面分析模型先识别标题、段落、表格、图片四类区块再按区块抽取内容表格统一转成Markdown格式因为LLM对Markdown表格的理解能力远强于原始文本这一步看着费时间但其实是在省后面的时间。我抽样检查了50份不同类型文档的解析结果把错误率压到5%以内才开始做切分。注意解析完一定要做抽样验证不要全量跑完再看。我当时跑了一个5000份文档的批次以为没问题结果发现其中一类合同模板的表格全被解析丢了等于这部分数据白做了。2.2 chunking固定窗口切一切是最省事但最差的做法最早我用固定长度为512个字符、重叠128字符的方式切分文档操作简单但很快发现问题很多语义完整的段落被拦腰切断检索时召回的内容经常是半截话。后来我改成结构化切分——先按文档的标题层级切成一个个section每个section内部如果太长再切小块。具体参数上我最终采用的是优先按Markdown标题切分小块目标长度控制在400到600个token重叠50个token。为什么是这个范围太长的话向量化时语义会被分散检索召回后喂给LLM也容易稀释注意力太短的话单块信息量不够检索命中率反而下降。500个token左右是一个经过多次实验验证的平衡点覆盖大多数场景。另外一个容易踩的坑是chunking方式改了之后必须全量重新做embedding。有一次我调整了切分策略但没重新跑向量库结果只更新了新增文档老数据还是按旧的切分方式存在检索结果新旧不一致排查了半天才发现是这个问题。2.3 embedding选型与向量库中英文混合场景怎么选embedding模型这块我建议你根据语料特点来做选择而不是盲目追新。我这次面对的语料是中英文混杂工程文档以中文为主技术术语大量是英文所以选的是对中英双语支持都比较好的bge系列模型实测下来的中文效果明显好于上一版用的OpenAI embedding接口。向量库选型方面我需要同时满足三点支持增量更新、支持混合检索、能在Kubernetes里部署。对比下来Qdrant和Milvus都比较合适我用的是Qdrant——部署运维更轻HNSW索引的性能对当前数据量完全够用。如果你的团队对PostgreSQL已经很熟也可以考虑pgvector省一个组件但检索性能和扩展性上要稍微妥协一些。向量检索不是万能的纯向量召回对关键词精确匹配的场景很吃亏。比如用户搜一个产品的精确型号XK-2000向量召回往往找不准确因为语义相近的文本太多了。我的做法是混合检索BM25做关键词召回向量做语义召回两者各自取Top N然后用RRFReciprocal Rank Fusion算法合并排序。实测下来混合检索比纯向量检索的头部命中率提高了10个百分点以上这个提升几乎白捡强烈建议你加上。2.4 rerank和上下文组装最后一道关别把好东西做烂召回阶段的目标是宁可多捞一点也不错杀一条——所以我通常让向量召回取Top 50、BM25召回取Top 30合并后可能有一大批候选。但如果把这50多条全部塞给LLM它会看不过来反而降低答案质量还会把延迟和token成本都拉高。这时候就需要rerank模型上场做粗召回到精排的过滤。我用的是bge-reranker-v2-m3在本地部署把混排后的候选段做精排只保留Top 5到Top 8再喂给LLM。效果非常明显测评集上的回答准确率提升了一截而且因为喂给LLM的token少了逻辑更聚焦。上下文组装环节我建议你在把检索结果送给LLM之前做三件事第一每段检索结果带上来源文档的标题、页码、原文路径方便后续溯源第二设定一个最大上下文预算比如3000个token超出部分截断防止用户原问题本身就很长时撑爆上下文第三按rerank得分排序后拼接而不只是按原始顺序拼。3. Agent网关它不是一个代理而是一个控制系统3.1 没网关的时候系统为什么会乱成一锅粥业务方一开始说我们不需要网关直接调模型API多简单。确实只有一个后端服务、每天几百次请求的时候直接用SDK调API没问题。但当接入方变成三四个团队、每个团队各自管理密钥、各自写重试逻辑、各自处理限流和报错时问题就来了有的团队把API Key硬编码在代码里有的团队超时重试直接打爆了上游最麻烦的是——没有统一出口想降级、想切换模型、想做权限控制都得改每个业务方的代码。网关的本质就是一个统一入口让业务方只跟网关打交道不直接碰模型供应商。这样底层的模型切换、路由策略、限流熔断、成本统计对业务方是透明的。3.2 路由策略把请求派给对的模型而不是所有请求都走最强的生产环境的模型调用成本不是小事如果所有请求都走最强模型月底账单会非常难看。我在网关里做了路由策略核心是按三个维度分派用户等级、问题复杂度、成本预算。比如普通用户的问题优先走便宜的小模型能答就答VIP用户或者需要深度推理的问题才走强模型对延迟敏感的场景走速度快的模型对质量敏感的场景走效果最好的模型。配置采用类似下面的YAMLroutes: - pattern: /chat strategy: cost_first targets: - id: fast-model weight: 70 - id: strong-model weight: 30 - pattern: /chat/deep strategy: quality_first targets: - id: strong-model weight: 100这个权重路由在灰度期特别好用。比如我想把新接入的模型慢慢放量先给它10%的流量观察几天没有异常再逐步调高到50%、100%整个过程中业务方完全无感知。3.3 限流、熔断与故障转移先保证系统活着再谈体验生产环境里比答案质量稍差更严重的是系统挂了完全不可用。所以网关里必须有完整的防护机制。限流我建议用令牌桶算法支持瞬间的流量突发而不是简单的固定窗口计数。我们的配置是每个用户每分钟100个token的配额超出直接返回429并提示稍后重试避免个别调用方把整个网关拖垮。熔断的逻辑是当某个模型的错误率连续30秒超过50%或者P99延迟超过5秒就把这个模型标记为不健康后续请求自动故障转移到备用模型等它恢复后再自动回归。这个能力救过我一次——有一次某个模型供应商服务抖动网关10秒内自动降级到了备用模型业务方几乎没感知到异常只有监控告警记录了这一过程。注意熔断恢复不能太激进。我一开始设置的恢复窗口是30秒结果某个模型抖动结束后还处于不稳定状态网关切回去又触发一次熔断来回抖动了三次。后来我把恢复窗口调大到2分钟并加了连续10次探测成功才恢复的条件问题就解决了。3.4 语义缓存让重复问题不重复花钱生产知识库有一个很明显的特点用户问的问题高度集中可能TOP 20的问题占了总流量的40%。如果每次请求都要走完整的检索LLM生成链路既费钱又费时间。所以我给网关加了一层语义缓存。语义缓存和普通KV缓存不一样普通缓存是问题完全相同才命中语义缓存是问题语义相似就命中。实现方式是先对用户问题做embedding然后去缓存库里找有没有余弦相似度大于0.95的历史问题如果有直接把当时生成的答案返回不再调用LLM。实测下来这层缓存把大约30%的重复请求拦截在了网关层省下的token成本相当可观。不过语义缓存有个大坑必须处理知识库数据更新了缓存里的答案可能就是过时的。我在语义缓存上挂了失效机制——每次知识库更新文档时把涉及该文档的缓存条目全部清除。虽然有部分缓存命中率下降但保证了答案不过期这个代价值得付。4. 可观测性少了这一环优化就是开盲盒4.1 全链路Trace从网关入口到检索结果的每一步都可见没有可观测性的时候线上出了问题只能靠用户反馈答案不对然后开发把日志翻个底朝天也未必能找到原因。这次我搭了一套全链路Trace体系每一个请求从进入网关开始就分配一个trace_id贯穿整个链路网关收到请求 → 判断是否命中语义缓存 → 路由到模型 → 知识库检索 → rerank → 拼接上下文 → 调用LLM → 返回答案。每个环节都会记录耗时、token用量、命中的文档ID、rerank得分等关键信息。出了问题直接按trace_id拉出整条链路的日志一眼就能看出是检索没召回相关内容还是rerank把正确文档排到了后面还是LLM没按要求用检索内容。这里我强烈建议给检索环节加上得分日志每次检索返回的文档得分都记下来。否则你会发现很多答案不对的问题是检索环节压根没找到对的文档而不是LLM不行。4.2 离线评估把感觉好用变成有数据证明好用优化过程中最难的一件事是怎么判断这次改动到底是变好了还是变坏了靠几个人手工问几个问题根本说明不了问题。我的做法是建了一套离线评估集从真实用户日志里抽样整理出200条问题每条都标注了标准答案和应该命中的文档。任何改动上线前先跑一遍离线评估集对比两个核心指标检索命中率Top-5命中的比例答案完整率人类评委或者用更强大的模型当裁判判断最终答案是否完整覆盖了标准答案要点有了这套评估体系切换rerank模型后效果是否变好这种问题就变成了一个可量化的判断题。我在优化过程中几乎每次改动都先跑评估集通过后再上线灰度大大降低了玄学调参的成分。顺便说一句维护评估集是个持续工作。每个月从线上新增问题里挑一批新的补充进去防止评估集和线上实际分布偏差越来越大。5. 优化实录从问题定位到方案落地的完整过程5.1 一个典型的线上问题排查过程讲一个这次优化过程中印象最深的问题。上线后某一天业务方反馈说用户问合同中的违约条款系统总是答非所问。我拿到trace_id一查链路显示检索环节确实召回了合同文档Top 50但rerank之后排在第一名的是一个讲违约金的计算方式的段落而用户问的其实是违约后的解约条件。检索召回没错rerank排错了——因为新的rerank模型在概念匹配上把这个段落排高了。定位之后解决方案不是换rerank模型而是在rerank之前增加一层意图过滤当用户的问题词里出现解约终止合同提前结束这些关键词时先把合同文档中关于合同解除的章节权重提高再进rerank。这个规则看着简单但效果立竿见影——离线评估集里相关的命中率直接从60%提到了90%以上。这个案例说明一个道理生产环境的问题往往是多层原因叠加的不要一上来就怀疑模型。先看数据Trace链路会告诉你问题到底出在哪一层。5.2 我反复踩过的几个坑最后整理几个这次优化过程中踩过的坑每一个都是用时间和线上告警换来的。第一个坑是改完chunking忘记重建全量向量。这个问题我前面提过但值得再强调一次——它很隐蔽因为新增文档会正常索引你会以为整个系统都是新的实际上老数据还在用旧的切分方式。最好的习惯是每次改动了切分逻辑或embedding模型就写进上线checklist强制全量重建。第二个坑是向量库索引参数没有调。Qdrant默认的HNSW参数在数据量小的时候没问题数据量上来了之后我最初没调参数P99检索延迟从200ms飙到800ms。后来把ef_search从64调到128M从16调到32检索精度和速度都恢复到了理想状态。记住向量检索的参数不是默认值就够的要配合数据量和召回场景去压测。第三个坑是网关超时设置不当引发连锁故障。一开始我把到模型供应商的HTTP超时设成了10秒想着模型要长思考时间给足一点。但某个供应商在故障时表现为保持连接但不返回导致网关线程全部被占住新的请求全部排队最终整个网关不可用。后来我把超时改为连接超时3秒读取超时30秒加上每路请求的最大执行时间限制这种问题就再没出现过。第四个坑是语义缓存误命中。语义相似度0.95的阈值听起来安全但实际操作中还是出现过用户问如何退款和退款政策是什么这种语义很相似但答案要点不同的情况。缓存命中后给出的过期答案差点引发客诉。我最后把阈值提高到0.97并且对一些重要主题涉及金额、政策变更的请求默认跳过缓存直接走实时链路。5.3 知识库更新的增量同步方案这次优化还面临一个现实问题知识库不是静态的每周都有新文档进来还要频率较高地更新。全量重建肯定不行运行成本太高。我最终做的方案是增量同步文档源接入一个统一的变更事件哪个文件新增、修改、删除都有钩子触发新增和修改的文档走解析→切分→embedding→写入向量库的新增链路同时记录这个文档对应的所有chunk ID更新时先删旧chunk再写新chunk每次增量更新后自动清除涉及该文档的语义缓存条目这个方案的难点在关联文档路径和chunk ID的对应关系必须维护好。我用的是一个meta表每次写入向量库时同时写一条meta记录里面存文档路径、页码、chunk ID。更新时按文档路径查meta表拿到所有旧chunk ID然后批量删除。这套机制上线后知识库的更新从原来的每周凌晨全量重建变成了文件改动后5分钟内生效业务方满意度大幅提升。写在最后这次优化做下来我最深的体会是生产级知识库和Agent网关没有一劳永逸的银弹靠的就是把每一个环节的细节抠到位。文档解析要认真做、chunking要想清楚语义边界、混合检索该上就上、rerank该加就加、网关的限流熔断要设好、可观测性要建设起来。每一步都不难但每一步都做扎实系统的整体表现才会有质的提升。最后分享一个小建议别追求一次性把所有优化做完。搭好监控和离线评估体系然后一个问题一个问题地解决每次改动都有数据验证这样的节奏最稳也最不容易翻车。
返回列表