diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java index 600361cd6f4..523d3b7113a 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java @@ -29,13 +29,6 @@ import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ai.model.tool.LegacyToolCallingManager; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.ai.model.tool.ToolExecutionResult; -import org.springframework.ai.tool.definition.ToolDefinition; -import org.springframework.ai.util.json.JsonParser; -import org.springframework.lang.Nullable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -57,7 +50,6 @@ import org.springframework.ai.chat.metadata.EmptyUsage; import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.chat.metadata.UsageUtils; -import org.springframework.ai.chat.model.AbstractToolCallSupport; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; @@ -70,10 +62,12 @@ import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.Media; import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.model.tool.ToolCallingChatOptions; +import org.springframework.ai.model.tool.ToolCallingManager; +import org.springframework.ai.model.tool.ToolExecutionResult; import org.springframework.ai.retry.RetryUtils; +import org.springframework.ai.tool.definition.ToolDefinition; +import org.springframework.ai.util.json.JsonParser; import org.springframework.http.ResponseEntity; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; @@ -91,7 +85,7 @@ * @author Alexandros Pappas * @since 1.0.0 */ -public class AnthropicChatModel extends AbstractToolCallSupport implements ChatModel { +public class AnthropicChatModel implements ChatModel { public static final String DEFAULT_MODEL_NAME = AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getValue(); @@ -132,111 +126,9 @@ public class AnthropicChatModel extends AbstractToolCallSupport implements ChatM */ private ChatModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION; - /** - * Construct a new {@link AnthropicChatModel} instance. - * @param anthropicApi the lower-level API for the Anthropic service. - * @deprecated Use {@link AnthropicChatModel.Builder}. - */ - @Deprecated - public AnthropicChatModel(AnthropicApi anthropicApi) { - this(anthropicApi, - AnthropicChatOptions.builder() - .model(DEFAULT_MODEL_NAME) - .maxTokens(DEFAULT_MAX_TOKENS) - .temperature(DEFAULT_TEMPERATURE) - .build()); - } - - /** - * Construct a new {@link AnthropicChatModel} instance. - * @param anthropicApi the lower-level API for the Anthropic service. - * @param defaultOptions the default options used for the chat completion requests. - * @deprecated Use {@link AnthropicChatModel.Builder}. - */ - @Deprecated - public AnthropicChatModel(AnthropicApi anthropicApi, AnthropicChatOptions defaultOptions) { - this(anthropicApi, defaultOptions, RetryUtils.DEFAULT_RETRY_TEMPLATE); - } - - /** - * Construct a new {@link AnthropicChatModel} instance. - * @param anthropicApi the lower-level API for the Anthropic service. - * @param defaultOptions the default options used for the chat completion requests. - * @param retryTemplate the retry template used to retry the Anthropic API calls. - * @deprecated Use {@link AnthropicChatModel.Builder}. - */ - @Deprecated - public AnthropicChatModel(AnthropicApi anthropicApi, AnthropicChatOptions defaultOptions, - RetryTemplate retryTemplate) { - this(anthropicApi, defaultOptions, retryTemplate, null); - } - - /** - * Construct a new {@link AnthropicChatModel} instance. - * @param anthropicApi the lower-level API for the Anthropic service. - * @param defaultOptions the default options used for the chat completion requests. - * @param retryTemplate the retry template used to retry the Anthropic API calls. - * @param functionCallbackResolver the function callback resolver used to resolve the - * function by its name. - * @deprecated Use {@link AnthropicChatModel.Builder}. - */ - @Deprecated - public AnthropicChatModel(AnthropicApi anthropicApi, AnthropicChatOptions defaultOptions, - RetryTemplate retryTemplate, FunctionCallbackResolver functionCallbackResolver) { - this(anthropicApi, defaultOptions, retryTemplate, functionCallbackResolver, List.of()); - } - - /** - * Construct a new {@link AnthropicChatModel} instance. - * @param anthropicApi the lower-level API for the Anthropic service. - * @param defaultOptions the default options used for the chat completion requests. - * @param retryTemplate the retry template used to retry the Anthropic API calls. - * @param functionCallbackResolver the function callback resolver used to resolve the - * function by its name. - * @param toolFunctionCallbacks the tool function callbacks used to handle the tool - * calls. - * @deprecated Use {@link AnthropicChatModel.Builder}. - */ - @Deprecated - public AnthropicChatModel(AnthropicApi anthropicApi, AnthropicChatOptions defaultOptions, - RetryTemplate retryTemplate, FunctionCallbackResolver functionCallbackResolver, - List toolFunctionCallbacks) { - this(anthropicApi, defaultOptions, retryTemplate, functionCallbackResolver, toolFunctionCallbacks, - ObservationRegistry.NOOP); - } - - /** - * Construct a new {@link AnthropicChatModel} instance. - * @param anthropicApi the lower-level API for the Anthropic service. - * @param defaultOptions the default options used for the chat completion requests. - * @param retryTemplate the retry template used to retry the Anthropic API calls. - * @param functionCallbackResolver the function callback resolver used to resolve the - * function by its name. - * @param toolFunctionCallbacks the tool function callbacks used to handle the tool - * calls. - * @deprecated Use {@link AnthropicChatModel.Builder}. - */ - @Deprecated - public AnthropicChatModel(AnthropicApi anthropicApi, AnthropicChatOptions defaultOptions, - RetryTemplate retryTemplate, @Nullable FunctionCallbackResolver functionCallbackResolver, - @Nullable List toolFunctionCallbacks, ObservationRegistry observationRegistry) { - this(anthropicApi, defaultOptions, - LegacyToolCallingManager.builder() - .functionCallbackResolver(functionCallbackResolver) - .functionCallbacks(toolFunctionCallbacks) - .build(), - retryTemplate, observationRegistry); - logger.warn("This constructor is deprecated and will be removed in the next milestone. " - + "Please use the MistralAiChatModel.Builder or the new constructor accepting ToolCallingManager instead."); - } - public AnthropicChatModel(AnthropicApi anthropicApi, AnthropicChatOptions defaultOptions, ToolCallingManager toolCallingManager, RetryTemplate retryTemplate, ObservationRegistry observationRegistry) { - // We do not pass the 'defaultOptions' to the AbstractToolSupport, - // because it modifies them. We are using ToolCallingManager instead, - // so we just pass empty options here. - super(null, AnthropicChatOptions.builder().build(), List.of()); Assert.notNull(anthropicApi, "anthropicApi cannot be null"); Assert.notNull(defaultOptions, "defaultOptions cannot be null"); @@ -470,10 +362,6 @@ Prompt buildRequestPrompt(Prompt prompt) { runtimeOptions = ModelOptionsUtils.copyToTarget(toolCallingChatOptions, ToolCallingChatOptions.class, AnthropicChatOptions.class); } - else if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) { - runtimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions, FunctionCallingOptions.class, - AnthropicChatOptions.class); - } else { runtimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class, AnthropicChatOptions.class); @@ -621,10 +509,6 @@ public static final class Builder { private RetryTemplate retryTemplate = RetryUtils.DEFAULT_RETRY_TEMPLATE; - private FunctionCallbackResolver functionCallbackResolver; - - private List toolCallbacks; - private ToolCallingManager toolCallingManager; private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; @@ -652,18 +536,6 @@ public Builder toolCallingManager(ToolCallingManager toolCallingManager) { return this; } - @Deprecated - public Builder functionCallbackResolver(FunctionCallbackResolver functionCallbackResolver) { - this.functionCallbackResolver = functionCallbackResolver; - return this; - } - - @Deprecated - public Builder toolCallbacks(List toolCallbacks) { - this.toolCallbacks = toolCallbacks; - return this; - } - public Builder observationRegistry(ObservationRegistry observationRegistry) { this.observationRegistry = observationRegistry; return this; @@ -671,22 +543,9 @@ public Builder observationRegistry(ObservationRegistry observationRegistry) { public AnthropicChatModel build() { if (toolCallingManager != null) { - Assert.isNull(functionCallbackResolver, - "functionCallbackResolver cannot be set when toolCallingManager is set"); - Assert.isNull(toolCallbacks, "toolCallbacks cannot be set when toolCallingManager is set"); - return new AnthropicChatModel(anthropicApi, defaultOptions, toolCallingManager, retryTemplate, observationRegistry); } - if (functionCallbackResolver != null) { - Assert.isNull(toolCallingManager, - "toolCallingManager cannot be set when functionCallbackResolver is set"); - List toolCallbacks = this.toolCallbacks != null ? this.toolCallbacks : List.of(); - - return new AnthropicChatModel(anthropicApi, defaultOptions, retryTemplate, functionCallbackResolver, - toolCallbacks, observationRegistry); - } - return new AnthropicChatModel(anthropicApi, defaultOptions, DEFAULT_TOOL_CALLING_MANAGER, retryTemplate, observationRegistry); } diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelObservationIT.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelObservationIT.java index 20a8f037aad..ecbfcf6e16a 100644 --- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelObservationIT.java +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelObservationIT.java @@ -34,6 +34,8 @@ import org.springframework.ai.chat.observation.DefaultChatModelObservationConvention; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; +import org.springframework.ai.model.tool.DefaultToolCallingManager; +import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.ai.observation.conventions.AiOperationType; import org.springframework.ai.observation.conventions.AiProvider; import org.springframework.beans.factory.annotation.Autowired; @@ -171,8 +173,7 @@ public AnthropicApi anthropicApi() { public AnthropicChatModel anthropicChatModel(AnthropicApi anthropicApi, TestObservationRegistry observationRegistry) { return new AnthropicChatModel(anthropicApi, AnthropicChatOptions.builder().build(), - RetryTemplate.defaultInstance(), new DefaultFunctionCallbackResolver(), List.of(), - observationRegistry); + ToolCallingManager.builder().build(), RetryTemplate.defaultInstance(), observationRegistry); } } diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/ChatCompletionRequestTests.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/ChatCompletionRequestTests.java index 5f1aedd51b6..5c723fdff9f 100644 --- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/ChatCompletionRequestTests.java +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/ChatCompletionRequestTests.java @@ -33,8 +33,10 @@ public class ChatCompletionRequestTests { @Test public void createRequestWithChatOptions() { - var client = new AnthropicChatModel(new AnthropicApi("TEST"), - AnthropicChatOptions.builder().model("DEFAULT_MODEL").temperature(66.6).build()); + var client = AnthropicChatModel.builder() + .anthropicApi(new AnthropicApi("TEST")) + .defaultOptions(AnthropicChatOptions.builder().model("DEFAULT_MODEL").temperature(66.6).build()) + .build(); var prompt = client.buildRequestPrompt(new Prompt("Test message content")); diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java index facbaf39411..cab07eaeac9 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java @@ -72,7 +72,6 @@ import org.springframework.ai.chat.metadata.PromptMetadata.PromptFilterMetadata; import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.chat.metadata.UsageUtils; -import org.springframework.ai.chat.model.AbstractToolCallSupport; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; @@ -85,16 +84,12 @@ import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.Media; import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallbackResolver; import org.springframework.ai.model.function.FunctionCallingOptions; -import org.springframework.ai.model.tool.LegacyToolCallingManager; import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.ai.model.tool.ToolExecutionResult; import org.springframework.ai.observation.conventions.AiProvider; import org.springframework.ai.tool.definition.ToolDefinition; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -119,7 +114,7 @@ * @see com.azure.ai.openai.OpenAIClient * @since 1.0.0 */ -public class AzureOpenAiChatModel extends AbstractToolCallSupport implements ChatModel { +public class AzureOpenAiChatModel implements ChatModel { private static final Logger logger = LoggerFactory.getLogger(AzureOpenAiChatModel.class); @@ -163,10 +158,6 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha public AzureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder, AzureOpenAiChatOptions defaultOptions, ToolCallingManager toolCallingManager, ObservationRegistry observationRegistry) { - // We do not pass the 'defaultOptions' to the AbstractToolSupport, - // because it modifies them. We are using ToolCallingManager instead, - // so we just pass empty options here. - super(null, AzureOpenAiChatOptions.builder().build(), List.of()); Assert.notNull(openAIClientBuilder, "com.azure.ai.openai.OpenAIClient must not be null"); Assert.notNull(defaultOptions, "defaultOptions cannot be null"); Assert.notNull(toolCallingManager, "toolCallingManager cannot be null"); @@ -488,10 +479,6 @@ ChatCompletionsOptions toAzureChatCompletionsOptions(Prompt prompt) { if (prompt.getOptions() != null) { AzureOpenAiChatOptions updatedRuntimeOptions; - if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) { - updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions, - FunctionCallingOptions.class, AzureOpenAiChatOptions.class); - } if (prompt.getOptions() instanceof ToolCallingChatOptions toolCallingChatOptions) { updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(toolCallingChatOptions, ToolCallingChatOptions.class, AzureOpenAiChatOptions.class); @@ -622,10 +609,6 @@ Prompt buildRequestPrompt(Prompt prompt) { runtimeOptions = ModelOptionsUtils.copyToTarget(toolCallingChatOptions, ToolCallingChatOptions.class, AzureOpenAiChatOptions.class); } - else if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) { - runtimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions, FunctionCallingOptions.class, - AzureOpenAiChatOptions.class); - } else { runtimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class, AzureOpenAiChatOptions.class); @@ -932,10 +915,6 @@ public static class Builder { private ToolCallingManager toolCallingManager; - private FunctionCallbackResolver functionCallbackResolver; - - private List toolFunctionCallbacks; - private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; private Builder() { @@ -956,18 +935,6 @@ public Builder toolCallingManager(ToolCallingManager toolCallingManager) { return this; } - @Deprecated - public Builder functionCallbackResolver(FunctionCallbackResolver functionCallbackResolver) { - this.functionCallbackResolver = functionCallbackResolver; - return this; - } - - @Deprecated - public Builder toolFunctionCallbacks(List toolFunctionCallbacks) { - this.toolFunctionCallbacks = toolFunctionCallbacks; - return this; - } - public Builder observationRegistry(ObservationRegistry observationRegistry) { this.observationRegistry = observationRegistry; return this; @@ -975,29 +942,9 @@ public Builder observationRegistry(ObservationRegistry observationRegistry) { public AzureOpenAiChatModel build() { if (toolCallingManager != null) { - Assert.isNull(functionCallbackResolver, - "functionCallbackResolver cannot be set when toolCallingManager is set"); - Assert.isNull(toolFunctionCallbacks, - "toolFunctionCallbacks cannot be set when toolCallingManager is set"); - return new AzureOpenAiChatModel(openAIClientBuilder, defaultOptions, toolCallingManager, observationRegistry); } - - if (functionCallbackResolver != null) { - Assert.isNull(toolCallingManager, - "toolCallingManager cannot be set when functionCallbackResolver is set"); - List toolCallbacks = this.toolFunctionCallbacks != null ? this.toolFunctionCallbacks - : List.of(); - - return new Builder().openAIClientBuilder(openAIClientBuilder) - .defaultOptions(defaultOptions) - .functionCallbackResolver(functionCallbackResolver) - .toolFunctionCallbacks(toolCallbacks) - .observationRegistry(observationRegistry) - .build(); - } - return new AzureOpenAiChatModel(openAIClientBuilder, defaultOptions, DEFAULT_TOOL_CALLING_MANAGER, observationRegistry); } diff --git a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java index f847bb98a32..96d4546504c 100644 --- a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java +++ b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java @@ -81,7 +81,6 @@ import org.springframework.ai.chat.metadata.ChatGenerationMetadata; import org.springframework.ai.chat.metadata.ChatResponseMetadata; import org.springframework.ai.chat.metadata.DefaultUsage; -import org.springframework.ai.chat.model.AbstractToolCallSupport; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; @@ -94,10 +93,6 @@ import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.Media; import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallingOptions; -import org.springframework.ai.model.tool.LegacyToolCallingManager; import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.ai.model.tool.ToolExecutionResult; @@ -134,7 +129,7 @@ * @author Jihoon Kim * @since 1.0.0 */ -public class BedrockProxyChatModel extends AbstractToolCallSupport implements ChatModel { +public class BedrockProxyChatModel implements ChatModel { private static final Logger logger = LoggerFactory.getLogger(BedrockProxyChatModel.class); @@ -160,33 +155,10 @@ public class BedrockProxyChatModel extends AbstractToolCallSupport implements Ch */ private ChatModelObservationConvention observationConvention; - /** - * @deprecated Use - * {@link #BedrockProxyChatModel(BedrockRuntimeClient, BedrockRuntimeAsyncClient, ToolCallingChatOptions, ObservationRegistry, ToolCallingManager)} - * instead. - */ - @Deprecated(forRemoval = true, since = "1.0.0-M6") - public BedrockProxyChatModel(BedrockRuntimeClient bedrockRuntimeClient, - BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient, FunctionCallingOptions defaultOptions, - FunctionCallbackResolver functionCallbackResolver, List toolFunctionCallbacks, - ObservationRegistry observationRegistry) { - - this(bedrockRuntimeClient, bedrockRuntimeAsyncClient, from(defaultOptions), observationRegistry, - LegacyToolCallingManager.builder() - .functionCallbackResolver(functionCallbackResolver) - .functionCallbacks(toolFunctionCallbacks) - .build()); - } - public BedrockProxyChatModel(BedrockRuntimeClient bedrockRuntimeClient, BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient, ToolCallingChatOptions defaultOptions, ObservationRegistry observationRegistry, ToolCallingManager toolCallingManager) { - // We do not pass the 'defaultOptions' to the AbstractToolSupport, - // because it modifies them. We are using ToolCallingManager instead, - // so we just pass empty options here. - super(null, FunctionCallingOptions.builder().build(), List.of()); - Assert.notNull(bedrockRuntimeClient, "bedrockRuntimeClient must not be null"); Assert.notNull(bedrockRuntimeAsyncClient, "bedrockRuntimeAsyncClient must not be null"); Assert.notNull(toolCallingManager, "toolCallingManager must not be null"); @@ -198,21 +170,6 @@ public BedrockProxyChatModel(BedrockRuntimeClient bedrockRuntimeClient, this.toolCallingManager = toolCallingManager; } - @Deprecated - private static ToolCallingChatOptions from(FunctionCallingOptions options) { - return ToolCallingChatOptions.builder() - .model(options.getModel()) - .maxTokens(options.getMaxTokens()) - .stopSequences(options.getStopSequences()) - .temperature(options.getTemperature()) - .topP(options.getTopP()) - .toolCallbacks(options.getFunctionCallbacks()) - .toolNames(options.getFunctions()) - .internalToolExecutionEnabled(options.getProxyToolCalls() != null ? !options.getProxyToolCalls() : false) - .toolContext(options.getToolContext()) - .build(); - } - private static ToolCallingChatOptions from(ChatOptions options) { return ToolCallingChatOptions.builder() .model(options.getModel()) @@ -294,9 +251,6 @@ Prompt buildRequestPrompt(Prompt prompt) { if (prompt.getOptions() instanceof ToolCallingChatOptions toolCallingChatOptions) { runtimeOptions = toolCallingChatOptions.copy(); } - else if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) { - runtimeOptions = from(functionCallingOptions); - } else { runtimeOptions = from(prompt.getOptions()); } @@ -799,10 +753,6 @@ public static final class Builder { private ToolCallingChatOptions defaultOptions = ToolCallingChatOptions.builder().build(); - private FunctionCallbackResolver functionCallbackResolver; - - private List toolFunctionCallbacks; - private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; private ChatModelObservationConvention customObservationConvention; @@ -819,160 +769,47 @@ public Builder toolCallingManager(ToolCallingManager toolCallingManager) { return this; } - /** - * @deprecated Use {@link #credentialsProvider(AwsCredentialsProvider)} instead. - */ - @Deprecated - public Builder withCredentialsProvider(AwsCredentialsProvider credentialsProvider) { - Assert.notNull(credentialsProvider, "'credentialsProvider' must not be null."); - this.credentialsProvider = credentialsProvider; - return this; - } - public Builder credentialsProvider(AwsCredentialsProvider credentialsProvider) { Assert.notNull(credentialsProvider, "'credentialsProvider' must not be null."); this.credentialsProvider = credentialsProvider; return this; } - /** - * @deprecated Use {@link #region(Region)} instead. - */ - @Deprecated - public Builder withRegion(Region region) { - Assert.notNull(region, "'region' must not be null."); - this.region = region; - return this; - } - public Builder region(Region region) { Assert.notNull(region, "'region' must not be null."); this.region = region; return this; } - /** - * @deprecated Use {@link #timeout(Duration)} instead. - */ - @Deprecated - public Builder withTimeout(Duration timeout) { - Assert.notNull(timeout, "'timeout' must not be null."); - this.timeout = timeout; - return this; - } - public Builder timeout(Duration timeout) { Assert.notNull(timeout, "'timeout' must not be null."); this.timeout = timeout; return this; } - /** - * @deprecated Use {@link #defaultOptions(ToolCallingChatOptions)} instead. - */ - @Deprecated - public Builder withDefaultOptions(FunctionCallingOptions defaultOptions) { - Assert.notNull(defaultOptions, "'defaultOptions' must not be null."); - return this.defaultOptions(ToolCallingChatOptions.builder() - .model(defaultOptions.getModel()) - .maxTokens(defaultOptions.getMaxTokens()) - .stopSequences(defaultOptions.getStopSequences()) - .temperature(defaultOptions.getTemperature()) - .topP(defaultOptions.getTopP()) - .toolCallbacks(defaultOptions.getFunctionCallbacks()) - .toolNames(defaultOptions.getFunctions()) - .internalToolExecutionEnabled( - defaultOptions.getProxyToolCalls() != null ? !defaultOptions.getProxyToolCalls() : false) - .toolContext(defaultOptions.getToolContext()) - .build()); - } - public Builder defaultOptions(ToolCallingChatOptions defaultOptions) { Assert.notNull(defaultOptions, "'defaultOptions' must not be null."); this.defaultOptions = defaultOptions; return this; } - /** - * @deprecated To be removed after M6 - */ - @Deprecated - public Builder withFunctionCallbackContext(FunctionCallbackResolver functionCallbackResolver) { - this.functionCallbackResolver = functionCallbackResolver; - return this; - } - - public Builder functionCallbackResolver(FunctionCallbackResolver functionCallbackResolver) { - this.functionCallbackResolver = functionCallbackResolver; - return this; - } - - /** - * @deprecated To be removed after M6 - */ - @Deprecated - public Builder withToolFunctionCallbacks(List toolFunctionCallbacks) { - this.toolFunctionCallbacks = toolFunctionCallbacks; - return this; - } - - /** - * @deprecated Use {@link #observationRegistry(ObservationRegistry)} instead. - */ - @Deprecated - public Builder withObservationRegistry(ObservationRegistry observationRegistry) { - Assert.notNull(observationRegistry, "'observationRegistry' must not be null."); - this.observationRegistry = observationRegistry; - return this; - } - public Builder observationRegistry(ObservationRegistry observationRegistry) { Assert.notNull(observationRegistry, "'observationRegistry' must not be null."); this.observationRegistry = observationRegistry; return this; } - /** - * @deprecated Use - * {@link #customObservationConvention(ChatModelObservationConvention)} instead. - */ - @Deprecated - public Builder withCustomObservationConvention(ChatModelObservationConvention observationConvention) { - Assert.notNull(observationConvention, "'observationConvention' must not be null."); - this.customObservationConvention = observationConvention; - return this; - } - public Builder customObservationConvention(ChatModelObservationConvention observationConvention) { Assert.notNull(observationConvention, "'observationConvention' must not be null."); this.customObservationConvention = observationConvention; return this; } - /** - * @deprecated Use {@link #bedrockRuntimeClient(BedrockRuntimeClient)} instead. - */ - @Deprecated - public Builder withBedrockRuntimeClient(BedrockRuntimeClient bedrockRuntimeClient) { - this.bedrockRuntimeClient = bedrockRuntimeClient; - return this; - } - public Builder bedrockRuntimeClient(BedrockRuntimeClient bedrockRuntimeClient) { this.bedrockRuntimeClient = bedrockRuntimeClient; return this; } - /** - * @deprecated Use {@link #bedrockRuntimeAsyncClient(BedrockRuntimeAsyncClient)} - * instead. - */ - @Deprecated - public Builder withBedrockRuntimeAsyncClient(BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient) { - this.bedrockRuntimeAsyncClient = bedrockRuntimeAsyncClient; - return this; - } - public Builder bedrockRuntimeAsyncClient(BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient) { this.bedrockRuntimeAsyncClient = bedrockRuntimeAsyncClient; return this; @@ -1008,23 +845,11 @@ public BedrockProxyChatModel build() { BedrockProxyChatModel bedrockProxyChatModel = null; if (this.toolCallingManager != null) { - Assert.isNull(functionCallbackResolver, - "functionCallbackResolver cannot be set when toolCallingManager is set"); - Assert.isNull(toolFunctionCallbacks, - "toolFunctionCallbacks cannot be set when toolCallingManager is set"); - bedrockProxyChatModel = new BedrockProxyChatModel(this.bedrockRuntimeClient, this.bedrockRuntimeAsyncClient, this.defaultOptions, this.observationRegistry, this.toolCallingManager); } - else if (this.functionCallbackResolver != null) { - Assert.isNull(toolCallingManager, - "toolCallingManager cannot be set when functionCallbackResolver is set"); - bedrockProxyChatModel = new BedrockProxyChatModel(this.bedrockRuntimeClient, - this.bedrockRuntimeAsyncClient, this.defaultOptions, this.functionCallbackResolver, - this.toolFunctionCallbacks, this.observationRegistry); - } else { bedrockProxyChatModel = new BedrockProxyChatModel(this.bedrockRuntimeClient, this.bedrockRuntimeAsyncClient, this.defaultOptions, this.observationRegistry, diff --git a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseChatClientIT.java b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseChatClientIT.java index cfbe4fd1b3d..7e0149374c2 100644 --- a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseChatClientIT.java +++ b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseChatClientIT.java @@ -36,7 +36,6 @@ import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.ai.converter.ListOutputConverter; -import org.springframework.ai.model.function.FunctionCallingOptions; import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.beans.factory.annotation.Autowired; @@ -367,7 +366,7 @@ void multiModalityEmbeddedImage(String modelName) throws IOException { // @formatter:off String response = ChatClient.create(this.chatModel).prompt() - .options(FunctionCallingOptions.builder().model(modelName).build()) + .options(ToolCallingChatOptions.builder().model(modelName).build()) .user(u -> u.text("Explain what do you see on this picture?") .media(MimeTypeUtils.IMAGE_PNG, new ClassPathResource("/test.png"))) .call() @@ -389,7 +388,7 @@ void multiModalityImageUrl2(String modelName) throws IOException { // @formatter:off String response = ChatClient.create(this.chatModel).prompt() // TODO consider adding model(...) method to ChatClient as a shortcut to - .options(FunctionCallingOptions.builder().model(modelName).build()) + .options(ToolCallingChatOptions.builder().model(modelName).build()) .user(u -> u.text("Explain what do you see on this picture?").media(MimeTypeUtils.IMAGE_PNG, url)) .call() .content(); diff --git a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseTestConfiguration.java b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseTestConfiguration.java index 7f2fd2c979f..a42a4beecba 100644 --- a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseTestConfiguration.java +++ b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseTestConfiguration.java @@ -21,7 +21,7 @@ import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; import software.amazon.awssdk.regions.Region; -import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.boot.SpringBootConfiguration; import org.springframework.context.annotation.Bean; @@ -42,7 +42,7 @@ public BedrockProxyChatModel bedrockConverseChatModel() { .region(Region.US_EAST_1) // .region(Region.US_EAST_1) .timeout(Duration.ofSeconds(120)) - .withDefaultOptions(FunctionCallingOptions.builder().model(modelId).build()) + .defaultOptions(ToolCallingChatOptions.builder().model(modelId).build()) .build(); } diff --git a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseUsageAggregationTests.java b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseUsageAggregationTests.java index 8066439cf96..09d7c83d675 100644 --- a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseUsageAggregationTests.java +++ b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseUsageAggregationTests.java @@ -16,11 +16,7 @@ package org.springframework.ai.bedrock.converse; -import java.util.List; - -import io.micrometer.observation.ObservationRegistry; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -40,8 +36,9 @@ import software.amazon.awssdk.services.bedrockruntime.model.ToolUseBlock; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.model.tool.ToolCallingChatOptions; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.isA; @@ -140,14 +137,13 @@ public void callWithToolUse() { given(this.bedrockRuntimeClient.converse(isA(ConverseRequest.class))).willReturn(converseResponseToolUse) .willReturn(converseResponseFinal); - FunctionCallback functionCallback = FunctionCallback.builder() - .function("getCurrentWeather", (Request request) -> "15.0°C") + ToolCallback toolCallback = FunctionToolCallback.builder("getCurrentWeather", (Request request) -> "15.0°C") .description("Gets the weather in location") .inputType(Request.class) .build(); var result = this.chatModel.call(new Prompt("What is the weather in Paris?", - FunctionCallingOptions.builder().functionCallbacks(functionCallback).build())); + ToolCallingChatOptions.builder().toolCallbacks(toolCallback).build())); assertThat(result).isNotNull(); assertThat(result.getResult().getOutput().getText()) diff --git a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModelIT.java b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModelIT.java index 3652d322bd1..0e79c7d0809 100644 --- a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModelIT.java +++ b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModelIT.java @@ -92,7 +92,7 @@ void roleTest(String modelName) { SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(this.systemResource); Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", "Bob", "voice", "pirate")); Prompt prompt = new Prompt(List.of(userMessage, systemMessage), - FunctionCallingOptions.builder().model(modelName).build()); + ToolCallingChatOptions.builder().model(modelName).build()); ChatResponse response = this.chatModel.call(prompt); assertThat(response.getResults()).hasSize(1); assertThat(response.getMetadata().getUsage().getCompletionTokens()).isGreaterThan(0); @@ -128,7 +128,7 @@ void testMessageHistory() { @Test void streamingWithTokenUsage() { - var promptOptions = FunctionCallingOptions.builder().temperature(0.0).build(); + var promptOptions = ToolCallingChatOptions.builder().temperature(0.0).build(); var prompt = new Prompt("List two colors of the Polish flag. Be brief.", promptOptions); var streamingTokenUsage = this.chatModel.stream(prompt).blockLast().getMetadata().getUsage(); @@ -255,9 +255,8 @@ void functionCallTestDeprecated() { List messages = new ArrayList<>(List.of(userMessage)); - var promptOptions = FunctionCallingOptions.builder() - .functionCallbacks(List.of(FunctionCallback.builder() - .function("getCurrentWeather", new MockWeatherService()) + var promptOptions = ToolCallingChatOptions.builder() + .toolCallbacks(List.of(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService()) .description( "Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.") .inputType(MockWeatherService.Request.class) @@ -305,10 +304,9 @@ void streamFunctionCallTest() { List messages = new ArrayList<>(List.of(userMessage)); - var promptOptions = FunctionCallingOptions.builder() + var promptOptions = ToolCallingChatOptions.builder() .model("anthropic.claude-3-5-sonnet-20240620-v1:0") - .functionCallbacks(List.of(FunctionCallback.builder() - .function("getCurrentWeather", new MockWeatherService()) + .toolCallbacks(List.of(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService()) .description( "Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.") .inputType(MockWeatherService.Request.class) @@ -333,7 +331,7 @@ void validateCallResponseMetadata() { String model = "anthropic.claude-3-5-sonnet-20240620-v1:0"; // @formatter:off ChatResponse response = ChatClient.create(this.chatModel).prompt() - .options(FunctionCallingOptions.builder().model(model).build()) + .options(ToolCallingChatOptions.builder().model(model).build()) .user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did") .call() .chatResponse(); @@ -348,7 +346,7 @@ void validateStreamCallResponseMetadata() { String model = "anthropic.claude-3-5-sonnet-20240620-v1:0"; // @formatter:off ChatResponse response = ChatClient.create(this.chatModel).prompt() - .options(FunctionCallingOptions.builder().model(model).build()) + .options(ToolCallingChatOptions.builder().model(model).build()) .user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did") .stream() .chatResponse() diff --git a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModelObservationIT.java b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModelObservationIT.java index ceb94457e2e..fb1b9c3077e 100644 --- a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModelObservationIT.java +++ b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModelObservationIT.java @@ -34,7 +34,6 @@ import org.springframework.ai.chat.observation.ChatModelObservationDocumentation.LowCardinalityKeyNames; import org.springframework.ai.chat.observation.DefaultChatModelObservationConvention; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.function.FunctionCallingOptions; import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.observation.conventions.AiOperationType; import org.springframework.ai.observation.conventions.AiProvider; @@ -68,7 +67,7 @@ void beforeEach() { @Test void observationForChatOperation() { - var options = FunctionCallingOptions.builder() + var options = ToolCallingChatOptions.builder() .model("anthropic.claude-3-5-sonnet-20240620-v1:0") .maxTokens(2048) .stopSequences(List.of("this-is-the-end")) @@ -90,7 +89,7 @@ void observationForChatOperation() { @Test void observationForStreamingChatOperation() { - var options = FunctionCallingOptions.builder() + var options = ToolCallingChatOptions.builder() .model("anthropic.claude-3-5-sonnet-20240620-v1:0") .maxTokens(2048) .stopSequences(List.of("this-is-the-end")) diff --git a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/client/BedrockNovaChatClientIT.java b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/client/BedrockNovaChatClientIT.java index cd90c436850..167b728203d 100644 --- a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/client/BedrockNovaChatClientIT.java +++ b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/client/BedrockNovaChatClientIT.java @@ -33,6 +33,7 @@ import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.model.Media; import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; @@ -185,7 +186,7 @@ public BedrockProxyChatModel bedrockConverseChatModel() { .credentialsProvider(EnvironmentVariableCredentialsProvider.create()) .region(Region.US_EAST_1) .timeout(Duration.ofSeconds(120)) - .withDefaultOptions(FunctionCallingOptions.builder().model(modelId).build()) + .defaultOptions(ToolCallingChatOptions.builder().model(modelId).build()) .build(); } diff --git a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/experiments/BedrockConverseChatModelMain3.java b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/experiments/BedrockConverseChatModelMain3.java index d81393bad18..8d408cf0f4e 100644 --- a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/experiments/BedrockConverseChatModelMain3.java +++ b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/experiments/BedrockConverseChatModelMain3.java @@ -24,8 +24,8 @@ import org.springframework.ai.bedrock.converse.BedrockProxyChatModel; import org.springframework.ai.bedrock.converse.MockWeatherService; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.model.tool.ToolCallingChatOptions; +import org.springframework.ai.tool.function.FunctionToolCallback; /** * Used for reverse engineering the protocol @@ -48,10 +48,9 @@ public static void main(String[] args) { // "What's the weather like in San Francisco, Tokyo, and Paris? Return the // temperature in Celsius.", "What's the weather like in Paris? Return the temperature in Celsius.", - FunctionCallingOptions.builder() + ToolCallingChatOptions.builder() .model(modelId) - .functionCallbacks(List.of(FunctionCallback.builder() - .function("getCurrentWeather", new MockWeatherService()) + .toolCallbacks(List.of(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService()) .description("Get the weather in location") .inputType(MockWeatherService.Request.class) .build())) diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java index 0c1e8e2af72..775086c820e 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java @@ -88,7 +88,7 @@ * @author Alexandros Pappas * @since 1.0.0 */ -public class MistralAiChatModel extends AbstractToolCallSupport implements ChatModel { +public class MistralAiChatModel implements ChatModel { private static final ChatModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultChatModelObservationConvention(); @@ -120,31 +120,9 @@ public class MistralAiChatModel extends AbstractToolCallSupport implements ChatM */ private ChatModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION; - /** - * @deprecated Use {@link MistralAiChatModel.Builder}. - */ - @Deprecated - public MistralAiChatModel(MistralAiApi mistralAiApi, MistralAiChatOptions options, - @Nullable FunctionCallbackResolver functionCallbackResolver, - @Nullable List toolFunctionCallbacks, RetryTemplate retryTemplate, - ObservationRegistry observationRegistry) { - this(mistralAiApi, options, - LegacyToolCallingManager.builder() - .functionCallbackResolver(functionCallbackResolver) - .functionCallbacks(toolFunctionCallbacks) - .build(), - retryTemplate, observationRegistry); - logger.warn("This constructor is deprecated and will be removed in the next milestone. " - + "Please use the MistralAiChatModel.Builder or the new constructor accepting ToolCallingManager instead."); - } - public MistralAiChatModel(MistralAiApi mistralAiApi, MistralAiChatOptions defaultOptions, ToolCallingManager toolCallingManager, RetryTemplate retryTemplate, ObservationRegistry observationRegistry) { - // We do not pass the 'defaultOptions' to the AbstractToolSupport, - // because it modifies them. We are using ToolCallingManager instead, - // so we just pass empty options here. - super(null, MistralAiChatOptions.builder().build(), List.of()); Assert.notNull(mistralAiApi, "mistralAiApi cannot be null"); Assert.notNull(defaultOptions, "defaultOptions cannot be null"); Assert.notNull(toolCallingManager, "toolCallingManager cannot be null"); @@ -552,10 +530,6 @@ public static class Builder { private ToolCallingManager toolCallingManager; - private FunctionCallbackResolver functionCallbackResolver; - - private List toolFunctionCallbacks; - private RetryTemplate retryTemplate = RetryUtils.DEFAULT_RETRY_TEMPLATE; private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; @@ -578,18 +552,6 @@ public Builder toolCallingManager(ToolCallingManager toolCallingManager) { return this; } - @Deprecated - public Builder functionCallbackResolver(FunctionCallbackResolver functionCallbackResolver) { - this.functionCallbackResolver = functionCallbackResolver; - return this; - } - - @Deprecated - public Builder toolFunctionCallbacks(List toolFunctionCallbacks) { - this.toolFunctionCallbacks = toolFunctionCallbacks; - return this; - } - public Builder retryTemplate(RetryTemplate retryTemplate) { this.retryTemplate = retryTemplate; return this; @@ -602,25 +564,9 @@ public Builder observationRegistry(ObservationRegistry observationRegistry) { public MistralAiChatModel build() { if (toolCallingManager != null) { - Assert.isNull(functionCallbackResolver, - "functionCallbackResolver cannot be set when toolCallingManager is set"); - Assert.isNull(toolFunctionCallbacks, - "toolFunctionCallbacks cannot be set when toolCallingManager is set"); - return new MistralAiChatModel(mistralAiApi, defaultOptions, toolCallingManager, retryTemplate, observationRegistry); } - - if (functionCallbackResolver != null) { - Assert.isNull(toolCallingManager, - "toolCallingManager cannot be set when functionCallbackResolver is set"); - List toolCallbacks = this.toolFunctionCallbacks != null ? this.toolFunctionCallbacks - : List.of(); - - return new MistralAiChatModel(mistralAiApi, defaultOptions, functionCallbackResolver, toolCallbacks, - retryTemplate, observationRegistry); - } - return new MistralAiChatModel(mistralAiApi, defaultOptions, DEFAULT_TOOL_CALLING_MANAGER, retryTemplate, observationRegistry); } diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java index 459e2720376..31070144baf 100644 --- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java +++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java @@ -32,7 +32,6 @@ import org.springframework.ai.chat.observation.DefaultChatModelObservationConvention; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.mistralai.api.MistralAiApi; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; import org.springframework.ai.observation.conventions.AiOperationType; import org.springframework.ai.observation.conventions.AiProvider; import org.springframework.beans.factory.annotation.Autowired; @@ -184,7 +183,6 @@ public MistralAiChatModel openAiChatModel(MistralAiApi mistralAiApi, return MistralAiChatModel.builder() .mistralAiApi(mistralAiApi) .defaultOptions(MistralAiChatOptions.builder().build()) - .functionCallbackResolver(new DefaultFunctionCallbackResolver()) .retryTemplate(RetryTemplate.defaultInstance()) .observationRegistry(observationRegistry) .build(); diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java index 0e1ee3158cb..ed740226e62 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java @@ -37,7 +37,6 @@ import org.springframework.ai.chat.metadata.ChatGenerationMetadata; import org.springframework.ai.chat.metadata.ChatResponseMetadata; import org.springframework.ai.chat.metadata.DefaultUsage; -import org.springframework.ai.chat.model.AbstractToolCallSupport; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; @@ -49,9 +48,6 @@ import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallingOptions; import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.ai.model.tool.ToolExecutionResult; @@ -86,7 +82,7 @@ * @author Ilayaperumal Gopinathan * @since 1.0.0 */ -public class OllamaChatModel extends AbstractToolCallSupport implements ChatModel { +public class OllamaChatModel implements ChatModel { private static final Logger logger = LoggerFactory.getLogger(OllamaChatModel.class); @@ -124,10 +120,6 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode public OllamaChatModel(OllamaApi ollamaApi, OllamaOptions defaultOptions, ToolCallingManager toolCallingManager, ObservationRegistry observationRegistry, ModelManagementOptions modelManagementOptions) { - // We do not pass the 'defaultOptions' to the AbstractToolSupport, - // because it modifies them. We are using ToolCallingManager instead, - // so we just pass empty options here. - super(null, OllamaOptions.builder().build(), List.of()); Assert.notNull(ollamaApi, "ollamaApi must not be null"); Assert.notNull(defaultOptions, "defaultOptions must not be null"); Assert.notNull(toolCallingManager, "toolCallingManager must not be null"); @@ -362,10 +354,6 @@ Prompt buildRequestPrompt(Prompt prompt) { runtimeOptions = ModelOptionsUtils.copyToTarget(toolCallingChatOptions, ToolCallingChatOptions.class, OllamaOptions.class); } - else if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) { - runtimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions, FunctionCallingOptions.class, - OllamaOptions.class); - } else { runtimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class, OllamaOptions.class); @@ -521,10 +509,6 @@ public static final class Builder { private ToolCallingManager toolCallingManager; - private FunctionCallbackResolver functionCallbackResolver; - - private List toolFunctionCallbacks; - private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; private ModelManagementOptions modelManagementOptions = ModelManagementOptions.defaults(); @@ -547,18 +531,6 @@ public Builder toolCallingManager(ToolCallingManager toolCallingManager) { return this; } - @Deprecated - public Builder functionCallbackResolver(FunctionCallbackResolver functionCallbackResolver) { - this.functionCallbackResolver = functionCallbackResolver; - return this; - } - - @Deprecated - public Builder toolFunctionCallbacks(List toolFunctionCallbacks) { - this.toolFunctionCallbacks = toolFunctionCallbacks; - return this; - } - public Builder observationRegistry(ObservationRegistry observationRegistry) { this.observationRegistry = observationRegistry; return this; @@ -571,30 +543,9 @@ public Builder modelManagementOptions(ModelManagementOptions modelManagementOpti public OllamaChatModel build() { if (toolCallingManager != null) { - Assert.isNull(functionCallbackResolver, - "functionCallbackResolver must not be set when toolCallingManager is set"); - Assert.isNull(toolFunctionCallbacks, - "toolFunctionCallbacks must not be set when toolCallingManager is set"); - return new OllamaChatModel(this.ollamaApi, this.defaultOptions, this.toolCallingManager, this.observationRegistry, this.modelManagementOptions); } - - if (functionCallbackResolver != null) { - Assert.isNull(toolCallingManager, - "toolCallingManager must not be set when functionCallbackResolver is set"); - List toolCallbacks = this.toolFunctionCallbacks != null ? this.toolFunctionCallbacks - : List.of(); - return OllamaChatModel.builder() - .ollamaApi(this.ollamaApi) - .defaultOptions(this.defaultOptions) - .functionCallbackResolver(this.functionCallbackResolver) - .toolFunctionCallbacks(toolCallbacks) - .observationRegistry(this.observationRegistry) - .modelManagementOptions(this.modelManagementOptions) - .build(); - } - return new OllamaChatModel(this.ollamaApi, this.defaultOptions, DEFAULT_TOOL_CALLING_MANAGER, this.observationRegistry, this.modelManagementOptions); } diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java index 9fbfecec468..ab5a47ad0eb 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java @@ -29,12 +29,6 @@ import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ai.model.tool.LegacyToolCallingManager; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.ai.model.tool.ToolExecutionResult; -import org.springframework.ai.tool.definition.ToolDefinition; -import org.springframework.lang.Nullable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -49,7 +43,6 @@ import org.springframework.ai.chat.metadata.RateLimit; import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.chat.metadata.UsageUtils; -import org.springframework.ai.chat.model.AbstractToolCallSupport; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; @@ -66,6 +59,9 @@ import org.springframework.ai.model.function.FunctionCallback; import org.springframework.ai.model.function.FunctionCallbackResolver; import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.model.tool.ToolCallingChatOptions; +import org.springframework.ai.model.tool.ToolCallingManager; +import org.springframework.ai.model.tool.ToolExecutionResult; import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.openai.api.OpenAiApi.ChatCompletion; import org.springframework.ai.openai.api.OpenAiApi.ChatCompletion.Choice; @@ -78,6 +74,7 @@ import org.springframework.ai.openai.api.common.OpenAiApiConstants; import org.springframework.ai.openai.metadata.support.OpenAiResponseHeaderExtractor; import org.springframework.ai.retry.RetryUtils; +import org.springframework.ai.tool.definition.ToolDefinition; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; import org.springframework.http.ResponseEntity; @@ -110,7 +107,7 @@ * @see StreamingChatModel * @see OpenAiApi */ -public class OpenAiChatModel extends AbstractToolCallSupport implements ChatModel { +public class OpenAiChatModel implements ChatModel { private static final Logger logger = LoggerFactory.getLogger(OpenAiChatModel.class); @@ -145,72 +142,8 @@ public class OpenAiChatModel extends AbstractToolCallSupport implements ChatMode */ private ChatModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION; - /** - * Initializes a new instance of the OpenAiChatModel. - * @param openAiApi The OpenAiApi instance to be used for interacting with the OpenAI - * Chat API. - * @param options The OpenAiChatOptions to configure the chat model. - * @param functionCallbackResolver The function callback resolver. - * @param retryTemplate The retry template. - * @deprecated Use OpenAiChatModel.Builder. - */ - @Deprecated - public OpenAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions options, - @Nullable FunctionCallbackResolver functionCallbackResolver, RetryTemplate retryTemplate) { - this(openAiApi, options, functionCallbackResolver, List.of(), retryTemplate); - } - - /** - * Initializes a new instance of the OpenAiChatModel. - * @param openAiApi The OpenAiApi instance to be used for interacting with the OpenAI - * Chat API. - * @param options The OpenAiChatOptions to configure the chat model. - * @param functionCallbackResolver The function callback resolver. - * @param toolFunctionCallbacks The tool function callbacks. - * @param retryTemplate The retry template. - * @deprecated Use OpenAiChatModel.Builder. - */ - @Deprecated - public OpenAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions options, - @Nullable FunctionCallbackResolver functionCallbackResolver, - @Nullable List toolFunctionCallbacks, RetryTemplate retryTemplate) { - this(openAiApi, options, functionCallbackResolver, toolFunctionCallbacks, retryTemplate, - ObservationRegistry.NOOP); - } - - /** - * Initializes a new instance of the OpenAiChatModel. - * @param openAiApi The OpenAiApi instance to be used for interacting with the OpenAI - * Chat API. - * @param options The OpenAiChatOptions to configure the chat model. - * @param functionCallbackResolver The function callback resolver. - * @param toolFunctionCallbacks The tool function callbacks. - * @param retryTemplate The retry template. - * @param observationRegistry The ObservationRegistry used for instrumentation. - * @deprecated Use OpenAiChatModel.Builder or OpenAiChatModel(OpenAiApi, - * OpenAiChatOptions, ToolCallingManager, RetryTemplate, ObservationRegistry). - */ - @Deprecated - public OpenAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions options, - @Nullable FunctionCallbackResolver functionCallbackResolver, - @Nullable List toolFunctionCallbacks, RetryTemplate retryTemplate, - ObservationRegistry observationRegistry) { - this(openAiApi, options, - LegacyToolCallingManager.builder() - .functionCallbackResolver(functionCallbackResolver) - .functionCallbacks(toolFunctionCallbacks) - .build(), - retryTemplate, observationRegistry); - logger.warn("This constructor is deprecated and will be removed in the next milestone. " - + "Please use the OpenAiChatModel.Builder or the new constructor accepting ToolCallingManager instead."); - } - public OpenAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions defaultOptions, ToolCallingManager toolCallingManager, RetryTemplate retryTemplate, ObservationRegistry observationRegistry) { - // We do not pass the 'defaultOptions' to the AbstractToolSupport, - // because it modifies them. We are using ToolCallingManager instead, - // so we just pass empty options here. - super(null, OpenAiChatOptions.builder().build(), List.of()); Assert.notNull(openAiApi, "openAiApi cannot be null"); Assert.notNull(defaultOptions, "defaultOptions cannot be null"); Assert.notNull(toolCallingManager, "toolCallingManager cannot be null"); @@ -749,10 +682,6 @@ public static final class Builder { private ToolCallingManager toolCallingManager; - private FunctionCallbackResolver functionCallbackResolver; - - private List toolFunctionCallbacks; - private RetryTemplate retryTemplate = RetryUtils.DEFAULT_RETRY_TEMPLATE; private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; @@ -775,18 +704,6 @@ public Builder toolCallingManager(ToolCallingManager toolCallingManager) { return this; } - @Deprecated - public Builder functionCallbackResolver(FunctionCallbackResolver functionCallbackResolver) { - this.functionCallbackResolver = functionCallbackResolver; - return this; - } - - @Deprecated - public Builder toolFunctionCallbacks(List toolFunctionCallbacks) { - this.toolFunctionCallbacks = toolFunctionCallbacks; - return this; - } - public Builder retryTemplate(RetryTemplate retryTemplate) { this.retryTemplate = retryTemplate; return this; @@ -799,25 +716,9 @@ public Builder observationRegistry(ObservationRegistry observationRegistry) { public OpenAiChatModel build() { if (toolCallingManager != null) { - Assert.isNull(functionCallbackResolver, - "functionCallbackResolver cannot be set when toolCallingManager is set"); - Assert.isNull(toolFunctionCallbacks, - "toolFunctionCallbacks cannot be set when toolCallingManager is set"); - return new OpenAiChatModel(openAiApi, defaultOptions, toolCallingManager, retryTemplate, observationRegistry); } - - if (functionCallbackResolver != null) { - Assert.isNull(toolCallingManager, - "toolCallingManager cannot be set when functionCallbackResolver is set"); - List toolCallbacks = this.toolFunctionCallbacks != null ? this.toolFunctionCallbacks - : List.of(); - - return new OpenAiChatModel(openAiApi, defaultOptions, functionCallbackResolver, toolCallbacks, - retryTemplate, observationRegistry); - } - return new OpenAiChatModel(openAiApi, defaultOptions, DEFAULT_TOOL_CALLING_MANAGER, retryTemplate, observationRegistry); } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelObservationIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelObservationIT.java index f341a23cbc4..33e03e447c0 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelObservationIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelObservationIT.java @@ -31,6 +31,8 @@ import org.springframework.ai.chat.observation.DefaultChatModelObservationConvention; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; +import org.springframework.ai.model.tool.DefaultToolCallingManager; +import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.ai.observation.conventions.AiOperationType; import org.springframework.ai.observation.conventions.AiProvider; import org.springframework.ai.openai.OpenAiChatModel; @@ -175,8 +177,7 @@ public OpenAiApi openAiApi() { @Bean public OpenAiChatModel openAiChatModel(OpenAiApi openAiApi, TestObservationRegistry observationRegistry) { return new OpenAiChatModel(openAiApi, OpenAiChatOptions.builder().build(), - new DefaultFunctionCallbackResolver(), List.of(), RetryTemplate.defaultInstance(), - observationRegistry); + ToolCallingManager.builder().build(), RetryTemplate.defaultInstance(), observationRegistry); } } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelProxyToolCallsIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelProxyToolCallsIT.java deleted file mode 100644 index bf492ce2021..00000000000 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelProxyToolCallsIT.java +++ /dev/null @@ -1,372 +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.openai.chat; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.micrometer.observation.ObservationRegistry; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.ToolResponseMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallingHelper; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.retry.RetryUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.util.CollectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest(classes = OpenAiChatModelProxyToolCallsIT.Config.class) -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") -class OpenAiChatModelProxyToolCallsIT { - - private static final Logger logger = LoggerFactory.getLogger(OpenAiChatModelProxyToolCallsIT.class); - - private static final String DEFAULT_MODEL = "gpt-4o-mini"; - - FunctionCallback functionDefinition = new FunctionCallingHelper.FunctionDefinition("getWeatherInLocation", - "Get the weather in location", """ - { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state e.g. San Francisco, CA" - }, - "unit": { - "type": "string", - "enum": ["C", "F"] - } - }, - "required": ["location", "unit"] - } - """); - - @Autowired - private OpenAiChatModel chatModel; - - // Helper class that reuses some of the {@link AbstractToolCallSupport} functionality - // to help to implement the function call handling logic on the client side. - private FunctionCallingHelper functionCallingHelper = new FunctionCallingHelper(); - - @SuppressWarnings("unchecked") - private static Map getFunctionArguments(String functionArguments) { - try { - return new ObjectMapper().readValue(functionArguments, Map.class); - } - catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - - // Function which will be called by the AI model. - private String getWeatherInLocation(String location, String unit) { - - double temperature = 0; - - if (location.contains("Paris")) { - temperature = 15; - } - else if (location.contains("Tokyo")) { - temperature = 10; - } - else if (location.contains("San Francisco")) { - temperature = 30; - } - - return String.format("The weather in %s is %s%s", location, temperature, unit); - } - - @Test - void functionCall() throws JsonMappingException, JsonProcessingException { - - List messages = List - .of(new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?")); - - var promptOptions = OpenAiChatOptions.builder().functionCallbacks(List.of(this.functionDefinition)).build(); - - var prompt = new Prompt(messages, promptOptions); - - boolean isToolCall = false; - - ChatResponse chatResponse = null; - - do { - - chatResponse = this.chatModel.call(prompt); - - // We will have to convert the chatResponse into OpenAI assistant message. - - // Note that the tool call check could be platform specific because the finish - // reasons. - isToolCall = this.functionCallingHelper.isToolCall(chatResponse, - Set.of(OpenAiApi.ChatCompletionFinishReason.TOOL_CALLS.name(), - OpenAiApi.ChatCompletionFinishReason.STOP.name())); - - if (isToolCall) { - - Optional toolCallGeneration = chatResponse.getResults() - .stream() - .filter(g -> !CollectionUtils.isEmpty(g.getOutput().getToolCalls())) - .findFirst(); - - assertThat(toolCallGeneration).isNotEmpty(); - - AssistantMessage assistantMessage = toolCallGeneration.get().getOutput(); - - List toolResponses = new ArrayList<>(); - - for (AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) { - - var functionName = toolCall.name(); - - assertThat(functionName).isEqualTo("getWeatherInLocation"); - - String functionArguments = toolCall.arguments(); - - @SuppressWarnings("unchecked") - Map argumentsMap = new ObjectMapper().readValue(functionArguments, Map.class); - - String functionResponse = getWeatherInLocation(argumentsMap.get("location").toString(), - argumentsMap.get("unit").toString()); - - toolResponses.add(new ToolResponseMessage.ToolResponse(toolCall.id(), functionName, - ModelOptionsUtils.toJsonString(functionResponse))); - } - - ToolResponseMessage toolMessageResponse = new ToolResponseMessage(toolResponses, Map.of()); - - List toolCallConversation = this.functionCallingHelper - .buildToolCallConversation(prompt.getInstructions(), assistantMessage, toolMessageResponse); - - assertThat(toolCallConversation).isNotEmpty(); - - prompt = new Prompt(toolCallConversation, prompt.getOptions()); - } - } - while (isToolCall); - - logger.info("Response: {}", chatResponse); - - assertThat(chatResponse.getResult().getOutput().getText()).contains("30", "10", "15"); - } - - @Test - void functionStream() throws JsonMappingException, JsonProcessingException { - - List messages = List - .of(new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?")); - - var promptOptions = OpenAiChatOptions.builder().functionCallbacks(List.of(this.functionDefinition)).build(); - - var prompt = new Prompt(messages, promptOptions); - - String response = processToolCall(prompt, Set.of(OpenAiApi.ChatCompletionFinishReason.TOOL_CALLS.name(), - OpenAiApi.ChatCompletionFinishReason.STOP.name()), toolCall -> { - - var functionName = toolCall.name(); - - assertThat(functionName).isEqualTo("getWeatherInLocation"); - - String functionArguments = toolCall.arguments(); - - Map argumentsMap = getFunctionArguments(functionArguments); - - String functionResponse = getWeatherInLocation(argumentsMap.get("location").toString(), - argumentsMap.get("unit").toString()); - - return functionResponse; - }) - .collectList() - .block() - .stream() - .map(cr -> cr.getResult().getOutput().getText()) - .collect(Collectors.joining()); - - logger.info("Response: {}", response); - - assertThat(response).contains("30", "10", "15"); - - } - - private Flux processToolCall(Prompt prompt, Set finishReasons, - Function customFunction) { - - Flux chatResponses = this.chatModel.stream(prompt); - - return chatResponses.flatMap(chatResponse -> { - - boolean isToolCall = this.functionCallingHelper.isToolCall(chatResponse, finishReasons); - - if (isToolCall) { - - Optional toolCallGeneration = chatResponse.getResults() - .stream() - .filter(g -> !CollectionUtils.isEmpty(g.getOutput().getToolCalls())) - .findFirst(); - - assertThat(toolCallGeneration).isNotEmpty(); - - AssistantMessage assistantMessage = toolCallGeneration.get().getOutput(); - - List toolResponses = new ArrayList<>(); - - for (AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) { - - String functionResponse = customFunction.apply(toolCall); - - toolResponses.add(new ToolResponseMessage.ToolResponse(toolCall.id(), toolCall.name(), - ModelOptionsUtils.toJsonString(functionResponse))); - } - - ToolResponseMessage toolMessageResponse = new ToolResponseMessage(toolResponses, Map.of()); - - List toolCallConversation = this.functionCallingHelper - .buildToolCallConversation(prompt.getInstructions(), assistantMessage, toolMessageResponse); - - assertThat(toolCallConversation).isNotEmpty(); - - var prompt2 = new Prompt(toolCallConversation, prompt.getOptions()); - - return processToolCall(prompt2, finishReasons, customFunction); - } - - return Flux.just(chatResponse); - }); - } - - @Test - void functionCall2() throws JsonMappingException, JsonProcessingException { - - List messages = List - .of(new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?")); - - var promptOptions = OpenAiChatOptions.builder().functionCallbacks(List.of(this.functionDefinition)).build(); - - var prompt = new Prompt(messages, promptOptions); - - ChatResponse chatResponse = this.functionCallingHelper.processCall(this.chatModel, prompt, - Set.of(OpenAiApi.ChatCompletionFinishReason.TOOL_CALLS.name(), - OpenAiApi.ChatCompletionFinishReason.STOP.name()), - toolCall -> { - - var functionName = toolCall.name(); - - assertThat(functionName).isEqualTo("getWeatherInLocation"); - - String functionArguments = toolCall.arguments(); - - Map argumentsMap = getFunctionArguments(functionArguments); - - String functionResponse = getWeatherInLocation(argumentsMap.get("location").toString(), - argumentsMap.get("unit").toString()); - - return functionResponse; - }); - - logger.info("Response: {}", chatResponse); - - assertThat(chatResponse.getResult().getOutput().getText()).contains("30", "10", "15"); - } - - @Test - void functionStream2() throws JsonMappingException, JsonProcessingException { - - List messages = List - .of(new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?")); - - var promptOptions = OpenAiChatOptions.builder().functionCallbacks(List.of(this.functionDefinition)).build(); - - var prompt = new Prompt(messages, promptOptions); - - Flux responses = this.functionCallingHelper.processStream(this.chatModel, prompt, - Set.of(OpenAiApi.ChatCompletionFinishReason.TOOL_CALLS.name(), - OpenAiApi.ChatCompletionFinishReason.STOP.name()), - toolCall -> { - - var functionName = toolCall.name(); - - assertThat(functionName).isEqualTo("getWeatherInLocation"); - - String functionArguments = toolCall.arguments(); - - Map argumentsMap = getFunctionArguments(functionArguments); - - String functionResponse = getWeatherInLocation(argumentsMap.get("location").toString(), - argumentsMap.get("unit").toString()); - - return functionResponse; - }); - - String response = responses.collectList() - .block() - .stream() - .map(cr -> cr.getResult().getOutput().getText()) - .collect(Collectors.joining()); - - logger.info("Response: {}", response); - - assertThat(response).contains("30", "10", "15"); - - } - - @SpringBootConfiguration - static class Config { - - @Bean - public OpenAiApi chatCompletionApi() { - return OpenAiApi.builder().apiKey(System.getenv("OPENAI_API_KEY")).build(); - } - - @Bean - public OpenAiChatModel openAiClient(OpenAiApi openAiApi, List toolFunctionCallbacks) { - // enable the proxy tool calls option. - var options = OpenAiChatOptions.builder().model(DEFAULT_MODEL).proxyToolCalls(true).build(); - - return new OpenAiChatModel(openAiApi, options, null, toolFunctionCallbacks, - RetryUtils.DEFAULT_RETRY_TEMPLATE, ObservationRegistry.NOOP); - } - - } - -} diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiPaymentTransactionIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiPaymentTransactionIT.java index b7d2fbc703a..864bddf2252 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiPaymentTransactionIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiPaymentTransactionIT.java @@ -34,8 +34,6 @@ import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor; import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain; import org.springframework.ai.converter.BeanOutputConverter; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallbackResolver; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.api.OpenAiApi; @@ -44,7 +42,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Description; import org.springframework.core.ParameterizedTypeReference; @@ -221,27 +218,15 @@ public OpenAiApi chatCompletionApi() { } @Bean - public OpenAiChatModel openAiClient(OpenAiApi openAiApi, FunctionCallbackResolver functionCallbackResolver) { + public OpenAiChatModel openAiClient(OpenAiApi openAiApi) { return OpenAiChatModel.builder() .openAiApi(openAiApi) .defaultOptions( OpenAiChatOptions.builder().model(ChatModel.GPT_4_O_MINI.getName()).temperature(0.1).build()) - .functionCallbackResolver(functionCallbackResolver) .retryTemplate(RetryUtils.DEFAULT_RETRY_TEMPLATE) .build(); } - /** - * Because of the OPEN_API_SCHEMA type, the FunctionCallbackResolver instance must - * different from the other JSON schema types. - */ - @Bean - public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { - DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); - manager.setApplicationContext(context); - return manager; - } - } } diff --git a/spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/tool/ToolCallingManagerTests.java b/spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/tool/ToolCallingManagerTests.java index f07591877bb..50bcfb21cb4 100644 --- a/spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/tool/ToolCallingManagerTests.java +++ b/spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/tool/ToolCallingManagerTests.java @@ -73,18 +73,6 @@ void explicitToolCallingExecutionWithNewOptions() { runExplicitToolCallingExecutionWithOptions(chatOptions, prompt); } - @Test - void explicitToolCallingExecutionWithLegacyOptions() { - ChatOptions chatOptions = FunctionCallingOptions.builder() - .functionCallbacks(ToolCallbacks.from(tools)) - .proxyToolCalls(true) - .build(); - Prompt prompt = new Prompt( - new UserMessage("What books written by %s are available in the library?".formatted("J.R.R. Tolkien")), - chatOptions); - runExplicitToolCallingExecutionWithOptions(chatOptions, prompt); - } - @Test void explicitToolCallingExecutionWithNewOptionsStream() { ChatOptions chatOptions = ToolCallingChatOptions.builder() diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/anthropic/tool/FunctionCallWithFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/anthropic/tool/FunctionCallWithFunctionBeanIT.java index 51e9e39be2f..927e4f1c8bd 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/anthropic/tool/FunctionCallWithFunctionBeanIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/anthropic/tool/FunctionCallWithFunctionBeanIT.java @@ -34,6 +34,7 @@ import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -96,7 +97,7 @@ void functionCallWithPortableFunctionCallingOptions() { "What's the weather like in San Francisco, in Paris, France and in Tokyo, Japan? Return the temperature in Celsius."); ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - FunctionCallingOptions.builder().function("weatherFunction").build())); + ToolCallingChatOptions.builder().toolNames("weatherFunction").build())); logger.info("Response: {}", response); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/minimax/FunctionCallbackWithPlainFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/minimax/FunctionCallbackWithPlainFunctionBeanIT.java index bedb41f5d7b..04d50fa784f 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/minimax/FunctionCallbackWithPlainFunctionBeanIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/minimax/FunctionCallbackWithPlainFunctionBeanIT.java @@ -35,6 +35,7 @@ import org.springframework.ai.minimax.MiniMaxChatModel; import org.springframework.ai.minimax.MiniMaxChatOptions; import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -97,11 +98,9 @@ void functionCallWithPortableFunctionCallingOptions() { UserMessage userMessage = new UserMessage( "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - FunctionCallingOptions functionOptions = FunctionCallingOptions.builder() - .function("weatherFunction") - .build(); + ToolCallingChatOptions toolOptions = ToolCallingChatOptions.builder().toolNames("weatherFunction").build(); - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions)); + ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), toolOptions)); logger.info("Response: {}", response); }); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/moonshot/tool/FunctionCallbackWithPlainFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/moonshot/tool/FunctionCallbackWithPlainFunctionBeanIT.java index a9beae2cf90..9700c173b20 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/moonshot/tool/FunctionCallbackWithPlainFunctionBeanIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/moonshot/tool/FunctionCallbackWithPlainFunctionBeanIT.java @@ -34,6 +34,7 @@ import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.moonshot.MoonshotChatModel; import org.springframework.ai.moonshot.MoonshotChatOptions; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -98,11 +99,9 @@ void functionCallWithPortableFunctionCallingOptions() { UserMessage userMessage = new UserMessage( "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius"); - FunctionCallingOptions functionOptions = FunctionCallingOptions.builder() - .function("weatherFunction") - .build(); + ToolCallingChatOptions toolOptions = ToolCallingChatOptions.builder().toolNames("weatherFunction").build(); - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions)); + ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), toolOptions)); logger.info("Response: {}", response); }); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vertexai/gemini/tool/FunctionCallWithFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vertexai/gemini/tool/FunctionCallWithFunctionBeanIT.java index eb31e3b8840..2bf2055455c 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vertexai/gemini/tool/FunctionCallWithFunctionBeanIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vertexai/gemini/tool/FunctionCallWithFunctionBeanIT.java @@ -17,6 +17,7 @@ package org.springframework.ai.autoconfigure.vertexai.gemini.tool; import java.util.List; +import java.util.Set; import java.util.function.Function; import org.junit.jupiter.api.Test; @@ -29,6 +30,7 @@ import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -109,14 +111,14 @@ void functionCallWithPortableFunctionCallingOptions() { """); ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - FunctionCallingOptions.builder().function("weatherFunction").build())); + ToolCallingChatOptions.builder().toolNames("weatherFunction").build())); logger.info("Response: {}", response); assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); response = chatModel.call(new Prompt(List.of(userMessage), - VertexAiGeminiChatOptions.builder().function("weatherFunction3").build())); + VertexAiGeminiChatOptions.builder().toolNames(Set.of("weatherFunction3")).build())); logger.info("Response: {}", response); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/zhipuai/tool/FunctionCallbackWithPlainFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/zhipuai/tool/FunctionCallbackWithPlainFunctionBeanIT.java index 714ea619c44..d244ff77e29 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/zhipuai/tool/FunctionCallbackWithPlainFunctionBeanIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/zhipuai/tool/FunctionCallbackWithPlainFunctionBeanIT.java @@ -34,6 +34,7 @@ import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.zhipuai.ZhiPuAiChatModel; import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -97,8 +98,8 @@ void functionCallWithPortableFunctionCallingOptions() { UserMessage userMessage = new UserMessage( "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - FunctionCallingOptions functionOptions = FunctionCallingOptions.builder() - .function("weatherFunction") + ToolCallingChatOptions functionOptions = ToolCallingChatOptions.builder() + .toolNames("weatherFunction") .build(); ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions));