混合文本检索将关键词检索(BM25)与语义检索(向量嵌入)结合,兼得精确关键词匹配与基于含义的召回,并使用 Reciprocal Rank Fusion(RRF)把两种方法的结果合并为单一排序列表。本指南介绍如何将 pg_textsearch(BM25)与 pgvector 结合使用,两个扩展都需安装。对于高并发量、过滤或高级 pgvector 调优(二值量化、HNSW 参数),请参考 pgvector-semantic-search 技能。
pg_textsearch 是面向 PostgreSQL 的新型 BM25 文本检索扩展,完全开源,既可在 Tiger Cloud 上托管使用,也可用于自管理部署。它提供真正的 BM25 排序,相比 PostgreSQL 内置的 ts_rank 往往能提升相关性,并在规模较大时可能带来更好性能。注意:pg_textsearch 目前处于预发布阶段,尚不建议用于生产环境;当前支持 PostgreSQL 17 和 18。
何时使用混合检索:当查询混合了特定术语(产品名、编码、专有名词)与概念性意图时使用混合检索;当含义比精确措辞更重要时(例如“如何修复慢查询”应匹配“查询优化”)只使用语义检索;当精确匹配至关重要时(例如错误码、SKU、法律引用)只使用关键词检索。混合检索通常比任一单独方法的召回率更高,代价是复杂度略有增加。
数据准备:将文档切分为更小的片段(通常 500–1000 token),并为每个片段存储其嵌入。BM25 与语义检索作用于相同的片段——这样保持融合简单,因为比较的是同类内容。
Golden Path(默认设置):启用扩展(CREATE EXTENSION IF NOT EXISTS vector; CREATE EXTENSION IF NOT EXISTS pg_textsearch;);创建同时包含文本与嵌入的表;为关键词检索创建 BM25 索引,为语义检索创建 HNSW 索引。
BM25 注意事项:<@> 操作符返回负值,值越低匹配越好,RRF 使用排名位置,因此不影响融合;可通过 text_config 匹配内容语言(如 'french'、'german');BM25 有 k1(词频饱和,默认 1.2)与 b(长度归一化,默认 0.75)参数,默认值效果良好,仅在相关性差时调优;分区表中每个分区维护本地统计,分数在各分区之间不可直接比较,当需要分数可比性时应查询单独分区。
RRF 查询模式:每个结果的分数为 1 / (k + rank),其中 k 为常数(通常为 60),结果在各次检索间求和后重新排序。建议从客户端并行运行两个查询以降低延迟,然后在客户端融合结果。文中提供了关键词检索查询、语义检索查询,以及 Python 与 TypeScript 的客户端 RRF 融合代码。
RRF 参数:k 默认 60,是平滑常数,值越大越减小排名差异;每次检索候选数默认 50,越大召回越好但工作量越大;最终返回数默认 10。若相关结果被漏掉可增大候选数,k=60 很少需要调优。
关键词与语义的权重:要偏向某一方法,可对其 RRF 贡献乘以权重。文中给出 Python 与 TypeScript 示例,将语义检索权重设为关键词的 2 倍。建议从相等权重(各 1.0)开始,再根据实测相关性调整。
使用 ML 模型重排序:为获得最高质量,可加入基于交叉编码器的重排序步骤。交叉编码器(如 cross-encoder/ms-marco-MiniLM-L-6-v2)比双编码器更准确,但对初始检索而言太慢——只对候选集使用。以更高的 LIMIT(如 100)运行相同的并行查询,然后:用 RRF 融合结果(保留更多候选用于重排序);用交叉编码器重排序;按重排序分数返回前 10。文中给出 Python 的 sentence_transformers 示例与 TypeScript 的 Cohere SDK 示例(Jina、Voyage 等用法类似)。重排序是可选的——仅混合 RRF 就已显著优于单一方法检索。
性能考虑:为两列都建索引(文本上的 BM25 索引、嵌入上的 HNSW 索引);限制候选池,每种方法 50–100 个候选通常足够;并行运行查询,客户端并行可降低延迟;监控延迟,混合检索会增加开销,确保两个索引都能放入内存。
使用 pgvectorscale 扩展:对于大规模数据集(1000 万以上向量)或带有选择性元数据过滤的工作负载,可考虑用 pgvectorscale 的 StreamingDiskANN 索引替代 HNSW 作为语义检索组件。使用 StreamingDiskANN 的场景:HNSW 无法放入内存的大数据集;按标签过滤的查询(如 tenant_id、category、tags);需要高性能过滤向量检索时。基于标签的过滤:StreamingDiskANN 支持在 smallint[] 标签列上建立过滤索引,标签与向量一起被索引,从而在不损失后过滤精度的情况下实现高效过滤检索。文中给出启用 pgvectorscale、创建带标签列的表、创建带标签过滤的 StreamingDiskANN 索引与 BM25 索引,以及使用 && (数组重叠)进行过滤语义检索的示例,并建议参考 pgvectorscale 文档了解过滤索引与调优参数的更多细节。
监控与调试:可临时 SET enable_seqscan = off 强制使用索引以验证(规划器在小表上可能偏好顺序扫描);分别用 EXPLAIN 验证 BM25 索引与 HNSW 索引是否被使用(查找 Index Scan using ... (bm25) / (hnsw));再 SET enable_seqscan = on 恢复正常运行;可通过查询 pg_indexes 与 pg_relation_size 检查索引大小。若在 enable_seqscan = off 时 EXPLAIN 仍显示顺序扫描,请确认索引存在且查询使用正确操作符(BM25 用 <@>,余弦用 <=>)。更多 pgvector 调试指导参见 pgvector-semantic-search 技能。
常见问题及处理:缺少精确匹配——关键词检索未返回,检查 BM25 索引是否存在、text_config 是否与内容语言匹配;语义结果差——嵌入模型不匹配,确保查询嵌入与存储嵌入使用同一模型;查询慢——候选池过大或缺少索引,减少内层 LIMIT,验证两个索引存在且被使用(EXPLAIN);结果偏斜——某一方法占主导,调整 RRF 权重,验证两次检索都返回合理候选。