Remove deprecated methods/classes from spring-ai-core

This commit is contained in:
Mark Pollack
2025-01-24 13:37:14 -05:00
parent 822576bf91
commit 7521cab9db
25 changed files with 66 additions and 580 deletions

View File

@@ -261,13 +261,8 @@ public class AnthropicApi {
/**
* 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;

View File

@@ -68,8 +68,8 @@ import org.springframework.util.StringUtils;
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() {

View File

@@ -255,7 +255,11 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
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();

View File

@@ -28,7 +28,6 @@ import org.springframework.ai.chat.messages.ToolResponseMessage;
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 class SpringAiCoreRuntimeHints implements RuntimeHintsRegistrar {
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);
}

View File

@@ -30,7 +30,6 @@ import org.springframework.ai.chat.client.observation.ChatClientObservationConve
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
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.StructuredOutputConverter;
@@ -221,29 +220,8 @@ public interface ChatClient {
ChatClientRequestSpec toolCallbacks(FunctionCallback... toolCallbacks);
/**
* @deprecated use {@link #functions(FunctionCallback...)} instead.
*/
@Deprecated
<I, O> ChatClientRequestSpec function(String name, String description,
java.util.function.Function<I, O> function);
/**
* @deprecated use {@link #functions(FunctionCallback...)} instead.
*/
@Deprecated
<I, O> ChatClientRequestSpec function(String name, String description,
java.util.function.BiFunction<I, ToolContext, O> function);
<I, O> ChatClientRequestSpec functions(FunctionCallback... functionCallbacks);
/**
* @deprecated use {@link #functions(FunctionCallback...)} instead.
*/
@Deprecated
<I, O> ChatClientRequestSpec function(String name, String description, Class<I> inputType,
java.util.function.Function<I, O> function);
ChatClientRequestSpec functions(String... functionBeanNames);
ChatClientRequestSpec toolContext(Map<String, Object> toolContext);
@@ -305,19 +283,6 @@ public interface ChatClient {
Builder defaultToolCallbacks(FunctionCallback... toolCallbacks);
/**
* @deprecated use {@link #defaultFunctions(FunctionCallback...)} instead.
*/
@Deprecated
<I, O> Builder defaultFunction(String name, String description, java.util.function.Function<I, O> function);
/**
* @deprecated use {@link #defaultFunctions(FunctionCallback...)} instead.
*/
@Deprecated
<I, O> Builder defaultFunction(String name, String description,
java.util.function.BiFunction<I, ToolContext, O> function);
Builder defaultFunctions(String... functionNames);
Builder defaultFunctions(FunctionCallback... functionCallbacks);

View File

@@ -57,14 +57,12 @@ import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
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;
@@ -549,11 +547,11 @@ public class DefaultChatClient implements ChatClient {
Flux<AdvisedResponse> stream = inputRequest.aroundAdvisorChainBuilder.build().nextAroundStream(initialAdvisedRequest);
return stream
.map(AdvisedResponse::response)
.doOnError(observation::error)
.doFinally(s -> observation.stop())
.contextWrite(ctx -> ctx.put(ObservationThreadLocalAccessor.KEY, observation));
// @formatter:on
.map(AdvisedResponse::response)
.doOnError(observation::error)
.doFinally(s -> observation.stop())
.contextWrite(ctx -> ctx.put(ObservationThreadLocalAccessor.KEY, observation));
// @formatter:on
});
}
@@ -695,8 +693,8 @@ public class DefaultChatClient implements ChatClient {
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
return chatModel.stream(advisedRequest.toPrompt())
.map(chatResponse -> new AdvisedResponse(chatResponse, Collections.unmodifiableMap(advisedRequest.adviseContext())))
.publishOn(Schedulers.boundedElastic()); // TODO add option to disable.
.map(chatResponse -> new AdvisedResponse(chatResponse, Collections.unmodifiableMap(advisedRequest.adviseContext())))
.publishOn(Schedulers.boundedElastic()); // TODO add option to disable.
}
});
// @formatter:on
@@ -863,57 +861,6 @@ public class DefaultChatClient implements ChatClient {
return this;
}
@Override
public <I, O> ChatClientRequestSpec function(String name, String description,
java.util.function.Function<I, O> 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 <I, O> ChatClientRequestSpec function(String name, String description,
java.util.function.BiFunction<I, ToolContext, O> 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 <I, O> ChatClientRequestSpec function(String name, String description, @Nullable Class<I> inputType,
java.util.function.Function<I, O> 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) {
return tools(functionBeanNames);
}

View File

@@ -168,13 +168,15 @@ public class DefaultChatClientBuilder implements Builder {
}
public <I, O> Builder defaultFunction(String name, String description, java.util.function.Function<I, O> function) {
this.defaultRequest.function(name, description, function);
this.defaultRequest
.functions(FunctionCallback.builder().function(name, function).description(description).build());
return this;
}
public <I, O> Builder defaultFunction(String name, String description,
java.util.function.BiFunction<I, ToolContext, O> biFunction) {
this.defaultRequest.function(name, description, biFunction);
this.defaultRequest
.functions(FunctionCallback.builder().function(name, biFunction).description(description).build());
return this;
}

View File

@@ -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<String, Object> adviseContext) {
return request;
}
default ChatResponse adviseResponse(ChatResponse response, Map<String, Object> adviseContext) {
return response;
}
default Flux<ChatResponse> adviseResponse(Flux<ChatResponse> fluxResponse, Map<String, Object> 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<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
ConcurrentHashMap<String, Object> 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));
}
}

View File

@@ -246,7 +246,7 @@ public class QuestionAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdv
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());
}

View File

@@ -97,7 +97,7 @@ public class SafeGuardAdvisor implements CallAroundAdvisor, StreamAroundAdvisor
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());
}

View File

@@ -102,11 +102,6 @@ public abstract class AbstractMessage implements Message {
return this.textContent;
}
@Override
public String getContent() {
return this.textContent;
}
/**
* Get the metadata of the message.
* @return the metadata of the message

View File

@@ -137,15 +137,6 @@ public class ChatResponse implements ModelResponse<Generation> {
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 class ChatResponse implements ModelResponse<Generation> {
return this;
}
/**
* @deprecated Use {@link #generations(List)} instead.
*/
@Deprecated
public Builder withGenerations(List<Generation> generations) {
this.generations = generations;
return this;
}
public Builder generations(List<Generation> generations) {
this.generations = generations;
return this;

View File

@@ -332,20 +332,6 @@ public class Document {
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.
* <p>

View File

@@ -27,12 +27,6 @@ import org.springframework.lang.Nullable;
*/
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();

View File

@@ -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

View File

@@ -134,29 +134,6 @@ public class Media {
}
}
/**
* 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

View File

@@ -348,46 +348,6 @@ public abstract class ModelOptionsUtils {
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.

View File

@@ -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 <I> the input type
* @param <O> the output type
* @author Christian Tzolov
* @author Sebastien Deleuze
* @deprecated in favor of {@link FunctionCallback.Builder}
*/
@Deprecated
public final class FunctionCallbackWrapper<I, O> extends AbstractFunctionCallback<I, O> {
private final BiFunction<I, ToolContext, O> biFunction;
FunctionCallbackWrapper(String name, String description, String inputTypeSchema, Type inputType,
Function<O, String> responseConverter, ObjectMapper objectMapper, BiFunction<I, ToolContext, O> 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 <I, O> Builder<I, O> builder(BiFunction<I, ToolContext, O> biFunction) {
return new Builder<>(biFunction);
}
/**
* Create a new {@link FunctionCallbackWrapper} instance.
* @deprecated use {@link FunctionCallback#builder()} instead.
*/
@Deprecated
public static <I, O> Builder<I, O> builder(Function<I, O> function) {
return new Builder<>(function);
}
/**
* Builder for {@link FunctionCallbackWrapper}.
*
* @param <I> the input type
* @param <O> the output type
* @deprecated in favor of {@link DefaultFunctionCallbackBuilder}
*/
@Deprecated
public static final class Builder<I, O> {
private final BiFunction<I, ToolContext, O> biFunction;
private final Function<I, O> function;
private String name;
private String description;
private Class<I> inputType;
private SchemaType schemaType = SchemaType.JSON_SCHEMA;
// By default the response is converted to a JSON string.
private Function<O, String> responseConverter = ModelOptionsUtils::toJsonString;
private String inputTypeSchema;
private ObjectMapper objectMapper;
private Builder(BiFunction<I, ToolContext, O> biFunction) {
Assert.notNull(biFunction, "Function must not be null");
this.biFunction = biFunction;
this.function = null;
}
private Builder(Function<I, O> function) {
Assert.notNull(function, "Function must not be null");
this.biFunction = null;
this.function = function;
}
@SuppressWarnings("unchecked")
private static <I, O> Class<I> resolveInputType(BiFunction<I, ToolContext, O> biFunction) {
return (Class<I>) TypeResolverHelper
.getBiFunctionInputClass((Class<BiFunction<I, ToolContext, O>>) biFunction.getClass());
}
@SuppressWarnings("unchecked")
private static <I, O> Class<I> resolveInputType(Function<I, O> function) {
return (Class<I>) TypeResolverHelper.getFunctionInputClass((Class<Function<I, O>>) function.getClass());
}
public Builder<I, O> withName(String name) {
Assert.hasText(name, "Name must not be empty");
this.name = name;
return this;
}
public Builder<I, O> withDescription(String description) {
Assert.hasText(description, "Description must not be empty");
this.description = description;
return this;
}
@SuppressWarnings("unchecked")
public Builder<I, O> withInputType(Class<?> inputType) {
this.inputType = (Class<I>) inputType;
return this;
}
public Builder<I, O> withResponseConverter(Function<O, String> responseConverter) {
Assert.notNull(responseConverter, "ResponseConverter must not be null");
this.responseConverter = responseConverter;
return this;
}
public Builder<I, O> withInputTypeSchema(String inputTypeSchema) {
Assert.hasText(inputTypeSchema, "InputTypeSchema must not be empty");
this.inputTypeSchema = inputTypeSchema;
return this;
}
public Builder<I, O> withObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
return this;
}
public Builder<I, O> withSchemaType(SchemaType schemaType) {
Assert.notNull(schemaType, "SchemaType must not be null");
this.schemaType = schemaType;
return this;
}
public FunctionCallbackWrapper<I, O> 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<I, ToolContext, O> 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);
}
}
}

View File

@@ -125,11 +125,6 @@ public final class SimpleVectorStoreContent implements Content {
return this.text;
}
@Override
public String getContent() {
return this.text;
}
@Override
public Map<String, Object> getMetadata() {
return this.metadata;

View File

@@ -1427,25 +1427,22 @@ class DefaultChatClientTests {
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"));
}
@@ -1454,7 +1451,11 @@ class DefaultChatClientTests {
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"));
}
@@ -1463,52 +1464,51 @@ class DefaultChatClientTests {
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"))
assertThatThrownBy(() -> spec
.functions(FunctionCallback.builder().function("name", (input, ctx) -> "hello").description("").build()))
.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))
.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"));
}

View File

@@ -66,7 +66,7 @@ class RetrievalAugmentationAdvisorTests {
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

View File

@@ -29,12 +29,13 @@ import org.mockito.Mock;
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 @@ class DefaultChatClientObservationConventionTests {
}
@Override
public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
return request;
}
@Override
public ChatResponse adviseResponse(ChatResponse response, Map<String, Object> adviseContext) {
return response;
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
return null;
}
};

View File

@@ -215,7 +215,7 @@ class MediaTests {
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 @@ class MediaTests {
}
};
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);
}

View File

@@ -378,8 +378,6 @@ public Flux<AdvisedResponse> 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:

View File

@@ -138,7 +138,7 @@ public class FunctionCallbackInPrompt2IT {
// @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