大多数 RAG 教程在进入生产阶段时就戛然而止了。你会将文档切分为 512 token 的分块,通过单一的嵌入模型进行处理,然后调用向量数据库进行简单的 top-k 检索。在演示中,这看起来很有说服力。问机器人关于公司的休假政策,它会返回一段连贯的文字。每个人都点头称赞。不幸的是,演示会骗人。

生产环境会暴露所有的捷径。固定的分块会从法律合同的赔偿条款中间切断。API 文档会变成重叠的噪音,淹没你真正需要的信号。延迟不断攀升,直到用户在答案到达之前就放弃了查询。我们曾撞上这堵墙,不得不进行重构。我们的检索层从“语义搜索加运气”进化为了一个经过精密测量和监测的流水线。结果是召回率达到 95%,延迟降低了 40%。以下是真正奏效的方法。

根据文档类型匹配分块策略

512 token 的默认设置之所以流行,是因为它简单,而不是因为它正确。不同的文档承载意义的方式不同,你的分块策略也应该反映这一点。

对于法律合同,请使用尊重结构边界的递归分块(recursive chunking)。法律语言是嵌套的。一个条款依赖于其上方的章节,如果在句子中间进行固定切分,会破坏义务逻辑的完整性。递归分块会首先尝试在自然分隔符(先是段落,然后是句子)处进行拆分,然后再施加 token 限制。这能保持赔偿或责任条款的完整。

对于API 文档,请使用函数感知分块(function-aware chunking)。开发者搜索的不是随机的段落,而是端点(endpoints)、参数和错误签名。一个分块应该将完整的函数签名、描述及其返回模式(return schema)作为一个逻辑单元包含在内。如果你将这个块切成两半,检索系统只会返回一半的上下文,而生成模型则会幻觉出剩下的部分。

对于支持工单,请依赖于遵循对话轮次的语义分块(semantic chunking)。支持线程是线性且重复的。客户重复问题,代理询问日志,客户随后附上日志。每一轮对话都是一个独立的语义单元。按轮次分块可以保留“谁在何时说了什么”,这在用户询问“代理在周二建议了什么?”时至关重要。

对于内部维基,可以尝试 Agentic 分块。将一个章节交给 LLM,让它决定一个话题在哪里结束,另一个话题在哪里开始。这在数据摄取时成本更高,但维基页面通常很乱。页面包含来自不同团队且互不相关的更新,人工定义的边界很少能奏效。让模型根据话题转移来划定边界,可以显著减少噪音。

要在单个流水线中运行多种策略,需要在摄取时按类型对文档进行标记。这种微小的 Schema 规范意识会立即带来回报。

结合多种搜索方法,而不是二选一

向量搜索能够理解意图,但在处理精确匹配时经常失败。如果你查询错误代码 ERR_CONNECTION_REFUSED 或特定的 SKU,稠密嵌入(dense embeddings)往往会返回概念相似但事实错误的搜索结果。BM25 作为经典的关键词稀疏检索方法,能完美处理精确字符串,但会丢失语义细微差别。你需要两者兼得。

使用混合检索(hybrid retrieval)。并行运行向量搜索和 BM25,然后使用倒数排名融合(Reciprocal Rank Fusion, RRF)将它们结合起来。RRF 会奖励两种方法都认为相关的文档,同时仍能从任一方法中提取出强有力的候选结果。其数学原理很简单,结果也很稳定:没有任何一种检索方法会主导最终排名。

融合之后,添加一个交叉编码器重排序器(cross-encoder reranker)。第一阶段——向量加稀疏检索——速度快且覆盖面广。随后,交叉编码器会对每个“查询-文档”对进行全注意力评分,这意味着它会真正对照原始问题来阅读候选文档。是的,这会增加延迟。在我们的案例中,大约增加了 50 到 100 毫秒。但精度的提升非常显著,这种权衡是显而易见的。如果你在意召回率,就不能跳过这一步。

在优化索引之前,先优化查询

用户编写查询时并不是为了你的搜索引擎,而是为了人类。 “它没法用”是一个常见的支持查询。一个模糊的功能描述是常见的内部维基搜索。如果你用这些原始输入去搜索索引,得到的结果只会是垃圾。

在查询进入检索器之前,先对其进行转换。

使用查询扩展 (query expansion) 来生成用户问题的多个版本。如果有人输入“server down”,你的系统还应该搜索“service unavailable”、“502 error”和“connection timeout”。覆盖这些意图变体将我们的召回率从 78% 提升到了 96%。这只是一个步骤,与收益相比,成本几乎可以忽略不计。

对于复杂问题,使用查询分解 (query decomposition)。当用户问“如何从旧的 billing API 迁移到新的 API,以及哪些破坏性变更会影响企业账户?”之类的问题时,将其分解为子问题。一个子问题针对迁移步骤,另一个针对企业特定的破坏性变更。每个子问题都能命中索引的不同部分。下游语言模型通过检索良好的分块 (chunks) 来综合最终答案,而不是在嘈杂的上下文窗口中进行猜测。

停止猜测超参数

一旦你拥有了多种分块策略、混合检索和查询转换,你就会面临一个组合问题。分块大小、重叠度、融合权重、重排序深度和扩展次数都会相互影响。孤立地调整其中一个会破坏另一个。在这个空间内进行网格搜索既浪费又缓慢。

请改用贝叶斯优化 (Bayesian optimization)。将其视为一项机器学习调优任务。明确定义你的目标:在保持延迟低于上限的同时,实现召回率最大化。构建一个黄金数据集 (golden dataset) —— 即包含几百个具有代表性的问题,且你确切知道应该检索哪些分块。然后让贝叶斯搜索高效地探索配置空间。它会建立一个关于哪些配置有效的概率模型,并接着测试最有希望的区域。

每个候选配置在进入预发布环境之前都必须通过黄金数据集。如果新的分块大小降低了召回率,或者更重的重排序器导致超出了延迟预算,优化过程会自动捕捉到这一点。这消除了主观臆断。你不再争论 256 还是 512 个 token “更好”,而是开始阅读结果。

结果

流水线的改进效果正如我们所期望的那样产生了复合效应。

  • Recall@10 从 78% 提升至 95%。
  • P95 延迟 从 850 ms 降至 320 ms。
  • 幻觉率 从 12% 降至 3%。
  • 单次查询成本 下降了 38%,这很大程度上是因为更好的检索让我们能够使用更小的生成模型和更少的 prompt token。

延迟的降低让团队中的一些人感到惊讶。添加重排序器和查询扩展听起来应该会减慢速度。但由于检索质量提高了,生成模型需要的提示 (prompting) 更少,猜测更少,重试也更少。良好的检索会让下游的一切都变得更便宜。

将检索视为基础设施

检索不是一个运行一次就忘掉的 notebook。它是基础设施,应该像管理代码一样进行管理。为你的分块策略进行版本控制。当法务团队发布新的合同模板时,在进入生产环境之前测试你的递归分割器 (recursive splitter)。将你的黄金数据集维护为动态文档,而不是上个季度的静态 CSV。在 CI 中实现评估自动化,这样当修改嵌入模型或融合权重的 pull request 提交时,在人工审核之前,系统就会自动给出包含召回率和延迟数据的评论。

你的用户永远不会问你运行的是哪种嵌入模型。他们不会关心你的分块启发式算法或重排序架构。他们只关心答案是否正确、是否快速到达,以及他们是否可以信任它。构建一个能够赢得这种信任的流水线,诚实地衡量它,不要再把检索视为事后才考虑的事情。

来源:Optimizing RAG At Scale
加入讨论:GyaanSetu AI Community