diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiEmbeddingOptions.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiEmbeddingOptions.java index d0efd22cd21..48ec238166c 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiEmbeddingOptions.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiEmbeddingOptions.java @@ -23,6 +23,7 @@ * The configuration information for the embedding requests. * * @author Christian Tzolov + * @author Thomas Vitale * @since 0.8.0 */ public class AzureOpenAiEmbeddingOptions implements EmbeddingOptions { @@ -123,6 +124,11 @@ public AzureOpenAiEmbeddingOptions build() { } + @Override + public String getModel() { + return getDeploymentName(); + } + public String getUser() { return this.user; } @@ -147,6 +153,7 @@ public void setInputType(String inputType) { this.inputType = inputType; } + @Override public Integer getDimensions() { return this.dimensions; } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereEmbeddingOptions.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereEmbeddingOptions.java index 420aff28d80..04a2b37d607 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereEmbeddingOptions.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereEmbeddingOptions.java @@ -25,6 +25,7 @@ /** * @author Christian Tzolov + * @author Thomas Vitale */ @JsonInclude(Include.NON_NULL) public class BedrockCohereEmbeddingOptions implements EmbeddingOptions { @@ -86,4 +87,14 @@ public void setTruncate(Truncate truncate) { this.truncate = truncate; } + @Override + public String getModel() { + return null; + } + + @Override + public Integer getDimensions() { + return null; + } + } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanEmbeddingOptions.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanEmbeddingOptions.java index d2770f245bb..51a957b7185 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanEmbeddingOptions.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanEmbeddingOptions.java @@ -24,6 +24,7 @@ /** * @author Wei Jiang + * @author Thomas Vitale */ @JsonInclude(Include.NON_NULL) public class BedrockTitanEmbeddingOptions implements EmbeddingOptions { @@ -62,4 +63,14 @@ public void setInputType(InputType inputType) { this.inputType = inputType; } + @Override + public String getModel() { + return null; + } + + @Override + public Integer getDimensions() { + return null; + } + } diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxEmbeddingOptions.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxEmbeddingOptions.java index 37a043fc315..15143b1c823 100644 --- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxEmbeddingOptions.java +++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxEmbeddingOptions.java @@ -24,6 +24,7 @@ * This class represents the options for MiniMax embedding. * * @author Geng Rong + * @author Thomas Vitale * @since 1.0.0 M1 */ @JsonInclude(Include.NON_NULL) @@ -59,6 +60,7 @@ public MiniMaxEmbeddingOptions build() { } + @Override public String getModel() { return this.model; } @@ -67,4 +69,9 @@ public void setModel(String model) { this.model = model; } + @Override + public Integer getDimensions() { + return null; + } + } diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingOptions.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingOptions.java index 1603ac18696..8f66999e964 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingOptions.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingOptions.java @@ -22,6 +22,7 @@ /** * @author Ricken Bazolo + * @author Thomas Vitale * @since 0.8.1 */ @JsonInclude(Include.NON_NULL) @@ -41,6 +42,7 @@ public static Builder builder() { return new Builder(); } + @Override public String getModel() { return this.model; } @@ -57,6 +59,11 @@ public void setEncodingFormat(String encodingFormat) { this.encodingFormat = encodingFormat; } + @Override + public Integer getDimensions() { + return null; + } + public static class Builder { protected MistralAiEmbeddingOptions options; diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java index 0cc2dfcbf59..4485be23f9a 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java @@ -498,6 +498,7 @@ public OllamaOptions withFunction(String functionName) { // ------------------- // Getters and Setters // ------------------- + @Override public String getModel() { return model; } @@ -762,6 +763,11 @@ public void setTruncate(Boolean truncate) { this.truncate = truncate; } + @Override + public Integer getDimensions() { + return null; + } + @Override public List getFunctionCallbacks() { return this.functionCallbacks; diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiEmbeddingModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiEmbeddingModel.java index 5ec103d5dbe..2b61674c2ea 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiEmbeddingModel.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiEmbeddingModel.java @@ -30,7 +30,6 @@ import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; import org.springframework.ai.embedding.observation.EmbeddingModelObservationDocumentation; import org.springframework.ai.embedding.observation.EmbeddingModelObservationContext; -import org.springframework.ai.embedding.observation.EmbeddingModelRequestOptions; import org.springframework.ai.model.ModelOptionsUtils; import org.springframework.ai.observation.AiOperationMetadata; import org.springframework.ai.observation.conventions.AiOperationType; @@ -39,9 +38,9 @@ import org.springframework.ai.openai.api.OpenAiApi.EmbeddingList; import org.springframework.ai.openai.metadata.OpenAiUsage; import org.springframework.ai.retry.RetryUtils; +import org.springframework.lang.Nullable; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import java.util.List; @@ -126,7 +125,7 @@ public OpenAiEmbeddingModel(OpenAiApi openAiApi, MetadataMode metadataMode, Open */ public OpenAiEmbeddingModel(OpenAiApi openAiApi, MetadataMode metadataMode, OpenAiEmbeddingOptions options, RetryTemplate retryTemplate, ObservationRegistry observationRegistry) { - Assert.notNull(openAiApi, "OpenAiService must not be null"); + Assert.notNull(openAiApi, "openAiApi must not be null"); Assert.notNull(metadataMode, "metadataMode must not be null"); Assert.notNull(options, "options must not be null"); Assert.notNull(retryTemplate, "retryTemplate must not be null"); @@ -147,12 +146,13 @@ public List embed(Document document) { @Override public EmbeddingResponse call(EmbeddingRequest request) { - org.springframework.ai.openai.api.OpenAiApi.EmbeddingRequest> apiRequest = createRequest(request); + OpenAiEmbeddingOptions requestOptions = mergeOptions(request.getOptions(), this.defaultOptions); + OpenAiApi.EmbeddingRequest> apiRequest = createRequest(request, requestOptions); var observationContext = EmbeddingModelObservationContext.builder() .embeddingRequest(request) .operationMetadata(buildOperationMetadata()) - .requestOptions(buildRequestOptions(apiRequest)) + .requestOptions(requestOptions) .build(); return EmbeddingModelObservationDocumentation.EMBEDDING_MODEL_OPERATION @@ -183,21 +183,31 @@ public EmbeddingResponse call(EmbeddingRequest request) { }); } - @SuppressWarnings("unchecked") - private OpenAiApi.EmbeddingRequest> createRequest(EmbeddingRequest request) { - org.springframework.ai.openai.api.OpenAiApi.EmbeddingRequest> apiRequest = (this.defaultOptions != null) - ? new org.springframework.ai.openai.api.OpenAiApi.EmbeddingRequest<>(request.getInstructions(), - this.defaultOptions.getModel(), this.defaultOptions.getEncodingFormat(), - this.defaultOptions.getDimensions(), this.defaultOptions.getUser()) - : new org.springframework.ai.openai.api.OpenAiApi.EmbeddingRequest<>(request.getInstructions(), - OpenAiApi.DEFAULT_EMBEDDING_MODEL); - - if (request.getOptions() != null && !EmbeddingOptions.EMPTY.equals(request.getOptions())) { - apiRequest = ModelOptionsUtils.merge(request.getOptions(), apiRequest, - org.springframework.ai.openai.api.OpenAiApi.EmbeddingRequest.class); + private OpenAiApi.EmbeddingRequest> createRequest(EmbeddingRequest request, + OpenAiEmbeddingOptions requestOptions) { + return new OpenAiApi.EmbeddingRequest<>(request.getInstructions(), requestOptions.getModel(), + requestOptions.getEncodingFormat(), requestOptions.getDimensions(), requestOptions.getUser()); + } + + /** + * Merge runtime and default {@link EmbeddingOptions} to compute the final options to + * use in the request. + */ + private OpenAiEmbeddingOptions mergeOptions(@Nullable EmbeddingOptions runtimeOptions, + OpenAiEmbeddingOptions defaultOptions) { + if (runtimeOptions == null) { + return defaultOptions; } - return apiRequest; + return OpenAiEmbeddingOptions.builder() + // Handle portable embedding options + .withModel(ModelOptionsUtils.mergeOption(runtimeOptions.getModel(), defaultOptions.getModel())) + .withDimensions( + ModelOptionsUtils.mergeOption(runtimeOptions.getDimensions(), defaultOptions.getDimensions())) + // Handle OpenAI specific embedding options + .withEncodingFormat(defaultOptions.getEncodingFormat()) + .withUser(defaultOptions.getUser()) + .build(); } private AiOperationMetadata buildOperationMetadata() { @@ -207,14 +217,6 @@ private AiOperationMetadata buildOperationMetadata() { .build(); } - private EmbeddingModelRequestOptions buildRequestOptions(OpenAiApi.EmbeddingRequest> request) { - return EmbeddingModelRequestOptions.builder() - .model(StringUtils.hasText(request.model()) ? request.model() : "unknown") - .dimensions(request.dimensions()) - .encodingFormat(request.encodingFormat()) - .build(); - } - /** * Use the provided convention for reporting observation data * @param observationConvention The provided convention diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiEmbeddingOptions.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiEmbeddingOptions.java index b82594987a4..dbd0a2979d0 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiEmbeddingOptions.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiEmbeddingOptions.java @@ -85,6 +85,7 @@ public OpenAiEmbeddingOptions build() { } + @Override public String getModel() { return this.model; } @@ -101,6 +102,7 @@ public void setEncodingFormat(String encodingFormat) { this.encodingFormat = encodingFormat; } + @Override public Integer getDimensions() { return this.dimensions; } diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiImageOptions.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiImageOptions.java index 9b7e30e8297..949614dae5c 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiImageOptions.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiImageOptions.java @@ -190,7 +190,18 @@ public void setResponseFormat(String responseFormat) { @Override public Integer getWidth() { - return this.width; + if (this.width != null) { + return this.width; + } + else if (this.size != null) { + try { + return Integer.parseInt(this.size.split("x")[0]); + } + catch (NumberFormatException ex) { + return null; + } + } + return null; } public void setWidth(Integer width) { @@ -200,7 +211,18 @@ public void setWidth(Integer width) { @Override public Integer getHeight() { - return this.height; + if (this.height != null) { + return this.height; + } + else if (this.size != null) { + try { + return Integer.parseInt(this.size.split("x")[1]); + } + catch (NumberFormatException ex) { + return null; + } + } + return null; } public void setHeight(Integer height) { @@ -230,7 +252,6 @@ public void setSize(String size) { } public String getSize() { - if (this.size != null) { return this.size; } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiImageOptionsTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiImageOptionsTests.java new file mode 100644 index 00000000000..7083d798021 --- /dev/null +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiImageOptionsTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.openai; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link OpenAiImageOptions}. + * + * @author Thomas Vitale + */ +class OpenAiImageOptionsTests { + + @Test + void whenImageDimensionsAreAllUnset() { + OpenAiImageOptions options = new OpenAiImageOptions(); + assertThat(options.getHeight()).isEqualTo(null); + assertThat(options.getWidth()).isEqualTo(null); + assertThat(options.getSize()).isEqualTo(null); + } + + @Test + void whenSizeIsSet() { + OpenAiImageOptions options = new OpenAiImageOptions(); + options.setSize("1920x1080"); + assertThat(options.getHeight()).isEqualTo(1080); + assertThat(options.getWidth()).isEqualTo(1920); + assertThat(options.getSize()).isEqualTo("1920x1080"); + } + + @Test + void whenWidthAndHeightAreSet() { + OpenAiImageOptions options = new OpenAiImageOptions(); + options.setWidth(1920); + options.setHeight(1080); + assertThat(options.getHeight()).isEqualTo(1080); + assertThat(options.getWidth()).isEqualTo(1920); + assertThat(options.getSize()).isEqualTo("1920x1080"); + } + + @Test + void whenWidthIsSet() { + OpenAiImageOptions options = new OpenAiImageOptions(); + options.setWidth(1920); + assertThat(options.getHeight()).isEqualTo(null); + assertThat(options.getWidth()).isEqualTo(1920); + // This is because "setWidth()" computes "size" without checking for null values. + assertThat(options.getSize()).isEqualTo("1920xnull"); + } + + @Test + void whenHeightIsSet() { + OpenAiImageOptions options = new OpenAiImageOptions(); + options.setHeight(1080); + assertThat(options.getHeight()).isEqualTo(1080); + assertThat(options.getWidth()).isEqualTo(null); + // This is because "setHeight()" computes "size" without checking for null values. + assertThat(options.getSize()).isEqualTo("nullx1080"); + } + +} diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/OpenAiEmbeddingModelObservationIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/OpenAiEmbeddingModelObservationIT.java index 3b4bc8070c4..6a3e4d0367f 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/OpenAiEmbeddingModelObservationIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/OpenAiEmbeddingModelObservationIT.java @@ -84,7 +84,6 @@ void observationForEmbeddingOperation() { OpenAiApi.EmbeddingModel.TEXT_EMBEDDING_3_SMALL.getValue()) .hasLowCardinalityKeyValue(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), responseMetadata.getModel()) .hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_EMBEDDING_ENCODING_FORMAT.asString(), "float") .hasHighCardinalityKeyValue(HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(), String.valueOf(responseMetadata.getUsage().getPromptTokens())) .hasHighCardinalityKeyValue(HighCardinalityKeyNames.USAGE_TOTAL_TOKENS.asString(), diff --git a/models/spring-ai-postgresml/src/main/java/org/springframework/ai/postgresml/PostgresMlEmbeddingOptions.java b/models/spring-ai-postgresml/src/main/java/org/springframework/ai/postgresml/PostgresMlEmbeddingOptions.java index 220d5d28319..c7059f15392 100644 --- a/models/spring-ai-postgresml/src/main/java/org/springframework/ai/postgresml/PostgresMlEmbeddingOptions.java +++ b/models/spring-ai-postgresml/src/main/java/org/springframework/ai/postgresml/PostgresMlEmbeddingOptions.java @@ -28,6 +28,7 @@ /** * @author Christian Tzolov + * @author Thomas Vitale */ @JsonInclude(Include.NON_NULL) public class PostgresMlEmbeddingOptions implements EmbeddingOptions { @@ -130,4 +131,14 @@ public void setMetadataMode(MetadataMode metadataMode) { this.metadataMode = metadataMode; } + @Override + public String getModel() { + return null; + } + + @Override + public Integer getDimensions() { + return null; + } + } diff --git a/models/spring-ai-qianfan/src/main/java/org/springframework/ai/qianfan/QianFanEmbeddingOptions.java b/models/spring-ai-qianfan/src/main/java/org/springframework/ai/qianfan/QianFanEmbeddingOptions.java index c4dedc38425..48864e2ada7 100644 --- a/models/spring-ai-qianfan/src/main/java/org/springframework/ai/qianfan/QianFanEmbeddingOptions.java +++ b/models/spring-ai-qianfan/src/main/java/org/springframework/ai/qianfan/QianFanEmbeddingOptions.java @@ -24,6 +24,7 @@ * This class represents the options for QianFan embedding. * * @author Geng Rong + * @author Thomas Vitale * @since 1.0 */ @JsonInclude(Include.NON_NULL) @@ -70,6 +71,7 @@ public QianFanEmbeddingOptions build() { } + @Override public String getModel() { return this.model; } @@ -86,4 +88,9 @@ public void setUser(String user) { this.user = user; } + @Override + public Integer getDimensions() { + return null; + } + } diff --git a/models/spring-ai-vertex-ai-embedding/src/main/java/org/springframework/ai/vertexai/embedding/multimodal/VertexAiMultimodalEmbeddingOptions.java b/models/spring-ai-vertex-ai-embedding/src/main/java/org/springframework/ai/vertexai/embedding/multimodal/VertexAiMultimodalEmbeddingOptions.java index 1e6bc8802c9..78a75fef20a 100644 --- a/models/spring-ai-vertex-ai-embedding/src/main/java/org/springframework/ai/vertexai/embedding/multimodal/VertexAiMultimodalEmbeddingOptions.java +++ b/models/spring-ai-vertex-ai-embedding/src/main/java/org/springframework/ai/vertexai/embedding/multimodal/VertexAiMultimodalEmbeddingOptions.java @@ -168,6 +168,7 @@ public VertexAiMultimodalEmbeddingOptions build() { } + @Override public String getModel() { return this.model; } @@ -176,6 +177,7 @@ public void setModel(String model) { this.model = model; } + @Override public Integer getDimensions() { return this.dimensions; } diff --git a/models/spring-ai-vertex-ai-embedding/src/main/java/org/springframework/ai/vertexai/embedding/text/VertexAiTextEmbeddingOptions.java b/models/spring-ai-vertex-ai-embedding/src/main/java/org/springframework/ai/vertexai/embedding/text/VertexAiTextEmbeddingOptions.java index a7b2e14020c..fe08b2d4bf2 100644 --- a/models/spring-ai-vertex-ai-embedding/src/main/java/org/springframework/ai/vertexai/embedding/text/VertexAiTextEmbeddingOptions.java +++ b/models/spring-ai-vertex-ai-embedding/src/main/java/org/springframework/ai/vertexai/embedding/text/VertexAiTextEmbeddingOptions.java @@ -183,6 +183,7 @@ public VertexAiTextEmbeddingOptions initializeDefaults() { return this; } + @Override public String getModel() { return this.model; } @@ -199,6 +200,7 @@ public void setTaskType(TaskType taskType) { this.taskType = taskType; } + @Override public Integer getDimensions() { return this.dimensions; } diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiEmbeddingOptions.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiEmbeddingOptions.java index e36a1c4c9fd..388384f2324 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiEmbeddingOptions.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiEmbeddingOptions.java @@ -24,6 +24,7 @@ * The ZhiPuAiEmbeddingOptions class represents the options for ZhiPuAI embedding. * * @author Geng Rong + * @author Thomas Vitale * @since 1.0.0 M1 */ @JsonInclude(Include.NON_NULL) @@ -59,6 +60,7 @@ public ZhiPuAiEmbeddingOptions build() { } + @Override public String getModel() { return this.model; } @@ -67,4 +69,9 @@ public void setModel(String model) { this.model = model; } + @Override + public Integer getDimensions() { + return null; + } + } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/embedding/DocumentEmbeddingRequest.java b/spring-ai-core/src/main/java/org/springframework/ai/embedding/DocumentEmbeddingRequest.java index c68a418bed6..8227b291095 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/embedding/DocumentEmbeddingRequest.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/embedding/DocumentEmbeddingRequest.java @@ -25,6 +25,7 @@ * Represents a request to embed a list of documents. * * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 */ public class DocumentEmbeddingRequest implements ModelRequest> { @@ -34,11 +35,11 @@ public class DocumentEmbeddingRequest implements ModelRequest> { private final EmbeddingOptions options; public DocumentEmbeddingRequest(Document... inputs) { - this(Arrays.asList(inputs), EmbeddingOptions.EMPTY); + this(Arrays.asList(inputs), EmbeddingOptionsBuilder.builder().build()); } public DocumentEmbeddingRequest(List inputs) { - this(inputs, EmbeddingOptions.EMPTY); + this(inputs, EmbeddingOptionsBuilder.builder().build()); } public DocumentEmbeddingRequest(List inputs, EmbeddingOptions options) { diff --git a/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingModel.java b/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingModel.java index 66a972f6266..a40ee22a670 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingModel.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingModel.java @@ -53,7 +53,7 @@ default List embed(String text) { */ default List> embed(List texts) { Assert.notNull(texts, "Texts must not be null"); - return this.call(new EmbeddingRequest(texts, EmbeddingOptions.EMPTY)) + return this.call(new EmbeddingRequest(texts, EmbeddingOptionsBuilder.builder().build())) .getResults() .stream() .map(Embedding::getOutput) @@ -67,7 +67,7 @@ default List> embed(List texts) { */ default EmbeddingResponse embedForResponse(List texts) { Assert.notNull(texts, "Texts must not be null"); - return this.call(new EmbeddingRequest(texts, EmbeddingOptions.EMPTY)); + return this.call(new EmbeddingRequest(texts, EmbeddingOptionsBuilder.builder().build())); } /** diff --git a/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingOptions.java b/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingOptions.java index 1152248e037..3fac8119034 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingOptions.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingOptions.java @@ -16,16 +16,24 @@ package org.springframework.ai.embedding; import org.springframework.ai.model.ModelOptions; +import org.springframework.lang.Nullable; /** * @author Christian Tzolov + * @author Thomas Vitale */ public interface EmbeddingOptions extends ModelOptions { - class EmptyEmbeddingOptions implements EmbeddingOptions { + /** + * Use the {@link EmbeddingOptionsBuilder} instead. + */ + @Deprecated(since = "1.0.0", forRemoval = true) + EmbeddingOptions EMPTY = EmbeddingOptionsBuilder.builder().build(); - } + @Nullable + String getModel(); - EmbeddingOptions EMPTY = new EmptyEmbeddingOptions(); + @Nullable + Integer getDimensions(); } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingOptionsBuilder.java b/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingOptionsBuilder.java new file mode 100644 index 00000000000..ab13dff1aaf --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/embedding/EmbeddingOptionsBuilder.java @@ -0,0 +1,73 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.embedding; + +/** + * @author Thomas Vitale + * @since 1.0.0 + */ +public class EmbeddingOptionsBuilder { + + private final DefaultEmbeddingOptions embeddingOptions = new DefaultEmbeddingOptions(); + + private EmbeddingOptionsBuilder() { + } + + public static EmbeddingOptionsBuilder builder() { + return new EmbeddingOptionsBuilder(); + } + + public EmbeddingOptionsBuilder withModel(String model) { + embeddingOptions.setModel(model); + return this; + } + + public EmbeddingOptionsBuilder withDimensions(Integer dimensions) { + embeddingOptions.setDimensions(dimensions); + return this; + } + + public EmbeddingOptions build() { + return embeddingOptions; + } + + private static class DefaultEmbeddingOptions implements EmbeddingOptions { + + private String model; + + private Integer dimensions; + + @Override + public String getModel() { + return this.model; + } + + public void setModel(String model) { + this.model = model; + } + + @Override + public Integer getDimensions() { + return this.dimensions; + } + + public void setDimensions(Integer dimensions) { + this.dimensions = dimensions; + } + + } + +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConvention.java b/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConvention.java index b2103b155dd..1331b2027e6 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConvention.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConvention.java @@ -27,6 +27,9 @@ */ public class DefaultEmbeddingModelObservationConvention implements EmbeddingModelObservationConvention { + private static final KeyValue REQUEST_MODEL_NONE = KeyValue + .of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.REQUEST_MODEL, KeyValue.NONE_VALUE); + private static final KeyValue RESPONSE_MODEL_NONE = KeyValue .of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL, KeyValue.NONE_VALUE); @@ -34,10 +37,6 @@ public class DefaultEmbeddingModelObservationConvention implements EmbeddingMode EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS, KeyValue.NONE_VALUE); - private static final KeyValue REQUEST_EMBEDDING_ENCODING_FORMAT_NONE = KeyValue.of( - EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_EMBEDDING_ENCODING_FORMAT, - KeyValue.NONE_VALUE); - private static final KeyValue USAGE_INPUT_TOKENS_NONE = KeyValue .of(EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.USAGE_INPUT_TOKENS, KeyValue.NONE_VALUE); @@ -53,8 +52,11 @@ public String getName() { @Override public String getContextualName(EmbeddingModelObservationContext context) { - return "%s %s".formatted(context.getOperationMetadata().operationType(), - context.getRequestOptions().getModel()); + if (StringUtils.hasText(context.getRequestOptions().getModel())) { + return "%s %s".formatted(context.getOperationMetadata().operationType(), + context.getRequestOptions().getModel()); + } + return context.getOperationMetadata().operationType(); } @Override @@ -65,7 +67,7 @@ public KeyValues getLowCardinalityKeyValues(EmbeddingModelObservationContext con protected KeyValue aiOperationType(EmbeddingModelObservationContext context) { return KeyValue.of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.AI_OPERATION_TYPE, - context.getOperationMetadata().operationType()); + context.getOperationType()); } protected KeyValue aiProvider(EmbeddingModelObservationContext context) { @@ -74,8 +76,11 @@ protected KeyValue aiProvider(EmbeddingModelObservationContext context) { } protected KeyValue requestModel(EmbeddingModelObservationContext context) { - return KeyValue.of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.REQUEST_MODEL, - context.getRequestOptions().getModel()); + if (StringUtils.hasText(context.getRequestOptions().getModel())) { + return KeyValue.of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.REQUEST_MODEL, + context.getRequestOptions().getModel()); + } + return REQUEST_MODEL_NONE; } protected KeyValue responseModel(EmbeddingModelObservationContext context) { @@ -89,8 +94,7 @@ protected KeyValue responseModel(EmbeddingModelObservationContext context) { @Override public KeyValues getHighCardinalityKeyValues(EmbeddingModelObservationContext context) { - return KeyValues.of(requestEmbeddingDimension(context), requestEmbeddingFormat(context), - usageInputTokens(context), usageTotalTokens(context)); + return KeyValues.of(requestEmbeddingDimension(context), usageInputTokens(context), usageTotalTokens(context)); } // Request @@ -104,15 +108,6 @@ protected KeyValue requestEmbeddingDimension(EmbeddingModelObservationContext co return REQUEST_EMBEDDING_DIMENSION_NONE; } - protected KeyValue requestEmbeddingFormat(EmbeddingModelObservationContext context) { - if (StringUtils.hasText(context.getRequestOptions().getEncodingFormat())) { - return KeyValue.of( - EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_EMBEDDING_ENCODING_FORMAT, - context.getRequestOptions().getEncodingFormat()); - } - return REQUEST_EMBEDDING_ENCODING_FORMAT_NONE; - } - // Response protected KeyValue usageInputTokens(EmbeddingModelObservationContext context) { diff --git a/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/EmbeddingModelObservationContext.java b/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/EmbeddingModelObservationContext.java index 5273675d77a..2e209d35f34 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/EmbeddingModelObservationContext.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/EmbeddingModelObservationContext.java @@ -15,10 +15,12 @@ */ package org.springframework.ai.embedding.observation; +import org.springframework.ai.embedding.EmbeddingOptions; import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.embedding.EmbeddingResponse; import org.springframework.ai.model.observation.ModelObservationContext; import org.springframework.ai.observation.AiOperationMetadata; +import org.springframework.ai.observation.conventions.AiOperationType; import org.springframework.util.Assert; /** @@ -29,19 +31,23 @@ */ public class EmbeddingModelObservationContext extends ModelObservationContext { - private final EmbeddingModelRequestOptions requestOptions; + private final EmbeddingOptions requestOptions; EmbeddingModelObservationContext(EmbeddingRequest embeddingRequest, AiOperationMetadata operationMetadata, - EmbeddingModelRequestOptions requestOptions) { + EmbeddingOptions requestOptions) { super(embeddingRequest, operationMetadata); Assert.notNull(requestOptions, "requestOptions cannot be null"); this.requestOptions = requestOptions; } - public EmbeddingModelRequestOptions getRequestOptions() { + public EmbeddingOptions getRequestOptions() { return requestOptions; } + public String getOperationType() { + return AiOperationType.EMBEDDING.value(); + } + public static Builder builder() { return new Builder(); } @@ -52,7 +58,7 @@ public static class Builder { private AiOperationMetadata operationMetadata; - private EmbeddingModelRequestOptions requestOptions; + private EmbeddingOptions requestOptions; private Builder() { } @@ -67,7 +73,7 @@ public Builder operationMetadata(AiOperationMetadata operationMetadata) { return this; } - public Builder requestOptions(EmbeddingModelRequestOptions requestOptions) { + public Builder requestOptions(EmbeddingOptions requestOptions) { this.requestOptions = requestOptions; return this; } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/EmbeddingModelObservationDocumentation.java b/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/EmbeddingModelObservationDocumentation.java index e7371a292be..8462a61d395 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/EmbeddingModelObservationDocumentation.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/EmbeddingModelObservationDocumentation.java @@ -112,16 +112,6 @@ public String asString() { } }, - /** - * The format the embeddings are returned in. - */ - REQUEST_EMBEDDING_ENCODING_FORMAT { - @Override - public String asString() { - return AiObservationAttributes.REQUEST_EMBEDDING_ENCODING_FORMAT.value(); - } - }, - // Usage /** diff --git a/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/EmbeddingModelRequestOptions.java b/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/EmbeddingModelRequestOptions.java deleted file mode 100644 index 4072e64f32b..00000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/EmbeddingModelRequestOptions.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.ai.embedding.observation; - -import org.springframework.ai.embedding.EmbeddingOptions; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * Represents client-side options for embedding model requests. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -public class EmbeddingModelRequestOptions implements EmbeddingOptions { - - private final String model; - - @Nullable - private final Integer dimensions; - - @Nullable - private final String encodingFormat; - - EmbeddingModelRequestOptions(Builder builder) { - Assert.hasText(builder.model, "model cannot be null or empty"); - - this.model = builder.model; - this.dimensions = builder.dimensions; - this.encodingFormat = builder.encodingFormat; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private String model; - - @Nullable - private Integer dimensions; - - @Nullable - private String encodingFormat; - - private Builder() { - } - - public Builder model(String model) { - this.model = model; - return this; - } - - public Builder dimensions(@Nullable Integer dimensions) { - this.dimensions = dimensions; - return this; - } - - public Builder encodingFormat(@Nullable String encodingFormat) { - this.encodingFormat = encodingFormat; - return this; - } - - public EmbeddingModelRequestOptions build() { - return new EmbeddingModelRequestOptions(this); - } - - } - - public String getModel() { - return model; - } - - @Nullable - public Integer getDimensions() { - return dimensions; - } - - @Nullable - public String getEncodingFormat() { - return encodingFormat; - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/AiObservationAttributes.java b/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/AiObservationAttributes.java index 00ce9b6bda6..29566449380 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/AiObservationAttributes.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/AiObservationAttributes.java @@ -79,10 +79,6 @@ public enum AiObservationAttributes { * The number of dimensions the resulting output embeddings have. */ REQUEST_EMBEDDING_DIMENSIONS("gen_ai.request.embedding.dimensions"), - /** - * The format the embeddings are returned in. - */ - REQUEST_EMBEDDING_ENCODING_FORMAT("gen_ai.request.embedding.encoding_format"), /** * The format in which the generated image is returned. diff --git a/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConventionTests.java b/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConventionTests.java index b69b43cdade..4a5cdf0a9af 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConventionTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConventionTests.java @@ -19,7 +19,7 @@ import io.micrometer.observation.Observation; import org.junit.jupiter.api.Test; import org.springframework.ai.chat.metadata.Usage; -import org.springframework.ai.embedding.EmbeddingOptions; +import org.springframework.ai.embedding.EmbeddingOptionsBuilder; import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.embedding.EmbeddingResponse; import org.springframework.ai.embedding.EmbeddingResponseMetadata; @@ -50,32 +50,42 @@ void shouldHaveName() { } @Test - void shouldHaveContextualName() { + void contextualNameWhenModelIsDefined() { EmbeddingModelObservationContext observationContext = EmbeddingModelObservationContext.builder() .embeddingRequest(generateEmbeddingRequest()) .operationMetadata(generateOperationMetadata()) - .requestOptions(EmbeddingModelRequestOptions.builder().model("mistral").build()) + .requestOptions(EmbeddingOptionsBuilder.builder().withModel("mistral").build()) .build(); assertThat(this.observationConvention.getContextualName(observationContext)).isEqualTo("embedding mistral"); } + @Test + void contextualNameWhenModelIsNotDefined() { + EmbeddingModelObservationContext observationContext = EmbeddingModelObservationContext.builder() + .embeddingRequest(generateEmbeddingRequest()) + .operationMetadata(generateOperationMetadata()) + .requestOptions(EmbeddingOptionsBuilder.builder().build()) + .build(); + assertThat(this.observationConvention.getContextualName(observationContext)).isEqualTo("embedding"); + } + @Test void supportsOnlyEmbeddingModelObservationContext() { EmbeddingModelObservationContext observationContext = EmbeddingModelObservationContext.builder() .embeddingRequest(generateEmbeddingRequest()) .operationMetadata(generateOperationMetadata()) - .requestOptions(EmbeddingModelRequestOptions.builder().model("supermodel").build()) + .requestOptions(EmbeddingOptionsBuilder.builder().withModel("supermodel").build()) .build(); assertThat(this.observationConvention.supportsContext(observationContext)).isTrue(); assertThat(this.observationConvention.supportsContext(new Observation.Context())).isFalse(); } @Test - void shouldHaveRequiredLowCardinalityKeyValues() { + void shouldHaveLowCardinalityKeyValuesWhenDefined() { EmbeddingModelObservationContext observationContext = EmbeddingModelObservationContext.builder() .embeddingRequest(generateEmbeddingRequest()) .operationMetadata(generateOperationMetadata()) - .requestOptions(EmbeddingModelRequestOptions.builder().model("mistral").build()) + .requestOptions(EmbeddingOptionsBuilder.builder().withModel("mistral").build()) .build(); assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext)).contains( KeyValue.of(LowCardinalityKeyNames.AI_OPERATION_TYPE.asString(), "embedding"), @@ -84,15 +94,11 @@ void shouldHaveRequiredLowCardinalityKeyValues() { } @Test - void shouldHaveOptionalKeyValues() { + void shouldHaveLowCardinalityKeyValuesWhenDefinedAndResponse() { EmbeddingModelObservationContext observationContext = EmbeddingModelObservationContext.builder() .embeddingRequest(generateEmbeddingRequest()) .operationMetadata(generateOperationMetadata()) - .requestOptions(EmbeddingModelRequestOptions.builder() - .model("supermodel") - .dimensions(1492) - .encodingFormat("vector") - .build()) + .requestOptions(EmbeddingOptionsBuilder.builder().withModel("mistral").withDimensions(1492).build()) .build(); observationContext.setResponse(new EmbeddingResponse(List.of(), new EmbeddingResponseMetadata("mistral-42", new TestUsage(), Map.of()))); @@ -100,29 +106,28 @@ void shouldHaveOptionalKeyValues() { .contains(KeyValue.of(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), "mistral-42")); assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)).contains( KeyValue.of(HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS.asString(), "1492"), - KeyValue.of(HighCardinalityKeyNames.REQUEST_EMBEDDING_ENCODING_FORMAT.asString(), "vector"), KeyValue.of(HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(), "1000"), KeyValue.of(HighCardinalityKeyNames.USAGE_TOTAL_TOKENS.asString(), "1000")); } @Test - void shouldHaveMissingKeyValues() { + void shouldHaveNoneKeyValuesWhenMissing() { EmbeddingModelObservationContext observationContext = EmbeddingModelObservationContext.builder() .embeddingRequest(generateEmbeddingRequest()) .operationMetadata(generateOperationMetadata()) - .requestOptions(EmbeddingModelRequestOptions.builder().model("supermodel").build()) + .requestOptions(EmbeddingOptionsBuilder.builder().build()) .build(); - assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext)) - .contains(KeyValue.of(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), KeyValue.NONE_VALUE)); + assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext)).contains( + KeyValue.of(LowCardinalityKeyNames.REQUEST_MODEL.asString(), KeyValue.NONE_VALUE), + KeyValue.of(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), KeyValue.NONE_VALUE)); assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)).contains( KeyValue.of(HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS.asString(), KeyValue.NONE_VALUE), - KeyValue.of(HighCardinalityKeyNames.REQUEST_EMBEDDING_ENCODING_FORMAT.asString(), KeyValue.NONE_VALUE), KeyValue.of(HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(), KeyValue.NONE_VALUE), KeyValue.of(HighCardinalityKeyNames.USAGE_TOTAL_TOKENS.asString(), KeyValue.NONE_VALUE)); } private EmbeddingRequest generateEmbeddingRequest() { - return new EmbeddingRequest(List.of(), EmbeddingOptions.EMPTY); + return new EmbeddingRequest(List.of(), EmbeddingOptionsBuilder.builder().build()); } private AiOperationMetadata generateOperationMetadata() { diff --git a/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/EmbeddingModelMeterObservationHandlerTests.java b/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/EmbeddingModelMeterObservationHandlerTests.java index 3b4daaf4072..d849b2b1ca4 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/EmbeddingModelMeterObservationHandlerTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/EmbeddingModelMeterObservationHandlerTests.java @@ -22,7 +22,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.ai.chat.metadata.Usage; -import org.springframework.ai.embedding.EmbeddingOptions; +import org.springframework.ai.embedding.EmbeddingOptionsBuilder; import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.embedding.EmbeddingResponse; import org.springframework.ai.embedding.EmbeddingResponseMetadata; @@ -89,12 +89,12 @@ private EmbeddingModelObservationContext generateObservationContext() { return EmbeddingModelObservationContext.builder() .embeddingRequest(generateEmbeddingRequest()) .operationMetadata(generateOperationMetadata()) - .requestOptions(EmbeddingModelRequestOptions.builder().model("mistral").build()) + .requestOptions(EmbeddingOptionsBuilder.builder().withModel("mistral").build()) .build(); } private EmbeddingRequest generateEmbeddingRequest() { - return new EmbeddingRequest(List.of(), EmbeddingOptions.EMPTY); + return new EmbeddingRequest(List.of(), EmbeddingOptionsBuilder.builder().build()); } private AiOperationMetadata generateOperationMetadata() { diff --git a/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/EmbeddingModelObservationContextTests.java b/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/EmbeddingModelObservationContextTests.java index f9e63fc0205..d7d46be96cb 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/EmbeddingModelObservationContextTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/EmbeddingModelObservationContextTests.java @@ -16,7 +16,7 @@ package org.springframework.ai.embedding.observation; import org.junit.jupiter.api.Test; -import org.springframework.ai.embedding.EmbeddingOptions; +import org.springframework.ai.embedding.EmbeddingOptionsBuilder; import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.observation.AiOperationMetadata; import org.springframework.ai.observation.conventions.AiOperationType; @@ -39,7 +39,7 @@ void whenMandatoryRequestOptionsThenReturn() { var observationContext = EmbeddingModelObservationContext.builder() .embeddingRequest(generateEmbeddingRequest()) .operationMetadata(generateOperationMetadata()) - .requestOptions(EmbeddingModelRequestOptions.builder().model("supermodel").build()) + .requestOptions(EmbeddingOptionsBuilder.builder().withModel("supermodel").build()) .build(); assertThat(observationContext).isNotNull(); @@ -56,7 +56,7 @@ void whenRequestOptionsIsNullThenThrow() { } private EmbeddingRequest generateEmbeddingRequest() { - return new EmbeddingRequest(List.of(), EmbeddingOptions.EMPTY); + return new EmbeddingRequest(List.of(), EmbeddingOptionsBuilder.builder().build()); } private AiOperationMetadata generateOperationMetadata() { diff --git a/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/EmbeddingModelRequestOptionsTests.java b/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/EmbeddingModelRequestOptionsTests.java deleted file mode 100644 index d85dac57e9c..00000000000 --- a/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/EmbeddingModelRequestOptionsTests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.ai.embedding.observation; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Unit tests for {@link EmbeddingModelRequestOptions}. - * - * @author Thomas Vitale - */ -class EmbeddingModelRequestOptionsTests { - - @Test - void whenMandatoryRequestOptionsThenReturn() { - var requestOptions = EmbeddingModelRequestOptions.builder().model("rowena").build(); - - assertThat(requestOptions).isNotNull(); - } - - @Test - void whenModelIsNullThenThrow() { - assertThatThrownBy(() -> EmbeddingModelRequestOptions.builder().build()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("model cannot be null or empty"); - } - - @Test - void whenModelIsEmptyThenThrow() { - assertThatThrownBy(() -> EmbeddingModelRequestOptions.builder().model("").build()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("model cannot be null or empty"); - } - -} \ No newline at end of file