使用 Elasticsearch 和 Go 进行混合搜索来打地鼠
了解如何使用 Elasticsearch 和 Elasticsearch Go 客户端结合关键字和矢量搜索实现混合搜索。
本系列的前几部分演示了如何使用 Elasticsearch Go 客户端进行传统的关键字搜索和矢量搜索。第三部分涉及混合搜索。我们将分享如何使用 Elasticsearch 和 Elasticsearch Go 客户端 结合矢量搜索和关键字搜索的 示例 。
准备工作
与本系列的第一部分一样,本示例也需要以下前提条件:
安装 Go 1.21 或更高版本
使用Go 文档中推荐的结构和软件包管理创建自己的 Go 仓库
创建自己的 Elasticsearch 集群,其中包含一组 基于啮齿类动物的页面 ,包括维基百科中与我们友好的 Gopher 的 页面 :

连接到 Elasticsearch
提醒一下,在我们的示例中,我们将使用 Go 客户端提供的Typed API。要为任何查询建立安全连接,都需要使用以下两种方法之一配置客户端:
云 ID 和 API 密钥(如果使用弹性云
群集 URL、用户名、密码和证书
连接到位于 Elastic Cloud 上的集群的过程如下:
func GetElasticsearchClient() (*elasticsearch.TypedClient, error) {
var cloudID = os.Getenv("ELASTIC_CLOUD_ID")
var apiKey = os.Getenv("ELASTIC_API_KEY")
var es, err = elasticsearch.NewTypedClient(elasticsearch.Config{
CloudID: cloudID,
APIKey: apiKey,
Logger: &elastictransport.ColorLogger{os.Stdout, true, true},
})
if err != nil {
return nil, fmt.Errorf("unable to connect: %w", err)
}
return es, nil
}然后,client 连接可用于搜索,这将在随后的章节中演示。
手动增强混合搜索
在组合任何一组搜索算法时,传统的方法是手动配置常量,以提高每种查询类型的效率。具体来说,为每个查询指定一个因子,然后将组合结果集与预期结果集进行比较,以确定查询的召回率。然后,我们对几组因子进行重复计算,选出最接近理想状态的因子。
例如,在两种查询类型中都指定Boost 字段,就可以将系数为0.8 的单一文本搜索查询与系数为0.2 的较低 knn 查询结合起来,如下例所示:
func HybridSearchWithBoost(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
var k = 10
var numCandidates = 10
var knnBoost float32 = 0.2
var queryBoost float32 = 0.8
res, err := client.Search().
Index("vector-search-rodents").
Knn(types.KnnSearch{
Field: "text_embedding.predicted_value",
Boost: &knnBoost,
K: &k,
NumCandidates: &numCandidates,
QueryVectorBuilder: &types.QueryVectorBuilder{
TextEmbedding: &types.TextEmbedding{
ModelId: "sentence-transformers__msmarco-minilm-l-12-v3",
ModelText: term,
},
}}).
Query(&types.Query{
Match: map[string]types.MatchQuery{
"title": {
Query: term,
Boost: &queryBoost,
},
},
}).
Do(context.Background())
if err != nil {
return nil, err
}
return getRodents(res.Hits.Hits)
}Boost 选项中为每个查询指定的因子会添加到文档得分中。通过将匹配查询的得分提高一个比 knn 查询更大的系数,关键词查询的结果将获得更高的权重。
手动提升的挑战在于,它需要进行调整,以找出能产生理想结果集的因素,这对不是搜索专家的人来说尤其如此。这只是一个尝试随机值的例子,看看什么值更接近你想要的结果集。
混合搜索中的互惠排名融合& Go 客户端
互惠排名融合(RRF)在 Elasticsearch 8.9 混合搜索技术预览版中发布。其目的是降低与调整相关的学习曲线,减少为优化结果集而试验各种因素的时间。
使用 RRF 时,文档得分通过以下算法混合得分重新计算:
score := 0.0
// q is a query in the set of queries (vector and keyword search)
for _, q := range queries {
// result(q) is the results
if document in result(q) {
// k is a ranking constant (default 60)
// rank(result(q), d) is the document's rank within result(q)
// range from 1 to the window_size (default 100)
score += 1.0 / (k + rank(result(q), d))
}
}
return score使用 RRF 的好处是,我们可以利用 Elasticsearch 中合理的默认值。排序常数k 默认为60 。在对大型数据集进行搜索时,为了在返回文档的相关性和查询性能之间进行权衡,每个考虑过的查询结果集的大小都限制在window_size 的值范围内,默认值为100 ,如文档中所述。
k 和windows_size 也可以在 Go 客户端Rank 方法中的Rrf 配置中进行配置,如下例所示:
func HybridSearchWithRRF(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
var k = 10
var numCandidates = 10
// Minimum required window size for the default result size of 10
var windowSize int64 = 10
var rankConstant int64 = 42
res, err := client.Search().
Index("vector-search-rodents").
Knn(types.KnnSearch{
Field: "text_embedding.predicted_value",
K: &k,
NumCandidates: &numCandidates,
QueryVectorBuilder: &types.QueryVectorBuilder{
TextEmbedding: &types.TextEmbedding{
ModelId: "sentence-transformers__msmarco-minilm-l-12-v3",
ModelText: term,
},
}}).
Query(&types.Query{
Match: map[string]types.MatchQuery{
"title": {Query: term},
},
}).
Rank(&types.RankContainer{
Rrf: &types.RrfRank{
WindowSize: &windowSize,
RankConstant: &rankConstant,
},
}).
Do(context.Background())
if err != nil {
return nil, err
}
return getRodents(res.Hits.Hits)
}结论
在此,我们讨论了如何使用 Elasticsearch Go 客户端 在 Elasticsearch 中将向量搜索和关键字搜索结合起来。
查看GitHub 仓库中本系列的所有代码。如果您还没有阅读本系列的所有代码,请查看第 1 部分和第 2 部分。
祝您打地鼠愉快




