
1. 生产级知识库和 Agent 网关到底在解决什么问题先把场景摆出来。你手头有一套 RAG 知识库可能是 Dify 流水线拉的也可能是 Ollama LangChain Chroma 自己拼的文档进了向量库检索也能跑通。然后你接了一个 Agent让它去查知识库回答问题。Demo 阶段一切正常一旦上生产问题就来了检索回来的片段答非所问、Agent 反复调用同一个工具、多个业务线共用一个网关导致互相挤占、某次文档更新后整个索引质量断崖式下跌。这些问题的根源往往不在模型本身而在知识库的检索质量和 Agent 网关的调度策略这两层。我最近做的优化核心就是把这两层拆开看知识库负责能不能找到对的料网关负责找到料之后怎么稳定地喂给模型并管住它的行为。这篇文章适合两类人看。一类是已经把 RAG 跑起来、但效果不稳定想深挖原因的人另一类是正在设计 Agent 网关、纠结工具路由和限流怎么做的人。我会把踩过的坑、参数怎么定、为什么这么定都摊开讲。基础概念不绕弯子但重点放在生产环境才会暴露的那些细节上。先给一个整体判断知识库和网关是两个独立的失效域必须分开监控、分开调优。把它们混在一起调你会永远搞不清到底是检索错了还是调度错了。这是我优化过程中第一个、也是最值钱的结论。2. 知识库检索质量从能搜到到搜得准2.1 分块策略决定了检索的天花板很多人搭知识库第一步就是调 embedding 模型觉得换个更强的模型效果就好了。实测下来分块策略对最终效果的影响远大于 embedding 模型的选择。原因很简单embedding 只能在你切好的块上做语义匹配块切得不对再强的模型也救不回来。我试过三种典型切法对比很明显分块方式块大小适用场景实测问题固定长度512 token结构松散的散文句子被拦腰截断语义残缺按标题层级动态有清晰层级的文档层级过深时块过大语义分块动态技术文档、规范计算开销高边界不稳定我最后采用的是标题层级 长度兜底的混合策略优先按 Markdown 标题切遇到单个块超过 800 token 就按段落二次切分同时给每个块加上它所属的标题路径作为前缀。这个前缀很关键它让一个孤立的段落重新获得了上下文。比如一个讲限流阈值的段落前缀带上Agent 网关 稳定性保障检索时语义就完整了。提示分块大小没有万能值。中文技术文档我实测 400 到 800 token 之间比较稳英文可以到 1000。别照搬别人的数字拿你自己的文档跑一轮召回率测试再定。2.2 混合检索不是可选项是必选项纯向量检索有个致命弱点对精确匹配的关键词不敏感。用户问maxkb 知识库怎么配置向量检索可能返回一堆讲知识库配置的通用内容却漏掉真正提到 maxkb 的那篇。反过来纯关键词检索又抓不住语义相近但用词不同的情况。所以生产级知识库我强烈建议上混合检索向量召回 BM25 关键词召回然后用 RRFReciprocal Rank Fusion融合。RRF 的好处是不需要调权重直接按排名倒数相加鲁棒性很好。公式很简单score Σ 1 / (k rank_i)k 一般取 60。这个值我试过 40 到 80差异不大60 是个稳妥的默认值。融合之后再过一个 rerank 模型精排取 top 3 到 5 个块喂给模型。注意rerank 之后的块数量不要贪多喂太多反而会稀释关键信息还会挤占上下文窗口。2.3 检索质量的可观测性怎么建优化最怕的是感觉变好了。我给自己搭了一套检索质量监控核心是三个指标召回率构造一批标准问答对看正确答案所在的块有没有被召回进 top K。命中位置正确答案排在第几位位置越靠前越好。空召回率检索结果为空或全部低于阈值的比例这个指标飙升通常意味着索引出问题了。具体做法是维护一个几十条的评测集每次改分块或换模型就跑一遍。别小看这几十条它能帮你挡住 80% 的优化反而变差的情况。我踩过一次坑换了个新 embedding 模型主观感觉回答更流畅了结果评测集一跑召回率掉了 12 个百分点原因是新模型对中文长文本的截断处理不一样。3. Agent 网关管住工具调用和流量这两件事3.1 网关的核心职责边界Agent 网关听起来玄乎拆开看其实就干两件事工具路由和流量治理。工具路由是决定 Agent 的某次请求该调用哪个工具、传什么参数流量治理是保证多个 Agent、多个业务线共用后端时不互相拖垮。我见过不少项目把网关做成了一个巨大的 if-else 分发器工具一多就维护不动。正确的做法是把工具描述结构化让路由基于描述而不是硬编码。每个工具注册时带上名称、功能描述、参数 schema、适用场景关键词。路由时先用关键词粗筛再用一个小模型或向量匹配做精排。这样加工具只需要注册不用改路由代码。3.2 工具调用的幂等和超时设计Agent 最容易出的事故是重复调用。模型有时候会连续两次调用同一个工具参数还一样。如果这个工具是写操作比如往知识库写数据就会产生脏数据。解决办法是给每次工具调用生成一个基于工具名 参数哈希的幂等键网关层做去重短时间内相同键的调用直接返回缓存结果。超时设计同样重要。我给的默认值是检索类工具 3 秒生成类工具 30 秒写操作 10 秒。超过就中断并返回明确的错误信息给 Agent让它决定是重试还是换策略。这里有个细节超时后不要直接抛异常要返回结构化的失败结果否则 Agent 拿到一个异常字符串会不知所措容易陷入死循环。# 工具调用的幂等 超时封装示意 import hashlib, time from functools import wraps def idempotent_tool(timeout3): cache {} def decorator(func): wraps(func) def wrapper(tool_name, params): key hashlib.md5( f{tool_name}{sorted(params.items())}.encode() ).hexdigest() if key in cache and time.time() - cache[key][ts] 60: return cache[key][result] start time.time() result func(tool_name, params) if time.time() - start timeout: return {status: timeout, retryable: True} cache[key] {result: result, ts: time.time()} return result return wrapper return decorator3.3 多租户下的限流与隔离当知识库和网关要服务多个业务线时必须做租户隔离。我采用的是令牌桶限流每个租户一个桶桶容量按业务重要性分配。关键点是限流要分维度不能只按请求数限还要按 token 消耗量限。因为一次长文档检索消耗的资源可能是短查询的几十倍。隔离还有一层是故障隔离。某个租户的慢查询不能拖垮整个网关。做法是给每个租户的请求走独立的线程池或协程池池满就快速失败返回 429 让上游退避重试。这个快速失败比排队等待在生产里更安全因为排队会让延迟雪崩。4. 知识库和网关联调时最容易踩的坑4.1 检索结果格式和 Agent 预期的错位这是联调阶段最高频的问题。知识库返回的是一堆带 score 的文本块Agent 期望的是结构化的答案或明确的引用来源。中间如果没有一层格式化Agent 就会把 score 当成内容的一部分或者把多个不相关的块硬拼在一起。我的做法是在网关里加一个结果规整层把检索块按 score 排序去掉低于阈值的合并相邻的块最后输出成答案片段 来源列表的结构。来源列表很重要它让 Agent 能给出引用也方便你排查问题。4.2 上下文窗口被检索结果撑爆Agent 的上下文窗口是有限的。如果检索返回 10 个块每个 800 token光检索结果就 8000 token再加上对话历史和系统提示很容易超限。超限之后模型要么报错要么悄悄截断导致关键信息丢失。解决办法是动态控制检索块数量。根据当前对话已占用的 token 数反推还能塞多少检索内容。我设的规则是检索内容最多占上下文窗口的 40%剩下的留给对话历史和生成。这个比例可以根据任务类型调纯问答可以高一些多轮对话要低一些。4.3 索引更新和线上查询的冲突知识库文档更新时如果直接重建索引线上查询会短暂不可用。我踩过一次坑白天更新文档重建索引花了 20 分钟这期间所有查询都返回旧数据或报错。正确做法是双索引切换新文档写入新索引构建完成后原子切换指针旧索引延迟删除。这样线上查询始终可用。如果用的是支持增量更新的向量库那就更简单但要小心删除操作——很多向量库的删除是软删除需要定期做 compaction否则索引会越来越臃肿检索变慢。5. 我实际用下来的一些参数和心得5.1 一套可复用的默认参数经过几轮调优我沉淀了一套默认参数新项目直接拿来用再按实际情况微调环节参数默认值调整方向分块块大小600 token文档密集调小分块重叠80 token一般不超 15%检索向量 top K20召回不足调大检索BM25 top K20同上融合RRF k60基本不用动精排rerank top N5上下文紧张调小网关检索超时3s慢库调大网关幂等窗口60s按业务调5.2 评测集比调参更重要我最大的心得是别在没有评测集的情况下调参。你会陷入改一个参数感觉好了再改一个又感觉差了的循环。花半天时间构造 50 条标准问答对标注好正确答案所在的文档块之后每次改动都跑一遍用数据说话。这半天能省下你后面几十个小时的瞎调。评测集的构造也有讲究要覆盖高频问题、边界问题比如问一个知识库里没有的东西、以及容易混淆的问题两个相似概念。我一般按 6:2:2 的比例分配。5.3 日志要记到能复现问题的粒度生产环境出问题最怕的是日志不够。我的日志里必记这几样原始 query、改写后的 query、召回的块 ID 和 score、rerank 后的顺序、最终喂给模型的完整 prompt、工具调用链路、每步耗时。有了这些任何一个 bad case 都能完整复现。注意日志里可能包含敏感的业务数据落盘前要做脱敏尤其是知识库里的原文内容。我一般只记块 ID 和摘要不记全文。5.4 关于模型选择的取舍embedding 模型和 rerank 模型的选择我的原则是优先选中文表现好、且能本地部署的。原因有两个一是数据不出域二是延迟可控。云端 API 虽然省事但网络抖动会直接影响检索延迟生产环境里这种不确定性很致命。本地部署的话用 Ollama 拉一个中文 embedding 模型配合一个轻量 rerank 模型在普通 GPU 上就能跑性价比很高。至于生成模型反而可以灵活一些因为生成不是瓶颈检索才是。生成模型可以按成本和效果权衡甚至不同租户用不同的模型。6. 后续可以继续深挖的方向这套东西跑稳之后我还在琢磨几个方向。一个是查询改写把用户口语化的提问改写成更适合检索的形式实测能提升不少召回率但改写本身也会引入误差需要评测集盯着。另一个是反馈闭环把用户对回答的点赞点踩收集起来反哺到 rerank 模型的微调上让检索越来越懂业务。还有一个是知识库的自动巡检定期跑一批探针问题发现召回率下降就告警。这个对文档频繁更新的场景特别有用能第一时间发现索引质量问题而不是等用户投诉。这些方向我还在试有结论了再单独写。眼下这套知识库加网关的组合已经在几个业务线上稳定跑了几个月最大的感受就是把检索和调度分开治理问题定位的效率会高一个数量级。以前一个 bad case 要查半天现在看日志就能定位到是分块问题、检索问题还是网关调度问题改起来有的放矢。