大多数团队构建第一个检索流水线的方式仍然如出一辙。他们选择一个固定的 token 限制(比如 512),将文档拆分为均匀的块,然后将这些块喂给向量数据库。在处理简单问题的少量数据集上,这看起来很神奇。但在生产环境中,它会彻底失效。

当一个条款在句子中间被切断时,法律合同就会碎成毫无意义的片段。如果一个 chunk 吞噬了三个不相关的函数,API 文档就会变成一团混乱的“噪音汤”。如果没有段落间的重叠,客户支持工单就会失去所有的叙事逻辑。结果是显而易见的:延迟增加、召回率低下,以及迫使生成器产生幻觉的回答。

我们拆解并重建了我们的检索层。结果是召回率从 78% 跃升至 95%,延迟降低了 62%,而且流水线终于表现得像真正的基础设施,而不是周末随手写的临时方案。以下是真正奏效的方法。

智能分块:结构重于 Token

第一个错误是假设每种文档的语言都是一样的。512 token 的分块对于叙述性散文是有意义的,但在其他地方几乎行不通。我们转向了一种尊重源文档结构的策略。

对于法律文档,我们使用递归分块(recursive chunking)。算法首先尝试根据章节(sections)和条款(articles)等高层边界进行拆分。如果一个章节仍然太长,它会寻找子章节,然后是段落,最后是句子。这保留了条款的逻辑嵌套。竞业禁止协议会保持完整,定义部分不会渗透到赔偿条款中。

API 文档需要具备结构感知能力的分块。函数签名、参数表及其示例请求应该放在一起。按固定 token 数量拆分往往会导致参数在一个 chunk 中,而示例在另一个 chunk 中。相反,我们按文档对象进行分块。一个 chunk 包含一个完整的端点或单个函数。这样,检索器就可以返回一个自包含的引用,从而真正回答问题。

客服工单自然适合语义分块(semantic chunking)。我们不再在 token 边界处切割,而是检测话题转换的位置。一个以登录投诉开始并转向账单问题的工单会被拆分为两个连贯的部分。每个部分都携带其所需的元数据,模型不再需要猜测用户真正关心的是哪个问题。

内部维基(wikis)则更加混乱。它们混合了散文、表格、图表和嵌入式讨论串。对于这些内容,我们使用智能体分块(agentic chunking)。一个小型语言模型会预读内容,并决定一个主题完整单元在哪里结束。这在数据摄取时会多花一点成本,但它消除了为每种新页面格式手动调整规则的人力琐事。

混合检索:全方位覆盖

向量搜索在捕捉模糊语义方面表现出色。如果你询问上传缓慢的问题,它会很乐意返回关于延迟和带宽的段落。但它在处理精确匹配方面却臭名昭著。如果开发者搜索错误代码 ERR_CONNECTION_REFUSED,稠密嵌入(dense embeddings)往往会将其视为普通的噪音。

BM25,这种经典的关键词算法,则恰恰相反。它能精准锁定字符串和稀有术语,却会忽略语义上的细微差别。一个关于“签署协议”的查询可能永远无法检索到标记为“执行合同”的内容。