You need to enable JavaScript to run this app.
导航
云搜索混合检索 + Rerank 操作示例(使用豆包embedding模型)
最近更新时间:2025.06.20 16:37:46首次发布时间:2025.06.20 16:37:46
我的收藏
有用
有用
无用
无用

本文介绍如何通过火山引擎云搜索服务使用混合检索、Rerank 等功能。

原理介绍

在搜索应用中,传统的 Keyword Search 一直是主要的搜索方法,它适合精确匹配查询的场景,能够提供低延迟和良好的结果可解释性,但是 Keyword Search 并没有考虑上下文信息,可能产生不相关的结果。最近几年,基于向量检索技术的搜索增强技术 Semantic Search 越来越流行,通过使用机器学习模型将数据对象(文本、图像、音视频等)转化成向量,向量距离代表对象间的相似性,如果使用的模型和问题领域相关性高,则往往能更好地理解上下文和搜索意图,进而提高搜索结果的相关性,反之,如果模型和问题领域相关性不高,则效果会大打折扣。
Keyword Search 和 Semantic Search 都存在明显的优劣势,那么是否可以通过组合它们的优点来整体提高搜索的相关性?答案是,简单的算术组合并不能收到预期的效果,主要原因有两个:

  • 首先是不同类型查询的评分并不在同一个可比较的维度,因此不能直接进行简单的算术计算。
  • 其次是在分布式检索系统中,评分通常在分片级别,需要对所有分片的评分进行全局归一化处理。

综上,我们需要寻找一种理想的查询类型来解决这些问题,它能单独执行每个查询子句,同时收集分片级别的查询结果,最后对所有查询的评分进行归一化合并后返回最终的结果,这就是混合搜索(Hybrid Search) 方案。
暂时无法在飞书文档外展示此内容
通常一次混合搜索查询可以分为以下几步:

  1. 查询阶段:使用混合查询子句进行 Keyword Search 和 Semantic Search。
  2. 评分归一化和合并阶段,该阶段在查询阶段之后。
    1. 由于每种查询类型都会提供不同范围的评分,该阶段对每一个查询子句的评分结果执行归一化操作,支持的归一化方法有 min_max、l2、rrf。
    2. 对归一化后的评分进行组合,组合方法有 arithmetic_mean、geometric_mean、harmonic_mean。
  3. 根据组合后的评分对文档重新排序并返回给用户。

实现思路

从前面原理介绍,我们可以看到要实现一个混合检索应用,至少需要用到这些基础技术设施

  • 全文检索引擎
  • 向量检索引擎
  • 用于向量 Embedding 的机器学习模型
  • 将文本、音频、视频等数据转化成向量的数据管道
  • 融合排序

火山引擎云搜索构建在开源的 Elasticsearch 和 OpenSearch 项目上,从第一天上线就支持了完善成熟的文本检索和向量检索能力,同时针对混合搜索场景也进行了一系列的功能迭代和演进,提供了开箱即用的混合搜索解决方案。本文将以图像搜索应用为例,介绍如何借助火山引擎云搜索服务的解决方案快速开发一个混合搜索应用。
暂时无法在飞书文档外展示此内容
其端到端流程概括如下:

  1. 配置创建相关对象
    1. Ingestion Pipeline:支持自动调用模型把图片转换向量并存到索引中
    2. Search Pipeline:支持把文本查询语句自动转换成向量以便进行相似度计算
    3. k-NN索引:存放向量的索引
  2. 将数据集数据写入 OpenSearch 实例,OpenSearch 会自动调用机器学习模型将文本转为 Embedding 向量。
  3. Client 端发起混合搜索查询时,OpenSearch 调用机器学习模型将传入的查询转为 Embedding 向量。
  4. OpenSearch 执行混合搜索请求处理,组合 Keyword Seach 和 Semantic Seach 的评分,返回搜索结果。

基础环境准备

初始化云搜索实例

部署云搜索实例

  • 创建实例,选择 OpenSearch 2.9.0
  • 【可选步骤】 "实例公网访问" 和 "Dashboards 公网访问"
  • 实例创建成功

启用云搜索相关插件

后面在写入/检索数据时使用到了推理服务,所以需要启用 OpenSearch 的相关插件。

插件安装完成后,实例状态变为 “运行中”。

创建 ML 服务

ML 服务创建成功:

部署 Embedding 模型

创建推理服务

说明

本示例中选用了 Doubao-embedding-large 模型,实际使用请根据实际需求选择合适模型。

  • 创建完成

获取 Embedding 服务的调用信息

后文在 OpenSearch 中创建 Pipeline 时使用该推理服务的的访问地址。

登录到 OpenSearch Dashboards

OpenSearch 实例中 可视化配置 -> 公网访问地址

创建 OpenSearch 索引

本示例创建一个名为 text_index 的索引。

PUT /text_index
{
  "settings": {
    "index": {
      "number_of_shards": "1",
      "number_of_replicas": "1",
      "refresh_interval": "10s",
      "knn": "true",          # 注意使用该配置,否则可能会导致向量检索失效
      "indexing": {
        "slowlog": {
          "level": "info",
          "threshold": {
            "index": {
              "warn": "200ms",
              "trace": "20ms",
              "debug": "50ms",
              "info": "100ms"
            }
          },
          "source": "1000"
        }
      },
      "search": {
        "slowlog": {
          "level": "info",
          "threshold": {
            "query": {
              "warn": "500ms",
              "trace": "50ms",
              "debug": "100ms",
              "info": "200ms"
            },
            "fetch": {
              "warn": "200ms",
              "trace": "50ms",
              "debug": "80ms",
              "info": "100ms"
            }
          }
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "text": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "text_knn": {
        "type": "knn_vector",
         "dimension": 4096,     ## 需要和推理服务中选择的 embedding 模型 dimension 一致。 
        "method": {
          "engine": "nmslib",
          "space_type": "cosinesimil",
          "name": "hnsw",
          "parameters": {}
        }
      }
    }
  }
}

如需修改为 DiskANN 配置,可参考 使用 DiskANN 向量引擎

创建 OpenSearch Embedding Pipeline

前置步骤

创建api-key

API Key 是推理服务接口调用时进行身份认证的关键信息,在正式调用推理服务之前需要创建 API Key,请注意妥善保管 API Key,请勿泄露。

获取embedding模型调用信息

创建 Pipeline 时使用该推理服务的的访问地址以及model信息。

创建 OpenSearch Insert Pipeline

本示例创建一个名为post_embedding 的 pipeline,该 Pipeline 在向 OpenSearch 中写入数据时使用。

PUT _ingest/pipeline/post_embedding
{
  "description": "model embedding pipeline for community inference",
  "processors": [
    {
      "remote_text_embedding": {
          "remote_config" : {
               "method" : "POST",
               "url" : "http://ark.cn-beijing.volces.com/api/v3/embeddings", ## 豆包embedding服务固定url
               "params" : { },
               "headers" : {
                   "Content-Type" : "application/json",
                   "Authorization": "Bearer {api—key}" ##创建的api-key
               },
               "advance_request_body" : {
                   "model" : "ep-xxxxxxx-xxxxxx" ##上一步embedding推理服务调用信息中的model字段值
               }
          }, 
          
          "field_map": {  
              "text": "text_knn"
          }
       } 
    }
  ]
}

创建 OpenSearch Search Pipeline

本示例创建一个名为search_pipeline 的 pipeline,该 Pipeline 在 OpenSearch 检索数据时使用。

PUT _search/pipeline/search_pipeline
{
    "description": "text embedding pipeline for remote inference",
    "request_processors": [
      {
        "remote_embedding": {
          "remote_config": {
            "method": "POST",
            "url" : "https://ark.cn-beijing.volces.com/api/v3/embeddings",
               "params" : { },
               "headers" : {
                   "Content-Type" : "application/json",
                   "Authorization": "Bearer {api—key}" ##创建的api-key
               },
               "advance_request_body" : {
                   "model" : "ep-xxxxxxx-xxxxxx" ##上一步embedding推理服务调用信息中的model字段值
               }
          }
        }
      }
    ],
    "phase_results_processors": [
        {
          "normalization-processor": {
            "normalization": {
              "technique": "min_max"
            },
            "combination": {
              "technique": "arithmetic_mean",
              "parameters": {}
            }
          }
        }
      ]
  }

写入测试数据

本示例中写入 100 条测试数据;

POST /text_index/_bulk?pipeline=post_embedding # 与前文创建的 insert pipeline 一致
{ "index": { "_id": 1 } }
{ "text": "口风琴" }
{ "index": { "_id": 2 } }
{ "text": "电子琴" }
{ "index": { "_id": 3 } }
{ "text": "吉他" }
{ "index": { "_id": 4 } }
{ "text": "钢琴" }
{ "index": { "_id": 5 } }
{ "text": "小提琴" }
{ "index": { "_id": 6 } }
{ "text": "鼓" }
{ "index": { "_id": 7 } }
{ "text": "萨克斯" }
{ "index": { "_id": 8 } }
{ "text": "低音提琴" }
{ "index": { "_id": 9 } }
{ "text": "电子合成器" }
{ "index": { "_id": 10 } }
{ "text": "风琴" }
{ "index": { "_id": 11 } }
{ "text": "竖琴" }
{ "index": { "_id": 12 } }
{ "text": "口琴" }
{ "index": { "_id": 13 } }
{ "text": "电子打击乐器" }
{ "index": { "_id": 14 } }
{ "text": "手风琴" }
{ "index": { "_id": 15 } }
{ "text": "鼓组" }
{ "index": { "_id": 16 } }
{ "text": "电子小号" }
{ "index": { "_id": 17 } }
{ "text": "铜管乐器" }
{ "index": { "_id": 18 } }
{ "text": "木管乐器" }
{ "index": { "_id": 19 } }
{ "text": "打击乐器" }
{ "index": { "_id": 20 } }
{ "text": "电子小号" }
{ "index": { "_id": 21 } }
{ "text": "牙刷" }
{ "index": { "_id": 22 } }
{ "text": "洗发水" }
{ "index": { "_id": 23 } }
{ "text": "漱口水" }
{ "index": { "_id": 24 } }
{ "text": "纸巾" }
{ "index": { "_id": 25 } }
{ "text": "洗衣粉" }
{ "index": { "_id": 26 } }
{ "text": "洗洁精" }
{ "index": { "_id": 27 } }
{ "text": "沐浴露" }
{ "index": { "_id": 28 } }
{ "text": "毛巾" }
{ "index": { "_id": 29 } }
{ "text": "垃圾袋" }
{ "index": { "_id": 30 } }
{ "text": "牙膏" }
{ "index": { "_id": 31 } }
{ "text": "香皂" }
{ "index": { "_id": 32 } }
{ "text": "洗面奶" }
{ "index": { "_id": 33 } }
{ "text": "清洁剂" }
{ "index": { "_id": 34 } }
{ "text": "洗手液" }
{ "index": { "_id": 35 } }
{ "text": "护肤霜" }
{ "index": { "_id": 36 } }
{ "text": "面膜" }
{ "index": { "_id": 37 } }
{ "text": "卫生纸" }
{ "index": { "_id": 38 } }
{ "text": "食品保鲜膜" }
{ "index": { "_id": 39 } }
{ "text": "水杯" }
{ "index": { "_id": 40 } }
{ "text": "器具清洗液" }
{ "index": { "_id": 41 } }
{ "text": "餐具消毒液" }
{ "index": { "_id": 42 } }
{ "text": "保鲜盒" }
{ "index": { "_id": 43 } }
{ "text": "便签纸" }
{ "index": { "_id": 44 } }
{ "text": "《活着》" }
{ "index": { "_id": 45 } }
{ "text": "《百年孤独》" }
{ "index": { "_id": 46 } }
{ "text": "《小王子》" }
{ "index": { "_id": 47 } }
{ "text": "《1984》" }
{ "index": { "_id": 48 } }
{ "text": "《骆驼祥子》" }
{ "index": { "_id": 49 } }
{ "text": "《红楼梦》" }
{ "index": { "_id": 50 } }
{ "text": "《西游记》" }
{ "index": { "_id": 51 } }
{ "text": "《三国演义》" }
{ "index": { "_id": 52 } }
{ "text": "《围城》" }
{ "index": { "_id": 53 } }
{ "text": "《悲惨世界》" }
{ "index": { "_id": 54 } }
{ "text": "《哈利·波特》" }
{ "index": { "_id": 55 } }
{ "text": "《无声告白》" }
{ "index": { "_id": 56 } }
{ "text": "《时间简史》" }
{ "index": { "_id": 57 } }
{ "text": "《追风筝的人》" }
{ "index": { "_id": 58 } }
{ "text": "《平凡的世界》" }
{ "index": { "_id": 59 } }
{ "text": "《三体》" }
{ "index": { "_id": 60 } }
{ "text": "《人类简史》" }
{ "index": { "_id": 61 } }
{ "text": "《安娜·卡列尼娜》" }
{ "index": { "_id": 62 } }
{ "text": "《月亮和六便士》" }
{ "index": { "_id": 63 } }
{ "text": "《未来简史》" }
{ "index": { "_id": 64 } }
{ "text": "北京" }
{ "index": { "_id": 65 } }
{ "text": "上海" }
{ "index": { "_id": 66 } }
{ "text": "广州" }
{ "index": { "_id": 67 } }
{ "text": "深圳" }
{ "index": { "_id": 68 } }
{ "text": "成都" }
{ "index": { "_id": 69 } }
{ "text": "重庆" }
{ "index": { "_id": 70 } }
{ "text": "杭州" }
{ "index": { "_id": 71 } }
{ "text": "武汉" }
{ "index": { "_id": 72 } }
{ "text": "西安" }
{ "index": { "_id": 73 } }
{ "text": "南京" }
{ "index": { "_id": 74 } }
{ "text": "青岛" }
{ "index": { "_id": 75 } }
{ "text": "郑州" }
{ "index": { "_id": 76 } }
{ "text": "福州" }
{ "index": { "_id": 77 } }
{ "text": "厦门" }
{ "index": { "_id": 78 } }
{ "text": "天津" }
{ "index": { "_id": 79 } }
{ "text": "大连" }
{ "index": { "_id": 80 } }
{ "text": "厦门" }
{ "index": { "_id": 81 } }
{ "text": "济南" }
{ "index": { "_id": 82 } }
{ "text": "青岛" }
{ "index": { "_id": 83 } }
{ "text": "昆明" }
{ "index": { "_id": 84 } }
{ "text": "哈尔滨" }
{ "index": { "_id": 85 } }
{ "text": "长沙" }
{ "index": { "_id": 86 } }
{ "text": "广州" }
{ "index": { "_id": 87 } }
{ "text": "合肥" }
{ "index": { "_id": 88 } }
{ "text": "南宁" }
{ "index": { "_id": 89 } }
{ "text": "太原" }
{ "index": { "_id": 90 } }
{ "text": "南昌" }
{ "index": { "_id": 91 } }
{ "text": "狗" }
{ "index": { "_id": 92 } }
{ "text": "猫" }
{ "index": { "_id": 93 } }
{ "text": "狐狸" }
{ "index": { "_id": 94 } }
{ "text": "大象" }
{ "index": { "_id": 95 } }
{ "text": "老虎" }
{ "index": { "_id": 96 } }
{ "text": "狮子" }
{ "index": { "_id": 97 } }
{ "text": "熊" }
{ "index": { "_id": 98 } }
{ "text": "长颈鹿" }
{ "index": { "_id": 99 } }
{ "text": "斑马" }
{ "index": { "_id": 100 } }
{ "text": "鳄鱼" }

能够从 OpenSearch 索引中查询出数据,代表写入成功。

数据检索测试

向量 + Keyword 混合检索

示例:

GET /text_index/_search?search_pipeline=search_pipeline # 注意与创建的 search pipeline 保持一致
{
    "_source": "text",
    "query": {
        "hybrid": {
            "queries": [
                {
                    "remote_neural": {
                        "text_knn": {
                            "query_text": "电吉他",
                            "k": 10
                        }
                    }
                },
                {
                    "intervals": {
                        "text": {
                            "match": {
                                "query": "电子琴",
                                "ordered": true,
                                "max_gaps": 3
                            }
                        }
                    }
                }
            ]
        }
    }
}

Rerank 测试

部署 Rerank 模型

操作步骤同上述部署 Embedding 模型步骤,此处不再赘述。

说明

本示例中选用了 BAAI/bge-reranker-v2-m3 模型,实际使用请根据实际需求选择合适模型。

复用embedding pipeline中创建的api key

API Key 是推理服务接口调用时进行身份认证的关键信息,在正式调用推理服务之前需要创建 API Key,请注意妥善保管 API Key,请勿泄露。

  • 导出 API Key 到本地环境变量:
export API_KEY="5cad31c3-**************-e5b7d3c9294d"

Rerank 接口调用

命令行调用 Rerank 接口

进入到 ML 服务 -> 推理服务 ,找到 Rerank 服务查看调用信息

curl http://{addr}/v1/rerank \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -X POST \
  -d '{
    "model": "BAAI/bge-reranker-v2-m3",
    "query": "电子琴",
    "top_n": 7,
    "documents": [
     "口风琴",
     "吉他",
     "北京",
     "牙刷",
     "电脑",
     "显示器",
     "电子琴",
     "电子",
     "鼓"
     ]
  }' | jq .

通过 OpenSearch Pipeline 使用 Rerank 模型

创建 OpenSearch Rerank pipeline

在 OpenSearch 中通过 Pipeline 使用 Rerank 模型需要通过 Rerank 推理服务的内部地址进行访问,首先需要获取 Rerank 访问地址。

  • 推理服务 -> Rerank 服务 -> 查看调用信息
  • 创建 Rerank pipeline

该 Pipeline 在查询 OpenSearch 时使用。本示例创建一个名为 search_pipeline_with_rerank 的 pipeline,包含 Embedding 和 Rerank 模型。

PUT _search/pipeline/search_pipeline_with_rerank
{
  "description": "text embedding pipeline for remote inference",
  "request_processors": [
    {
         "remote_embedding": {
          "remote_config": {
            "method": "POST",
            "url" : "https://ark.cn-beijing.volces.com/api/v3/embeddings",
               "params" : { },
               "headers" : {
                   "Content-Type" : "application/json",
                   "Authorization": "Bearer {api-key}" ##创建的api-key
               },
               "advance_request_body" : {
                   "model" : "ep-xxxxx-xxxxx" ##embedding推理服务,调用信息中的model字段值
               }
          }
        }
    }
  ]
  ,
  "phase_results_processors": [
    {
      "normalization-processor": {
        "normalization": {
          "technique": "min_max"
        },
        "combination": {
          "technique": "arithmetic_mean",
          "parameters": {
            "weights": [
              0.3,
              0.7
            ]
          }
        }
      }
    }
  ]
  ,
  "response_processors": [
    {
      "remote_rerank": {
        "ml_opensearch": {
          "remote_config": {
            "method": "POST",
            "url": "{rerank_url}",
            "params": {
              "token": "{api_key}"
            },
            "headers": {
              "Content-Type": "application/json"
            },
            "advance_request_body": {
              "model": "{rerank_model}"
            }
          }
        },
        "context": {
          "document_fields": [
            "text"
          ]
        }
      }
    }
  ]
}

说明

请根据业务实际情况替换上述代码中的以下变量:

  • search_pipeline_with_rerank
  • api-key
  • ep-xxxxx-xxxxx
  • rerank_url
  • api_key
  • rerank_model

使用 OpenSearch Rerank pipeline

使用 Rerank pipeline 时在 Query 中需要添加 ext 信息(如下示例中的 search_pipeline_with_rerank 变量)。
其中 ext.remote_rerank.query_context.query_text 设置相应提问内容。

GET /text_index/_search?search_pipeline=search_pipeline_with_rerank
{
  "_source": "text",
  "query": {
    "hybrid": {
      "queries": [
        {
          "remote_neural": {
            "text_knn": {
              "query_text": "电吉他",
              "k": 10
            }
          }
        },
        {
          "intervals": {
            "text": {
              "match": {
                "query": "电子琴",
                "ordered": true,
                "max_gaps": 3
              }
            }
          }
        }
      ]
    }
  },
  "ext": {
    "remote_rerank": {
      "query_context": {
        "query_text": "哪个答案和电吉他、电子琴最匹配?"
        }
      }
    }
  }