diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java index bd40285eadc..553a489f30d 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java @@ -104,8 +104,13 @@ private MistralAiApi mistralAiApi(String apiKey, String commonApiKey, String bas Assert.hasText(resolvedApiKey, "Mistral API key must be set"); Assert.hasText(resoledBaseUrl, "Mistral base URL must be set"); - return new MistralAiApi(resoledBaseUrl, resolvedApiKey, restClientBuilder, webClientBuilder, - responseErrorHandler); + return MistralAiApi.builder() + .baseUrl(resoledBaseUrl) + .apiKey(resolvedApiKey) + .restClientBuilder(restClientBuilder) + .webClientBuilder(webClientBuilder) + .responseErrorHandler(responseErrorHandler) + .build(); } } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingAutoConfiguration.java index db0605dc37a..f4ee4a5b779 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingAutoConfiguration.java @@ -68,9 +68,13 @@ public MistralAiEmbeddingModel mistralAiEmbeddingModel(MistralAiCommonProperties embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(), restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); - var embeddingModel = new MistralAiEmbeddingModel(mistralAiApi, embeddingProperties.getMetadataMode(), - embeddingProperties.getOptions(), retryTemplate, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); + var embeddingModel = MistralAiEmbeddingModel.builder() + .mistralAiApi(mistralAiApi) + .metadataMode(embeddingProperties.getMetadataMode()) + .options(embeddingProperties.getOptions()) + .retryTemplate(retryTemplate) + .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) + .build(); observationConvention.ifAvailable(embeddingModel::setObservationConvention); @@ -86,7 +90,12 @@ private MistralAiApi mistralAiApi(String apiKey, String commonApiKey, String bas Assert.hasText(resolvedApiKey, "Mistral API key must be set"); Assert.hasText(resoledBaseUrl, "Mistral base URL must be set"); - return new MistralAiApi(resoledBaseUrl, resolvedApiKey, restClientBuilder, responseErrorHandler); + return MistralAiApi.builder() + .baseUrl(resoledBaseUrl) + .apiKey(apiKey) + .restClientBuilder(restClientBuilder) + .responseErrorHandler(responseErrorHandler) + .build(); } } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java index fe9320172a4..28c746755d5 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java @@ -68,10 +68,18 @@ public MistralAiModerationModel mistralAiModerationModel(MistralAiCommonProperti Assert.hasText(resolvedApiKey, "Mistral API key must be set"); Assert.hasText(resoledBaseUrl, "Mistral base URL must be set"); - var mistralAiModerationAi = new MistralAiModerationApi(resoledBaseUrl, resolvedApiKey, - restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); + var mistralAiModerationApi = MistralAiModerationApi.builder() + .baseUrl(resoledBaseUrl) + .apiKey(resolvedApiKey) + .restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder)) + .responseErrorHandler(responseErrorHandler) + .build(); - return new MistralAiModerationModel(mistralAiModerationAi, retryTemplate, moderationProperties.getOptions()); + return MistralAiModerationModel.builder() + .mistralAiModerationApi(mistralAiModerationApi) + .retryTemplate(retryTemplate) + .options(moderationProperties.getOptions()) + .build(); } } diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java index b9838dcedf1..0a4f0d10671 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java @@ -83,6 +83,7 @@ * @author luocongqiu * @author Ilayaperumal Gopinathan * @author Alexandros Pappas + * @author Jason Smith * @since 1.0.0 */ public class MistralAiChatModel implements ChatModel { @@ -123,6 +124,7 @@ public class MistralAiChatModel implements ChatModel { */ private ChatModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION; + @Deprecated public MistralAiChatModel(MistralAiApi mistralAiApi, MistralAiChatOptions defaultOptions, ToolCallingManager toolCallingManager, RetryTemplate retryTemplate, ObservationRegistry observationRegistry) { @@ -538,7 +540,7 @@ public static final class Builder { .model(MistralAiApi.ChatModel.SMALL.getValue()) .build(); - private ToolCallingManager toolCallingManager; + private ToolCallingManager toolCallingManager = DEFAULT_TOOL_CALLING_MANAGER; private ToolExecutionEligibilityPredicate toolExecutionEligibilityPredicate = new DefaultToolExecutionEligibilityPredicate(); @@ -581,11 +583,7 @@ public Builder observationRegistry(ObservationRegistry observationRegistry) { } public MistralAiChatModel build() { - if (this.toolCallingManager != null) { - return new MistralAiChatModel(this.mistralAiApi, this.defaultOptions, this.toolCallingManager, - this.retryTemplate, this.observationRegistry, this.toolExecutionEligibilityPredicate); - } - return new MistralAiChatModel(this.mistralAiApi, this.defaultOptions, DEFAULT_TOOL_CALLING_MANAGER, + return new MistralAiChatModel(this.mistralAiApi, this.defaultOptions, this.toolCallingManager, this.retryTemplate, this.observationRegistry, this.toolExecutionEligibilityPredicate); } diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java index 2b392d5176a..3fcd2d30457 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java @@ -45,6 +45,7 @@ * @author Christian Tzolov * @author Thomas Vitale * @author Alexandros Pappas + * @author Jason Smith * @since 0.8.1 */ @JsonInclude(JsonInclude.Include.NON_NULL) @@ -403,7 +404,7 @@ public boolean equals(Object obj) { && Objects.equals(this.toolContext, other.toolContext); } - public static class Builder { + public static final class Builder { private final MistralAiChatOptions options = new MistralAiChatOptions(); diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingModel.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingModel.java index 347128f0ea0..c8f4c0cbc90 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingModel.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingModel.java @@ -47,6 +47,7 @@ * @see AbstractEmbeddingModel * @author Ricken Bazolo * @author Thomas Vitale + * @author Jason Smith * @since 1.0.0 */ public class MistralAiEmbeddingModel extends AbstractEmbeddingModel { @@ -73,20 +74,24 @@ public class MistralAiEmbeddingModel extends AbstractEmbeddingModel { */ private EmbeddingModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION; + @Deprecated public MistralAiEmbeddingModel(MistralAiApi mistralAiApi) { this(mistralAiApi, MetadataMode.EMBED); } + @Deprecated public MistralAiEmbeddingModel(MistralAiApi mistralAiApi, MetadataMode metadataMode) { this(mistralAiApi, metadataMode, MistralAiEmbeddingOptions.builder().withModel(MistralAiApi.EmbeddingModel.EMBED.getValue()).build(), RetryUtils.DEFAULT_RETRY_TEMPLATE); } + @Deprecated public MistralAiEmbeddingModel(MistralAiApi mistralAiApi, MistralAiEmbeddingOptions options) { this(mistralAiApi, MetadataMode.EMBED, options, RetryUtils.DEFAULT_RETRY_TEMPLATE); } + @Deprecated public MistralAiEmbeddingModel(MistralAiApi mistralAiApi, MetadataMode metadataMode, MistralAiEmbeddingOptions options, RetryTemplate retryTemplate) { this(mistralAiApi, metadataMode, options, retryTemplate, ObservationRegistry.NOOP); @@ -188,4 +193,53 @@ public void setObservationConvention(EmbeddingModelObservationConvention observa this.observationConvention = observationConvention; } + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + + private MistralAiApi mistralAiApi; + + private MetadataMode metadataMode = MetadataMode.EMBED; + + private MistralAiEmbeddingOptions options = MistralAiEmbeddingOptions.builder() + .withModel(MistralAiApi.EmbeddingModel.EMBED.getValue()) + .build(); + + private RetryTemplate retryTemplate = RetryUtils.DEFAULT_RETRY_TEMPLATE; + + private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; + + public Builder mistralAiApi(MistralAiApi mistralAiApi) { + this.mistralAiApi = mistralAiApi; + return this; + } + + public Builder metadataMode(MetadataMode metadataMode) { + this.metadataMode = metadataMode; + return this; + } + + public Builder options(MistralAiEmbeddingOptions options) { + this.options = options; + return this; + } + + public Builder retryTemplate(RetryTemplate retryTemplate) { + this.retryTemplate = retryTemplate; + return this; + } + + public Builder observationRegistry(ObservationRegistry observationRegistry) { + this.observationRegistry = observationRegistry; + return this; + } + + public MistralAiEmbeddingModel build() { + return new MistralAiEmbeddingModel(mistralAiApi, metadataMode, options, retryTemplate, observationRegistry); + } + + } + } 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 98412490fe0..149b2303585 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 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. @@ -28,6 +28,7 @@ * * @author Ricken Bazolo * @author Thomas Vitale + * @author Jason Smith * @since 0.8.1 */ @JsonInclude(Include.NON_NULL) @@ -70,7 +71,7 @@ public Integer getDimensions() { return null; } - public static class Builder { + public static final class Builder { protected MistralAiEmbeddingOptions options; diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java index 82937b2b957..809140993b1 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 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. @@ -60,10 +60,15 @@ * @author Ricken Bazolo * @author Christian Tzolov * @author Thomas Vitale + * @author Jason Smith * @since 1.0.0 */ public class MistralAiApi { + public static Builder builder() { + return new Builder(); + } + public static final String PROVIDER_NAME = AiProvider.MISTRAL_AI.value(); private static final String DEFAULT_BASE_URL = "https://api.mistral.ai"; @@ -78,45 +83,48 @@ public class MistralAiApi { /** * Create a new client api with DEFAULT_BASE_URL - * @param mistralAiApiKey Mistral api Key. + * @param apiKey Mistral api Key. */ - public MistralAiApi(String mistralAiApiKey) { - this(DEFAULT_BASE_URL, mistralAiApiKey); + @Deprecated + public MistralAiApi(String apiKey) { + this(DEFAULT_BASE_URL, apiKey); } /** * Create a new client api. * @param baseUrl api base URL. - * @param mistralAiApiKey Mistral api Key. + * @param apiKey Mistral api Key. */ - public MistralAiApi(String baseUrl, String mistralAiApiKey) { - this(baseUrl, mistralAiApiKey, RestClient.builder(), RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER); + @Deprecated + public MistralAiApi(String baseUrl, String apiKey) { + this(baseUrl, apiKey, RestClient.builder(), RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER); } /** * Create a new client api. * @param baseUrl api base URL. - * @param mistralAiApiKey Mistral api Key. + * @param apiKey Mistral api Key. * @param restClientBuilder RestClient builder. * @param responseErrorHandler Response error handler. */ - public MistralAiApi(String baseUrl, String mistralAiApiKey, RestClient.Builder restClientBuilder, + @Deprecated + public MistralAiApi(String baseUrl, String apiKey, RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { - this(baseUrl, mistralAiApiKey, restClientBuilder, WebClient.builder(), responseErrorHandler); + this(baseUrl, apiKey, restClientBuilder, WebClient.builder(), responseErrorHandler); } /** * Create a new client api. * @param baseUrl api base URL. - * @param mistralAiApiKey Mistral api Key. + * @param apiKey Mistral api Key. * @param restClientBuilder RestClient builder. * @param responseErrorHandler Response error handler. */ - public MistralAiApi(String baseUrl, String mistralAiApiKey, RestClient.Builder restClientBuilder, + public MistralAiApi(String baseUrl, String apiKey, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) { Consumer jsonContentHeaders = headers -> { - headers.setBearerAuth(mistralAiApiKey); + headers.setBearerAuth(apiKey); headers.setContentType(MediaType.APPLICATION_JSON); }; @@ -1074,4 +1082,53 @@ public record ChunkChoice( } + public static final class Builder { + + private String baseUrl = DEFAULT_BASE_URL; + + private String apiKey; + + private RestClient.Builder restClientBuilder = RestClient.builder(); + + private WebClient.Builder webClientBuilder = WebClient.builder(); + + private ResponseErrorHandler responseErrorHandler = RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER; + + public Builder baseUrl(String baseUrl) { + Assert.hasText(baseUrl, "baseUrl cannot be null or empty"); + this.baseUrl = baseUrl; + return this; + } + + public Builder apiKey(String apiKey) { + Assert.hasText(apiKey, "apiKey cannot be null or empty"); + this.apiKey = apiKey; + return this; + } + + public Builder restClientBuilder(RestClient.Builder restClientBuilder) { + Assert.notNull(restClientBuilder, "restClientBuilder cannot be null"); + this.restClientBuilder = restClientBuilder; + return this; + } + + public Builder webClientBuilder(WebClient.Builder webClientBuilder) { + Assert.notNull(webClientBuilder, "webClientBuilder cannot be null"); + this.webClientBuilder = webClientBuilder; + return this; + } + + public Builder responseErrorHandler(ResponseErrorHandler responseErrorHandler) { + Assert.notNull(responseErrorHandler, "responseErrorHandler cannot be null"); + this.responseErrorHandler = responseErrorHandler; + return this; + } + + public MistralAiApi build() { + return new MistralAiApi(this.baseUrl, this.apiKey, this.restClientBuilder, this.webClientBuilder, + this.responseErrorHandler); + } + + } + } diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiModerationApi.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiModerationApi.java index da147de5052..df222d26e85 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiModerationApi.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiModerationApi.java @@ -33,6 +33,7 @@ * MistralAI Moderation API. * * @author Ricken Bazolo + * @author Jason Smith * @see T fromJson(String json, Class targetClass) { try { diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/api/tool/PaymentStatusFunctionCallingIT.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/api/tool/PaymentStatusFunctionCallingIT.java index f2c03cd3d90..aba86d7b795 100644 --- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/api/tool/PaymentStatusFunctionCallingIT.java +++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/api/tool/PaymentStatusFunctionCallingIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 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. @@ -50,6 +50,7 @@ * AI Function Calling guide. * * @author Christian Tzolov + * @author Jason Smith * @since 0.8.1 */ // @Disabled("See https://github.com/spring-projects/spring-ai/issues/1853") @@ -106,7 +107,7 @@ public void toolFunctionCall() throws JsonProcessingException { List messages = new ArrayList<>( List.of(new ChatCompletionMessage("What's the status of my transaction with id T1001?", Role.USER))); - MistralAiApi mistralApi = new MistralAiApi(System.getenv("MISTRAL_AI_API_KEY")); + MistralAiApi mistralApi = MistralAiApi.builder().apiKey(System.getenv("MISTRAL_AI_API_KEY")).build(); ResponseEntity response = mistralApi.chatCompletionEntity(new ChatCompletionRequest(messages, MistralAiApi.ChatModel.LARGE.getValue(), List.of(paymentStatusTool, paymentDateTool), ToolChoice.AUTO)); diff --git a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java index 004bf32d226..19977577f96 100644 --- a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java +++ b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java @@ -335,7 +335,9 @@ public VectorStore qdrantVectorStore(EmbeddingModel embeddingModel, QdrantClient @Bean public EmbeddingModel embeddingModel() { - return new MistralAiEmbeddingModel(new MistralAiApi(System.getenv("MISTRAL_AI_API_KEY"))); + return MistralAiEmbeddingModel.builder() + .mistralAiApi(MistralAiApi.builder().apiKey(System.getenv("MISTRAL_AI_API_KEY")).build()) + .build(); } } diff --git a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreObservationIT.java b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreObservationIT.java index 901e732f321..c421b82c369 100644 --- a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreObservationIT.java @@ -206,7 +206,9 @@ public VectorStore qdrantVectorStore(EmbeddingModel embeddingModel, QdrantClient @Bean public EmbeddingModel embeddingModel() { - return new MistralAiEmbeddingModel(new MistralAiApi(System.getenv("MISTRAL_AI_API_KEY"))); + return MistralAiEmbeddingModel.builder() + .mistralAiApi(MistralAiApi.builder().apiKey(System.getenv("MISTRAL_AI_API_KEY")).build()) + .build(); } }