全文向量混合检索结合了全文检索和纯向量检索,相较于单纯的全文检索或向量检索,其检索结果通常更加精确,相似度也更高。本文介绍如何使用Lindorm向量引擎的全文向量混合检索功能。
前提条件
准备工作
在使用高级特性前,您需要先安装Java High Level REST Client并连接搜索引擎。具体操作,请参见准备工作。
全文+向量双路召回(RRF融合检索)
在一些查询场景中,您需要综合考虑全文索引和向量索引的排序,根据一定的打分规则对各自返回的结果进一步进行加权计算,并得到最终的排名。
创建索引
以下示例使用hsnw算法。
String indexName = "vector_text_hybridSearch";
int dim = 3;
CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);
createIndexRequest.settings(Map.of(
"index", Map.of(
"number_of_shards", 2,
"knn", true)));
createIndexRequest.mapping(Map.of(
"_source", Map.of(
"excludes", new String[] { "vector1" }
),
"properties", Map.of(
"vector1", Map.of(
"type", "knn_vector",
"dimension", dim,
"data_type", "float",
"method", Map.of(
"engine", "lvector",
"name", "hnsw",
"space_type", "l2",
"parameters", Map.of(
"m", 24,
"ef_construction", 500
)
)
),
"text_field", Map.of(
"type", "text",
"analyzer", "ik_max_word"
),
"field1", Map.of(
"type", "long"
)
)
));
CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
System.out.println("createIndexResponse: " + createIndexResponse.index());
数据写入
// 准备批量请求
String index = "vector_text_hybridSearch";
BulkRequest bulkRequest = new BulkRequest();
// 添加多个 IndexRequest,每个 IndexRequest 表示一个文档
bulkRequest.add(new IndexRequest(index).id("1").source(Map.of(
"field1", 1, "field2", "flag1", "vector1", new float[]{2.5f, 2.3f, 2.4f}, "text_field", "hello test5")));
bulkRequest.add(new IndexRequest(index).id("2").source(Map.of(
"field1", 2, "field2", "flag1", "vector1", new float[]{2.6f, 2.3f, 2.4f}, "text_field", "hello test6 test5")));
bulkRequest.add(new IndexRequest(index).id("3").source(Map.of(
"field1", 3, "field2", "flag1", "vector1", new float[]{2.7f, 2.3f, 2.4f}, "text_field", "hello test7")));
bulkRequest.add(new IndexRequest(index).id("4").source(Map.of(
"field1", 4, "field2", "flag2", "vector1", new float[]{2.8f, 2.3f, 2.4f}, "text_field", "hello test8 test7")));
bulkRequest.add(new IndexRequest(index).id("5").source(Map.of(
"field1", 5, "field2", "flag2", "vector1", new float[]{2.9f, 2.3f, 2.4f}, "text_field", "hello test9")));
bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
if (bulkResponse.hasFailures()) {
// 处理可能的失败情况
System.out.println("Bulk operation had failures:");
System.out.println(bulkResponse.buildFailureMessage());
} else {
System.out.println("Bulk operation completed successfully.");
}
数据查询(融合查询)
RRF计算方式如下:
进行查询时系统会根据传入的rrf_rank_constant参数,对全文检索和向量检索分别获得的topK结果进行处理。对于每个返回的文档_id,使用公式1/(rrf_rank_constant + rank(i))
计算得分,其中rank(i)表示该文档在结果中的排名。
如果某个文档_id同时出现在全文检索和向量检索的topK结果中,其最终得分为两种检索方法计算得分之和。而仅出现在其中一种检索结果中的文档,则只保留该检索方法的得分。
以rrf_rank_constant = 1
为例,计算结果如下:
# doc | queryA | queryB | score
_id: 1 = 1.0/(1+1) + 0 = 0.5
_id: 2 = 1.0/(1+2) + 0 = 0.33
_id: 4 = 0 + 1.0/(1+2) = 0.33
_id: 5 = 0 + 1.0/(1+1) = 0.5
支持通过_search接口或_msearch_rrf接口进行融合查询,两种接口的对比如下:
接口 | 开源性 | 易读性 | 是否支持全文、向量检索比例调整 |
接口 | 开源性 | 易读性 | 是否支持全文、向量检索比例调整 |
_search | 兼容 | 不易读 | 支持 |
_msearch_rrf | 自研接口 | 易读 | 不支持 |
以下是两种场景下使用_search接口或_msearch_rrf接口的具体写法:
无标量字段过滤的场景
优点:兼容开源_search接口,支持通过rrf_knn_weight_factor参数调整全文检索与纯向量检索的比例。
缺点:写法较为复杂。
在ext.lvector扩展字段中,不设置filter_type,则表示该RRF检索只包含全文检索和纯向量检索,同时向量检索中无需进行标量字段的过滤。
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
Map<String, Object> queryBody = Map.of(
"knn", Map.of(
"vector1", Map.of(
"vector", new float[]{2.8f, 2.3f, 2.4f},
"filter", Map.of(
"match", Map.of(
"text_field", "test5 test6 test7 test8 test9"
)
),
"k", 10
)
));
searchSourceBuilder.query(QueryBuilders.wrapperQuery(new Gson().toJson(queryBody)));
Map<String, String> ext = Map.of(
"hybrid_search_type", "filter_rrf",
"rrf_rank_constant", "60",
"rrf_knn_weight_factor", "0.5");
searchSourceBuilder.ext(Collections.singletonList(new LVectorExtBuilder("lvector", ext)));
// _source接口可以进行配置,使返回值携带属性
searchSourceBuilder.fetchSource(new FetchSourceContext(true));
searchRequest.source(searchSourceBuilder);
searchRequest.indices(index);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(searchResponse);
类LVectorExtBuilder
的定义代码,请参见LVectorExtBuilder。
优点:写法较清晰。
缺点:不兼容开源_search接口,不支持调整全文检索与纯向量检索的比例。
Request searchRequest = new Request("GET", "/_msearch_rrf?re_score=true&rrf_rank_constant=60&pretty");
String jsonString = """
{"index": "vector_text_hybridSearch"}
{"size":10,"_source":false, "query":{"match":{"text_field":"test5 test6 test7 test8 test9"}}}
{"index": "vector_text_hybridSearch"}
{"size":10,"_source":false,"query":{"knn":{"vector1":{"vector":[2.8,2.3,2.4],"k":10}}}}
""";
searchRequest.setJsonEntity(jsonString);
Response response = client.getLowLevelClient().performRequest(searchRequest);
String responseBody = EntityUtils.toString(response.getEntity());
System.out.println("msearchRrf responseBody = " + responseBody);
连接参数中必须添加re_score=true
。
包含标量字段过滤场景
在ext.lvector扩展字段中设置filter_type参数,则表示该RRF检索中的向量检索还需进行标量字段的过滤。
RRF融合检索时,如果希望携带filter过滤条件,需要将全文检索的query条件和用于过滤的filter条件分别设置到两个bool表达式中,通过bool.must进行连接。must中的第一个bool表达式将用于全文检索,计算全文匹配度得分。must中第二个bool filter表达式将用于knn检索的过滤条件。
String index = "vector_text_hybridSearch";
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
Map<String, Object> queryBody = Map.of(
"knn", Map.of(
"vector1", Map.of(
"vector", List.of(2.8, 2.3, 2.4),
"filter", Map.of(
"bool", Map.of(
"must", List.of(
Map.of(
"bool", Map.of(
"must", List.of(
Map.of(
"match", Map.of(
"text_field", Map.of(
"query", "test5 test6 test7 test8 test9"
)
)
)
)
)
),
Map.of(
"bool", Map.of(
"filter", List.of(
Map.of(
"range", Map.of(
"field1", Map.of(
"gt", 2
)
)
)
)
)
)
)
)
),
"k", 10
)
)
);
searchSourceBuilder.query(QueryBuilders.wrapperQuery(new Gson().toJson(queryBody)));
Map<String, String> ext = Map.of(
"filter_type", "efficient_filter",
"hybrid_search_type", "filter_rrf",
"rrf_rank_constant", "60",
"rrf_knn_weight_factor", "0.5");
searchSourceBuilder.ext(Collections.singletonList(new LVectorExtBuilder("lvector", ext)));
// _source接口可以进行配置,使返回值携带属性
searchSourceBuilder.fetchSource(new FetchSourceContext(true));
searchRequest.source(searchSourceBuilder);
searchRequest.indices(index);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(searchResponse);
类LVectorExtBuilder
的定义代码,请参见LVectorExtBuilder。
自研_msearch_rrf接口仅支持通过Java Low Level REST Client调用,具体操作,请参见包含标量字段过滤场景。
- 本页导读 (1)
- 前提条件
- 准备工作
- 全文+向量双路召回(RRF融合检索)
- 创建索引
- 数据写入
- 数据查询(融合查询)