Skip to content
Merged
Show file tree
Hide file tree
Changes from 97 commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
9f4e2ad
test
kderusso Feb 12, 2025
a561017
Revert "test"
kderusso Feb 12, 2025
acb14a2
Refactor InferenceService to allow passing in chunking settings
kderusso Jan 24, 2025
933c74d
Add chunking config to inference field metadata and store in semantic…
kderusso Jan 24, 2025
6b11f01
Fix test compilation errors
kderusso Jan 27, 2025
7c4ba49
Hacking around trying to get ingest to work
kderusso Jan 27, 2025
edbfde3
Debugging
kderusso Jan 27, 2025
c255745
[CI] Auto commit changes from spotless
Jan 28, 2025
889875f
POC works and update TODO to fix this
kderusso Jan 28, 2025
c87753e
[CI] Auto commit changes from spotless
Jan 28, 2025
c2c9a52
Refactor chunking settings from model settings to field inference req…
kderusso Feb 12, 2025
c9bfa32
A bit of cleanup
kderusso Feb 12, 2025
122aeee
Revert a bunch of changes to try to narrow down what broke CI
kderusso Feb 14, 2025
b42f262
test
kderusso Feb 12, 2025
75404b5
Revert "test"
kderusso Feb 12, 2025
0dcadfa
Merge from main
kderusso Feb 14, 2025
a54804e
Fix InferenceFieldMetadataTest
kderusso Feb 14, 2025
675819c
[CI] Auto commit changes from spotless
Feb 14, 2025
ccef5cc
Add chunking settings back in
kderusso Feb 14, 2025
70ac065
Update builder to use new map
kderusso Mar 4, 2025
4ac2460
Merge from main
kderusso Mar 4, 2025
20d596c
Fix compilation errors after merge
kderusso Mar 4, 2025
178a9db
Debugging tests
kderusso Mar 4, 2025
7f1e99d
debugging
kderusso Mar 4, 2025
f74c803
Merge branch 'main' into kderusso/support-configurable-chunking
kderusso Mar 4, 2025
65acf8f
Cleanup
kderusso Mar 4, 2025
51d9aae
Add yaml test
kderusso Mar 4, 2025
7aaaee8
Update tests
kderusso Mar 4, 2025
d306eae
Add chunking to test inference service
kderusso Mar 4, 2025
7cf7589
Trying to get tests to work
kderusso Mar 5, 2025
89e040d
Shard bulk inference test never specifies chunking settings
kderusso Mar 5, 2025
233defd
Fix test
kderusso Mar 5, 2025
0b2ebf6
Always process batches in order
kderusso Mar 5, 2025
a807ab5
Fix chunking in test inference service and yaml tests
kderusso Mar 5, 2025
dc48c28
[CI] Auto commit changes from spotless
Mar 5, 2025
3ed5631
Refactor - remove convenience method with default chunking settings
kderusso Mar 6, 2025
c95789d
Fix ShardBulkInferenceActionFilterTests
kderusso Mar 6, 2025
75031e1
Fix ElasticsearchInternalServiceTests
kderusso Mar 6, 2025
d051cd2
Fix SemanticTextFieldMapperTests
kderusso Mar 6, 2025
8913177
[CI] Auto commit changes from spotless
Mar 6, 2025
2ab5aec
Fix test data to fit within bounds
kderusso Mar 6, 2025
92d70dc
Add additional yaml test cases
kderusso Mar 6, 2025
1106671
Playing with xcontent parsing
kderusso Mar 6, 2025
11ab0f9
Merge from main
kderusso Mar 6, 2025
fb2cc28
A little cleanup
kderusso Mar 7, 2025
1745dc9
Update docs/changelog/121041.yaml
kderusso Mar 7, 2025
a2cdc42
Merge from main
kderusso Mar 10, 2025
525eed2
Fix failures introduced by merge
kderusso Mar 10, 2025
45ab0eb
[CI] Auto commit changes from spotless
Mar 10, 2025
42c8449
Address PR feedback
kderusso Mar 13, 2025
71edec2
[CI] Auto commit changes from spotless
Mar 13, 2025
6a37449
Fix predicate in updated test
kderusso Mar 13, 2025
c076f92
Better handling of null/empty ChunkingSettings
kderusso Mar 13, 2025
2ef235c
Update parsing settings
kderusso Mar 13, 2025
f739639
Merge from main
kderusso Mar 13, 2025
311d840
Fix errors post merge
kderusso Mar 13, 2025
e3e15d2
PR feedback
kderusso Mar 19, 2025
4224159
[CI] Auto commit changes from spotless
Mar 19, 2025
ad090d7
PR feedback and fix Xcontent parsing for SemanticTextField
kderusso Mar 19, 2025
a8ef9a1
Remove chunking settings check to use what's passed in from sender se…
kderusso Mar 19, 2025
db06fd9
Fix some tests
kderusso Mar 20, 2025
b5f2929
Cleanup
kderusso Mar 20, 2025
fccbd61
Test failure whack-a-mole
kderusso Mar 20, 2025
aab16e7
Cleanup
kderusso Mar 20, 2025
1d54066
Merge from main
kderusso Mar 20, 2025
023c227
Refactor to handle memory optimized bulk shard inference actions - th…
kderusso Mar 21, 2025
b26b9a2
[CI] Auto commit changes from spotless
Mar 21, 2025
1c84cc2
Minor cleanup
kderusso Mar 24, 2025
165c19e
A bit more cleanup
kderusso Mar 24, 2025
735982a
Spotless
kderusso Mar 24, 2025
b2839fc
Revert change
kderusso Mar 24, 2025
ecc9bb3
Update chunking setting update logic
kderusso Mar 24, 2025
4c992d8
Go back to serializing maps
kderusso Mar 24, 2025
c807199
Revert change to model settings - source still errors on missing mode…
kderusso Mar 24, 2025
c152281
Fix updating chunking settings
kderusso Mar 25, 2025
561f583
Merge main into kderusso/support-configurable-chunking
kderusso Mar 25, 2025
9907a64
Look up model if null
kderusso Mar 26, 2025
688c637
Fix test
kderusso Mar 26, 2025
7693ef6
Merge main into kderusso/support-configurable-chunking
kderusso Mar 26, 2025
fa9247a
Work around https://github.com/elastic/elasticsearch/issues/125723 in…
kderusso Mar 27, 2025
1d8931c
Add BWC tests
kderusso Mar 27, 2025
ab7752e
Add chunking_settings to docs
kderusso Mar 27, 2025
d2ef735
Merge main into kderusso/support-configurable-chunking
kderusso Mar 27, 2025
cd4d32b
Refactor/rename
kderusso Mar 28, 2025
5db7ed4
Address minor PR feedback
kderusso Mar 28, 2025
e477639
Add test case for null update
kderusso Mar 28, 2025
7d85fd3
PR feedback - adjust refactor of chunked inputs
kderusso Mar 28, 2025
845a732
Refactored AbstractTestInferenceService to return offsets instead of …
kderusso Mar 28, 2025
2ed86d4
[CI] Auto commit changes from spotless
Mar 28, 2025
0a54972
Fix tests where chunk output was of size 3
kderusso Mar 28, 2025
8cf287b
Update mappings per PR feedback
kderusso Mar 28, 2025
a9c7512
PR Feedback
kderusso Apr 1, 2025
65b8893
Merge main into branch
kderusso Apr 3, 2025
546e333
Fix problems related to merge
kderusso Apr 3, 2025
612d2fb
PR optimization
kderusso Apr 3, 2025
9fb17a6
Fix test
kderusso Apr 3, 2025
bf8c460
Delete extra file
kderusso Apr 3, 2025
2b5589d
Merge main into branch
kderusso Apr 3, 2025
1e3fc22
Merge from main
kderusso Apr 3, 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
5 changes: 5 additions & 0 deletions docs/changelog/121041.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 121041
summary: Support configurable chunking in `semantic_text` fields
area: Relevance
type: enhancement
issues: []
160 changes: 119 additions & 41 deletions docs/reference/elasticsearch/mapping-reference/semantic-text.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ static TransportVersion def(int id) {
public static final TransportVersion REPOSITORIES_METADATA_AS_PROJECT_CUSTOM = def(9_042_0_00);
public static final TransportVersion BATCHED_QUERY_PHASE_VERSION = def(9_043_0_00);
public static final TransportVersion REMOTE_EXCEPTION = def(9_044_0_00);
public static final TransportVersion SEMANTIC_TEXT_CHUNKING_CONFIG = def(9_045_00_0);

/*
* STOP! READ THIS FIRST! No, really,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.elasticsearch.TransportVersions;
import org.elasticsearch.cluster.Diff;
import org.elasticsearch.cluster.SimpleDiffable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xcontent.ToXContentFragment;
Expand All @@ -22,8 +23,11 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.elasticsearch.TransportVersions.SEMANTIC_TEXT_CHUNKING_CONFIG;

/**
* Contains inference field data for fields.
* As inference is done in the coordinator node to avoid re-doing it at shard / replica level, the coordinator needs to check for the need
Expand All @@ -35,21 +39,30 @@ public final class InferenceFieldMetadata implements SimpleDiffable<InferenceFie
private static final String INFERENCE_ID_FIELD = "inference_id";
private static final String SEARCH_INFERENCE_ID_FIELD = "search_inference_id";
private static final String SOURCE_FIELDS_FIELD = "source_fields";
static final String CHUNKING_SETTINGS_FIELD = "chunking_settings";

private final String name;
private final String inferenceId;
private final String searchInferenceId;
private final String[] sourceFields;
private final Map<String, Object> chunkingSettings;

public InferenceFieldMetadata(String name, String inferenceId, String[] sourceFields) {
this(name, inferenceId, inferenceId, sourceFields);
public InferenceFieldMetadata(String name, String inferenceId, String[] sourceFields, Map<String, Object> chunkingSettings) {
this(name, inferenceId, inferenceId, sourceFields, chunkingSettings);
}

public InferenceFieldMetadata(String name, String inferenceId, String searchInferenceId, String[] sourceFields) {
public InferenceFieldMetadata(
String name,
String inferenceId,
String searchInferenceId,
String[] sourceFields,
Map<String, Object> chunkingSettings
) {
this.name = Objects.requireNonNull(name);
this.inferenceId = Objects.requireNonNull(inferenceId);
this.searchInferenceId = Objects.requireNonNull(searchInferenceId);
this.sourceFields = Objects.requireNonNull(sourceFields);
this.chunkingSettings = chunkingSettings != null ? Map.copyOf(chunkingSettings) : null;
}

public InferenceFieldMetadata(StreamInput input) throws IOException {
Expand All @@ -61,6 +74,11 @@ public InferenceFieldMetadata(StreamInput input) throws IOException {
this.searchInferenceId = this.inferenceId;
}
this.sourceFields = input.readStringArray();
if (input.getTransportVersion().onOrAfter(SEMANTIC_TEXT_CHUNKING_CONFIG)) {
this.chunkingSettings = input.readGenericMap();
} else {
this.chunkingSettings = null;
}
}

@Override
Expand All @@ -71,6 +89,9 @@ public void writeTo(StreamOutput out) throws IOException {
out.writeString(searchInferenceId);
}
out.writeStringArray(sourceFields);
if (out.getTransportVersion().onOrAfter(SEMANTIC_TEXT_CHUNKING_CONFIG)) {
out.writeGenericMap(chunkingSettings);
}
}

@Override
Expand All @@ -81,16 +102,22 @@ public boolean equals(Object o) {
return Objects.equals(name, that.name)
&& Objects.equals(inferenceId, that.inferenceId)
&& Objects.equals(searchInferenceId, that.searchInferenceId)
&& Arrays.equals(sourceFields, that.sourceFields);
&& Arrays.equals(sourceFields, that.sourceFields)
&& Objects.equals(chunkingSettings, that.chunkingSettings);
}

@Override
public int hashCode() {
int result = Objects.hash(name, inferenceId, searchInferenceId);
int result = Objects.hash(name, inferenceId, searchInferenceId, chunkingSettings);
result = 31 * result + Arrays.hashCode(sourceFields);
return result;
}

@Override
public String toString() {
return Strings.toString(this);
}

public String getName() {
return name;
}
Expand All @@ -107,6 +134,10 @@ public String[] getSourceFields() {
return sourceFields;
}

public Map<String, Object> getChunkingSettings() {
return chunkingSettings;
}

public static Diff<InferenceFieldMetadata> readDiffFrom(StreamInput in) throws IOException {
return SimpleDiffable.readDiffFrom(InferenceFieldMetadata::new, in);
}
Expand All @@ -119,6 +150,11 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
builder.field(SEARCH_INFERENCE_ID_FIELD, searchInferenceId);
}
builder.array(SOURCE_FIELDS_FIELD, sourceFields);
if (chunkingSettings != null) {
builder.startObject(CHUNKING_SETTINGS_FIELD);
builder.mapContents(chunkingSettings);
builder.endObject();
}
return builder.endObject();
}

Expand All @@ -131,6 +167,7 @@ public static InferenceFieldMetadata fromXContent(XContentParser parser) throws
String currentFieldName = null;
String inferenceId = null;
String searchInferenceId = null;
Map<String, Object> chunkingSettings = null;
List<String> inputFields = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
Expand All @@ -151,6 +188,8 @@ public static InferenceFieldMetadata fromXContent(XContentParser parser) throws
}
}
}
} else if (CHUNKING_SETTINGS_FIELD.equals(currentFieldName)) {
chunkingSettings = parser.map();
} else {
parser.skipChildren();
}
Expand All @@ -159,7 +198,8 @@ public static InferenceFieldMetadata fromXContent(XContentParser parser) throws
name,
inferenceId,
searchInferenceId == null ? inferenceId : searchInferenceId,
inputFields.toArray(String[]::new)
inputFields.toArray(String[]::new),
chunkingSettings
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.inference;

import org.elasticsearch.core.Nullable;

import java.util.List;

public record ChunkInferenceInput(String input, @Nullable ChunkingSettings chunkingSettings) {

public ChunkInferenceInput(String input) {
this(input, null);
}

public static List<String> inputs(List<ChunkInferenceInput> chunkInferenceInputs) {
return chunkInferenceInputs.stream().map(ChunkInferenceInput::input).toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
import org.elasticsearch.common.io.stream.VersionedNamedWriteable;
import org.elasticsearch.xcontent.ToXContentObject;

import java.util.Map;

public interface ChunkingSettings extends ToXContentObject, VersionedNamedWriteable {
ChunkingStrategy getChunkingStrategy();

Map<String, Object> asMap();
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,18 @@ void unifiedCompletionInfer(
/**
* Chunk long text.
*
* @param model The model
* @param query Inference query, mainly for re-ranking
* @param input Inference input
* @param taskSettings Settings in the request to override the model's defaults
* @param inputType For search, ingest etc
* @param timeout The timeout for the request
* @param listener Chunked Inference result listener
* @param model The model
* @param query Inference query, mainly for re-ranking
* @param input Inference input
* @param taskSettings Settings in the request to override the model's defaults
* @param inputType For search, ingest etc
* @param timeout The timeout for the request
* @param listener Chunked Inference result listener
*/
void chunkedInfer(
Model model,
@Nullable String query,
List<String> input,
List<ChunkInferenceInput> input,
Map<String, Object> taskSettings,
InputType inputType,
TimeValue timeout,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,8 @@ private static InferenceFieldMetadata randomInferenceFieldMetadata(String name)
name,
randomIdentifier(),
randomIdentifier(),
randomSet(1, 5, ESTestCase::randomIdentifier).toArray(String[]::new)
randomSet(1, 5, ESTestCase::randomIdentifier).toArray(String[]::new),
InferenceFieldMetadataTests.generateRandomChunkingSettings()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
import org.elasticsearch.xcontent.XContentParser;

import java.io.IOException;
import java.util.Map;
import java.util.function.Predicate;

import static org.elasticsearch.cluster.metadata.InferenceFieldMetadata.CHUNKING_SETTINGS_FIELD;
import static org.hamcrest.Matchers.equalTo;

public class InferenceFieldMetadataTests extends AbstractXContentTestCase<InferenceFieldMetadata> {
Expand All @@ -37,11 +39,6 @@ protected InferenceFieldMetadata createTestInstance() {
return createTestItem();
}

@Override
protected Predicate<String> getRandomFieldsExcludeFilter() {
return p -> p.equals(""); // do not add elements at the top-level as any element at this level is parsed as a new inference field
}

@Override
protected InferenceFieldMetadata doParseInstance(XContentParser parser) throws IOException {
if (parser.nextToken() == XContentParser.Token.START_OBJECT) {
Expand All @@ -58,18 +55,57 @@ protected boolean supportsUnknownFields() {
return true;
}

@Override
protected Predicate<String> getRandomFieldsExcludeFilter() {
// do not add elements at the top-level as any element at this level is parsed as a new inference field,
// and do not add additional elements to chunking maps as they will fail parsing with extra data
return field -> field.equals("") || field.contains(CHUNKING_SETTINGS_FIELD);
}

private static InferenceFieldMetadata createTestItem() {
String name = randomAlphaOfLengthBetween(3, 10);
String inferenceId = randomIdentifier();
String searchInferenceId = randomIdentifier();
String[] inputFields = generateRandomStringArray(5, 10, false, false);
return new InferenceFieldMetadata(name, inferenceId, searchInferenceId, inputFields);
Map<String, Object> chunkingSettings = generateRandomChunkingSettings();
return new InferenceFieldMetadata(name, inferenceId, searchInferenceId, inputFields, chunkingSettings);
}

public static Map<String, Object> generateRandomChunkingSettings() {
if (randomBoolean()) {
return null; // Defaults to model chunking settings
}
return randomBoolean() ? generateRandomWordBoundaryChunkingSettings() : generateRandomSentenceBoundaryChunkingSettings();
}

private static Map<String, Object> generateRandomWordBoundaryChunkingSettings() {
return Map.of("strategy", "word_boundary", "max_chunk_size", randomIntBetween(20, 100), "overlap", randomIntBetween(1, 50));
}

private static Map<String, Object> generateRandomSentenceBoundaryChunkingSettings() {
return Map.of(
"strategy",
"sentence_boundary",
"max_chunk_size",
randomIntBetween(20, 100),
"sentence_overlap",
randomIntBetween(0, 1)
);
}

public void testNullCtorArgsThrowException() {
assertThrows(NullPointerException.class, () -> new InferenceFieldMetadata(null, "inferenceId", "searchInferenceId", new String[0]));
assertThrows(NullPointerException.class, () -> new InferenceFieldMetadata("name", null, "searchInferenceId", new String[0]));
assertThrows(NullPointerException.class, () -> new InferenceFieldMetadata("name", "inferenceId", null, new String[0]));
assertThrows(NullPointerException.class, () -> new InferenceFieldMetadata("name", "inferenceId", "searchInferenceId", null));
assertThrows(
NullPointerException.class,
() -> new InferenceFieldMetadata(null, "inferenceId", "searchInferenceId", new String[0], Map.of())
);
assertThrows(
NullPointerException.class,
() -> new InferenceFieldMetadata("name", null, "searchInferenceId", new String[0], Map.of())
);
assertThrows(NullPointerException.class, () -> new InferenceFieldMetadata("name", "inferenceId", null, new String[0], Map.of()));
assertThrows(
NullPointerException.class,
() -> new InferenceFieldMetadata("name", "inferenceId", "searchInferenceId", null, Map.of())
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import org.apache.lucene.search.Query;
import org.elasticsearch.cluster.metadata.InferenceFieldMetadata;
import org.elasticsearch.cluster.metadata.InferenceFieldMetadataTests;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.plugins.Plugin;
Expand Down Expand Up @@ -102,7 +103,13 @@ private static class TestInferenceFieldMapper extends FieldMapper implements Inf

@Override
public InferenceFieldMetadata getMetadata(Set<String> sourcePaths) {
return new InferenceFieldMetadata(fullPath(), INFERENCE_ID, SEARCH_INFERENCE_ID, sourcePaths.toArray(new String[0]));
return new InferenceFieldMetadata(
fullPath(),
INFERENCE_ID,
SEARCH_INFERENCE_ID,
sourcePaths.toArray(new String[0]),
InferenceFieldMetadataTests.generateRandomChunkingSettings()
);
}

@Override
Expand Down
Loading
Loading