作者:来自 Elastic Carlos Delgado
在 ES|QL 中对你的 dense_vector 数据进行向量搜索。
更多阅读:Elasticsearch:使用推理端点及语义搜索演示
动手体验 Elasticsearch:查看我们的 sample notebooks,开始免费的 cloud trial,或者在本地机器上试用 Elastic。
你现在可以使用 Elasticsearch Query Language (ES|QL) 进行 vector 搜索!ES|QL 可以检索、过滤并评分 dense_vector 字段。使用 k-nearest neighbors (KNN) 查询进行快速、近似的最近邻搜索,适合大规模数据。使用 vector similarity 函数进行精确搜索和自定义评分。
在 ES|QL 中使用 KNN 比在 Query DSL 中更简单。prefilters 和每个 shard 要检索的结果数量会从 ES|QL 查询自动推断。
什么是 vector 搜索?
现代搜索不再仅限于精确的 keyword 匹配。用户希望系统能理解含义,而不仅仅是文本。这就是向量 embeddings 和 Elasticsearch 的 dense_vector 字段类型的用武之地。
在 Elasticsearch 中使用 vector 搜索最简单的方法是使用 semantic_text 字段类型。它允许你自动生成 text embeddings、执行语义搜索,并处理 chunking。然而,当以下情况出现时,你可能想使用 dense_vector:
- 你已经在使用 dense_vector 字段。
- 你使用的是非文本数据,比如 images、sound 或 video。
- 你需要在向 Elasticsearch ingestion 之前单独生成 embeddings。
- 你需要执行自定义或高级评分。
- 你希望进行精确的 nearest neighbors 搜索。
dense_vector 存储由 machine learning models 生成的数值 embeddings。这些 embeddings 捕捉语义相似性:含义相近的文档在高维空间中向量彼此接近。
使用 vectors,你可以构建:
ES|QL 为 Elasticsearch 带来了 query-piped 体验的强大功能。对 dense_vector 字段的一流支持意味着你现在可以在 ES|QL 中直接使用 vectors 检索、过滤、评分和搜索,同时处理文本和非文本数据。
本文将演示如何在 ES|QL 中使用 dense_vector 字段,从基本检索到近似和精确相似度搜索,以及如何将 vector 搜索作为 hybrid search 策略的一部分。
基础:检索 vector 数据
假设你有一个 mapping 类似于:
`
1. {
2. "mappings": {
3. "properties": {
4. "title": { "type": "text" },
5. "category": { "type": "keyword" },
6. "content_vector": {
7. "type": "dense_vector",
8. "dims": 384,
9. "similarity": "cosine"
10. }
11. }
12. }
13. }
`AI写代码
你可以像检索其他列一样检索 vector 字段:
`
1. FROM documents
2. | KEEP title, content_vector
3. | LIMIT 5
`AI写代码
请记住,vectors 可能很大。用于探索和调试时,检索 vector 数据可能有用,但在生产环境中,除非确实必要,否则应避免返回完整的 vector 数据。
你可以使用熟悉的 ES|QL 构造来检查有多少行包含 vector 信息:
`
1. FROM documents
2. | WHERE content_vector IS NOT NULL
3. | STATS non_null = COUNT(*)
`AI写代码
近似搜索使用 KNN
Vector search 意味着找到与给定 query vector 最相似的 vectors。
对于大型数据集,最常见的方法是近似最近邻(approximate nearest neighbor - ANN)搜索。ANN 通过使用允许快速计算相似 vectors 的数据结构来寻找最相似的 vectors,但不能保证考虑到所有 vectors。
ES|QL 通过 KNN 函数提供近似搜索:
`
1. FROM documents MEDATADATA _score
2. | WHERE KNN(content_vector, [0.12, -0.03, 0.98, ...])
3. | SORT _score DESC
4. | KEEP title, _score
5. | LIMIT 10
`AI写代码
这个简单示例:
- 在 content_vector 字段上搜索。
- 使用密集向量查询 [0.12, -0.03, 0.98, ...] 来搜索与其相似的向量。
- 按分数排序,使用
KNN函数填充的 METADATA _score 属性。 - 仅保留 title 和 score,因为 content_vector 字段不需要返回,这样可以避免加载它的内容。
- 使用 LIMIT 检索前 10 个元素,这会自动将 KNN 函数的 k 设置为 10。
KNN 函数可以通过以下选项进一步自定义:
`
1. FROM documents MEDATADATA _score
2. | WHERE KNN(content_vector, [0.12, -0.03, 0.98, ...], {"k": 20, "min_candidates": 100, "rescore_oversample": 4.0})
3. | SORT _score DESC
4. | KEEP title, _score
5. | LIMIT 10
`AI写代码
查看 KNN 函数的命名参数,可以获得可用参数的完整说明。
将 KNN 与过滤器结合使用
你可以缩小向量搜索的候选集合:
`
1. FROM documents METADATA _score
2. | WHERE category == "tutorial"
3. | WHERE KNN(content_vector, [0.12, -0.03, 0.98, ...])
4. | SORT _score DESC
5. | LIMIT 10
6. | KEEP title, category, _score
`AI写代码
当然,你可以使用任何其他 WHERE 子句来过滤结果,或者将 KNN 作为过滤表达式的一部分:
`
1. FROM documents METADATA _score
2. | WHERE published_date > NOW() - 1 hour AND LENGTH(category) > 10 AND KNN(content_vector, [0.12, -0.03, 0.98, ...])
`AI写代码
KNN 使用简单
在 ES|QL 中使用 KNN 更简单。你不需要显式地为查询指定 prefilters 或 k。
Prefilters 用于确保 KNN 查询返回预期数量的结果。Prefilters 会直接应用于 KNN 搜索,而不是在查询之后应用。
请记住,KNN 会返回请求的 top k 结果。如果在 KNN 查询之后应用过滤器,查询返回的一些结果可能会被过滤掉。如果发生这种情况,我们将检索到的结果数量少于预期。
Query DSL 的 knn 查询包含一个用于指定 prefilters 的部分:
`
1. POST my-image-index/_search
2. {
3. "query" : {
4. "knn": {
5. "field": "content_vector",
6. "query_vector": [0.12, -0.03, 0.98, ...],
7. "filter" : {
8. "term" : { "category" : "tutorial" }
9. }
10. }
11. }
12. }
`AI写代码
在 ES|QL 中使用 KNN 时,你不需要关心 prefilters。所有过滤器都会作为 KNN 函数的 prefilters 应用,所以不需要将它们作为特定选项或命令指定;只需使用 WHERE,让 ES|QL 自动处理即可!
KNN 还允许指定每个 shard 检索的结果数量,也就是 k 参数。类似于 Query DSL,k 默认等于查询中指定的 LIMIT。
精确搜索使用 vector similarity 函数
KNN 的设计目标是快速,这使它非常适合大规模数据集(数十万到数百万个向量)和对延迟敏感的应用。代价是结果是近似的,但通常非常准确。
有时你希望进行精确相似度计算,而不是近似搜索,例如:
- 当你的数据集较小时。
- 当查询中使用的过滤器非常严格,只选择数据集的小子集时。
ES|QL 提供了以下 vector similarity 函数:
使用这些函数,你可以计算查询向量与查询检索到的所有向量的相似度。
下面的查询使用与上面 KNN 示例相同的 mapping,但使用余弦相似度进行精确搜索:
`
1. FROM documents
2. | EVAL similarity = V_COSINE(content_vector, [0.12, -0.03, 0.98, ...])
3. | SORT similarity DESC
4. | KEEP title, similarity
5. | LIMIT 10
`AI写代码
这个查询:
- 使用 V_COSINE 向量相似度函数计算相似度。
- 根据计算出的相似度进行排序。
- 保留前 10 个最相似的结果。
语义搜索
进行语义搜索时,你会尝试将文本查询与向量匹配。当然,你可以先计算 embeddings 来获取查询向量,然后将查询向量直接提供给向量搜索。
但更简单的方法是让 Elasticsearch 使用 TEXT_EMBEDDING 函数为你计算 embeddings:
`
1. FROM documents METADATA _score
2. | WHERE KNN(content_vector, TEXT_EMBEDDING("my semantic query", inference_id)
3. | SORT _score DESC
4. | LIMIT 10
5. | KEEP title, _score
`AI写代码
TEXT_EMBEDDING 使用已经存在的 inference endpoint 自动计算 embeddings 并在你的查询中使用它们。
混合搜索
大多数搜索不仅依赖 vector 数据;它们还需要结合 lexical search,这样才能兼顾两者优势:
- Lexical 信息适合精确匹配单词和同义词,并提供用户正在寻找特定词的强信号。
- Vectors 捕捉含义和意图,使用相似短语或不在词汇上相关的术语。
结合 vector search 和 lexical search 最好使用 FORK 和 FUSE:
`
1. FROM documents METADATA _score, _id, _index
2. | FORK
3. (WHERE KNN(content_vector, TEXT_EMBEDDING("my query")) | SORT _score DESC | LIMIT 10)
4. (WHERE MATCH(title, "my query") | SORT _score DESC | LIMIT 10)
5. | FUSE
6. | SORT _score DESC
7. | LIMIT 10
`AI写代码
上面的查询:
这允许你完全控制想执行的查询、每个查询要获取的结果数量,以及如何组合结果。
查看我们的多阶段检索博客文章,了解现代搜索的工作原理以及通过 ES|QL 实现的简便方式。
自定义评分
使用 ES|QL 计算自定义评分很容易!只需使用 _score 元数据字段来计算你的自定义评分:
`
1. FROM documents METADATA _score
2. | WHERE KNN(content_vector, TEXT_EMBEDDING("my semantic query", inference_id)
3. | EVAL my_custom_score = _score * 1.5 + ...
4. | SORT my_custom_score DESC
5. | LIMIT 10
`AI写代码
如果你使用的是精确搜索,你已经有了向量相似度的评估,可以对其进行微调:
`
1. FROM documents
2. | EVAL similarity = V_COSINE(content_vector, [0.12, -0.03, 0.98, ...])
3. | EVAL my_custom_score = similarity * 1.5 + ...
4. | SORT my_custom_score DESC
5. | LIMIT 10
`AI写代码
与 Query DSL 的 script_score 相比,这种方法更简单、更迭代,并且完美适合 ES|QL 的执行流程。
使用 query 参数
在使用查询向量时,你可以像之前的示例那样直接在查询中指定它。但你可能已经注意到,我们使用了省略号(...)来表示还有更多数据。
Dense vectors 通常是高维的;它们可能有数百或数千个维度,因此在查询中直接复制粘贴查询向量会让理解或推理变得困难,因为你会在屏幕上看到成千上万的数值。
记住,你可以使用 ES|QL query 参数来为查询提供参数:
`
1. POST _query
2. {
3. "query": """
4. FROM documents
5. | WHERE KNN(content_vector, ?query_vector)
6. | SORT _score DESC
7. | KEEP title, _score
8. | LIMIT 10
9. """,
10. "params": [{"query_vector" : [0.12, -0.03, 0.98, ...]}]
11. }
`AI写代码
这有助于将查询逻辑与参数分离,让你可以专注于查询逻辑,而不是被具体参数束缚。
对向量使用 query 参数也更高效,因为这样通过 request parser 解析向量比通过 ES|QL parser 更快。
结论
ES|QL 不仅支持 vector search,还将其自然地融入数据查询方式中。它允许你用单一强大的语法处理文本、向量及二者之间的一切,包括:
- Vector search,包括 approximate 和 exact。
- Semantic search,使用文本对向量数据进行搜索。
- Hybrid search,结合文本搜索和向量搜索的最佳结果。
- Custom vector scoring,使用 EVAL 和 ES|QL 构造函数。
ES|QL 中的 vector search 比 Query DSL 更简单,通过推断 prefilters 和参数,并与 ES|QL 提供的丰富表达式无缝集成。
将 KNN 定义为多阶段检索管道的一部分只是查询的一环;你可以继续使用 filters,与其他文本函数结合进行 hybrid search,并在向量结果上应用 reranking 或 query completion。
我们将继续增加向量函数,用于 dense vectors 的向量运算和聚合,让你充分利用 ES|QL 的全部功能操作向量数据。
祝你 (vector) 搜索愉快!