Skip to content

Commit 8172961

Browse files
Samiul-TheSoccerFanelasticsearchmachineelasticmachine
committed
[Semantic Text] Integration Test (elastic#125141)
* Initial draft test with index version setup * Adding test in phases * [CI] Auto commit changes from spotless * Adding test for search functionality * Adding test for highlighting * Adding randomization during selection process * Fix code styles by running spotlessApply * Fix code styles by running spotlessApply * Fixing forbiddenAPIcall issue * Decoupled namedWritables to use separate fake plugin and simplified other override methods * Updating settings string to variable and removed unused code * Fix SemanticQueryBuilder dependencies * fix setting maximum number of tests to run * utilizing semantci_text index version param and removed unwanted override --------- Co-authored-by: elasticsearchmachine <[email protected]> Co-authored-by: Elastic Machine <[email protected]>
1 parent c7a85ae commit 8172961

File tree

3 files changed

+217
-0
lines changed

3 files changed

+217
-0
lines changed

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,13 @@ public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
284284
return entries;
285285
}
286286

287+
@Override
288+
public List<QuerySpec<?>> getQueries() {
289+
List<QuerySpec<?>> querySpecs = new ArrayList<>(super.getQueries());
290+
filterPlugins(SearchPlugin.class).stream().flatMap(p -> p.getQueries().stream()).forEach(querySpecs::add);
291+
return querySpecs;
292+
}
293+
287294
@Override
288295
public List<NamedXContentRegistry.Entry> getNamedXContent() {
289296
List<NamedXContentRegistry.Entry> entries = new ArrayList<>(super.getNamedXContent());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.inference.integration;
9+
10+
import org.elasticsearch.action.DocWriteResponse;
11+
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
12+
import org.elasticsearch.action.search.SearchRequest;
13+
import org.elasticsearch.cluster.metadata.IndexMetadata;
14+
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
15+
import org.elasticsearch.common.settings.Settings;
16+
import org.elasticsearch.core.TimeValue;
17+
import org.elasticsearch.index.IndexVersion;
18+
import org.elasticsearch.index.IndexVersions;
19+
import org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper;
20+
import org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapperTestUtils;
21+
import org.elasticsearch.inference.SimilarityMeasure;
22+
import org.elasticsearch.license.LicenseSettings;
23+
import org.elasticsearch.plugins.Plugin;
24+
import org.elasticsearch.search.builder.SearchSourceBuilder;
25+
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
26+
import org.elasticsearch.test.ESIntegTestCase;
27+
import org.elasticsearch.test.index.IndexVersionUtils;
28+
import org.elasticsearch.xcontent.XContentBuilder;
29+
import org.elasticsearch.xcontent.XContentFactory;
30+
import org.elasticsearch.xpack.core.ml.inference.MlInferenceNamedXContentProvider;
31+
import org.elasticsearch.xpack.inference.LocalStateInferencePlugin;
32+
import org.elasticsearch.xpack.inference.Utils;
33+
import org.elasticsearch.xpack.inference.mock.TestDenseInferenceServiceExtension;
34+
import org.elasticsearch.xpack.inference.mock.TestSparseInferenceServiceExtension;
35+
import org.elasticsearch.xpack.inference.queries.SemanticQueryBuilder;
36+
import org.elasticsearch.xpack.inference.registry.ModelRegistry;
37+
import org.junit.Before;
38+
39+
import java.util.Collection;
40+
import java.util.HashMap;
41+
import java.util.List;
42+
import java.util.Locale;
43+
import java.util.Map;
44+
import java.util.Set;
45+
import java.util.stream.Collectors;
46+
47+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
48+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHighlight;
49+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
50+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertResponse;
51+
import static org.hamcrest.Matchers.equalTo;
52+
53+
public class SemanticTextIndexVersionIT extends ESIntegTestCase {
54+
private static final int MAXIMUM_NUMBER_OF_VERSIONS_TO_TEST = 25;
55+
private static final String SPARSE_SEMANTIC_FIELD = "sparse_field";
56+
private static final String DENSE_SEMANTIC_FIELD = "dense_field";
57+
private List<IndexVersion> selectedVersions;
58+
59+
@Before
60+
public void setup() throws Exception {
61+
ModelRegistry modelRegistry = internalCluster().getCurrentMasterNodeInstance(ModelRegistry.class);
62+
DenseVectorFieldMapper.ElementType elementType = randomFrom(DenseVectorFieldMapper.ElementType.values());
63+
// dot product means that we need normalized vectors; it's not worth doing that in this test
64+
SimilarityMeasure similarity = randomValueOtherThan(
65+
SimilarityMeasure.DOT_PRODUCT,
66+
() -> randomFrom(DenseVectorFieldMapperTestUtils.getSupportedSimilarities(elementType))
67+
);
68+
int dimensions = DenseVectorFieldMapperTestUtils.randomCompatibleDimensions(elementType, 100);
69+
Utils.storeSparseModel(modelRegistry);
70+
Utils.storeDenseModel(modelRegistry, dimensions, similarity, elementType);
71+
72+
Set<IndexVersion> availableVersions = IndexVersionUtils.allReleasedVersions()
73+
.stream()
74+
.filter(indexVersion -> indexVersion.onOrAfter(IndexVersions.SEMANTIC_TEXT_FIELD_TYPE))
75+
.collect(Collectors.toSet());
76+
77+
selectedVersions = randomSubsetOf(Math.min(availableVersions.size(), MAXIMUM_NUMBER_OF_VERSIONS_TO_TEST), availableVersions);
78+
}
79+
80+
@Override
81+
protected boolean forbidPrivateIndexSettings() {
82+
return false;
83+
}
84+
85+
@Override
86+
protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) {
87+
return Settings.builder().put(otherSettings).put(LicenseSettings.SELF_GENERATED_LICENSE_TYPE.getKey(), "trial").build();
88+
}
89+
90+
@Override
91+
protected Collection<Class<? extends Plugin>> nodePlugins() {
92+
return List.of(LocalStateInferencePlugin.class, FakeMlPlugin.class);
93+
}
94+
95+
/**
96+
* Generate settings for an index with a specific version.
97+
*/
98+
private Settings getIndexSettingsWithVersion(IndexVersion version) {
99+
return Settings.builder().put(indexSettings()).put(IndexMetadata.SETTING_VERSION_CREATED, version).build();
100+
}
101+
102+
/**
103+
* This test creates an index, ingests data, and performs searches (including highlighting when applicable)
104+
* for a selected subset of index versions.
105+
*/
106+
public void testSemanticText() throws Exception {
107+
for (IndexVersion version : selectedVersions) {
108+
String indexName = "test_semantic_" + randomAlphaOfLength(5).toLowerCase(Locale.ROOT);
109+
XContentBuilder mapping = XContentFactory.jsonBuilder()
110+
.startObject()
111+
.startObject("properties")
112+
.startObject(SPARSE_SEMANTIC_FIELD)
113+
.field("type", "semantic_text")
114+
.field("inference_id", TestSparseInferenceServiceExtension.TestInferenceService.NAME)
115+
.endObject()
116+
.startObject(DENSE_SEMANTIC_FIELD)
117+
.field("type", "semantic_text")
118+
.field("inference_id", TestDenseInferenceServiceExtension.TestInferenceService.NAME)
119+
.endObject()
120+
.endObject()
121+
.endObject();
122+
123+
assertAcked(prepareCreate(indexName).setSettings(getIndexSettingsWithVersion(version)).setMapping(mapping).get());
124+
125+
// Test index creation with expected version id
126+
assertTrue("Index " + indexName + " should exist", indexExists(indexName));
127+
assertEquals(
128+
"Index version should match",
129+
version.id(),
130+
client().admin()
131+
.indices()
132+
.prepareGetSettings(TimeValue.THIRTY_SECONDS, indexName)
133+
.get()
134+
.getIndexToSettings()
135+
.get(indexName)
136+
.getAsVersionId(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion::fromId)
137+
.id()
138+
);
139+
140+
// Test data ingestion
141+
String[] text = new String[] { "inference test", "another inference test" };
142+
Map<String, String[]> sourceMap = new HashMap<>();
143+
sourceMap.put(SPARSE_SEMANTIC_FIELD, text);
144+
sourceMap.put(DENSE_SEMANTIC_FIELD, text);
145+
DocWriteResponse docWriteResponse = client().prepareIndex(indexName).setSource(sourceMap).get();
146+
147+
assertEquals("Document should be created", "created", docWriteResponse.getResult().toString().toLowerCase(Locale.ROOT));
148+
149+
// Ensure index is ready
150+
client().admin().indices().refresh(new RefreshRequest(indexName)).get();
151+
ensureGreen(indexName);
152+
153+
// Semantic search with sparse embedding
154+
SearchSourceBuilder sparseSourceBuilder = new SearchSourceBuilder().query(
155+
new SemanticQueryBuilder(SPARSE_SEMANTIC_FIELD, "inference")
156+
).trackTotalHits(true);
157+
158+
assertResponse(
159+
client().search(new SearchRequest(indexName).source(sparseSourceBuilder)),
160+
response -> { assertHitCount(response, 1L); }
161+
);
162+
163+
// Highlighting semantic search with sparse embedding
164+
SearchSourceBuilder sparseSourceHighlighterBuilder = new SearchSourceBuilder().query(
165+
new SemanticQueryBuilder(SPARSE_SEMANTIC_FIELD, "inference")
166+
).highlighter(new HighlightBuilder().field(SPARSE_SEMANTIC_FIELD)).trackTotalHits(true);
167+
168+
assertResponse(client().search(new SearchRequest(indexName).source(sparseSourceHighlighterBuilder)), response -> {
169+
assertHighlight(response, 0, SPARSE_SEMANTIC_FIELD, 0, 2, equalTo("inference test"));
170+
assertHighlight(response, 0, SPARSE_SEMANTIC_FIELD, 1, 2, equalTo("another inference test"));
171+
});
172+
173+
// Semantic search with text embedding
174+
SearchSourceBuilder textSourceBuilder = new SearchSourceBuilder().query(
175+
new SemanticQueryBuilder(DENSE_SEMANTIC_FIELD, "inference")
176+
).trackTotalHits(true);
177+
178+
assertResponse(
179+
client().search(new SearchRequest(indexName).source(textSourceBuilder)),
180+
response -> { assertHitCount(response, 1L); }
181+
);
182+
183+
// Highlighting semantic search with text embedding
184+
SearchSourceBuilder textSourceHighlighterBuilder = new SearchSourceBuilder().query(
185+
new SemanticQueryBuilder(DENSE_SEMANTIC_FIELD, "inference")
186+
).highlighter(new HighlightBuilder().field(DENSE_SEMANTIC_FIELD)).trackTotalHits(true);
187+
188+
assertResponse(client().search(new SearchRequest(indexName).source(textSourceHighlighterBuilder)), response -> {
189+
assertHighlight(response, 0, DENSE_SEMANTIC_FIELD, 0, 2, equalTo("inference test"));
190+
assertHighlight(response, 0, DENSE_SEMANTIC_FIELD, 1, 2, equalTo("another inference test"));
191+
});
192+
193+
beforeIndexDeletion();
194+
assertAcked(client().admin().indices().prepareDelete(indexName));
195+
}
196+
}
197+
198+
public static class FakeMlPlugin extends Plugin {
199+
@Override
200+
public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
201+
return new MlInferenceNamedXContentProvider().getNamedWriteables();
202+
}
203+
}
204+
}

x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/LocalStateInferencePlugin.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.elasticsearch.inference.InferenceServiceExtension;
1414
import org.elasticsearch.license.XPackLicenseState;
1515
import org.elasticsearch.plugins.SearchPlugin;
16+
import org.elasticsearch.search.fetch.subphase.highlight.Highlighter;
1617
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
1718
import org.elasticsearch.xpack.core.ssl.SSLService;
1819
import org.elasticsearch.xpack.inference.mock.TestDenseInferenceServiceExtension;
@@ -63,6 +64,11 @@ public Map<String, Mapper.TypeParser> getMappers() {
6364
return inferencePlugin.getMappers();
6465
}
6566

67+
@Override
68+
public Map<String, Highlighter> getHighlighters() {
69+
return inferencePlugin.getHighlighters();
70+
}
71+
6672
@Override
6773
public Collection<MappedActionFilter> getMappedActionFilters() {
6874
return inferencePlugin.getMappedActionFilters();

0 commit comments

Comments
 (0)