Semantic text field type

edit

This functionality is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features.

The semantic_text field type automatically generates embeddings for text content using an inference endpoint. Long passages are automatically chunked to smaller sections to enable the processing of larger corpuses of text.

The semantic_text field type specifies an inference endpoint identifier that will be used to generate embeddings. You can create the inference endpoint by using the Create inference API. This field type and the semantic query type make it simpler to perform semantic search on your data.

If you don’t specify an inference endpoint, the inference_id field defaults to .elser-2-elasticsearch, a preconfigured endpoint for the elasticsearch service.

Using semantic_text, you won’t need to specify how to generate embeddings for your data, or how to index it. The inference endpoint automatically determines the embedding generation, indexing, and query to use.

If you use the preconfigured .elser-2-elasticsearch endpoint, you can set up semantic_text with the following API request:

resp = client.indices.create(
    index="my-index-000001",
    mappings={
        "properties": {
            "inference_field": {
                "type": "semantic_text"
            }
        }
    },
)
print(resp)
const response = await client.indices.create({
  index: "my-index-000001",
  mappings: {
    properties: {
      inference_field: {
        type: "semantic_text",
      },
    },
  },
});
console.log(response);
PUT my-index-000001
{
  "mappings": {
    "properties": {
      "inference_field": {
        "type": "semantic_text"
      }
    }
  }
}

To use a custom inference endpoint instead of the default .elser-2-elasticsearch, you must Create inference API and specify its inference_id when setting up the semantic_text field type.

resp = client.indices.create(
    index="my-index-000002",
    mappings={
        "properties": {
            "inference_field": {
                "type": "semantic_text",
                "inference_id": "my-openai-endpoint"
            }
        }
    },
)
print(resp)
const response = await client.indices.create({
  index: "my-index-000002",
  mappings: {
    properties: {
      inference_field: {
        type: "semantic_text",
        inference_id: "my-openai-endpoint",
      },
    },
  },
});
console.log(response);
PUT my-index-000002
{
  "mappings": {
    "properties": {
      "inference_field": {
        "type": "semantic_text",
        "inference_id": "my-openai-endpoint" 
      }
    }
  }
}

The inference_id of the inference endpoint to use to generate embeddings.

The recommended way to use semantic_text is by having dedicated inference endpoints for ingestion and search. This ensures that search speed remains unaffected by ingestion workloads, and vice versa. After creating dedicated inference endpoints for both, you can reference them using the inference_id and search_inference_id parameters when setting up the index mapping for an index that uses the semantic_text field.

resp = client.indices.create(
    index="my-index-000003",
    mappings={
        "properties": {
            "inference_field": {
                "type": "semantic_text",
                "inference_id": "my-elser-endpoint-for-ingest",
                "search_inference_id": "my-elser-endpoint-for-search"
            }
        }
    },
)
print(resp)
const response = await client.indices.create({
  index: "my-index-000003",
  mappings: {
    properties: {
      inference_field: {
        type: "semantic_text",
        inference_id: "my-elser-endpoint-for-ingest",
        search_inference_id: "my-elser-endpoint-for-search",
      },
    },
  },
});
console.log(response);
PUT my-index-000003
{
  "mappings": {
    "properties": {
      "inference_field": {
        "type": "semantic_text",
        "inference_id": "my-elser-endpoint-for-ingest",
        "search_inference_id": "my-elser-endpoint-for-search"
      }
    }
  }
}

Parameters for semantic_text fields

edit
inference_id
(Required, string) Inference endpoint that will be used to generate embeddings for the field. By default, .elser-2-elasticsearch is used. This parameter cannot be updated. Use the Create inference API to create the endpoint. If search_inference_id is specified, the inference endpoint will only be used at index time.
search_inference_id
(Optional, string) Inference endpoint that will be used to generate embeddings at query time. You can update this parameter by using the Update mapping API. Use the Create inference API to create the endpoint. If not specified, the inference endpoint defined by inference_id will be used at both index and query time.

Inference endpoint validation

edit

The inference_id will not be validated when the mapping is created, but when documents are ingested into the index. When the first document is indexed, the inference_id will be used to generate underlying indexing structures for the field.

Removing an inference endpoint will cause ingestion of documents and semantic queries to fail on indices that define semantic_text fields with that inference endpoint as their inference_id. Trying to delete an inference endpoint that is used on a semantic_text field will result in an error.

Text chunking

edit

Inference endpoints have a limit on the amount of text they can process. To allow for large amounts of text to be used in semantic search, semantic_text automatically generates smaller passages if needed, called chunks.

Each chunk refers to a passage of the text and the corresponding embedding generated from it. When querying, the individual passages will be automatically searched for each document, and the most relevant passage will be used to compute a score.

For more details on chunking and how to configure chunking settings, see Configuring chunking in the Inference API documentation.

Refer to this tutorial to learn more about semantic search using semantic_text and the semantic query.

Extracting Relevant Fragments from Semantic Text

edit

You can extract the most relevant fragments from a semantic text field by using the highlight parameter in the Search API.

resp = client.indices.create(
    index="test-index",
    query={
        "semantic": {
            "field": "my_semantic_field"
        }
    },
    highlight={
        "fields": {
            "my_semantic_field": {
                "type": "semantic",
                "number_of_fragments": 2,
                "order": "score"
            }
        }
    },
)
print(resp)
const response = await client.indices.create({
  index: "test-index",
  query: {
    semantic: {
      field: "my_semantic_field",
    },
  },
  highlight: {
    fields: {
      my_semantic_field: {
        type: "semantic",
        number_of_fragments: 2,
        order: "score",
      },
    },
  },
});
console.log(response);
PUT test-index
{
    "query": {
        "semantic": {
            "field": "my_semantic_field"
        }
    },
    "highlight": {
        "fields": {
            "my_semantic_field": {
                "type": "semantic",
                "number_of_fragments": 2,  
                "order": "score"           
            }
        }
    }
}

Specifies the maximum number of fragments to return.

Sorts highlighted fragments by score when set to score. By default, fragments will be output in the order they appear in the field (order: none).

Customizing semantic_text indexing

edit

semantic_text uses defaults for indexing data based on the inference endpoint specified. It enables you to quickstart your semantic search by providing automatic inference and a dedicated query so you don’t need to provide further details.

In case you want to customize data indexing, use the sparse_vector or dense_vector field types and create an ingest pipeline with an inference processor to generate the embeddings. This tutorial walks you through the process. In these cases - when you use sparse_vector or dense_vector field types instead of the semantic_text field type to customize indexing - using the semantic_query is not supported for querying the field data.

Updates to semantic_text fields

edit

Updates that use scripts are not supported for an index contains a semantic_text field. Even if the script targets non-semantic_text fields, the update will fail when the index contains a semantic_text field.

copy_to support

edit

The semantic_text field type can be the target of copy_to fields. This means you can use a single semantic_text field to collect the values of other fields for semantic search. Each value has its embeddings calculated separately; each field value is a separate set of chunk(s) in the resulting embeddings.

This imposes a restriction on bulk requests and ingestion pipelines that update documents with semantic_text fields. In these cases, all fields that are copied to a semantic_text field, including the semantic_text field value, must have a value to ensure every embedding is calculated correctly.

For example, the following mapping:

resp = client.indices.create(
    index="test-index",
    mappings={
        "properties": {
            "infer_field": {
                "type": "semantic_text",
                "inference_id": ".elser-2-elasticsearch"
            },
            "source_field": {
                "type": "text",
                "copy_to": "infer_field"
            }
        }
    },
)
print(resp)
PUT test-index
{
    "mappings": {
        "properties": {
            "infer_field": {
                "type": "semantic_text",
                "inference_id": ".elser-2-elasticsearch"
            },
            "source_field": {
                "type": "text",
                "copy_to": "infer_field"
            }
        }
    }
}

Will need the following bulk update request to ensure that infer_field is updated correctly:

resp = client.bulk(
    index="test-index",
    operations=[
        {
            "update": {
                "_id": "1"
            }
        },
        {
            "doc": {
                "infer_field": "updated inference field",
                "source_field": "updated source field"
            }
        }
    ],
)
print(resp)
const response = await client.bulk({
  index: "test-index",
  operations: [
    {
      update: {
        _id: "1",
      },
    },
    {
      doc: {
        infer_field: "updated inference field",
        source_field: "updated source field",
      },
    },
  ],
});
console.log(response);
PUT test-index/_bulk
{"update": {"_id": "1"}}
{"doc": {"infer_field": "updated inference field", "source_field": "updated source field"}}

Notice that both the semantic_text field and the source field are updated in the bulk request.

Limitations

edit

semantic_text field types have the following limitations:

  • semantic_text fields are not currently supported as elements of nested fields.
  • semantic_text fields can’t currently be set as part of Dynamic templates.
  • semantic_text fields can’t be defined as multi-fields of another field, nor can they contain other fields as multi-fields.