Remove deprecated methods/classes from spring-ai-core
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user