Skip to content

fix(semantic highlighter): add vector similarity queries and bbq_disk support #138140

Merged
mromaios merged 19 commits intoelastic:mainfrom
mromaios:fix_min_similarity_in_semantic_highlighter
Nov 24, 2025
Merged

fix(semantic highlighter): add vector similarity queries and bbq_disk support #138140
mromaios merged 19 commits intoelastic:mainfrom
mromaios:fix_min_similarity_in_semantic_highlighter

Conversation

@mromaios
Copy link
Contributor

@mromaios mromaios commented Nov 17, 2025

Closes: #136056

This PR improves the handling of vector based queries in the SemanticTextHighlighter.

  • The highlighter will now correctly extract the underlying vector data from queries wrapped in VectorSimilarityQuery (e.g. when a similarity threshold is set in a knn query) as well as vector queries on a bbq_disk dense vector type.

# Create an index
PUT test-index
{
  "mappings": {
    "properties": {
      "inference_field": {
        "type": "semantic_text",
        "inference_id": ".multilingual-e5-small-elasticsearch"
      }
    }
  }
}

# Add a document
POST test-index/_doc/1
{
  "inference_field": "This is a test sentence for semantic search"
}

# Issue a highlighting query  
GET /test-index/_search
{
  "from" : 0,
  "size" : 10,
  "sort" : [
    {
      "_score" : {
        "order" : "asc"
      }
    }
  ],
  "query" : {
    "knn" : {
      "field" : "inference_field",
      "query_vector_builder" : {
        "text_embedding" : {
          "model_text" : "sentence"
        }
      },
      "similarity": 0.8,
      "k" : 12,
      "num_candidates" : 90
    }
  },
  "highlight" : {
    "fields" : {
      "inference_field" : {
        "order" : "score",
        "number_of_fragments" : 1
      }
    }
  }
}

---------------------
{
  "took" : 1811,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "test-index",
        "_id" : "1",
        "_score" : 0.92290497,
        "_source" : {
          "inference_field" : "This is a test sentence for semantic search"
        },
        "highlight" : {
          "inference_field" : [
            "This is a test sentence for semantic search"
          ]
        },
        "sort" : [
          0.92290497
        ]
      }
    ]
  }
}
@mromaios mromaios changed the title fix(semantic highlighter): handle vector similarity queries in semantic text highlighter Nov 17, 2025
@mromaios mromaios self-assigned this Nov 17, 2025
@mromaios mromaios added >bug :SearchOrg/Relevance Label for the Search (solution/org) Relevance team labels Nov 17, 2025
@elasticsearchmachine
Copy link
Collaborator

Hi @mromaios, I've created a changelog YAML for you.

@mromaios mromaios marked this pull request as ready for review November 19, 2025 01:00
@elasticsearchmachine elasticsearchmachine added the Team:Search - Relevance The Search organization Search Relevance team label Nov 19, 2025
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/search-relevance (Team:Search - Relevance)

@mromaios mromaios marked this pull request as draft November 19, 2025 10:06
…om:mromaios/elasticsearch into fix_min_similarity_in_semantic_highlighter
@mromaios mromaios changed the title fix(semantic highlighter): handle vector similarity queries Nov 19, 2025
@mromaios mromaios requested a review from a team November 19, 2025 13:25
Copy link
Contributor

@Mikep86 Mikep86 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, I like the approach! I think this is good to go with a little more work on the tests 🚀

Comment on lines 269 to 294
@Override
public void visitLeaf(Query query) {
private void visitLeaf(Query query, Float similarity) {
if (query instanceof KnnFloatVectorQuery knnQuery) {
queries.add(fieldType.createExactKnnQuery(VectorData.fromFloats(knnQuery.getTargetCopy()), null));
queries.add(fieldType.createExactKnnQuery(VectorData.fromFloats(knnQuery.getTargetCopy()), similarity));
} else if (query instanceof KnnByteVectorQuery knnQuery) {
queries.add(fieldType.createExactKnnQuery(VectorData.fromBytes(knnQuery.getTargetCopy()), null));
queries.add(fieldType.createExactKnnQuery(VectorData.fromBytes(knnQuery.getTargetCopy()), similarity));
} else if (query instanceof MatchAllDocsQuery) {
queries.add(new MatchAllDocsQuery());
} else if (query instanceof DenseVectorQuery.Floats floatsQuery) {
queries.add(fieldType.createExactKnnQuery(VectorData.fromFloats(floatsQuery.getQuery()), null));
queries.add(fieldType.createExactKnnQuery(VectorData.fromFloats(floatsQuery.getQuery()), similarity));
} else if (query instanceof IVFKnnFloatVectorQuery ivfQuery) {
queries.add(fieldType.createExactKnnQuery(VectorData.fromFloats(ivfQuery.getQuery()), similarity));
} else if (query instanceof RescoreKnnVectorQuery rescoreQuery) {
visitLeaf(rescoreQuery.innerQuery(), similarity);
} else if (query instanceof VectorSimilarityQuery similarityQuery) {
visitLeaf(similarityQuery.getInnerKnnQuery(), similarityQuery.getSimilarity());
}
}

@Override
public void visitLeaf(Query query) {
visitLeaf(query, null);

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice use of recursion to solve the problem here ❤️

Comment on lines 246 to 278
// TODO: figure out why this fails
// @SuppressWarnings("unchecked")
// public void testDenseVectorWithDiscBBQ() throws Exception {
// var mapperService = createDefaultMapperService(useLegacyFormat);
// Map<String, Object> queryMap = (Map<String, Object>) queries.get("dense_vector_1");
// float[] vector = readDenseVector(queryMap.get("embeddings"));
// var fieldType = (SemanticTextFieldMapper.SemanticTextFieldType)
// mapperService.mappingLookup().getFieldType(SEMANTIC_FIELD_E5_DISK_BBQ);
//
// KnnVectorQueryBuilder knnQuery = new KnnVectorQueryBuilder(
// fieldType.getEmbeddingsField().fullPath(),
// vector,
// 10,
// 10,
// 10f,
// null,
// null
// );
// NestedQueryBuilder nestedQueryBuilder = new NestedQueryBuilder(fieldType.getChunksField().fullPath(), knnQuery, ScoreMode.Max);
// var shardRequest = createShardSearchRequest(nestedQueryBuilder);
// var sourceToParse = new SourceToParse("0", readSampleDoc(useLegacyFormat), XContentType.JSON);
//
// String[] expectedPassages = ((List<String>) queryMap.get("expected_with_similarity_threshold")).toArray(String[]::new);
// assertHighlightOneDoc(
// mapperService,
// shardRequest,
// sourceToParse,
// SEMANTIC_FIELD_E5_DISK_BBQ,
// expectedPassages.length,
// HighlightBuilder.Order.SCORE,
// expectedPassages
// );
// }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any insights on the problems with this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, me 😓🤦

Sooooo, of course we don't create embeddings in the UT, but read them from the gzipped file.

var sourceToParse = new SourceToParse("0", readSampleDoc(useLegacyFormat), XContentType.JSON);

which I hadn't updated to include the new field.

Copy link
Member

@kderusso kderusso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! On top of Mike's comments, I would love to see a yaml test added under 90_semantic_text_highlighter.yml as well as the associated BWC. Remember that for yaml tests we'll also need to make sure to create a feature/capability for Green CI 👍

mromaios and others added 2 commits November 21, 2025 23:18
…ec/test/inference/90_semantic_text_highlighter_bwc.yml

Co-authored-by: Kathleen DeRusso <kathleen.derusso@elastic.co>
@mromaios mromaios added the auto-backport Automatically create backport pull requests when merged label Nov 24, 2025
@mromaios mromaios merged commit 8603933 into elastic:main Nov 24, 2025
34 checks passed
@elasticsearchmachine
Copy link
Collaborator

💔 Backport failed

You can use sqren/backport to manually backport by running backport --upstream elastic/elasticsearch --pr 138140

@mromaios
Copy link
Contributor Author

💚 All backports created successfully

Status Branch Result
9.2

Questions ?

Please refer to the Backport tool documentation

mromaios added a commit to mromaios/elasticsearch that referenced this pull request Nov 24, 2025
… support (elastic#138140)

(cherry picked from commit 8603933)

# Conflicts:
#	x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java
mromaios added a commit to mromaios/elasticsearch that referenced this pull request Nov 25, 2025
… support (elastic#138140)

(cherry picked from commit 8603933)

# Conflicts:
#	x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java
#	x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/90_semantic_text_highlighter.yml
mromaios added a commit to mromaios/elasticsearch that referenced this pull request Nov 25, 2025
… support (elastic#138140)

(cherry picked from commit 8603933)

# Conflicts:
#	server/src/main/java/org/elasticsearch/search/vectors/IVFKnnFloatVectorQuery.java
#	x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java
#	x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/90_semantic_text_highlighter.yml
@mromaios
Copy link
Contributor Author

💚 All backports created successfully

Status Branch Result
9.1
8.19

Questions ?

Please refer to the Backport tool documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-backport Automatically create backport pull requests when merged backport pending >bug :SearchOrg/Relevance Label for the Search (solution/org) Relevance team Team:Search - Relevance The Search organization Search Relevance team v8.19.8 v9.1.8 v9.2.2 v9.3.0

4 participants