Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
3c67123
Implement OpenShift AI integration for chat completion, embeddings, a…
Jan-Kazlouski-elastic Oct 15, 2025
fdb22ff
Refactor OpenShift AI service settings to use underscores in constant…
Jan-Kazlouski-elastic Oct 15, 2025
8ce569e
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Oct 16, 2025
b268e08
Add constructor to OpenShiftAiChatCompletionServiceSettings for URL h…
Jan-Kazlouski-elastic Oct 16, 2025
9cae6b1
Add unit tests
Jan-Kazlouski-elastic Oct 16, 2025
f804331
[CI] Auto commit changes from spotless
Oct 16, 2025
af2fcd6
Add tests for UnifiedCompletionRequest model ID overrides in OpenShif…
Jan-Kazlouski-elastic Oct 17, 2025
b98d8d6
Add unit tests for OpenShiftAiChatCompletionResponseHandler
Jan-Kazlouski-elastic Oct 17, 2025
b19342f
Add unit tests for OpenShiftAiChatCompletionServiceSettings
Jan-Kazlouski-elastic Oct 17, 2025
6af168c
Update request type description in OpenShiftAiCompletionResponseHandler
Jan-Kazlouski-elastic Oct 17, 2025
aadbfde
Refactor OpenShiftAiEmbeddingsServiceSettings to improve validation l…
Jan-Kazlouski-elastic Oct 17, 2025
fc5c182
Update OpenShiftAiChatCompletionRequestEntity to use new method for m…
Jan-Kazlouski-elastic Oct 17, 2025
d664644
Add unit tests for OpenShiftAiChatCompletionRequestEntity serialization
Jan-Kazlouski-elastic Oct 17, 2025
e6d4079
Add unit tests for OpenShiftAiEmbeddingsRequest and update model crea…
Jan-Kazlouski-elastic Oct 20, 2025
e0ecbc7
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Oct 20, 2025
fb31094
Add unit tests for OpenShiftAiEmbeddingsRequestEntity
Jan-Kazlouski-elastic Oct 20, 2025
8e78337
Fix Typo in OpenShiftAiRerankTaskSettings, add tests for request models
Jan-Kazlouski-elastic Oct 20, 2025
ce2cf92
Add unit tests for OpenShiftAiRerankServiceSettings and OpenShiftAiRe…
Jan-Kazlouski-elastic Oct 20, 2025
6c6dfe5
[CI] Auto commit changes from spotless
Oct 20, 2025
52d439f
Add unit tests for OpenShiftAiRerankServiceSettings and OpenShiftAiRe…
Jan-Kazlouski-elastic Oct 20, 2025
d63f84f
Refactor tests in OpenShiftAIRerankRequestEntityTests and OpenShiftAi…
Jan-Kazlouski-elastic Oct 21, 2025
63c2a58
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Oct 21, 2025
0a6da54
Enhance OpenShift AI service with detailed comments and utility class…
Jan-Kazlouski-elastic Oct 21, 2025
5aef343
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Oct 22, 2025
cc706be
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Oct 23, 2025
55bf99d
Refactor OpenShiftAiModel
Jan-Kazlouski-elastic Oct 23, 2025
d73f5da
Remove unused rateLimitSettings field from OpenShiftAiModel
Jan-Kazlouski-elastic Oct 23, 2025
016efd6
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Oct 24, 2025
636d72a
Fix JavaDoc and update transport version
Jan-Kazlouski-elastic Oct 24, 2025
95e7ef5
Add JavaDoc comments and null checks for OpenShift AI request entity
Jan-Kazlouski-elastic Oct 24, 2025
065779d
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Oct 27, 2025
bad5f31
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Oct 29, 2025
291f40a
Enhance mutation logic in OpenShift AI service settings tests
Jan-Kazlouski-elastic Oct 29, 2025
c34f6c4
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Oct 30, 2025
e55f9fd
Update Transport Versions and refactor unit tests
Jan-Kazlouski-elastic Oct 30, 2025
c9640e2
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Oct 31, 2025
c46a1e1
Fix OpenShiftAiServiceTests, update transport version
Jan-Kazlouski-elastic Oct 31, 2025
53e8118
Enhance OpenShift AI model tests to support chunking settings and imp…
Jan-Kazlouski-elastic Oct 31, 2025
1c3a86b
Merge remote-tracking branch 'origin/main' into feature/nvidia-integr…
Jan-Kazlouski-elastic Nov 4, 2025
7e6d696
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Nov 5, 2025
d68a636
Fix PR comments
Jan-Kazlouski-elastic Nov 5, 2025
7f40459
Refactor accept methods in OpenShift AI models to include task settin…
Jan-Kazlouski-elastic Nov 5, 2025
ffd491d
Refactor OpenShift AI model to use model ID directly instead of Unifi…
Jan-Kazlouski-elastic Nov 5, 2025
4fc2556
Update JinaAI rerank model token limit in rerankerWindowSize method
Jan-Kazlouski-elastic Nov 5, 2025
dbc1c56
Enhance documentation for OpenShift AI models and add task settings h…
Jan-Kazlouski-elastic Nov 6, 2025
e9fbce7
Refactor OpenShift AI Rerank handler to use JinaAIResponseHandler, us…
Jan-Kazlouski-elastic Nov 6, 2025
aeec397
Fix parameter documentation for modelId in OpenShift AI service setti…
Jan-Kazlouski-elastic Nov 6, 2025
8813936
Refactor OpenShift AI service settings to streamline common settings …
Jan-Kazlouski-elastic Nov 6, 2025
544ed20
Add check for empty or unchanged task settings in OpenShift AI rerank…
Jan-Kazlouski-elastic Nov 6, 2025
7d0b5e1
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Nov 6, 2025
8b5c407
Replace TIMEOUT constant with ESTestCase.TEST_REQUEST_TIMEOUT in Open…
Jan-Kazlouski-elastic Nov 6, 2025
9aaddfd
Refactor OpenShift AI service tests to use constants for URL, model I…
Jan-Kazlouski-elastic Nov 6, 2025
bd3cf05
Add assertions for OpenShift AI embeddings model service settings
Jan-Kazlouski-elastic Nov 6, 2025
0902e9a
Refactor assertions in OpenShift AI tests to use Hamcrest matchers
Jan-Kazlouski-elastic Nov 7, 2025
a1a56b6
Update assertions in OpenShift AI chat completion model tests to use …
Jan-Kazlouski-elastic Nov 7, 2025
6e7fbb4
Fix formatting issues in error messages for OpenShift AI chat complet…
Jan-Kazlouski-elastic Nov 7, 2025
8c5524c
Refactor OpenShift AI tests to use constants for model ID, API key, a…
Jan-Kazlouski-elastic Nov 7, 2025
e9b5d97
Refactor OpenShift AI chat completion tests to use constants for URL,…
Jan-Kazlouski-elastic Nov 7, 2025
356bd84
Refactor OpenShift AI tests to use getFirst() for request retrieval
Jan-Kazlouski-elastic Nov 7, 2025
401ce6b
Remove redundant request body assertions in OpenShift AI action creat…
Jan-Kazlouski-elastic Nov 7, 2025
40d07ae
Remove redundant assertions in OpenShift AI action creator tests
Jan-Kazlouski-elastic Nov 7, 2025
a10dc51
Rename input variables in OpenShift AI action creator tests for clarity
Jan-Kazlouski-elastic Nov 7, 2025
662ccc6
Refactor error message assertions in OpenShift AI tests for improved …
Jan-Kazlouski-elastic Nov 7, 2025
00d803f
Refactor OpenShift AI action creator tests to use NO_RETRY_SETTINGS f…
Jan-Kazlouski-elastic Nov 7, 2025
9b4d560
Refactor OpenShift AI action creator tests to improve variable naming…
Jan-Kazlouski-elastic Nov 7, 2025
dc3a27f
Enhance OpenShift AI action creator tests with additional task settin…
Jan-Kazlouski-elastic Nov 7, 2025
c752a84
Refactor error message assertion in OpenShift AI tests for improved f…
Jan-Kazlouski-elastic Nov 7, 2025
b1c243b
Remove redundant assertion in OpenShift AI action creator tests for c…
Jan-Kazlouski-elastic Nov 7, 2025
9de3fd6
Ensure non-null values for user-defined dimensions and URI in OpenShi…
Jan-Kazlouski-elastic Nov 7, 2025
5f0cc26
Remove redundant test for OpenShift AI embeddings service settings se…
Jan-Kazlouski-elastic Nov 7, 2025
21bda22
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Nov 8, 2025
01eb0ac
Refactor assertions in OpenShift AI tests for consistency and clarity
Jan-Kazlouski-elastic Nov 8, 2025
c75b710
Enhance OpenShift AI service settings tests for clarity and completeness
Jan-Kazlouski-elastic Nov 8, 2025
27654f8
Refactor OpenShift AI request tests to improve variable naming and as…
Jan-Kazlouski-elastic Nov 8, 2025
73e75dc
Refactor OpenShift AI test constants for improved clarity and consist…
Jan-Kazlouski-elastic Nov 8, 2025
d37cfaa
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Nov 10, 2025
30319bd
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Nov 10, 2025
1de3b06
Refactor OpenShift AI action creator tests for improved readability a…
Jan-Kazlouski-elastic Nov 10, 2025
9a7586f
Add DIMENSIONS_SET_BY_USER constant and refactor variable names for c…
Jan-Kazlouski-elastic Nov 10, 2025
bb54f4b
Update OpenShift AI embeddings request tests to pass null for dimensions
Jan-Kazlouski-elastic Nov 10, 2025
5fd79a7
Refactor OpenShift AI test constants for improved clarity and consist…
Jan-Kazlouski-elastic Nov 10, 2025
6aa70a6
Refactor OpenShift AI test field names for clarity and consistency
Jan-Kazlouski-elastic Nov 10, 2025
e5c58b4
Add validation tests for invalid and empty URL in OpenShift AI settings
Jan-Kazlouski-elastic Nov 10, 2025
4a0c7ba
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Nov 11, 2025
0e1b14b
Refactor OpenShift AI test constants for improved clarity and consist…
Jan-Kazlouski-elastic Nov 12, 2025
f242b74
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Nov 12, 2025
a9974cc
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Nov 12, 2025
70bebeb
Add "openshift_ai" to various service lists in InferenceGetServicesIT
Jan-Kazlouski-elastic Nov 12, 2025
b30282d
Fix embeddings input handling in OpenShiftAiActionCreator
Jan-Kazlouski-elastic Nov 12, 2025
6825e6c
Merge remote-tracking branch 'origin/main' into openshift-ai-integration
Jan-Kazlouski-elastic Nov 13, 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
Original file line number Diff line number Diff line change
@@ -1 +1 @@
9213000
9216000
2 changes: 1 addition & 1 deletion server/src/main/resources/transport/upper_bounds/9.3.csv
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ml_inference_openshift_ai_added,9213000
ml_inference_openshift_ai_added,9216000
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public final class ServiceFields {

public static final String SIMILARITY = "similarity";
public static final String DIMENSIONS = "dimensions";
public static final String DIMENSIONS_SET_BY_USER = "dimensions_set_by_user";
// Typically we use this to define the maximum tokens for the input text (text being sent to an integration)
public static final String MAX_INPUT_TOKENS = "max_input_tokens";
public static final String URL = "url";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ protected OpenShiftAiServiceSettings(StreamInput in) throws IOException {
*/
protected OpenShiftAiServiceSettings(@Nullable String modelId, URI uri, @Nullable RateLimitSettings rateLimitSettings) {
this.modelId = modelId;
this.uri = uri;
this.uri = Objects.requireNonNull(uri);
this.rateLimitSettings = Objects.requireNonNullElse(rateLimitSettings, DEFAULT_RATE_LIMIT_SETTINGS);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.Objects;

import static org.elasticsearch.xpack.inference.services.ServiceFields.DIMENSIONS;
import static org.elasticsearch.xpack.inference.services.ServiceFields.DIMENSIONS_SET_BY_USER;
import static org.elasticsearch.xpack.inference.services.ServiceFields.MAX_INPUT_TOKENS;
import static org.elasticsearch.xpack.inference.services.ServiceFields.SIMILARITY;
import static org.elasticsearch.xpack.inference.services.ServiceUtils.createUri;
Expand All @@ -40,7 +41,6 @@
*/
public class OpenShiftAiEmbeddingsServiceSettings extends OpenShiftAiServiceSettings {
public static final String NAME = "openshift_ai_embeddings_service_settings";
static final String DIMENSIONS_SET_BY_USER = "dimensions_set_by_user";

private final Integer dimensions;
private final SimilarityMeasure similarity;
Expand Down Expand Up @@ -137,7 +137,7 @@ public OpenShiftAiEmbeddingsServiceSettings(
this.dimensions = dimensions;
this.similarity = similarity;
this.maxInputTokens = maxInputTokens;
this.dimensionsSetByUser = dimensionsSetByUser;
this.dimensionsSetByUser = Objects.requireNonNull(dimensionsSetByUser);
}

/**
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@
import org.elasticsearch.xpack.inference.services.settings.DefaultSecretSettings;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;

public class OpenShiftAiChatCompletionModelTests extends ESTestCase {

private static final String MODEL_VALUE = "model_name";
private static final String API_KEY_VALUE = "test_api_key";
private static final String URL_VALUE = "http://www.abc.com";
private static final String ALTERNATE_MODEL_VALUE = "different_model";

public static OpenShiftAiChatCompletionModel createCompletionModel(String url, String apiKey, String modelName) {
return createModelWithTaskType(url, apiKey, modelName, TaskType.COMPLETION);
}
Expand All @@ -34,23 +41,30 @@ public static OpenShiftAiChatCompletionModel createModelWithTaskType(String url,
}

public void testOverrideWith_UnifiedCompletionRequest_KeepsSameModelId() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that this test adds much value as is, since I can't think of any reasonable way it might be expected to fail in the event of a bug, and it doesn't allow us to differentiate between the case where the model name is overridden and the case where the original model name is used, because the outcome is the same in both.

If the OpenShiftAiChatCompletionModel.of() method was modified to return the original model if the model names were equal, then this test could use assertThat(overriddenModel, is(sameInstance(model))); to make a meaningful assertion. The sameInstance() assertion could also be used in testOverrideWith_UnifiedCompletionRequest_KeepsNullIfNoModelIdProvided() and testOverrideWith_UnifiedCompletionRequest_UsesModelFields_WhenRequestDoesNotOverride() to strengthen those tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Fixed.

var model = createCompletionModel("url", "api_key", "model_name");
var overriddenModel = OpenShiftAiChatCompletionModel.of(model, "model_name");
var model = createCompletionModel(URL_VALUE, API_KEY_VALUE, MODEL_VALUE);
var overriddenModel = OpenShiftAiChatCompletionModel.of(model, MODEL_VALUE);

assertThat(overriddenModel.getServiceSettings().modelId(), is("model_name"));
assertThat(overriddenModel, is(sameInstance(model)));
}

public void testOverrideWith_UnifiedCompletionRequest_OverridesExistingModelId() {
var model = createCompletionModel("url", "api_key", "model_name");
var overriddenModel = OpenShiftAiChatCompletionModel.of(model, "different_model");
var model = createCompletionModel(URL_VALUE, API_KEY_VALUE, MODEL_VALUE);
var overriddenModel = OpenShiftAiChatCompletionModel.of(model, ALTERNATE_MODEL_VALUE);

assertThat(overriddenModel.getServiceSettings().modelId(), is("different_model"));
assertThat(overriddenModel.getServiceSettings().modelId(), is(ALTERNATE_MODEL_VALUE));
}

public void testOverrideWith_UnifiedCompletionRequest_UsesModelFields_WhenRequestDoesNotOverride() {
var model = createCompletionModel("url", "api_key", "model_name");
var model = createCompletionModel(URL_VALUE, API_KEY_VALUE, MODEL_VALUE);
var overriddenModel = OpenShiftAiChatCompletionModel.of(model, null);

assertThat(overriddenModel, is(sameInstance(model)));
}

public void testOverrideWith_UnifiedCompletionRequest_KeepsNullIfNoModelIdProvided() {
var model = createCompletionModel(URL_VALUE, API_KEY_VALUE, null);
var overriddenModel = OpenShiftAiChatCompletionModel.of(model, null);

assertThat(overriddenModel.getServiceSettings().modelId(), is("model_name"));
assertThat(overriddenModel, is(sameInstance(model)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import static org.mockito.Mockito.when;

public class OpenShiftAiChatCompletionResponseHandlerTests extends ESTestCase {
private static final String URL_VALUE = "http://www.abc.com";
private static final String INFERENCE_ID = "id";
private final OpenShiftAiChatCompletionResponseHandler responseHandler = new OpenShiftAiChatCompletionResponseHandler(
"chat completions",
(a, b) -> mock()
Expand All @@ -49,19 +51,20 @@ public void testFailNotFound() throws IOException {
{
"error" : {
"code" : "not_found",
"message" : "Resource not found at [https://api.llama.ai/v1/chat/completions] for request from inference entity id [id] \
"message" : "Resource not found at [%s] for request from inference entity id [%s] \
status [404]. Error message: [{\\"detail\\":\\"Not Found\\"}]",
"type" : "openshift_ai_error"
}
}""")));
}""".formatted(URL_VALUE, INFERENCE_ID))));
}

public void testFailBadRequest() throws IOException {
var responseJson = XContentHelper.stripWhitespace("""
{
"object": "error",
"message": "[{'type': 'missing', 'loc': ('body', 'messages'), 'msg': 'Field required', 'input': {'model': 'llama-31-8b-ins\
truct', '1messages': [{'role': 'user', 'content': 'What is deep learning?'}], 'max_tokens': 2, 'stream': True}}]",
"message": "[{'type': 'missing', 'loc': ('body', 'messages'), 'msg': 'Field required', \
'input': {'model': 'llama-31-8b-instruct', 'messages': [{'role': 'user', 'content': 'What is deep learning?'}], \
'max_tokens': 2, 'stream': True}}]",
"type": "Bad Request",
"param": null,
"code": 400
Expand All @@ -74,14 +77,14 @@ public void testFailBadRequest() throws IOException {
{
"error": {
"code": "bad_request",
"message": "Received a bad request status code for request from inference entity id [id] status [400].\
Error message: [{\\"object\\":\\"error\\",\\"message\\":\\"[{'type': 'missing', 'loc': ('body', 'messages'), 'msg': 'Field r\
equired', 'input': {'model': 'llama-31-8b-ins truct', '1messages': [{'role': 'user', 'content': 'What is deep learning?'}]\
, 'max_tokens': 2, 'stream': True}}]\\",\\"type\\":\\"Bad Request\\",\\"param\\":null,\\"code\\":400}]",
"message": "Received a bad request status code for request from inference entity id [%s] status [400]. Error message: \
[{\\"object\\":\\"error\\",\\"message\\":\\"[{'type': 'missing', 'loc': ('body', 'messages'), 'msg': 'Field required', \
'input': {'model': 'llama-31-8b-instruct', 'messages': [{'role': 'user', 'content': 'What is deep learning?'}], \
'max_tokens': 2, 'stream': True}}]\\",\\"type\\":\\"Bad Request\\",\\"param\\":null,\\"code\\":400}]",
"type": "openshift_ai_error"
}
}
""")));
""".formatted(INFERENCE_ID))));
}

public void testFailValidationWithInvalidJson() throws IOException {
Expand All @@ -95,12 +98,12 @@ public void testFailValidationWithInvalidJson() throws IOException {
{
"error": {
"code": "bad_request",
"message": "Received a server error status code for request from inference entity id [id] status [500]. Error message: \
[what? this isn't a json\\n]",
"message": "Received a server error status code for request from inference entity id [%s] status [500]. \
Error message: [what? this isn't a json\\n]",
"type": "openshift_ai_error"
}
}
""")));
""".formatted(INFERENCE_ID))));
}

private String invalidResponseJson(String responseJson, int statusCode) throws IOException {
Expand All @@ -124,9 +127,9 @@ private Exception invalidResponse(String responseJson, int statusCode) {

private static Request mockRequest() throws URISyntaxException {
var request = mock(Request.class);
when(request.getInferenceEntityId()).thenReturn("id");
when(request.getInferenceEntityId()).thenReturn(INFERENCE_ID);
when(request.isStreaming()).thenReturn(true);
when(request.getURI()).thenReturn(new URI("https://api.llama.ai/v1/chat/completions"));
when(request.getURI()).thenReturn(new URI(URL_VALUE));
return request;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,19 @@
public class OpenShiftAiChatCompletionServiceSettingsTests extends AbstractBWCWireSerializationTestCase<
OpenShiftAiChatCompletionServiceSettings> {

public static final String MODEL_ID = "some model";
public static final String CORRECT_URL = "https://www.elastic.co";
public static final int RATE_LIMIT = 2;
private static final String MODEL_VALUE = "some_model";
private static final String URL_VALUE = "http://www.abc.com";
private static final String INVALID_URL_VALUE = "^^^";
private static final int RATE_LIMIT = 2;

public void testFromMap_AllFields_Success() {
var serviceSettings = OpenShiftAiChatCompletionServiceSettings.fromMap(
new HashMap<>(
Map.of(
ServiceFields.MODEL_ID,
MODEL_ID,
MODEL_VALUE,
ServiceFields.URL,
CORRECT_URL,
URL_VALUE,
RateLimitSettings.FIELD_NAME,
new HashMap<>(Map.of(RateLimitSettings.REQUESTS_PER_MINUTE_FIELD, RATE_LIMIT))
)
Expand All @@ -54,7 +55,7 @@ public void testFromMap_AllFields_Success() {

assertThat(
serviceSettings,
is(new OpenShiftAiChatCompletionServiceSettings(MODEL_ID, CORRECT_URL, new RateLimitSettings(RATE_LIMIT)))
is(new OpenShiftAiChatCompletionServiceSettings(MODEL_VALUE, URL_VALUE, new RateLimitSettings(RATE_LIMIT)))
);
}

Expand All @@ -63,56 +64,85 @@ public void testFromMap_MissingModelId_Success() {
new HashMap<>(
Map.of(
ServiceFields.URL,
CORRECT_URL,
URL_VALUE,
RateLimitSettings.FIELD_NAME,
new HashMap<>(Map.of(RateLimitSettings.REQUESTS_PER_MINUTE_FIELD, RATE_LIMIT))
)
),
ConfigurationParseContext.PERSISTENT
);

assertThat(serviceSettings, is(new OpenShiftAiChatCompletionServiceSettings(null, CORRECT_URL, new RateLimitSettings(RATE_LIMIT))));
assertThat(serviceSettings, is(new OpenShiftAiChatCompletionServiceSettings(null, URL_VALUE, new RateLimitSettings(RATE_LIMIT))));
}

public void testFromMap_MissingUrl_ThrowsException() {
testFromMap_InvalidUrl(
Map.of(
ServiceFields.MODEL_ID,
MODEL_VALUE,
RateLimitSettings.FIELD_NAME,
new HashMap<>(Map.of(RateLimitSettings.REQUESTS_PER_MINUTE_FIELD, RATE_LIMIT))
),
"Validation Failed: 1: [service_settings] does not contain the required setting [url];"
);
}

public void testFromMap_InvalidUrl_ThrowsException() {
testFromMap_InvalidUrl(
Map.of(
ServiceFields.URL,
INVALID_URL_VALUE,
ServiceFields.MODEL_ID,
MODEL_VALUE,
RateLimitSettings.FIELD_NAME,
new HashMap<>(Map.of(RateLimitSettings.REQUESTS_PER_MINUTE_FIELD, RATE_LIMIT))
),
"""
Validation Failed: 1: [service_settings] Invalid url [^^^] received for field [url]. \
Error: unable to parse url [^^^]. Reason: Illegal character in path;"""
);
}

public void testFromMap_EmptyUrl_ThrowsException() {
testFromMap_InvalidUrl(
Map.of(
ServiceFields.URL,
"",
ServiceFields.MODEL_ID,
MODEL_VALUE,
RateLimitSettings.FIELD_NAME,
new HashMap<>(Map.of(RateLimitSettings.REQUESTS_PER_MINUTE_FIELD, RATE_LIMIT))
),
"Validation Failed: 1: [service_settings] Invalid value empty string. [url] must be a non-empty string;"
);
}

private static void testFromMap_InvalidUrl(Map<String, Object> serviceSettingsMap, String expectedErrorMessage) {
var thrownException = expectThrows(
ValidationException.class,
() -> OpenShiftAiChatCompletionServiceSettings.fromMap(
new HashMap<>(
Map.of(
ServiceFields.MODEL_ID,
MODEL_ID,
RateLimitSettings.FIELD_NAME,
new HashMap<>(Map.of(RateLimitSettings.REQUESTS_PER_MINUTE_FIELD, RATE_LIMIT))
)
),
ConfigurationParseContext.PERSISTENT
)
() -> OpenShiftAiChatCompletionServiceSettings.fromMap(new HashMap<>(serviceSettingsMap), ConfigurationParseContext.PERSISTENT)
);

assertThat(
thrownException.getMessage(),
containsString("Validation Failed: 1: [service_settings] does not contain the required setting [url];")
);
assertThat(thrownException.getMessage(), containsString(expectedErrorMessage));
}

public void testFromMap_MissingRateLimit_Success() {
var serviceSettings = OpenShiftAiChatCompletionServiceSettings.fromMap(
new HashMap<>(Map.of(ServiceFields.MODEL_ID, MODEL_ID, ServiceFields.URL, CORRECT_URL)),
new HashMap<>(Map.of(ServiceFields.MODEL_ID, MODEL_VALUE, ServiceFields.URL, URL_VALUE)),
ConfigurationParseContext.PERSISTENT
);

assertThat(serviceSettings, is(new OpenShiftAiChatCompletionServiceSettings(MODEL_ID, CORRECT_URL, null)));
assertThat(serviceSettings, is(new OpenShiftAiChatCompletionServiceSettings(MODEL_VALUE, URL_VALUE, null)));
}

public void testToXContent_WritesAllValues() throws IOException {
var serviceSettings = OpenShiftAiChatCompletionServiceSettings.fromMap(
new HashMap<>(
Map.of(
ServiceFields.MODEL_ID,
MODEL_ID,
MODEL_VALUE,
ServiceFields.URL,
CORRECT_URL,
URL_VALUE,
RateLimitSettings.FIELD_NAME,
new HashMap<>(Map.of(RateLimitSettings.REQUESTS_PER_MINUTE_FIELD, RATE_LIMIT))
)
Expand All @@ -125,20 +155,20 @@ public void testToXContent_WritesAllValues() throws IOException {
String xContentResult = Strings.toString(builder);
var expected = XContentHelper.stripWhitespace("""
{
"model_id": "some model",
"url": "https://www.elastic.co",
"model_id": "%s",
"url": "%s",
"rate_limit": {
"requests_per_minute": 2
}
}
""");
""".formatted(MODEL_VALUE, URL_VALUE));

assertThat(xContentResult, is(expected));
}

public void testToXContent_DoesNotWriteOptionalValues_DefaultRateLimit() throws IOException {
var serviceSettings = OpenShiftAiChatCompletionServiceSettings.fromMap(
new HashMap<>(Map.of(ServiceFields.URL, CORRECT_URL)),
new HashMap<>(Map.of(ServiceFields.URL, URL_VALUE)),
ConfigurationParseContext.PERSISTENT
);

Expand All @@ -147,12 +177,12 @@ public void testToXContent_DoesNotWriteOptionalValues_DefaultRateLimit() throws
String xContentResult = Strings.toString(builder);
var expected = XContentHelper.stripWhitespace("""
{
"url": "https://www.elastic.co",
"url": "%s",
"rate_limit": {
"requests_per_minute": 3000
}
}
""");
""".formatted(URL_VALUE));
assertThat(xContentResult, is(expected));
}

Expand Down
Loading