语义搜索
语义搜索会考虑查询的上下文和意图。在 OpenSearch 中,语义搜索通过文本嵌入模型实现。语义搜索会创建密集向量(浮点数列表),并将数据摄取到向量索引中。
先决条件
在使用语义搜索之前,您必须设置一个文本嵌入模型。有关更多信息,请参阅选择模型。
配置语义搜索
有两种方法可以配置语义搜索
- 自动化工作流(推荐用于快速设置):以最少配置自动创建摄取管道和索引。
- 手动设置(推荐用于自定义配置):手动配置每个组件以获得更大的灵活性和控制。
- 使用语义字段(推荐用于快速设置和可选定制):使用
semantic
字段手动配置索引,以简化设置过程,同时仍允许一定程度的配置。
自动化工作流
OpenSearch 提供了一个工作流模板,可自动创建摄取管道和索引。创建工作流时,您必须为已配置的模型提供模型 ID。请查看语义搜索工作流模板的默认设置,以确定是否需要更新任何参数。例如,如果模型维度与默认值(1024
)不同,请在 output_dimension
参数中指定模型的维度。要创建默认语义搜索工作流,请发送以下请求
POST /_plugins/_flow_framework/workflow?use_case=semantic_search&provision=true
{
"create_ingest_pipeline.model_id": "mBGzipQB2gmRjlv_dOoB"
}
OpenSearch 会为创建的工作流返回一个工作流 ID:
{
"workflow_id" : "U_nMXJUBq_4FYQzMOS4B"
}
要检查工作流状态,请发送以下请求:
GET /_plugins/_flow_framework/workflow/U_nMXJUBq_4FYQzMOS4B/_status
工作流完成后,state
将变为 COMPLETED
。该工作流创建以下组件
- 一个名为
nlp-ingest-pipeline
的摄入管道 - 一个名为
my-nlp-index
的索引
您现在可以继续步骤 3 和 4,将文档摄取到索引中并搜索索引。
手动设置
要手动配置语义搜索,请按照以下步骤操作
步骤 1:创建摄入管道
为了生成向量嵌入,您需要创建一个摄取管道,其中包含一个text_embedding
处理器,该处理器会将文档字段中的文本转换为向量嵌入。处理器的 field_map
决定了从哪些输入字段生成向量嵌入以及将嵌入存储到哪些输出字段。
以下示例请求创建了一个摄取管道,其中 passage_text
中的文本将被转换为文本嵌入,并且嵌入将存储在 passage_embedding
中
PUT /_ingest/pipeline/nlp-ingest-pipeline
{
"description": "A text embedding pipeline",
"processors": [
{
"text_embedding": {
"model_id": "bQ1J8ooBpBj3wT4HVUsb",
"field_map": {
"passage_text": "passage_embedding"
}
}
}
]
}
要将长文本拆分为段落,请在 text_embedding
处理器之前使用 text_chunking
摄取处理器。有关更多信息,请参阅文本分块。
步骤 2:创建用于摄取的索引
为了使用管道中定义的文本嵌入处理器,请创建一个向量索引,并将上一步中创建的管道添加为默认管道。确保 field_map
中定义的字段映射为正确的类型。继续前面的示例,passage_embedding
字段必须映射为 k-NN 向量,其维度与模型维度匹配。同样,passage_text
字段应映射为 text
。
以下示例请求创建了一个设置了默认摄入管道的向量索引
PUT /my-nlp-index
{
"settings": {
"index.knn": true,
"default_pipeline": "nlp-ingest-pipeline"
},
"mappings": {
"properties": {
"id": {
"type": "text"
},
"passage_embedding": {
"type": "knn_vector",
"dimension": 768,
"method": {
"engine": "lucene",
"space_type": "l2",
"name": "hnsw",
"parameters": {}
}
},
"passage_text": {
"type": "text"
}
}
}
}
有关创建向量索引及其支持方法的更多信息,请参阅创建向量索引。
步骤 3:将文档摄取到索引中
要将文档摄取到上一步创建的索引中,请发送以下请求:
PUT /my-nlp-index/_doc/1
{
"passage_text": "Hello world",
"id": "s1"
}
PUT /my-nlp-index/_doc/2
{
"passage_text": "Hi planet",
"id": "s2"
}
在文档被摄取到索引中之前,摄取管道会在文档上运行 text_embedding
处理器,为 passage_text
字段生成文本嵌入。索引文档包含 passage_text
字段(包含原始文本)和 passage_embedding
字段(包含向量嵌入)。
步骤 4:搜索索引
要在您的索引上执行向量搜索,请在搜索模型 API 或查询 DSL 查询中使用 neural
查询子句。您可以通过使用向量搜索过滤器来优化结果。
以下示例请求使用布尔查询来组合一个过滤器子句和两个查询子句——一个神经查询和一个 match
查询。script_score
查询为查询子句分配自定义权重
GET /my-nlp-index/_search
{
"_source": {
"excludes": [
"passage_embedding"
]
},
"query": {
"bool": {
"filter": {
"wildcard": { "id": "*1" }
},
"should": [
{
"script_score": {
"query": {
"neural": {
"passage_embedding": {
"query_text": "Hi world",
"model_id": "bQ1J8ooBpBj3wT4HVUsb",
"k": 100
}
}
},
"script": {
"source": "_score * 1.5"
}
}
},
{
"script_score": {
"query": {
"match": {
"passage_text": "Hi world"
}
},
"script": {
"source": "_score * 1.7"
}
}
}
]
}
}
}
响应包含匹配的文档
{
"took" : 36,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.2251667,
"hits" : [
{
"_index" : "my-nlp-index",
"_id" : "1",
"_score" : 1.2251667,
"_source" : {
"passage_text" : "Hello world",
"id" : "s1"
}
}
]
}
}
在索引或字段上设置默认模型
neural
查询需要一个模型 ID 来生成向量嵌入。为了避免在每个神经查询请求中传递模型 ID,您可以在向量索引或字段上设置默认模型。
首先,创建一个包含 neural_query_enricher
请求处理器的搜索管道。要为索引设置默认模型,请在 default_model_id
参数中提供模型 ID。要为特定字段设置默认模型,请在 neural_field_default_id
映射中提供字段名称和相应的模型 ID。如果同时提供 default_model_id
和 neural_field_default_id
,则 neural_field_default_id
优先
PUT /_search/pipeline/default_model_pipeline
{
"request_processors": [
{
"neural_query_enricher" : {
"default_model_id": "bQ1J8ooBpBj3wT4HVUsb",
"neural_field_default_id": {
"my_field_1": "uZj0qYoBMtvQlfhaYeud",
"my_field_2": "upj0qYoBMtvQlfhaZOuM"
}
}
}
]
}
然后为您的索引设置默认模型
PUT /my-nlp-index/_settings
{
"index.search.default_pipeline" : "default_model_pipeline"
}
您现在可以在搜索时省略模型 ID
GET /my-nlp-index/_search
{
"_source": {
"excludes": [
"passage_embedding"
]
},
"query": {
"neural": {
"passage_embedding": {
"query_text": "Hi world",
"k": 100
}
}
}
}
响应包含这两个文档
{
"took" : 41,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.22762,
"hits" : [
{
"_index" : "my-nlp-index",
"_id" : "2",
"_score" : 1.22762,
"_source" : {
"passage_text" : "Hi planet",
"id" : "s2"
}
},
{
"_index" : "my-nlp-index",
"_id" : "1",
"_score" : 1.2251667,
"_source" : {
"passage_text" : "Hello world",
"id" : "s1"
}
}
]
}
}
使用语义字段
要使用 semantic
字段手动配置语义搜索,请按照以下步骤操作。有关更多信息,包括使用 semantic
字段时的限制,请参阅语义字段类型。
步骤 1:创建带有语义字段的索引
创建索引并在 semantic
字段中指定 model_id
。在此示例中,semantic
字段为 passage_text
。OpenSearch 会根据模型配置自动创建相应的嵌入字段。不需要摄取管道——OpenSearch 会在索引期间使用指定模型自动生成嵌入
PUT /my-nlp-index
{
"settings": {
"index.knn": true
},
"mappings": {
"properties": {
"id": {
"type": "text"
},
"passage_text": {
"type": "semantic",
"model_id": "9kPWYJcBmp4cG9LrbAvW"
}
}
}
}
创建索引后,您可以检索其映射以验证嵌入字段是否已自动创建
GET /my-nlp-index/_mapping
{
"my-nlp-index": {
"mappings": {
"properties": {
"id": {
"type": "text"
},
"passage_text": {
"type": "semantic",
"model_id": "9kPWYJcBmp4cG9LrbAvW",
"raw_field_type": "text"
},
"passage_text_semantic_info": {
"properties": {
"embedding": {
"type": "knn_vector",
"dimension": 384,
"method": {
"engine": "faiss",
"space_type": "l2",
"name": "hnsw",
"parameters": {}
}
},
"model": {
"properties": {
"id": {
"type": "text",
"index": false
},
"name": {
"type": "text",
"index": false
},
"type": {
"type": "text",
"index": false
}
}
}
}
}
}
}
}
}
步骤 2:将文档摄取到索引中
要将文档摄取到上一步创建的索引中,请发送以下请求:
PUT /my-nlp-index/_doc/1
{
"passage_text": "Hello world",
"id": "s1"
}
在文档被摄取到索引中之前,OpenSearch 会运行一个内置的摄取管道,该管道会生成嵌入并将其存储在 passage_text_semantic_info.embedding
字段中。要验证嵌入是否正确生成,您可以运行搜索请求来检索文档
GET /my-nlp-index/_doc/1
{
"_index": "my-nlp-index",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"passage_text": "Hello world",
"passage_text_semantic_info": {
"model": {
"name": "huggingface/sentence-transformers/all-MiniLM-L6-v2",
"id": "9kPWYJcBmp4cG9LrbAvW",
"type": "TEXT_EMBEDDING"
},
"embedding": [
-0.034477286,
...
]
},
"id": "s1"
}
}
步骤 3:搜索索引
要查询 semantic
字段的嵌入,请提供 semantic
字段的名称(在此示例中为 passage_text
)和查询文本。无需指定 model_id
——OpenSearch 会自动从索引映射中的字段配置中检索它,并重写查询以定位底层嵌入字段
GET /my-nlp-index/_search
{
"_source": {
"excludes": [
"passage_text_semantic_info"
]
},
"query": {
"neural": {
"passage_text": {
"query_text": "Hi world"
}
}
}
}
响应包含匹配的文档
{
"took": 48,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.7564365,
"hits": [
{
"_index": "my-nlp-index",
"_id": "1",
"_score": 0.7564365,
"_source": {
"passage_text": "Hello world",
"id": "s1"
}
}
]
}
}
后续步骤
- 浏览我们的语义搜索教程,了解如何构建 AI 搜索应用程序。