Skip to content
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
d2c22a6
Do highlighting in RankFeatureShardPhase
kderusso Apr 2, 2025
3f52ac7
Propagate to
kderusso Apr 2, 2025
0ffaf94
Only rerank the first snippet
kderusso Apr 3, 2025
f99c33e
Notes
kderusso May 21, 2025
0196a7c
Support reranking based on max score of multiple snippets per document
kderusso May 21, 2025
d796953
Update from main
kderusso May 22, 2025
1dc80a1
Fix compilation error
kderusso May 22, 2025
7537c21
Merge from main
kderusso May 28, 2025
f68b720
Fix merge compile errors
kderusso May 28, 2025
b053fa3
Merge update from main
kderusso Jun 12, 2025
bbee29f
Merge main into branch
kderusso Jun 13, 2025
c4bcb43
Fix compilation error after upstream merge
kderusso Jun 13, 2025
ce8364b
Combine featureData and snippets
kderusso Jun 13, 2025
d8dbaab
Remove docIndices from RankFeatureDoc
kderusso Jun 13, 2025
b909ab9
Update API - remove max size, default num snippets, rename
kderusso Jun 13, 2025
243e169
Merge branch 'main' into kderusso/rerank-snippet-poc
kderusso Jun 16, 2025
1684fba
Merge main into kderusso/rerank-snippet-poc
kderusso Jun 27, 2025
e6208ec
Add hardcoded max token length
kderusso Jun 27, 2025
e3259d9
Fix error in docId calculation
kderusso Jun 27, 2025
16dbca0
Fix snippet calculation
kderusso Jun 30, 2025
fee2b0d
Fix test compilation errors
kderusso Jun 30, 2025
9119d26
Merge branch 'main' into kderusso/rerank-snippet-poc
kderusso Jul 1, 2025
b418f87
Fix more test compilation failures
kderusso Jul 1, 2025
c30da72
Cleanup
kderusso Jul 1, 2025
cf2c8b3
Minor variable cleanup
kderusso Jul 1, 2025
4675586
Add some tests
kderusso Jul 1, 2025
129efac
Refactor method signatures to provide only a single custom input, fix…
kderusso Jul 2, 2025
4519d61
Refactor/rename
kderusso Jul 2, 2025
17ec3a8
Minor cleanup
kderusso Jul 2, 2025
9ae38c9
[CI] Auto commit changes from spotless
Jul 3, 2025
ffbcddd
Rewrite highlight match query
kderusso Jul 8, 2025
8a82b13
[CI] Auto commit changes from spotless
Jul 8, 2025
d5411db
Fix wonky syntax
kderusso Jul 8, 2025
3dfba46
Stash changes
kderusso Jul 8, 2025
ee69e3e
Move rewrite to RankBuilder
kderusso Jul 9, 2025
c24db3b
Cleanup
kderusso Jul 9, 2025
7c6848d
[CI] Auto commit changes from spotless
Jul 9, 2025
35d27a9
Fix rewrite getting semantic query and cleanup tests
kderusso Jul 9, 2025
b6aaf8f
[CI] Auto commit changes from spotless
Jul 9, 2025
251bd49
Cleanup
kderusso Jul 9, 2025
09c8b48
Update docs/changelog/129369.yaml
kderusso Jul 9, 2025
e9ebe8d
Merge main into kderusso/rerank-snippet-poc
kderusso Jul 9, 2025
584e7ea
Fix integration test compilation errors
kderusso Jul 9, 2025
aaa3948
Fix some test compilation errors introduced by refactor
kderusso Jul 9, 2025
15bdcef
Merge main into kderusso/rerank-snippet-poc
kderusso Jul 10, 2025
75568a9
Fix tests
kderusso Jul 10, 2025
143ed4a
Addressed PR feedback moving snippet generation down into text simila…
kderusso Jul 14, 2025
2418e41
[CI] Auto commit changes from spotless
Jul 15, 2025
ed08549
Minor cleanup
kderusso Jul 15, 2025
ab8916b
Merge main into kderusso/rerank-snippet-poc
kderusso Jul 15, 2025
b641438
Fix CI test compilation failures
kderusso Jul 15, 2025
a835902
Fix tests
kderusso Jul 15, 2025
1a79987
Minor cleanup
kderusso Jul 16, 2025
d330d5e
Merge branch 'main' into kderusso/rerank-snippet-poc
kderusso Jul 16, 2025
9e53b17
Merge main into kderusso/rerank-snippet-poc
kderusso Jul 17, 2025
f88d678
Increase highlighter fragment size for snippets
kderusso Jul 21, 2025
3b08e12
Add feature flag for snippet reranking
kderusso Jul 21, 2025
55277a0
Merge main into kderusso/rerank-snippet-poc
kderusso Jul 21, 2025
d2e4611
Set noMatchSize on highlighting request
kderusso Jul 22, 2025
44c2c71
Remove leftover throws
kderusso Jul 22, 2025
6e8521a
Merge branch 'main' into kderusso/rerank-snippet-poc
kderusso Jul 22, 2025
d56726c
Consolidate snippet rank input
kderusso Jul 22, 2025
40c447d
Update x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/…
kderusso Jul 22, 2025
1ad29a7
Update x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/…
kderusso Jul 22, 2025
cc07332
Refactor move/rename SnippetConfig
kderusso Jul 22, 2025
5a870a6
Merge branch 'main' into kderusso/rerank-snippet-poc
kderusso Jul 22, 2025
32f59b3
Consolidate default endpoints
kderusso Jul 23, 2025
b7e0a45
Merge main into kderusso/rerank-snippet-poc
kderusso Jul 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog/129369.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 129369
summary: Support semantic reranking using contextual snippets instead of entire field
text
area: Relevance
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ public class CcsCommonYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
.feature(FeatureFlag.TIME_SERIES_MODE)
.feature(FeatureFlag.SUB_OBJECTS_AUTO_ENABLED)
.feature(FeatureFlag.IVF_FORMAT)
.feature(FeatureFlag.SYNTHETIC_VECTORS);
.feature(FeatureFlag.SYNTHETIC_VECTORS)
.feature(FeatureFlag.RERANK_SNIPPETS);

private static ElasticsearchCluster remoteCluster = ElasticsearchCluster.local()
.name(REMOTE_CLUSTER_NAME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public class RcsCcsCommonYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
.feature(FeatureFlag.SUB_OBJECTS_AUTO_ENABLED)
.feature(FeatureFlag.IVF_FORMAT)
.feature(FeatureFlag.SYNTHETIC_VECTORS)
.feature(FeatureFlag.RERANK_SNIPPETS)
.user("test_admin", "x-pack-test-password");

private static ElasticsearchCluster fulfillingCluster = ElasticsearchCluster.local()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class SmokeTestMultiNodeClientYamlTestSuiteIT extends ESClientYamlSuiteTe
.feature(FeatureFlag.USE_LUCENE101_POSTINGS_FORMAT)
.feature(FeatureFlag.IVF_FORMAT)
.feature(FeatureFlag.SYNTHETIC_VECTORS)
.feature(FeatureFlag.RERANK_SNIPPETS)
.build();

public SmokeTestMultiNodeClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class ClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
.feature(FeatureFlag.USE_LUCENE101_POSTINGS_FORMAT)
.feature(FeatureFlag.IVF_FORMAT)
.feature(FeatureFlag.SYNTHETIC_VECTORS)
.feature(FeatureFlag.RERANK_SNIPPETS)
.build();

public ClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ public RankShardResult buildRankFeatureShardResult(SearchHits hits, int shardId)
RankFeatureDoc[] rankFeatureDocs = new RankFeatureDoc[hits.getHits().length];
for (int i = 0; i < hits.getHits().length; i++) {
rankFeatureDocs[i] = new RankFeatureDoc(hits.getHits()[i].docId(), hits.getHits()[i].getScore(), shardId);
rankFeatureDocs[i].featureData(hits.getHits()[i].field(field).getValue().toString());
rankFeatureDocs[i].featureData(List.of(hits.getHits()[i].field(field).getValue().toString()));
}
return new RankFeatureShardResult(rankFeatureDocs);
} catch (Exception ex) {
Expand All @@ -210,7 +210,7 @@ public RankFeaturePhaseRankCoordinatorContext buildRankFeaturePhaseCoordinatorCo
protected void computeScores(RankFeatureDoc[] featureDocs, ActionListener<float[]> scoreListener) {
float[] scores = new float[featureDocs.length];
for (int i = 0; i < featureDocs.length; i++) {
scores[i] = Float.parseFloat(featureDocs[i].featureData);
scores[i] = Float.parseFloat(featureDocs[i].featureData.get(0));
}
scoreListener.onResponse(scores);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ protected void computeScores(RankFeatureDoc[] featureDocs, ActionListener<float[
l.onResponse(scores);
});

List<String> featureData = Arrays.stream(featureDocs).map(x -> x.featureData).toList();
List<String> featureData = Arrays.stream(featureDocs).map(x -> x.featureData).flatMap(List::stream).toList();
TestRerankingActionRequest request = generateRequest(featureData);
try {
ActionType<TestRerankingActionResponse> action = actionType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ static TransportVersion def(int id) {
public static final TransportVersion SHARD_WRITE_LOAD_IN_CLUSTER_INFO = def(9_126_0_00);
public static final TransportVersion ESQL_SAMPLE_OPERATOR_STATUS = def(9_127_0_00);
public static final TransportVersion ESQL_TOPN_TIMINGS = def(9_128_0_00);
public static final TransportVersion RERANK_SNIPPETS = def(9_129_0_00);

/*
* STOP! READ THIS FIRST! No, really,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1187,9 +1187,11 @@ public SearchSourceBuilder rewrite(QueryRewriteContext context) throws IOExcepti
sliceBuilder,
sorts,
rescoreBuilders,
highlightBuilder
highlightBuilder,
rankBuilder
)
));

if (retrieverBuilder != null) {
var newRetriever = retrieverBuilder.rewrite(context);
if (newRetriever != retrieverBuilder) {
Expand All @@ -1205,6 +1207,11 @@ public SearchSourceBuilder rewrite(QueryRewriteContext context) throws IOExcepti
}
}

RankBuilder rankBuilder = null;
if (this.rankBuilder != null) {
rankBuilder = this.rankBuilder.rewrite(context);
}

List<SubSearchSourceBuilder> subSearchSourceBuilders = Rewriteable.rewrite(this.subSearchSourceBuilders, context);
QueryBuilder postQueryBuilder = null;
if (this.postQueryBuilder != null) {
Expand All @@ -1229,7 +1236,8 @@ public SearchSourceBuilder rewrite(QueryRewriteContext context) throws IOExcepti
|| aggregations != this.aggregations
|| rescoreBuilders != this.rescoreBuilders
|| sorts != this.sorts
|| this.highlightBuilder != highlightBuilder;
|| this.highlightBuilder != highlightBuilder
|| this.rankBuilder != rankBuilder;
if (rewritten) {
return shallowCopy(
subSearchSourceBuilders,
Expand All @@ -1239,7 +1247,8 @@ public SearchSourceBuilder rewrite(QueryRewriteContext context) throws IOExcepti
this.sliceBuilder,
sorts,
rescoreBuilders,
highlightBuilder
highlightBuilder,
rankBuilder
);
}
return this;
Expand All @@ -1257,7 +1266,8 @@ public SearchSourceBuilder shallowCopy() {
sliceBuilder,
sorts,
rescoreBuilders,
highlightBuilder
highlightBuilder,
rankBuilder
);
}

Expand All @@ -1274,7 +1284,8 @@ private SearchSourceBuilder shallowCopy(
SliceBuilder slice,
List<SortBuilder<?>> sorts,
List<RescorerBuilder> rescoreBuilders,
HighlightBuilder highlightBuilder
HighlightBuilder highlightBuilder,
RankBuilder rankBuilder
) {
SearchSourceBuilder rewrittenBuilder = new SearchSourceBuilder();
rewrittenBuilder.aggregations = aggregations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ public Field(String name) {
this.name = name;
}

private Field(Field template, QueryBuilder builder) {
Field(Field template, QueryBuilder builder) {
super(template, builder);
name = template.name;
fragmentOffset = template.fragmentOffset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static class Field {
private final String field;
private final FieldOptions fieldOptions;

Field(String field, FieldOptions fieldOptions) {
public Field(String field, FieldOptions fieldOptions) {
assert field != null;
assert fieldOptions != null;
this.field = field;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.UpdateForV10;
import org.elasticsearch.features.NodeFeature;
import org.elasticsearch.index.query.QueryRewriteContext;
import org.elasticsearch.search.SearchService;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.rank.context.QueryPhaseRankCoordinatorContext;
Expand Down Expand Up @@ -60,6 +61,10 @@ public final void writeTo(StreamOutput out) throws IOException {
doWriteTo(out);
}

public RankBuilder rewrite(QueryRewriteContext queryRewriteContext) throws IOException {
return this;
}

protected abstract void doWriteTo(StreamOutput out) throws IOException;

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import org.elasticsearch.core.Nullable;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.search.rank.RankShardResult;

/**
Expand All @@ -37,4 +38,13 @@ public String getField() {
*/
@Nullable
public abstract RankShardResult buildRankFeatureShardResult(SearchHits hits, int shardId);

/**
* Prepares a SearchContext with any additional information needed before executing
* commands on shards.
* @param context SearchContext
*/
public void prepareForFetch(SearchContext context) {
// Default no-op
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
package org.elasticsearch.search.rank.feature;

import org.apache.lucene.search.Explanation;
import org.elasticsearch.TransportVersions;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.search.rank.RankDoc;
import org.elasticsearch.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.List;
import java.util.Objects;

/**
Expand All @@ -26,29 +28,38 @@ public class RankFeatureDoc extends RankDoc {
public static final String NAME = "rank_feature_doc";

// TODO: update to support more than 1 fields; and not restrict to string data
public String featureData;
public List<String> featureData;

public RankFeatureDoc(int doc, float score, int shardIndex) {
super(doc, score, shardIndex);
}

public RankFeatureDoc(StreamInput in) throws IOException {
super(in);
featureData = in.readOptionalString();
if (in.getTransportVersion().onOrAfter(TransportVersions.RERANK_SNIPPETS)) {
featureData = in.readOptionalStringCollectionAsList();
} else {
String featureDataString = in.readOptionalString();
featureData = featureDataString == null ? null : List.of(featureDataString);
}
}

@Override
public Explanation explain(Explanation[] sources, String[] queryNames) {
throw new UnsupportedOperationException("explain is not supported for {" + getClass() + "}");
}

public void featureData(String featureData) {
public void featureData(List<String> featureData) {
this.featureData = featureData;
}

@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeOptionalString(featureData);
if (out.getTransportVersion().onOrAfter(TransportVersions.RERANK_SNIPPETS)) {
out.writeOptionalStringCollection(featureData);
} else {
out.writeOptionalString(featureData.get(0));
}
}

@Override
Expand All @@ -59,7 +70,7 @@ protected boolean doEquals(RankDoc rd) {

@Override
protected int doHashCode() {
return Objects.hashCode(featureData);
return Objects.hash(featureData);
}

@Override
Expand All @@ -69,6 +80,6 @@ public String getWriteableName() {

@Override
protected void doToXContent(XContentBuilder builder, Params params) throws IOException {
builder.field("featureData", featureData);
builder.array("featureData", featureData);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ public static void prepareForFetch(SearchContext searchContext, RankFeatureShard

RankFeaturePhaseRankShardContext rankFeaturePhaseRankShardContext = shardContext(searchContext);
if (rankFeaturePhaseRankShardContext != null) {
assert rankFeaturePhaseRankShardContext.getField() != null : "field must not be null";
searchContext.fetchFieldsContext(
new FetchFieldsContext(Collections.singletonList(new FieldAndFormat(rankFeaturePhaseRankShardContext.getField(), null)))
);
String field = rankFeaturePhaseRankShardContext.getField();
assert field != null : "field must not be null";
searchContext.fetchFieldsContext(new FetchFieldsContext(Collections.singletonList(new FieldAndFormat(field, null))));
rankFeaturePhaseRankShardContext.prepareForFetch(searchContext);
searchContext.storedFieldsContext(StoredFieldsContext.fromList(Collections.singletonList(StoredFieldsContext._NONE_)));
searchContext.addFetchResult();
Arrays.sort(request.getDocIds());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.search.rank.feature;

import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.index.query.QueryBuilder;

import java.io.IOException;
import java.util.Objects;

public class RerankSnippetConfig implements Writeable {

public final Integer numSnippets;
public final QueryBuilder snippetQueryBuilder;

public static final int DEFAULT_NUM_SNIPPETS = 1;

public RerankSnippetConfig(StreamInput in) throws IOException {
this.numSnippets = in.readOptionalVInt();
this.snippetQueryBuilder = in.readOptionalNamedWriteable(QueryBuilder.class);
}

public RerankSnippetConfig(Integer numSnippets) {
this(numSnippets, null);
}

public RerankSnippetConfig(Integer numSnippets, QueryBuilder snippetQueryBuilder) {
this.numSnippets = numSnippets;
this.snippetQueryBuilder = snippetQueryBuilder;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeOptionalVInt(numSnippets);
out.writeOptionalNamedWriteable(snippetQueryBuilder);
}

public Integer numSnippets() {
return numSnippets;
}

public QueryBuilder snippetQueryBuilder() {
return snippetQueryBuilder;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RerankSnippetConfig that = (RerankSnippetConfig) o;
return Objects.equals(numSnippets, that.numSnippets) && Objects.equals(snippetQueryBuilder, that.snippetQueryBuilder);
}

@Override
public int hashCode() {
return Objects.hash(numSnippets, snippetQueryBuilder);
}
}
Loading
Loading