大多数 RAG 原型的底层逻辑都大同小异。有人将 PDF 喂进流水线,将文本切成整齐的 512 token 分块,丢进向量数据库,然后就觉得大功告成了。对于走廊演示(hallway demo)来说,这看起来可能很惊艳。但在生产环境中,它会彻底崩溃。

固定分块并不关心它切断了什么。它可能会在赔偿条款的中途切断一份法律合同。它会将五个不相关的 API 端点塞进同一个上下文窗口,让模型淹没在噪声中。它会迫使你检索比实际需要更多的片段,从而导致延迟增加并消耗大量 token。结果就是回答不完整、产生幻觉以及用户体验挫败。

我们将检索层彻底拆解并重新构建。最终得到的系统在召回率达到 95% 的同时,还将延迟降低了 40%。以下是我们的具体做法。

为什么固定分块在生产环境中会失效

512 token 的默认设置并非设计使然。它是早期嵌入模型上下文窗口和库默认设置的副产品。它易于实现,但依赖它则是灾难性的。

文档并非千篇一律。一个法律条款可能会长达七百个 token 而没有清晰的分界点。如果你在 512 个 token 处切断,就会产生两个孤立的片段。当律师或合规官询问关于责任上限的问题时,系统只会返回一半的义务说明。语言模型会幻觉出缺失的另一半,或者更糟,直接否认上限的存在。

API 文档则面临相反的问题。一个 500 token 的分块可能会吞掉整个模块:身份验证标头、错误代码、速率限制和 webhook 模式。当开发者询问如何处理 AUTH_4027 时,检索器会抛出一堆不相关的函数。模型别无选择,只能将它们平均化成一堆毫无意义的废话。

糟糕的分块还会增加延迟。弱碎片的出现意味着你需要更大的 top-k 来覆盖一个主题。更多的分块意味着更长的 prompt。更长的 prompt 意味着更慢的生成速度和更高的账单。用户体验会因这些细微的缺陷而逐渐瓦解。

让分块与文档匹配

我们不再仅仅计算 token,而是开始研读材料。正确的分块策略取决于源文档的结构。

法律文档需要具有条款感知边界的递归字符分块。分割器会尊重层级结构:它首先寻找章节标题,然后是编号段落,最后是自然的句子断点。它绝不会切断子条款,也不会将一个义务性短语拆分到不同的分块中。当你检索有关赔偿的段落时,你会得到完整的条款、上限以及例外情况。

API 文档要求感知结构的分块。我们根据函数定义进行解析,而不是根据 token 预算。每个分块都包含完整的函数签名、其参数描述以及紧邻的错误处理说明。如果开发者搜索特定的方法,他们会收到整个契约,而不是被截断在任意切分点内的片段。

支持工单充满了噪声且是非线性的。一个讨论串可能以错误报告开始,引入一个临时解决方案,最后以内部升级说明结束。语义分块通过测量句子之间的嵌入相似度来检测主题转移。我们只允许在自然的主题边界处进行切分,因此关于登录失败的对话会与关于计费周期的后续讨论保持分离。

维基 (Wikis) 是最难处理的。它们内容庞大、链接交错且组织松散。我们使用了智能体分块(agentic chunking),即使用轻量级 LLM 阅读页面并根据主题连贯性决定切分点。这在数据摄取阶段的成本略高,但生成的分块是自包含且随时可供检索的。关于部署最佳实践的页面会被切分为逻辑单元:预检、回滚程序和监控设置,而不是随意的文本块。

混合检索:关键词与向量并行

稠密向量搜索能够理解含义,但在处理精确字符串方面表现糟糕。如果用户搜索精确的错误代码(如 AUTH_4027)或客户名称(如 "Stark Industries"),向量嵌入可能会偏离目标,因为它们优化的是概念上的接近度,而非字符级的准确度。

通过 BM25 进行的纯关键词搜索则具有相反的缺陷。它能完美地找到 AUTH_4027,但会错过“授权失败”与“登录被拒绝”之间的概念联系。

We run both in parallel. BM25 and vector search operate independently over the same corpus. Their result lists are merged using Reciprocal Rank Fusion, which reorders candidates by balancing their positional ranks. You do not need calibrated weights. You simply get the precision of exact match and the intuition of semantic search in a single ranked list.

Then we add a cross-encoder reranker. This is a separate model that scores each passage against the original query, producing a relevance signal far finer than either retriever alone. It adds about 50 milliseconds of latency. It increases recall by 15 percent. If you care about answer quality, that trade is non-negotiable.

Query Expansion: Fix the Search Before It Starts

Bad queries are the dirty secret of every retrieval system. Users do not write like your embedding space. They type "it broke." They paste truncated stack traces. They use internal jargon your index has never seen.

We transform the query before it ever touches the index. First, we expand a single query into three to five diverse search terms. If the original is "payment failed," we also search for "transaction error," "billing declined," and "charge unsuccessful