From 9d9bdf657cc2f80b1f7f0926c686c48161461e30 Mon Sep 17 00:00:00 2001 From: Samiul Monir Date: Fri, 1 Aug 2025 00:39:49 -0400 Subject: [PATCH 1/5] initial implementation for multi_match --- .../xpack/inference/InferencePlugin.java | 4 +- ...nticMultiMatchQueryRewriteInterceptor.java | 108 ++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferencePlugin.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferencePlugin.java index 1a2de4cc6b31f..2970dce923300 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferencePlugin.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferencePlugin.java @@ -95,6 +95,7 @@ import org.elasticsearch.xpack.inference.mapper.SemanticTextFieldMapper; import org.elasticsearch.xpack.inference.queries.SemanticKnnVectorQueryRewriteInterceptor; import org.elasticsearch.xpack.inference.queries.SemanticMatchQueryRewriteInterceptor; +import org.elasticsearch.xpack.inference.queries.SemanticMultiMatchQueryRewriteInterceptor; import org.elasticsearch.xpack.inference.queries.SemanticQueryBuilder; import org.elasticsearch.xpack.inference.queries.SemanticSparseVectorQueryRewriteInterceptor; import org.elasticsearch.xpack.inference.rank.random.RandomRankBuilder; @@ -571,7 +572,8 @@ public List getQueryRewriteInterceptors() { return List.of( new SemanticKnnVectorQueryRewriteInterceptor(), new SemanticMatchQueryRewriteInterceptor(), - new SemanticSparseVectorQueryRewriteInterceptor() + new SemanticSparseVectorQueryRewriteInterceptor(), + new SemanticMultiMatchQueryRewriteInterceptor() ); } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java new file mode 100644 index 0000000000000..401609bf35422 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.queries; + +import org.elasticsearch.action.ResolvedIndices; +import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.MultiMatchQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryRewriteContext; +import org.elasticsearch.plugins.internal.rewriter.QueryRewriteInterceptor; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class SemanticMultiMatchQueryRewriteInterceptor implements QueryRewriteInterceptor { + + @Override + public QueryBuilder interceptAndRewrite(QueryRewriteContext context, QueryBuilder queryBuilder) { + if (queryBuilder instanceof MultiMatchQueryBuilder == false) { + return queryBuilder; + } + + MultiMatchQueryBuilder multiMatchBuilder = (MultiMatchQueryBuilder) queryBuilder; + ResolvedIndices resolvedIndices = context.getResolvedIndices(); + if (resolvedIndices == null) { + return queryBuilder; + } + + Map semanticFields = new HashMap<>(); + Map otherFields = new HashMap<>(); + Collection allIndicesMetadata = resolvedIndices.getConcreteLocalIndicesMetadata().values(); + + for (Map.Entry fieldEntry : multiMatchBuilder.fields().entrySet()) { + String fieldName = fieldEntry.getKey(); + boolean isSemanticInAnyIndex = false; + for (IndexMetadata indexMetadata : allIndicesMetadata) { + if (indexMetadata.getInferenceFields().containsKey(fieldName)) { + isSemanticInAnyIndex = true; + break; + } + } + if (isSemanticInAnyIndex) { + semanticFields.put(fieldName, fieldEntry.getValue()); + } else { + otherFields.put(fieldName, fieldEntry.getValue()); + } + } + + if (semanticFields.isEmpty()) { + return queryBuilder; + } + + BoolQueryBuilder rewrittenQuery = new BoolQueryBuilder(); + if (otherFields.isEmpty() == false) { + MultiMatchQueryBuilder lexicalPart = new MultiMatchQueryBuilder(multiMatchBuilder.value()); + lexicalPart.fields(otherFields); + lexicalPart.type(multiMatchBuilder.type()); + lexicalPart.operator(multiMatchBuilder.operator()); + lexicalPart.analyzer(multiMatchBuilder.analyzer()); + lexicalPart.slop(multiMatchBuilder.slop()); + if (multiMatchBuilder.fuzziness() != null) { + lexicalPart.fuzziness(multiMatchBuilder.fuzziness()); + } + lexicalPart.prefixLength(multiMatchBuilder.prefixLength()); + lexicalPart.maxExpansions(multiMatchBuilder.maxExpansions()); + lexicalPart.minimumShouldMatch(multiMatchBuilder.minimumShouldMatch()); + lexicalPart.fuzzyRewrite(multiMatchBuilder.fuzzyRewrite()); + if (multiMatchBuilder.tieBreaker() != null) { + lexicalPart.tieBreaker(multiMatchBuilder.tieBreaker()); + } + lexicalPart.lenient(multiMatchBuilder.lenient()); + lexicalPart.zeroTermsQuery(multiMatchBuilder.zeroTermsQuery()); + lexicalPart.autoGenerateSynonymsPhraseQuery(multiMatchBuilder.autoGenerateSynonymsPhraseQuery()); + lexicalPart.fuzzyTranspositions(multiMatchBuilder.fuzzyTranspositions()); + rewrittenQuery.should(lexicalPart); + } + + if (semanticFields.isEmpty() == false) { + BoolQueryBuilder semanticPart = new BoolQueryBuilder(); + for (Map.Entry fieldEntry : semanticFields.entrySet()) { + SemanticQueryBuilder semanticQueryBuilder = new SemanticQueryBuilder(fieldEntry.getKey(), multiMatchBuilder.value().toString(), true); + if (fieldEntry.getValue() != 1.0f) { + semanticQueryBuilder.boost(fieldEntry.getValue()); + } + semanticPart.should(semanticQueryBuilder); + } + rewrittenQuery.should(semanticPart); + } + + rewrittenQuery.boost(multiMatchBuilder.boost()); + rewrittenQuery.queryName(multiMatchBuilder.queryName()); + + return rewrittenQuery; + } + + @Override + public String getQueryName() { + return MultiMatchQueryBuilder.NAME; + } +} From 9a03651c338a3b14238207f41e8ecf56810db006 Mon Sep 17 00:00:00 2001 From: Samiul Monir Date: Fri, 1 Aug 2025 02:07:35 -0400 Subject: [PATCH 2/5] query type --- ...nticMultiMatchQueryRewriteInterceptor.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java index 401609bf35422..780e8f526d5b6 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java @@ -10,8 +10,10 @@ import org.elasticsearch.action.ResolvedIndices; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.DisMaxQueryBuilder; import org.elasticsearch.index.query.MultiMatchQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.QueryRewriteContext; import org.elasticsearch.plugins.internal.rewriter.QueryRewriteInterceptor; @@ -58,6 +60,38 @@ public QueryBuilder interceptAndRewrite(QueryRewriteContext context, QueryBuilde return queryBuilder; } + MultiMatchQueryBuilder.Type type = multiMatchBuilder.type(); + if (type == MultiMatchQueryBuilder.Type.CROSS_FIELDS || + type == MultiMatchQueryBuilder.Type.PHRASE || + type == MultiMatchQueryBuilder.Type.PHRASE_PREFIX) { + throw new IllegalArgumentException("Query type [" + type.parseField().getPreferredName() + "] is not supported with semantic_text fields"); + } + + if (type == MultiMatchQueryBuilder.Type.BEST_FIELDS) { + DisMaxQueryBuilder disMaxQuery = QueryBuilders.disMaxQuery(); + if (otherFields.isEmpty() == false) { + MultiMatchQueryBuilder lexicalPart = new MultiMatchQueryBuilder(multiMatchBuilder.value()); + lexicalPart.fields(otherFields); + lexicalPart.type(multiMatchBuilder.type()); + disMaxQuery.add(lexicalPart); + } + for (Map.Entry fieldEntry : semanticFields.entrySet()) { + SemanticQueryBuilder semanticQuery = new SemanticQueryBuilder(fieldEntry.getKey(), multiMatchBuilder.value().toString(), true); + if (fieldEntry.getValue() != 1.0f) { + semanticQuery.boost(fieldEntry.getValue()); + } + disMaxQuery.add(semanticQuery); + } + Float tieBreaker = multiMatchBuilder.tieBreaker(); + if (tieBreaker != null) { + disMaxQuery.tieBreaker(tieBreaker); + } + disMaxQuery.boost(multiMatchBuilder.boost()); + disMaxQuery.queryName(multiMatchBuilder.queryName()); + return disMaxQuery; + } + + // Fallback for other types like MOST_FIELDS and BOOL_PREFIX BoolQueryBuilder rewrittenQuery = new BoolQueryBuilder(); if (otherFields.isEmpty() == false) { MultiMatchQueryBuilder lexicalPart = new MultiMatchQueryBuilder(multiMatchBuilder.value()); From 497f20c8b4fb8348e96fb3855718ff26947ac7ca Mon Sep 17 00:00:00 2001 From: Samiul Monir Date: Fri, 1 Aug 2025 02:12:15 -0400 Subject: [PATCH 3/5] optimized --- ...nticMultiMatchQueryRewriteInterceptor.java | 136 +++++++++--------- 1 file changed, 71 insertions(+), 65 deletions(-) diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java index 780e8f526d5b6..493e85e3c3774 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java @@ -42,13 +42,8 @@ public QueryBuilder interceptAndRewrite(QueryRewriteContext context, QueryBuilde for (Map.Entry fieldEntry : multiMatchBuilder.fields().entrySet()) { String fieldName = fieldEntry.getKey(); - boolean isSemanticInAnyIndex = false; - for (IndexMetadata indexMetadata : allIndicesMetadata) { - if (indexMetadata.getInferenceFields().containsKey(fieldName)) { - isSemanticInAnyIndex = true; - break; - } - } + boolean isSemanticInAnyIndex = allIndicesMetadata.stream() + .anyMatch(indexMetadata -> indexMetadata.getInferenceFields().containsKey(fieldName)); if (isSemanticInAnyIndex) { semanticFields.put(fieldName, fieldEntry.getValue()); } else { @@ -67,72 +62,83 @@ public QueryBuilder interceptAndRewrite(QueryRewriteContext context, QueryBuilde throw new IllegalArgumentException("Query type [" + type.parseField().getPreferredName() + "] is not supported with semantic_text fields"); } - if (type == MultiMatchQueryBuilder.Type.BEST_FIELDS) { - DisMaxQueryBuilder disMaxQuery = QueryBuilders.disMaxQuery(); - if (otherFields.isEmpty() == false) { - MultiMatchQueryBuilder lexicalPart = new MultiMatchQueryBuilder(multiMatchBuilder.value()); - lexicalPart.fields(otherFields); - lexicalPart.type(multiMatchBuilder.type()); - disMaxQuery.add(lexicalPart); - } - for (Map.Entry fieldEntry : semanticFields.entrySet()) { - SemanticQueryBuilder semanticQuery = new SemanticQueryBuilder(fieldEntry.getKey(), multiMatchBuilder.value().toString(), true); - if (fieldEntry.getValue() != 1.0f) { - semanticQuery.boost(fieldEntry.getValue()); + QueryBuilder rewrittenQuery; + switch (type) { + case BEST_FIELDS: + DisMaxQueryBuilder disMaxQuery = QueryBuilders.disMaxQuery(); + if (otherFields.isEmpty() == false) { + disMaxQuery.add(createLexicalQuery(multiMatchBuilder, otherFields)); } - disMaxQuery.add(semanticQuery); - } - Float tieBreaker = multiMatchBuilder.tieBreaker(); - if (tieBreaker != null) { - disMaxQuery.tieBreaker(tieBreaker); - } - disMaxQuery.boost(multiMatchBuilder.boost()); - disMaxQuery.queryName(multiMatchBuilder.queryName()); - return disMaxQuery; + for (Map.Entry fieldEntry : semanticFields.entrySet()) { + disMaxQuery.add(createSemanticQuery(multiMatchBuilder.value().toString(), fieldEntry)); + } + Float tieBreaker = multiMatchBuilder.tieBreaker(); + if (tieBreaker != null) { + disMaxQuery.tieBreaker(tieBreaker); + } + rewrittenQuery = disMaxQuery; + break; + case MOST_FIELDS: + case BOOL_PREFIX: + default: + BoolQueryBuilder boolQuery = new BoolQueryBuilder(); + if (otherFields.isEmpty() == false) { + boolQuery.should(createLexicalQuery(multiMatchBuilder, otherFields)); + } + if (semanticFields.isEmpty() == false) { + boolQuery.should(createSemanticQuery(multiMatchBuilder.value().toString(), semanticFields)); + } + rewrittenQuery = boolQuery; + break; } - // Fallback for other types like MOST_FIELDS and BOOL_PREFIX - BoolQueryBuilder rewrittenQuery = new BoolQueryBuilder(); - if (otherFields.isEmpty() == false) { - MultiMatchQueryBuilder lexicalPart = new MultiMatchQueryBuilder(multiMatchBuilder.value()); - lexicalPart.fields(otherFields); - lexicalPart.type(multiMatchBuilder.type()); - lexicalPart.operator(multiMatchBuilder.operator()); - lexicalPart.analyzer(multiMatchBuilder.analyzer()); - lexicalPart.slop(multiMatchBuilder.slop()); - if (multiMatchBuilder.fuzziness() != null) { - lexicalPart.fuzziness(multiMatchBuilder.fuzziness()); - } - lexicalPart.prefixLength(multiMatchBuilder.prefixLength()); - lexicalPart.maxExpansions(multiMatchBuilder.maxExpansions()); - lexicalPart.minimumShouldMatch(multiMatchBuilder.minimumShouldMatch()); - lexicalPart.fuzzyRewrite(multiMatchBuilder.fuzzyRewrite()); - if (multiMatchBuilder.tieBreaker() != null) { - lexicalPart.tieBreaker(multiMatchBuilder.tieBreaker()); - } - lexicalPart.lenient(multiMatchBuilder.lenient()); - lexicalPart.zeroTermsQuery(multiMatchBuilder.zeroTermsQuery()); - lexicalPart.autoGenerateSynonymsPhraseQuery(multiMatchBuilder.autoGenerateSynonymsPhraseQuery()); - lexicalPart.fuzzyTranspositions(multiMatchBuilder.fuzzyTranspositions()); - rewrittenQuery.should(lexicalPart); + rewrittenQuery.boost(multiMatchBuilder.boost()); + rewrittenQuery.queryName(multiMatchBuilder.queryName()); + return rewrittenQuery; + } + + private QueryBuilder createLexicalQuery(MultiMatchQueryBuilder original, Map lexicalFields) { + MultiMatchQueryBuilder lexicalPart = new MultiMatchQueryBuilder(original.value()); + lexicalPart.fields(lexicalFields); + lexicalPart.type(original.type()); + lexicalPart.operator(original.operator()); + lexicalPart.analyzer(original.analyzer()); + lexicalPart.slop(original.slop()); + if (original.fuzziness() != null) { + lexicalPart.fuzziness(original.fuzziness()); + } + lexicalPart.prefixLength(original.prefixLength()); + lexicalPart.maxExpansions(original.maxExpansions()); + lexicalPart.minimumShouldMatch(original.minimumShouldMatch()); + lexicalPart.fuzzyRewrite(original.fuzzyRewrite()); + if (original.tieBreaker() != null) { + lexicalPart.tieBreaker(original.tieBreaker()); } + lexicalPart.lenient(original.lenient()); + lexicalPart.zeroTermsQuery(original.zeroTermsQuery()); + lexicalPart.autoGenerateSynonymsPhraseQuery(original.autoGenerateSynonymsPhraseQuery()); + lexicalPart.fuzzyTranspositions(original.fuzzyTranspositions()); + return lexicalPart; + } - if (semanticFields.isEmpty() == false) { - BoolQueryBuilder semanticPart = new BoolQueryBuilder(); - for (Map.Entry fieldEntry : semanticFields.entrySet()) { - SemanticQueryBuilder semanticQueryBuilder = new SemanticQueryBuilder(fieldEntry.getKey(), multiMatchBuilder.value().toString(), true); - if (fieldEntry.getValue() != 1.0f) { - semanticQueryBuilder.boost(fieldEntry.getValue()); - } - semanticPart.should(semanticQueryBuilder); - } - rewrittenQuery.should(semanticPart); + private QueryBuilder createSemanticQuery(String queryText, Map semanticFields) { + if (semanticFields.size() == 1) { + return createSemanticQuery(queryText, semanticFields.entrySet().iterator().next()); } - rewrittenQuery.boost(multiMatchBuilder.boost()); - rewrittenQuery.queryName(multiMatchBuilder.queryName()); + BoolQueryBuilder boolQuery = new BoolQueryBuilder(); + for (Map.Entry fieldEntry : semanticFields.entrySet()) { + boolQuery.should(createSemanticQuery(queryText, fieldEntry)); + } + return boolQuery; + } - return rewrittenQuery; + private QueryBuilder createSemanticQuery(String queryText, Map.Entry fieldEntry) { + SemanticQueryBuilder semanticQuery = new SemanticQueryBuilder(fieldEntry.getKey(), queryText, true); + if (fieldEntry.getValue() != 1.0f) { + semanticQuery.boost(fieldEntry.getValue()); + } + return semanticQuery; } @Override From 5ed5d6c87a845ed1b588b1c29aa7d785f8cfdc61 Mon Sep 17 00:00:00 2001 From: Samiul Monir Date: Fri, 1 Aug 2025 03:29:46 -0400 Subject: [PATCH 4/5] supply modelRegistry and add warning --- .../xpack/inference/InferencePlugin.java | 2 +- ...nticMultiMatchQueryRewriteInterceptor.java | 63 ++++++++++++++++--- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferencePlugin.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferencePlugin.java index 2970dce923300..ea3160bb3675d 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferencePlugin.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferencePlugin.java @@ -573,7 +573,7 @@ public List getQueryRewriteInterceptors() { new SemanticKnnVectorQueryRewriteInterceptor(), new SemanticMatchQueryRewriteInterceptor(), new SemanticSparseVectorQueryRewriteInterceptor(), - new SemanticMultiMatchQueryRewriteInterceptor() + new SemanticMultiMatchQueryRewriteInterceptor(getModelRegistry()) ); } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java index 493e85e3c3774..34f8f82d3d048 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java @@ -9,21 +9,37 @@ import org.elasticsearch.action.ResolvedIndices; import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.cluster.metadata.InferenceFieldMetadata; +import org.elasticsearch.common.logging.HeaderWarning; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.DisMaxQueryBuilder; import org.elasticsearch.index.query.MultiMatchQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.QueryRewriteContext; +import org.elasticsearch.inference.MinimalServiceSettings; +import org.elasticsearch.inference.TaskType; import org.elasticsearch.plugins.internal.rewriter.QueryRewriteInterceptor; +import org.elasticsearch.xpack.inference.registry.ModelRegistry; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; public class SemanticMultiMatchQueryRewriteInterceptor implements QueryRewriteInterceptor { + private static final String SCORE_MISMATCH_WARNING = "multi_match query is targeting a mixture of semantic_text fields with dense " + + "and sparse models, or a mixture of semantic_text and non-inference fields. Score ranges will not be comparable."; + + private final Supplier modelRegistrySupplier; + + public SemanticMultiMatchQueryRewriteInterceptor(Supplier modelRegistrySupplier) { + this.modelRegistrySupplier = Objects.requireNonNull(modelRegistrySupplier); + } + @Override public QueryBuilder interceptAndRewrite(QueryRewriteContext context, QueryBuilder queryBuilder) { if (queryBuilder instanceof MultiMatchQueryBuilder == false) { @@ -40,12 +56,29 @@ public QueryBuilder interceptAndRewrite(QueryRewriteContext context, QueryBuilde Map otherFields = new HashMap<>(); Collection allIndicesMetadata = resolvedIndices.getConcreteLocalIndicesMetadata().values(); + boolean hasDenseSemanticField = false; + boolean hasSparseSemanticField = false; + + ModelRegistry modelRegistry = modelRegistrySupplier.get(); + if (modelRegistry == null) { + // Should not happen in a sane lifecycle, but protect against it + return queryBuilder; + } + for (Map.Entry fieldEntry : multiMatchBuilder.fields().entrySet()) { String fieldName = fieldEntry.getKey(); - boolean isSemanticInAnyIndex = allIndicesMetadata.stream() - .anyMatch(indexMetadata -> indexMetadata.getInferenceFields().containsKey(fieldName)); - if (isSemanticInAnyIndex) { + InferenceFieldMetadata inferenceMetadata = findInferenceMetadata(fieldName, allIndicesMetadata); + + if (inferenceMetadata != null) { semanticFields.put(fieldName, fieldEntry.getValue()); + MinimalServiceSettings settings = modelRegistry.getMinimalServiceSettings(inferenceMetadata.getSearchInferenceId()); + if (settings != null) { + if (settings.taskType() == TaskType.TEXT_EMBEDDING) { + hasDenseSemanticField = true; + } else if (settings.taskType() == TaskType.SPARSE_EMBEDDING) { + hasSparseSemanticField = true; + } + } } else { otherFields.put(fieldName, fieldEntry.getValue()); } @@ -55,6 +88,10 @@ public QueryBuilder interceptAndRewrite(QueryRewriteContext context, QueryBuilde return queryBuilder; } + if (hasDenseSemanticField && (hasSparseSemanticField || otherFields.isEmpty() == false)) { + HeaderWarning.addWarning(SCORE_MISMATCH_WARNING); + } + MultiMatchQueryBuilder.Type type = multiMatchBuilder.type(); if (type == MultiMatchQueryBuilder.Type.CROSS_FIELDS || type == MultiMatchQueryBuilder.Type.PHRASE || @@ -97,6 +134,21 @@ public QueryBuilder interceptAndRewrite(QueryRewriteContext context, QueryBuilde return rewrittenQuery; } + @Override + public String getQueryName() { + return MultiMatchQueryBuilder.NAME; + } + + private InferenceFieldMetadata findInferenceMetadata(String fieldName, Collection allIndicesMetadata) { + for (IndexMetadata indexMetadata : allIndicesMetadata) { + InferenceFieldMetadata inferenceMetadata = indexMetadata.getInferenceFields().get(fieldName); + if (inferenceMetadata != null) { + return inferenceMetadata; + } + } + return null; + } + private QueryBuilder createLexicalQuery(MultiMatchQueryBuilder original, Map lexicalFields) { MultiMatchQueryBuilder lexicalPart = new MultiMatchQueryBuilder(original.value()); lexicalPart.fields(lexicalFields); @@ -140,9 +192,4 @@ private QueryBuilder createSemanticQuery(String queryText, Map.Entry Date: Fri, 1 Aug 2025 18:28:42 +0000 Subject: [PATCH 5/5] [CI] Auto commit changes from spotless --- .../SemanticMultiMatchQueryRewriteInterceptor.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java index 34f8f82d3d048..435212ae2f387 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/queries/SemanticMultiMatchQueryRewriteInterceptor.java @@ -22,7 +22,6 @@ import org.elasticsearch.plugins.internal.rewriter.QueryRewriteInterceptor; import org.elasticsearch.xpack.inference.registry.ModelRegistry; -import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -93,10 +92,12 @@ public QueryBuilder interceptAndRewrite(QueryRewriteContext context, QueryBuilde } MultiMatchQueryBuilder.Type type = multiMatchBuilder.type(); - if (type == MultiMatchQueryBuilder.Type.CROSS_FIELDS || - type == MultiMatchQueryBuilder.Type.PHRASE || - type == MultiMatchQueryBuilder.Type.PHRASE_PREFIX) { - throw new IllegalArgumentException("Query type [" + type.parseField().getPreferredName() + "] is not supported with semantic_text fields"); + if (type == MultiMatchQueryBuilder.Type.CROSS_FIELDS + || type == MultiMatchQueryBuilder.Type.PHRASE + || type == MultiMatchQueryBuilder.Type.PHRASE_PREFIX) { + throw new IllegalArgumentException( + "Query type [" + type.parseField().getPreferredName() + "] is not supported with semantic_text fields" + ); } QueryBuilder rewrittenQuery;