Elasticsearch 混合检索一句 `retriever.rrf`,把语义召回与关键词召回融合到极致

发布于:2025-08-03 ⋅ 阅读:(11) ⋅ 点赞:(0)

1. 为什么要混合检索

  • 全文检索(Lexical)
    BM25 基于词频 & 逆文档频率,适合 代码、SKU、专用名词 等需要精确匹配的场景。

  • 语义检索(Semantic)
    稀疏/稠密向量捕捉上下文与同义词,能理解 自然语言问题,召回“不含关键词但含同义含义”的文档。

痛点:只用其中一种都容易漏召回或误召回;混合检索(Hybrid)把两种结果 加权融合,既保留精确命中,又能补充语义相关。

2. 先决条件与模型

组件 版本建议 作用
Elasticsearch ≥ 8.11 内置 semantic_text 字段 & RRF
ELSER v2 Inference API 默认稀疏向量模型,零样本即用
Kibana 同版本 Data Visualizer 上传 TSV

Serverless / Cloud 已默认带 Inference Endpoint;自托管需先 POST _ml/inference/<model_id>

3. 创建“双通道”Mapping

PUT /semantic-embeddings
{
  "mappings": {
    "properties": {
      "semantic_text": {           # 语义向量通道
        "type": "semantic_text"
      },
      "content": {                 # 原文关键词通道
        "type": "text",
        "copy_to": "semantic_text" # 自动送进向量流水线
      }
    }
  }
}
  • semantic_text 字段写入即异步调用 Inference 生成 稀疏向量
  • copy_to 可保留原文供 BM25,也让模型见到完整上下文

4. 数据落盘:上传 MS MARCO 子集

  1. 下载 msmarco-passagetest2019-top1000.tsv
  2. Kibana ➜ Machine Learning ➜ Data Visualizer
  3. Override settings:第一列 id,第二列 content
  4. Import 为 test-data(≈ 18 万行)

5. reindex 批量补嵌入

POST /_reindex?wait_for_completion=false
{
  "source": {
    "index": "test-data",
    "size" : 500                # 小 batch 快速观察
  },
  "dest": { "index": "semantic-embeddings" }
}
  • 拿到 task_id,可 GET _tasks/<id> 实时看进度
  • 数据量大可先小批量测试,确认 OK 再全量

提示:老索引若已在生产,可零停机开一个临时别名,用 reindex + alias swap 平滑切换。


6. Query DSL:RRF 融合

GET /semantic-embeddings/_search
{
  "retriever": {
    "rrf": {
      "retrievers": [
        {                   # 1⃣ 关键词 BM25
          "standard": {
            "query": {
              "match": {
                "content": "How to avoid muscle soreness while running?"
              }
            }
          }
        },
        {                   # 2⃣ 语义稀疏向量
          "standard": {
            "query": {
              "semantic": {
                "field": "semantic_text",
                "query": "How to avoid muscle soreness while running?"
              }
            }
          }
        }
      ],
      "rank_constant": 60   # 可选,默认 60,调大则更强调高位文档
    }
  },
  "size": 10                # 返回融合后前 10
}

返回字段亮点

"_score": 0.0328,     // RRF 加权得分
"_rank" : 1,          // 融合后排名
"semantic_text.inference.embeddings": { … } // 稀疏向量局部

7. ES|QL 写法简例

FROM semantic-embeddings
| RETRIEVE RRF(
    STANDARD(match(content, "muscle soreness running")),
    STANDARD(semantic(semantic_text, "muscle soreness running"))
  )
| LIMIT 10

ES|QL 的 RETRIEVE RRF() 语法目前仍在 8.x 试验标志下,若未开启可继续用 DSL。

8. 常见坑 & 调优

问题 解决方案
语义结果不佳 确认写入是否触发 Inference;ELSER 支持 ml.inference_id 显式指定
rank_constant 怎么调? 一般 10–100 之间实验:
• 调大 ➜ 关键词靠前
• 调小 ➜ 语义影响更大
条目多但 _score 很低 属 RRF 正常特性,可忽略,但排序仍正确
reindex 太慢 利用 pipeline + max_docs 分段;或先临时 alias 新写旧读,后台慢迁移

9. 小结

混合搜索流程三步走:

  1. 双字段索引 —— content 做 BM25,semantic_text 存稀疏向量
  2. 批量重建嵌入 —— 用 reindex 触发 Inference Pipeline
  3. RRF 融合查询 —— retriever.rrf 同时喂 BM25 与语义查询

这样即可在 Elastic Stack 中零依赖外部向量库,享受 “含义 + 关键词” 一拍即合的搜索体验。试试把你公司的 FAQ、文档、产品描述喂给它,你会惊喜发现搜索结果瞬间「既准又全」!