Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f2605af
Adding todos
jonathan-buttner Sep 19, 2025
b0c82e5
Merge branch 'main' of github.com:elastic/elasticsearch into ml-eis-c…
jonathan-buttner Sep 25, 2025
4750b48
Starting changes
jonathan-buttner Sep 26, 2025
08a13bd
Merge branch 'main' of github.com:elastic/elasticsearch into ml-eis-c…
jonathan-buttner Oct 2, 2025
a009e36
Creating conversion functionality
jonathan-buttner Oct 3, 2025
e44d200
Trying to figure out bug
jonathan-buttner Oct 6, 2025
5e5e17e
Starting test changes
jonathan-buttner Oct 7, 2025
b4b80a4
Test changes
jonathan-buttner Oct 7, 2025
e6eed4f
[CI] Auto commit changes from spotless
Oct 8, 2025
5d1ef9c
Merge branch 'main' of github.com:elastic/elasticsearch into ml-eis-c…
jonathan-buttner Oct 8, 2025
5fbb740
Adding more tests
jonathan-buttner Oct 8, 2025
4168fb5
Merge branch 'ml-eis-call-model-reg' of github.com:jonathan-buttner/e…
jonathan-buttner Oct 8, 2025
b29d60d
[CI] Auto commit changes from spotless
Oct 8, 2025
92b79e0
Removing unnecessary files
jonathan-buttner Oct 8, 2025
ba620e3
Merge branch 'ml-eis-call-model-reg' of github.com:jonathan-buttner/e…
jonathan-buttner Oct 8, 2025
7db8baf
Fixing tests
jonathan-buttner Oct 9, 2025
5db2279
Adding some comments
jonathan-buttner Oct 9, 2025
407788f
Merge branch 'main' of github.com:elastic/elasticsearch into ml-eis-c…
jonathan-buttner Oct 9, 2025
2314635
Adding more comments and tests
jonathan-buttner Oct 9, 2025
e390213
Trying to fix yaml tests
jonathan-buttner Oct 9, 2025
d4a1a03
Adding requirement on contains
jonathan-buttner Oct 9, 2025
4dfbcd5
Adding test for unauthorized model
jonathan-buttner Oct 9, 2025
028ea33
Switching tests for error message change
jonathan-buttner Oct 9, 2025
11b5e3c
Adding compatability library
jonathan-buttner Oct 9, 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
1 change: 1 addition & 0 deletions x-pack/plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ tasks.named("yamlRestCompatTestTransform").configure({ task ->
task.skipTest("esql/46_downsample/Query stats on downsampled index", "Extra function required to enable the field type")
task.skipTest("esql/46_downsample/Render stats from downsampled index", "Extra function required to enable the field type")
task.skipTest("esql/46_downsample/Sort from multiple indices one with aggregate metric double", "Extra function required to enable the field type")
task.skipTest("inference/inference_crud/Test get missing model", "Error message changed")
})

tasks.named('yamlRestCompatTest').configure {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public void testWithInferenceNotConfigured() {
""";
ResponseException re = expectThrows(ResponseException.class, () -> runEsqlQuery(query));

assertThat(re.getMessage(), containsString("Inference endpoint not found"));
assertThat(re.getMessage(), containsString("Inference endpoint [inexistent] not found"));
assertEquals(404, re.getResponse().getStatusLine().getStatusCode());
}

Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugin/inference/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ apply plugin: 'elasticsearch.internal-es-plugin'
apply plugin: 'elasticsearch.internal-cluster-test'
apply plugin: 'elasticsearch.internal-yaml-rest-test'
apply plugin: 'elasticsearch.internal-test-artifact'
apply plugin: 'elasticsearch.yaml-rest-compat-test'

restResources {
restApi {
Expand Down Expand Up @@ -407,6 +408,13 @@ tasks.named('yamlRestTest') {
usesDefaultDistribution("Uses the inference API")
}

tasks.named("yamlRestCompatTestTransform").configure({ task ->
task.skipTest("inference/40_semantic_text_query/Query a field with an invalid inference ID", "Error message changed")
task.skipTest("inference/40_semantic_text_query/Query a field with an invalid search inference ID", "Error message changed")
task.skipTest("inference/70_text_similarity_rank_retriever/Text similarity reranking fails if the inference ID does not exist", "Error message changed")
task.skipTest("inference/70_text_similarity_rank_retriever/Text similarity reranking fails if the inference ID does not exist and result set is empty", "Error message changed")
})

artifacts {
restXpackTests(new File(projectDir, "src/yamlRestTest/resources/rest-api-spec/test"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ public class BaseMockEISAuthServerTest extends ESRestTestCase {
protected static final MockElasticInferenceServiceAuthorizationServer mockEISServer =
new MockElasticInferenceServiceAuthorizationServer();

static {
// Ensure that the mock EIS server has an authorized response prior to the cluster starting
mockEISServer.enqueueAuthorizeAllModelsResponse();
}

private static ElasticsearchCluster cluster = ElasticsearchCluster.local()
.distribution(DistributionType.DEFAULT)
.setting("xpack.license.self_generated.type", "trial")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
package org.elasticsearch.xpack.inference;

import org.elasticsearch.inference.TaskType;
import org.junit.BeforeClass;

import java.io.IOException;
import java.util.List;
Expand All @@ -22,24 +21,11 @@
import static org.hamcrest.Matchers.is;

public class InferenceGetModelsWithElasticInferenceServiceIT extends BaseMockEISAuthServerTest {

/**
* This is done before the class because I've run into issues where another class that extends {@link BaseMockEISAuthServerTest}
* results in an authorization response not being queued up for the new Elasticsearch Node in time. When the node starts up, it
* retrieves authorization. If the request isn't queued up when that happens the tests will fail. From my testing locally it seems
* like the base class's static functionality to queue a response is only done once and not for each subclass.
*
* My understanding is that the @Before will be run after the node starts up and wouldn't be sufficient to handle
* this scenario. That is why this needs to be @BeforeClass.
*/
@BeforeClass
public static void init() {
// Ensure the mock EIS server has an authorized response ready
mockEISServer.enqueueAuthorizeAllModelsResponse();
}

public void testGetDefaultEndpoints() throws IOException {
mockEISServer.enqueueAuthorizeAllModelsResponse();
var allModels = getAllModels();

mockEISServer.enqueueAuthorizeAllModelsResponse();
var chatCompletionModels = getModels("_all", TaskType.CHAT_COMPLETION);

assertThat(allModels, hasSize(7));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import org.elasticsearch.common.Strings;
import org.elasticsearch.inference.TaskType;
import org.junit.Before;
import org.junit.BeforeClass;

import java.io.IOException;
import java.util.List;
Expand All @@ -32,21 +31,6 @@ public void setUp() throws Exception {
mockEISServer.enqueueAuthorizeAllModelsResponse();
}

/**
* This is done before the class because I've run into issues where another class that extends {@link BaseMockEISAuthServerTest}
* results in an authorization response not being queued up for the new Elasticsearch Node in time. When the node starts up, it
* retrieves authorization. If the request isn't queued up when that happens the tests will fail. From my testing locally it seems
* like the base class's static functionality to queue a response is only done once and not for each subclass.
*
* My understanding is that the @Before will be run after the node starts up and wouldn't be sufficient to handle
* this scenario. That is why this needs to be @BeforeClass.
*/
@BeforeClass
public static void init() {
// Ensure the mock EIS server has an authorized response ready
mockEISServer.enqueueAuthorizeAllModelsResponse();
}

public void testGetServicesWithoutTaskType() throws IOException {
assertThat(
allProviders(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* 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.
*
* this file has been contributed to by a Generative AI
*/

package org.elasticsearch.xpack.inference;

import org.elasticsearch.client.Request;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.inference.TaskType;
import org.elasticsearch.test.cluster.ElasticsearchCluster;
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.junit.ClassRule;

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

import static org.elasticsearch.xpack.inference.InferenceBaseRestTest.assertStatusOkOrCreated;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;

public class InferenceGetServicesWithoutEisIT extends ESRestTestCase {

@ClassRule
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
.distribution(DistributionType.DEFAULT)
.setting("xpack.license.self_generated.type", "trial")
.setting("xpack.security.enabled", "true")
// This plugin is located in the inference/qa/test-service-plugin package, look for TestInferenceServicePlugin
.plugin("inference-service-test")
.user("x_pack_rest_user", "x-pack-test-password")
.build();

@Override
protected String getTestRestCluster() {
return cluster.getHttpAddresses();
}

@Override
protected Settings restClientSettings() {
String token = basicAuthHeaderValue("x_pack_rest_user", new SecureString("x-pack-test-password".toCharArray()));
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
}

public void testGetServicesWithoutTaskType() throws IOException {
assertThat(allProviders(), not(hasItem("elastic")));
}

private List<String> allProviders() throws IOException {
return providers(getAllServices());
}

@SuppressWarnings("unchecked")
private List<String> providers(List<Object> services) {
return services.stream().map(service -> {
var serviceConfig = (Map<String, Object>) service;
return (String) serviceConfig.get("service");
}).toList();
}

public void testGetServicesWithTextEmbeddingTaskType() throws IOException {
var providers = providersFor(TaskType.TEXT_EMBEDDING);
assertThat(providers.size(), not(equalTo(0)));
assertThat(providers, not(hasItem("elastic")));
}

private List<String> providersFor(TaskType taskType) throws IOException {
return providers(getServices(taskType));
}

public void testGetServicesWithRerankTaskType() throws IOException {
var providers = providersFor(TaskType.RERANK);
assertThat(providers.size(), not(equalTo(0)));
assertThat(providersFor(TaskType.RERANK), not(hasItem("elastic")));
}

public void testGetServicesWithCompletionTaskType() throws IOException {
var providers = providersFor(TaskType.COMPLETION);
assertThat(providers.size(), not(equalTo(0)));
assertThat(providersFor(TaskType.COMPLETION), not(hasItem("elastic")));
}

public void testGetServicesWithChatCompletionTaskType() throws IOException {
var providers = providersFor(TaskType.CHAT_COMPLETION);
assertThat(providers.size(), not(equalTo(0)));
assertThat(providersFor(TaskType.CHAT_COMPLETION), not(hasItem("elastic")));
}

public void testGetServicesWithSparseEmbeddingTaskType() throws IOException {
var providers = providersFor(TaskType.SPARSE_EMBEDDING);
assertThat(providers.size(), not(equalTo(0)));
assertThat(providersFor(TaskType.SPARSE_EMBEDDING), not(hasItem("elastic")));
}

private List<Object> getAllServices() throws IOException {
var endpoint = Strings.format("_inference/_services");
return getInternalAsList(endpoint);
}

private List<Object> getServices(TaskType taskType) throws IOException {
var endpoint = Strings.format("_inference/_services/%s", taskType);
return getInternalAsList(endpoint);
}

private List<Object> getInternalAsList(String endpoint) throws IOException {
var request = new Request("GET", endpoint);
var response = client().performRequest(request);
assertStatusOkOrCreated(response);
return entityAsList(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public void testRetrievingInferenceEndpoint_ThrowsException_WhenIndexNodeIsNotAv

var proxyResponse = sendInferenceProxyRequest(inferenceId);
var exception = expectThrows(ElasticsearchException.class, () -> proxyResponse.actionGet(TEST_REQUEST_TIMEOUT));
assertThat(exception.toString(), containsString("Failed to load inference endpoint with secrets [test-index-id-2]"));
assertThat(exception.toString(), containsString("Failed to load inference endpoint [test-index-id-2]"));

var causeException = exception.getCause();
assertThat(causeException, instanceOf(SearchPhaseExecutionException.class));
Expand Down Expand Up @@ -196,7 +196,7 @@ public void testRetrievingInferenceEndpoint_ThrowsException_WhenSecretsIndexNode
var proxyResponse = sendInferenceProxyRequest(inferenceId);

var exception = expectThrows(ElasticsearchException.class, () -> proxyResponse.actionGet(TEST_REQUEST_TIMEOUT));
assertThat(exception.toString(), containsString("Failed to load inference endpoint with secrets [test-secrets-index-id]"));
assertThat(exception.toString(), containsString("Failed to load inference endpoint [test-secrets-index-id]"));

var causeException = exception.getCause();

Expand Down
Loading