From e8ce5e768f46cd4b727ff41c68abc84a9878315b Mon Sep 17 00:00:00 2001 From: Ilayaperumal Gopinathan Date: Thu, 9 Jan 2025 16:44:29 +0000 Subject: [PATCH] Remove deprecated methods/classes from spring-ai-core --- .../ai/anthropic/api/AnthropicApi.java | 7 +- .../converse/api/ConverseApiUtils.java | 4 +- ...enAiChatClientMultipleFunctionCallsIT.java | 6 +- .../ai/aot/SpringAiCoreRuntimeHints.java | 3 +- .../ai/chat/client/ChatClient.java | 34 --- .../ai/chat/client/DefaultChatClient.java | 53 ----- .../chat/client/DefaultChatClientBuilder.java | 6 +- .../chat/client/RequestResponseAdvisor.java | 91 -------- .../client/advisor/QuestionAnswerAdvisor.java | 2 +- .../chat/client/advisor/SafeGuardAdvisor.java | 2 +- .../ai/chat/messages/AbstractMessage.java | 5 - .../ai/chat/model/ChatResponse.java | 19 -- .../springframework/ai/document/Document.java | 14 -- .../ai/embedding/EmbeddingOptions.java | 6 - .../org/springframework/ai/model/Content.java | 8 - .../org/springframework/ai/model/Media.java | 23 -- .../ai/model/ModelOptionsUtils.java | 40 ---- .../function/FunctionCallbackWrapper.java | 210 ------------------ .../vectorstore/SimpleVectorStoreContent.java | 5 - .../chat/client/DefaultChatClientTests.java | 64 +++--- .../RetrievalAugmentationAdvisorTests.java | 2 +- ...tChatClientObservationConventionTests.java | 18 +- .../springframework/ai/model/MediaTests.java | 5 +- .../modules/ROOT/pages/api/advisors.adoc | 2 - .../tool/FunctionCallbackInPrompt2IT.java | 2 +- 25 files changed, 59 insertions(+), 572 deletions(-) delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/client/RequestResponseAdvisor.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java index 8503201d21a..b4785d3f38f 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java @@ -261,13 +261,8 @@ public enum ChatModel implements ChatModelDescription { /** * The CLAUDE_2_0 */ - CLAUDE_2("claude-2.0"), + CLAUDE_2("claude-2.0"); - /** - * The CLAUDE_INSTANT_1_2 - */ - @Deprecated - CLAUDE_INSTANT_1_2("claude-instant-1.2"); // @formatter:on private final String value; diff --git a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtils.java b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtils.java index 84f7cfde43a..0ffa4eaafd3 100644 --- a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtils.java +++ b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtils.java @@ -68,8 +68,8 @@ public final class ConverseApiUtils { public static final ChatResponse EMPTY_CHAT_RESPONSE = ChatResponse.builder() - .withGenerations(List.of()) - .withMetadata("empty", true) + .generations(List.of()) + .metadata("empty", true) .build(); private ConverseApiUtils() { diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMultipleFunctionCallsIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMultipleFunctionCallsIT.java index 11a7699b79a..915d6884561 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMultipleFunctionCallsIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMultipleFunctionCallsIT.java @@ -255,7 +255,11 @@ void functionCallWithExplicitInputType() throws NoSuchMethodException { String content = chatClient.prompt() .user("What's the weather like in Shanghai?") - .function("currentTemp", "get current temp", MyFunction.Req.class, function) + .functions(FunctionCallback.builder() + .function("currentTemp", function) + .description("get current temp") + .inputType(MyFunction.Req.class) + .build()) .call() .content(); diff --git a/spring-ai-core/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java b/spring-ai-core/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java index f3774a241b0..2c488914aa1 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java @@ -28,7 +28,6 @@ import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallbackWrapper; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -44,7 +43,7 @@ public void registerHints(@NonNull RuntimeHints hints, @Nullable ClassLoader cla var chatTypes = Set.of(AbstractMessage.class, AssistantMessage.class, ToolResponseMessage.class, Message.class, MessageType.class, UserMessage.class, SystemMessage.class, DefaultFunctionCallbackResolver.class, - FunctionCallback.class, FunctionCallbackWrapper.class); + FunctionCallback.class); for (var c : chatTypes) { hints.reflection().registerType(c); } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/ChatClient.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/ChatClient.java index ae017b66357..15de10a1ceb 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/ChatClient.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/ChatClient.java @@ -215,29 +215,8 @@ interface ChatClientRequestSpec { ChatClientRequestSpec options(T options); - /** - * @deprecated use {@link #functions(FunctionCallback...)} instead. - */ - @Deprecated - ChatClientRequestSpec function(String name, String description, - java.util.function.Function function); - - /** - * @deprecated use {@link #functions(FunctionCallback...)} instead. - */ - @Deprecated - ChatClientRequestSpec function(String name, String description, - java.util.function.BiFunction function); - ChatClientRequestSpec functions(FunctionCallback... functionCallbacks); - /** - * @deprecated use {@link #functions(FunctionCallback...)} instead. - */ - @Deprecated - ChatClientRequestSpec function(String name, String description, Class inputType, - java.util.function.Function function); - ChatClientRequestSpec functions(String... functionBeanNames); ChatClientRequestSpec toolContext(Map toolContext); @@ -293,19 +272,6 @@ interface Builder { Builder defaultSystem(Consumer systemSpecConsumer); - /** - * @deprecated use {@link #defaultFunctions(FunctionCallback...)} instead. - */ - @Deprecated - Builder defaultFunction(String name, String description, java.util.function.Function function); - - /** - * @deprecated use {@link #defaultFunctions(FunctionCallback...)} instead. - */ - @Deprecated - Builder defaultFunction(String name, String description, - java.util.function.BiFunction function); - Builder defaultFunctions(String... functionNames); Builder defaultFunctions(FunctionCallback... functionCallbacks); diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java index 127390df7da..d1651cd7991 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java @@ -53,14 +53,12 @@ import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.StreamingChatModel; -import org.springframework.ai.chat.model.ToolContext; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.ai.converter.StructuredOutputConverter; import org.springframework.ai.model.Media; import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallbackWrapper; import org.springframework.core.Ordered; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.Resource; @@ -836,57 +834,6 @@ public ChatClientRequestSpec options(T options) { return this; } - @Override - public ChatClientRequestSpec function(String name, String description, - java.util.function.Function function) { - Assert.hasText(name, "name cannot be null or empty"); - Assert.hasText(description, "description cannot be null or empty"); - Assert.notNull(function, "function cannot be null"); - - var fcw = FunctionCallbackWrapper.builder(function) - .withDescription(description) - .withName(name) - .withResponseConverter(Object::toString) - .build(); - this.functionCallbacks.add(fcw); - return this; - } - - @Override - public ChatClientRequestSpec function(String name, String description, - java.util.function.BiFunction biFunction) { - - Assert.hasText(name, "name cannot be null or empty"); - Assert.hasText(description, "description cannot be null or empty"); - Assert.notNull(biFunction, "biFunction cannot be null"); - - var fcw = FunctionCallbackWrapper.builder(biFunction) - .withDescription(description) - .withName(name) - .withResponseConverter(Object::toString) - .build(); - this.functionCallbacks.add(fcw); - return this; - } - - @Override - public ChatClientRequestSpec function(String name, String description, @Nullable Class inputType, - java.util.function.Function function) { - - Assert.hasText(name, "name cannot be null or empty"); - Assert.hasText(description, "description cannot be null or empty"); - Assert.notNull(function, "function cannot be null"); - - var fcw = FunctionCallback.builder() - .function(name, function) - .description(description) - .responseConverter(Object::toString) - .inputType(inputType) - .build(); - this.functionCallbacks.add(fcw); - return this; - } - public ChatClientRequestSpec functions(String... functionBeanNames) { Assert.notNull(functionBeanNames, "functionBeanNames cannot be null"); Assert.noNullElements(functionBeanNames, "functionBeanNames cannot contain null elements"); diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClientBuilder.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClientBuilder.java index 7f4fbdbff17..adb3c0995b4 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClientBuilder.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClientBuilder.java @@ -148,13 +148,15 @@ public Builder defaultSystem(Consumer systemSpecConsumer) { } public Builder defaultFunction(String name, String description, java.util.function.Function function) { - this.defaultRequest.function(name, description, function); + this.defaultRequest + .functions(FunctionCallback.builder().function(name, function).description(description).build()); return this; } public Builder defaultFunction(String name, String description, java.util.function.BiFunction biFunction) { - this.defaultRequest.function(name, description, biFunction); + this.defaultRequest + .functions(FunctionCallback.builder().function(name, biFunction).description(description).build()); return this; } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/RequestResponseAdvisor.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/RequestResponseAdvisor.java deleted file mode 100644 index 798b1ed1dd5..00000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/RequestResponseAdvisor.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2023-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.chat.client; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import reactor.core.publisher.Flux; - -import org.springframework.ai.chat.client.advisor.api.AdvisedRequest; -import org.springframework.ai.chat.client.advisor.api.AdvisedResponse; -import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor; -import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain; -import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisor; -import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; - -/** - * Advisor called before and after the {@link ChatModel#call(Prompt)} and - * {@link ChatModel#stream(Prompt)} methods calls. The {@link ChatClient} maintains a - * chain of advisors with shared advise context. - * - * @deprecated since 1.0.0 M3 please use {@link CallAroundAdvisor} or - * {@link StreamAroundAdvisor} instead. - * @author Christian Tzolov - * @since 1.0.0 - */ -@Deprecated -public interface RequestResponseAdvisor extends CallAroundAdvisor, StreamAroundAdvisor { - - @Override - default String getName() { - return this.getClass().getSimpleName(); - } - - default AdvisedRequest adviseRequest(AdvisedRequest request, Map adviseContext) { - return request; - } - - default ChatResponse adviseResponse(ChatResponse response, Map adviseContext) { - return response; - } - - default Flux adviseResponse(Flux fluxResponse, Map context) { - return fluxResponse; - } - - @Override - default AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { - var context = new HashMap<>(advisedRequest.adviseContext()); - var requestPrim = adviseRequest(advisedRequest, context); - advisedRequest = AdvisedRequest.from(requestPrim).adviseContext(Collections.unmodifiableMap(context)).build(); - - var advisedResponse = chain.nextAroundCall(advisedRequest); - - context = new HashMap<>(advisedResponse.adviseContext()); - var chatResponse = adviseResponse(advisedResponse.response(), context); - return new AdvisedResponse(chatResponse, Collections.unmodifiableMap(context)); - } - - default Flux aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { - - ConcurrentHashMap context = new ConcurrentHashMap<>(advisedRequest.adviseContext()); - - advisedRequest = adviseRequest(advisedRequest, context); - - var advisedResponseStream = chain.nextAroundStream(advisedRequest); - - return this.adviseResponse(advisedResponseStream.map(ar -> ar.response()), context) - .map(chatResponse -> new AdvisedResponse(chatResponse, context)); - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/QuestionAnswerAdvisor.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/QuestionAnswerAdvisor.java index 7ca09e5872a..d962b5d37f0 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/QuestionAnswerAdvisor.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/QuestionAnswerAdvisor.java @@ -246,7 +246,7 @@ private AdvisedRequest before(AdvisedRequest request) { private AdvisedResponse after(AdvisedResponse advisedResponse) { ChatResponse.Builder chatResponseBuilder = ChatResponse.builder().from(advisedResponse.response()); - chatResponseBuilder.withMetadata(RETRIEVED_DOCUMENTS, advisedResponse.adviseContext().get(RETRIEVED_DOCUMENTS)); + chatResponseBuilder.metadata(RETRIEVED_DOCUMENTS, advisedResponse.adviseContext().get(RETRIEVED_DOCUMENTS)); return new AdvisedResponse(chatResponseBuilder.build(), advisedResponse.adviseContext()); } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/SafeGuardAdvisor.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/SafeGuardAdvisor.java index 384ecbf8970..fd94d594c55 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/SafeGuardAdvisor.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/SafeGuardAdvisor.java @@ -97,7 +97,7 @@ public Flux aroundStream(AdvisedRequest advisedRequest, StreamA private AdvisedResponse createFailureResponse(AdvisedRequest advisedRequest) { return new AdvisedResponse(ChatResponse.builder() - .withGenerations(List.of(new Generation(new AssistantMessage(this.failureResponse)))) + .generations(List.of(new Generation(new AssistantMessage(this.failureResponse)))) .build(), advisedRequest.adviseContext()); } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java index ee3701d8104..8f3dd228510 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java @@ -102,11 +102,6 @@ public String getText() { return this.textContent; } - @Override - public String getContent() { - return this.textContent; - } - /** * Get the metadata of the message. * @return the metadata of the message diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/model/ChatResponse.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/model/ChatResponse.java index 16e19053e30..3b82319de6b 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/model/ChatResponse.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/model/ChatResponse.java @@ -137,15 +137,6 @@ public Builder from(ChatResponse other) { return this.metadata(other.chatResponseMetadata); } - /** - * @deprecated Use {@link #metadata(String, Object)} instead. - */ - @Deprecated - public Builder withMetadata(String key, Object value) { - this.chatResponseMetadataBuilder.keyValue(key, value); - return this; - } - public Builder metadata(String key, Object value) { this.chatResponseMetadataBuilder.keyValue(key, value); return this; @@ -164,16 +155,6 @@ public Builder metadata(ChatResponseMetadata other) { return this; } - /** - * @deprecated Use {@link #generations(List)} instead. - */ - @Deprecated - public Builder withGenerations(List generations) { - this.generations = generations; - return this; - - } - public Builder generations(List generations) { this.generations = generations; return this; diff --git a/spring-ai-core/src/main/java/org/springframework/ai/document/Document.java b/spring-ai-core/src/main/java/org/springframework/ai/document/Document.java index 7d02ae50e6a..dee15dd6e86 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/document/Document.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/document/Document.java @@ -332,20 +332,6 @@ public Builder text(@Nullable String text) { return this; } - /** - * Sets the text content of the document. - * @param text the text content to set - * @return the builder instance - * @deprecated since 1.0.0-M5, use {@link #text(String)} instead as it more - * accurately reflects that this Document instance will contain text rather than - * generic content. This method will be removed in a future release. - */ - @Deprecated(since = "1.0.0-M5", forRemoval = true) - public Builder content(@Nullable String text) { - this.text = text; - return this; - } - /** * Sets the media content of the document. *

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 8871191dace..73a9d4d3ade 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 @@ -27,12 +27,6 @@ */ public interface EmbeddingOptions extends ModelOptions { - /** - * Use the {@link EmbeddingOptionsBuilder} instead. - */ - @Deprecated(since = "1.0.0", forRemoval = true) - EmbeddingOptions EMPTY = EmbeddingOptionsBuilder.builder().build(); - @Nullable String getModel(); diff --git a/spring-ai-core/src/main/java/org/springframework/ai/model/Content.java b/spring-ai-core/src/main/java/org/springframework/ai/model/Content.java index 585866c954c..1496d89a069 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/model/Content.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/model/Content.java @@ -35,14 +35,6 @@ public interface Content { */ String getText(); - /** - * Get the content of the message. - * @return the content of the message - * @deprecated Use getText - */ - @Deprecated(since = "1.0.0.M5") - String getContent(); - /** * Get the metadata associated with the content. * @return the metadata associated with the content diff --git a/spring-ai-core/src/main/java/org/springframework/ai/model/Media.java b/spring-ai-core/src/main/java/org/springframework/ai/model/Media.java index c7494caaa59..c0e6c3fb949 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/model/Media.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/model/Media.java @@ -134,29 +134,6 @@ public Media(MimeType mimeType, Resource resource) { } } - /** - * Create a new Media instance. - * @param mimeType the media MIME type - * @param resource the media resource - * @param id the media id - * @deprecated Use {@link Builder} instead. - */ - @Deprecated(since = "1.0.0.M5") - public Media(MimeType mimeType, Resource resource, String id) { - Assert.notNull(mimeType, "MimeType must not be null"); - Assert.notNull(resource, "Resource must not be null"); - try { - byte[] bytes = resource.getContentAsByteArray(); - this.mimeType = mimeType; - this.id = id; - this.data = bytes; - this.name = generateDefaultName(mimeType); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - /** * Creates a new Media builder. * @return a new Media builder instance diff --git a/spring-ai-core/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java b/spring-ai-core/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java index a64e22705f2..475227b9692 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java @@ -348,46 +348,6 @@ private static String toGetName(String name) { return "get" + name.substring(0, 1).toUpperCase() + name.substring(1); } - /** - * Generates JSON Schema (version 2020_12) for the given class. - * @param clazz the class to generate JSON Schema from. - * @param toUpperCaseTypeValues if true, the type values are converted to upper case. - * @return the generated JSON Schema as a String. - * @deprecated use {@link #getJsonSchema(Type, boolean)} instead. - */ - @Deprecated(since = "1.0 M4") - public static String getJsonSchema(Class clazz, boolean toUpperCaseTypeValues) { - - if (SCHEMA_GENERATOR_CACHE.get() == null) { - - JacksonModule jacksonModule = new JacksonModule(JacksonOption.RESPECT_JSONPROPERTY_REQUIRED); - Swagger2Module swaggerModule = new Swagger2Module(); - - SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, - OptionPreset.PLAIN_JSON) - .with(Option.EXTRA_OPEN_API_FORMAT_VALUES) - .with(Option.PLAIN_DEFINITION_KEYS) - .with(swaggerModule) - .with(jacksonModule); - - if (KotlinDetector.isKotlinReflectPresent()) { - configBuilder.with(new KotlinModule()); - } - - SchemaGeneratorConfig config = configBuilder.build(); - SchemaGenerator generator = new SchemaGenerator(config); - SCHEMA_GENERATOR_CACHE.compareAndSet(null, generator); - } - - ObjectNode node = SCHEMA_GENERATOR_CACHE.get().generateSchema(clazz); - // Required for OpenAPI 3.0 (at least Vertex AI version of it). - if (toUpperCaseTypeValues) { - toUpperCaseTypeValues(node); - } - - return node.toPrettyString(); - } - /** * Generates JSON Schema (version 2020_12) for the given class. * @param inputType the input {@link Type} to generate JSON Schema from. diff --git a/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java b/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java deleted file mode 100644 index 80e2b2472b2..00000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2023-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.model.function; - -import java.lang.reflect.Type; -import java.util.function.BiFunction; -import java.util.function.Function; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.json.JsonMapper; - -import org.springframework.ai.chat.model.ToolContext; -import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.ai.util.JacksonUtils; -import org.springframework.util.Assert; - -/** - * Note that the underlying function is responsible for converting the output into format - * that can be consumed by the Model. The default implementation converts the output into - * String before sending it to the Model. Provide a custom function responseConverter - * implementation to override this. - * - * @param the input type - * @param the output type - * @author Christian Tzolov - * @author Sebastien Deleuze - * @deprecated in favor of {@link FunctionCallback.Builder} - */ -@Deprecated -public final class FunctionCallbackWrapper extends AbstractFunctionCallback { - - private final BiFunction biFunction; - - FunctionCallbackWrapper(String name, String description, String inputTypeSchema, Type inputType, - Function responseConverter, ObjectMapper objectMapper, BiFunction function) { - super(name, description, inputTypeSchema, inputType, responseConverter, objectMapper); - Assert.notNull(function, "Function must not be null"); - this.biFunction = function; - } - - @Override - public O apply(I input, ToolContext context) { - return this.biFunction.apply(input, context); - } - - /** - * @deprecated use {@link FunctionCallback#builder()} instead. - */ - @Deprecated - public static Builder builder(BiFunction biFunction) { - return new Builder<>(biFunction); - } - - /** - * Create a new {@link FunctionCallbackWrapper} instance. - * @deprecated use {@link FunctionCallback#builder()} instead. - */ - @Deprecated - public static Builder builder(Function function) { - return new Builder<>(function); - } - - /** - * Builder for {@link FunctionCallbackWrapper}. - * - * @param the input type - * @param the output type - * @deprecated in favor of {@link DefaultFunctionCallbackBuilder} - */ - @Deprecated - public static final class Builder { - - private final BiFunction biFunction; - - private final Function function; - - private String name; - - private String description; - - private Class inputType; - - private SchemaType schemaType = SchemaType.JSON_SCHEMA; - - // By default the response is converted to a JSON string. - private Function responseConverter = ModelOptionsUtils::toJsonString; - - private String inputTypeSchema; - - private ObjectMapper objectMapper; - - private Builder(BiFunction biFunction) { - Assert.notNull(biFunction, "Function must not be null"); - this.biFunction = biFunction; - this.function = null; - } - - private Builder(Function function) { - Assert.notNull(function, "Function must not be null"); - this.biFunction = null; - this.function = function; - } - - @SuppressWarnings("unchecked") - private static Class resolveInputType(BiFunction biFunction) { - return (Class) TypeResolverHelper - .getBiFunctionInputClass((Class>) biFunction.getClass()); - } - - @SuppressWarnings("unchecked") - private static Class resolveInputType(Function function) { - return (Class) TypeResolverHelper.getFunctionInputClass((Class>) function.getClass()); - } - - public Builder withName(String name) { - Assert.hasText(name, "Name must not be empty"); - this.name = name; - return this; - } - - public Builder withDescription(String description) { - Assert.hasText(description, "Description must not be empty"); - this.description = description; - return this; - } - - @SuppressWarnings("unchecked") - public Builder withInputType(Class inputType) { - this.inputType = (Class) inputType; - return this; - } - - public Builder withResponseConverter(Function responseConverter) { - Assert.notNull(responseConverter, "ResponseConverter must not be null"); - this.responseConverter = responseConverter; - return this; - } - - public Builder withInputTypeSchema(String inputTypeSchema) { - Assert.hasText(inputTypeSchema, "InputTypeSchema must not be empty"); - this.inputTypeSchema = inputTypeSchema; - return this; - } - - public Builder withObjectMapper(ObjectMapper objectMapper) { - Assert.notNull(objectMapper, "ObjectMapper must not be null"); - this.objectMapper = objectMapper; - return this; - } - - public Builder withSchemaType(SchemaType schemaType) { - Assert.notNull(schemaType, "SchemaType must not be null"); - this.schemaType = schemaType; - return this; - } - - public FunctionCallbackWrapper build() { - - Assert.hasText(this.name, "Name must not be empty"); - Assert.hasText(this.description, "Description must not be empty"); - Assert.notNull(this.responseConverter, "ResponseConverter must not be null"); - - if (this.objectMapper == null) { - this.objectMapper = JsonMapper.builder() - .addModules(JacksonUtils.instantiateAvailableModules()) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) - .build(); - } - - if (this.inputType == null) { - if (this.function != null) { - this.inputType = resolveInputType(this.function); - } - else { - this.inputType = resolveInputType(this.biFunction); - } - } - - if (this.inputTypeSchema == null) { - boolean upperCaseTypeValues = this.schemaType == SchemaType.OPEN_API_SCHEMA; - this.inputTypeSchema = ModelOptionsUtils.getJsonSchema(this.inputType, upperCaseTypeValues); - } - - BiFunction finalBiFunction = (this.biFunction != null) ? this.biFunction - : (request, context) -> this.function.apply(request); - - return new FunctionCallbackWrapper<>(this.name, this.description, this.inputTypeSchema, this.inputType, - this.responseConverter, this.objectMapper, finalBiFunction); - } - - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/SimpleVectorStoreContent.java b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/SimpleVectorStoreContent.java index b44389678d8..69e720a2c2f 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/SimpleVectorStoreContent.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/SimpleVectorStoreContent.java @@ -125,11 +125,6 @@ public String getText() { return this.text; } - @Override - public String getContent() { - return this.text; - } - @Override public Map getMetadata() { return this.metadata; diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/DefaultChatClientTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/DefaultChatClientTests.java index 5f7951ca5cd..ba7b4dce11d 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/DefaultChatClientTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/DefaultChatClientTests.java @@ -1389,25 +1389,22 @@ void whenFunctionDescriptionIsNullThenThrow() { void whenFunctionDescriptionIsEmptyThenThrow() { ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build(); ChatClient.ChatClientRequestSpec spec = chatClient.prompt(); - assertThatThrownBy(() -> spec.function("name", "", input -> "hello")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("description cannot be null or empty"); - } - - @Test - void whenFunctionLambdaIsNullThenThrow() { - ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build(); - ChatClient.ChatClientRequestSpec spec = chatClient.prompt(); - assertThatThrownBy(() -> spec.function("name", "description", (Function) null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("function cannot be null"); + assertThatThrownBy(() -> spec.functions(FunctionCallback.builder() + .function("name", input -> "hello") + .description("") + .inputType(String.class) + .build())).isInstanceOf(IllegalArgumentException.class).hasMessage("Description must not be empty"); } @Test void whenFunctionThenReturn() { ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build(); ChatClient.ChatClientRequestSpec spec = chatClient.prompt(); - spec = spec.function("name", "description", String.class, input -> "hello"); + spec = spec.functions(FunctionCallback.builder() + .function("name", input -> "hello") + .inputType(String.class) + .description("description") + .build()); DefaultChatClient.DefaultChatClientRequestSpec defaultSpec = (DefaultChatClient.DefaultChatClientRequestSpec) spec; assertThat(defaultSpec.getFunctionCallbacks()).anyMatch(callback -> callback.getName().equals("name")); } @@ -1416,7 +1413,11 @@ void whenFunctionThenReturn() { void whenFunctionAndInputTypeThenReturn() { ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build(); ChatClient.ChatClientRequestSpec spec = chatClient.prompt(); - spec = spec.function("name", "description", String.class, input -> "hello"); + spec = spec.functions(FunctionCallback.builder() + .function("name", input -> "hello") + .inputType(String.class) + .description("description") + .build()); DefaultChatClient.DefaultChatClientRequestSpec defaultSpec = (DefaultChatClient.DefaultChatClientRequestSpec) spec; assertThat(defaultSpec.getFunctionCallbacks()).anyMatch(callback -> callback.getName().equals("name")); } @@ -1425,52 +1426,51 @@ void whenFunctionAndInputTypeThenReturn() { void whenBiFunctionNameIsNullThenThrow() { ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build(); ChatClient.ChatClientRequestSpec spec = chatClient.prompt(); - assertThatThrownBy(() -> spec.function(null, "description", (input, ctx) -> "hello")) + assertThatThrownBy(() -> spec.functions( + FunctionCallback.builder().function(null, (input, ctx) -> "hello").description("description").build())) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("name cannot be null or empty"); + .hasMessage("Name must not be empty"); } @Test void whenBiFunctionNameIsEmptyThenThrow() { ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build(); ChatClient.ChatClientRequestSpec spec = chatClient.prompt(); - assertThatThrownBy(() -> spec.function("", "description", (input, ctx) -> "hello")) + assertThatThrownBy(() -> spec.functions( + FunctionCallback.builder().function("", (input, ctx) -> "hello").description("description").build())) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("name cannot be null or empty"); + .hasMessage("Name must not be empty"); } @Test void whenBiFunctionDescriptionIsNullThenThrow() { ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build(); ChatClient.ChatClientRequestSpec spec = chatClient.prompt(); - assertThatThrownBy(() -> spec.function("name", null, (input, ctx) -> "hello")) + assertThatThrownBy(() -> spec + .functions(FunctionCallback.builder().function("name", (input, ctx) -> "hello").description(null).build())) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("description cannot be null or empty"); + .hasMessage("Description must not be empty"); } @Test void whenBiFunctionDescriptionIsEmptyThenThrow() { ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build(); ChatClient.ChatClientRequestSpec spec = chatClient.prompt(); - assertThatThrownBy(() -> spec.function("name", "", (input, ctx) -> "hello")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("description cannot be null or empty"); - } - - @Test - void whenBiFunctionLambdaIsNullThenThrow() { - ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build(); - ChatClient.ChatClientRequestSpec spec = chatClient.prompt(); - assertThatThrownBy(() -> spec.function("name", "description", (BiFunction) null)) + assertThatThrownBy(() -> spec + .functions(FunctionCallback.builder().function("name", (input, ctx) -> "hello").description("").build())) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("biFunction cannot be null"); + .hasMessage("Description must not be empty"); } @Test void whenBiFunctionThenReturn() { ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build(); ChatClient.ChatClientRequestSpec spec = chatClient.prompt(); - spec = spec.function("name", "description", (input, ctx) -> "hello"); + spec = spec.functions(FunctionCallback.builder() + .function("name", (input, ctx) -> "hello") + .description("description") + .inputType(String.class) + .build()); DefaultChatClient.DefaultChatClientRequestSpec defaultSpec = (DefaultChatClient.DefaultChatClientRequestSpec) spec; assertThat(defaultSpec.getFunctionCallbacks()).anyMatch(callback -> callback.getName().equals("name")); } diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/advisor/RetrievalAugmentationAdvisorTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/advisor/RetrievalAugmentationAdvisorTests.java index fdcaae84068..a86f31f24bf 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/advisor/RetrievalAugmentationAdvisorTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/advisor/RetrievalAugmentationAdvisorTests.java @@ -66,7 +66,7 @@ void theOneWithTheDocumentRetriever() { var chatModel = mock(ChatModel.class); var promptCaptor = ArgumentCaptor.forClass(Prompt.class); given(chatModel.call(promptCaptor.capture())).willReturn(ChatResponse.builder() - .withGenerations(List.of(new Generation(new AssistantMessage("Felix Felicis")))) + .generations(List.of(new Generation(new AssistantMessage("Felix Felicis")))) .build()); // Document Retriever diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/DefaultChatClientObservationConventionTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/DefaultChatClientObservationConventionTests.java index 0f1e4814277..99fb24c571d 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/DefaultChatClientObservationConventionTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/DefaultChatClientObservationConventionTests.java @@ -29,12 +29,13 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.ai.chat.client.DefaultChatClient.DefaultChatClientRequestSpec; -import org.springframework.ai.chat.client.RequestResponseAdvisor; import org.springframework.ai.chat.client.advisor.api.AdvisedRequest; +import org.springframework.ai.chat.client.advisor.api.AdvisedResponse; +import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor; +import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain; import org.springframework.ai.chat.client.observation.ChatClientObservationDocumentation.HighCardinalityKeyNames; import org.springframework.ai.chat.client.observation.ChatClientObservationDocumentation.LowCardinalityKeyNames; import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.model.function.FunctionCallback; import org.springframework.ai.observation.conventions.AiProvider; import org.springframework.ai.observation.conventions.SpringAiKind; @@ -57,8 +58,8 @@ class DefaultChatClientObservationConventionTests { DefaultChatClientRequestSpec request; - static RequestResponseAdvisor dummyAdvisor(String name) { - return new RequestResponseAdvisor() { + static CallAroundAdvisor dummyAdvisor(String name) { + return new CallAroundAdvisor() { @Override public String getName() { @@ -71,13 +72,8 @@ public int getOrder() { } @Override - public AdvisedRequest adviseRequest(AdvisedRequest request, Map context) { - return request; - } - - @Override - public ChatResponse adviseResponse(ChatResponse response, Map adviseContext) { - return response; + public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { + return null; } }; diff --git a/spring-ai-core/src/test/java/org/springframework/ai/model/MediaTests.java b/spring-ai-core/src/test/java/org/springframework/ai/model/MediaTests.java index 112563bd5e7..5d189936447 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/model/MediaTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/model/MediaTests.java @@ -215,7 +215,7 @@ void testMediaConstructorWithResourceAndId() throws IOException { Resource resource = new ByteArrayResource(data); String id = "123"; - Media media = new Media(mimeType, resource, id); + Media media = Media.builder().mimeType(mimeType).data(resource).id(id).build(); assertThat(media.getMimeType()).isEqualTo(mimeType); assertThat(media.getData()).isInstanceOf(byte[].class); @@ -247,7 +247,8 @@ public byte[] getContentAsByteArray() throws IOException { } }; - assertThatThrownBy(() -> new Media(Media.Format.IMAGE_PNG, failingResource, "123")) + assertThatThrownBy( + () -> Media.builder().mimeType(Media.Format.IMAGE_PNG).data(failingResource).id("123").build()) .isInstanceOf(RuntimeException.class) .hasCauseInstanceOf(IOException.class); } diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/advisors.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/advisors.adoc index 56e61a7ee50..405eb60883a 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/advisors.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/advisors.adoc @@ -378,8 +378,6 @@ public Flux aroundStream(AdvisedRequest advisedRequest, StreamA == Backward Compatibility IMPORTANT: The `AdvisedRequest` class is moved to a new package. -While the `RequestResponseAdvisor` interface is still available it is marked as deprecated and will be removed around the M3 release. -It is recommended to use the new `CallAroundAdvisor` and `StreamAroundAdvisor` interfaces for new implementations. == Breaking API Changes The Spring AI Advisor Chain underwent significant changes from version 1.0 M2 to 1.0 M3. Here are the key modifications: diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackInPrompt2IT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackInPrompt2IT.java index aa0018ee803..f0988a39d10 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackInPrompt2IT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackInPrompt2IT.java @@ -138,7 +138,7 @@ void streamingFunctionCallTest() { // @formatter:off String content = ChatClient.builder(chatModel).build().prompt() .user("What's the weather like in San Francisco, Tokyo, and Paris?") - .function("CurrentWeatherService", "Get the weather in location", new MockWeatherService()) + .functions(FunctionCallback.builder().function("CurrentWeatherService", new MockWeatherService()).description("Get the weather in location").build()) .stream().content() .collectList().block().stream().collect(Collectors.joining()); // @formatter:on