refactor: remove inter package dependency cycles in spring-ai-model

Remove circular dependencies:

Move utility classes from various packages to dedicated support packages:
- Move ToolCallbacks from ai.tool to ai.support
- Move UsageUtils to UsageCalculator in ai.support
- Move tool.util to tool.support
- Create new ToolDefinitions utility class

Support packages for the classes in question is more idomatic in spring than util packages.

Signed-off-by: Mark Pollack <mark.pollack@broadcom.com>
This commit is contained in:
Mark Pollack
2025-05-08 02:39:54 -04:00
committed by Ilayaperumal Gopinathan
parent 548abed3c1
commit ebfa5b9b2c
42 changed files with 156 additions and 117 deletions

View File

@@ -36,7 +36,7 @@ import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfigura
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.ai.tool.ToolCallbacks;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

View File

@@ -26,7 +26,6 @@ import org.springframework.ai.tool.StaticToolCallbackProvider;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.execution.DefaultToolExecutionExceptionProcessor;
import org.springframework.ai.tool.execution.ToolExecutionExceptionProcessor;
import org.springframework.ai.tool.function.FunctionToolCallback;
@@ -34,6 +33,7 @@ import org.springframework.ai.tool.method.MethodToolCallback;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.ai.tool.resolution.DelegatingToolCallbackResolver;
import org.springframework.ai.tool.resolution.ToolCallbackResolver;
import org.springframework.ai.tool.support.ToolDefinitions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@@ -187,7 +187,7 @@ class ToolCallingAutoConfigurationTests {
public ToolCallback toolCallbacks6() {
var toolMethod = ReflectionUtils.findMethod(WeatherService.class, "getAlert", String.class);
return MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod).build())
.toolDefinition(ToolDefinitions.builder(toolMethod).build())
.toolMethod(toolMethod)
.toolObject(new WeatherService())
.build();

View File

@@ -25,6 +25,7 @@ import io.modelcontextprotocol.spec.McpSchema.Tool;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
/**
@@ -84,7 +85,7 @@ public class AsyncMcpToolCallback implements ToolCallback {
*/
@Override
public ToolDefinition getToolDefinition() {
return ToolDefinition.builder()
return DefaultToolDefinition.builder()
.name(McpToolUtils.prefixedToolName(this.asyncMcpClient.getClientInfo().name(), this.tool.name()))
.description(this.tool.description())
.inputSchema(ModelOptionsUtils.toJsonString(this.tool.inputSchema()))

View File

@@ -27,7 +27,7 @@ import reactor.core.publisher.Flux;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.util.ToolUtils;
import org.springframework.ai.tool.support.ToolUtils;
import org.springframework.util.CollectionUtils;
/**

View File

@@ -26,6 +26,7 @@ import io.modelcontextprotocol.spec.McpSchema.Tool;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
/**
@@ -88,7 +89,7 @@ public class SyncMcpToolCallback implements ToolCallback {
*/
@Override
public ToolDefinition getToolDefinition() {
return ToolDefinition.builder()
return DefaultToolDefinition.builder()
.name(McpToolUtils.prefixedToolName(this.mcpClient.getClientInfo().name(), this.tool.name()))
.description(this.tool.description())
.inputSchema(ModelOptionsUtils.toJsonString(this.tool.inputSchema()))

View File

@@ -16,7 +16,6 @@
package org.springframework.ai.mcp;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
@@ -25,7 +24,7 @@ import io.modelcontextprotocol.spec.McpSchema.Tool;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.util.ToolUtils;
import org.springframework.ai.tool.support.ToolUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

View File

@@ -35,6 +35,7 @@ import org.junit.jupiter.api.Test;
import reactor.test.StepVerifier;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
import static org.assertj.core.api.Assertions.assertThat;
@@ -193,7 +194,7 @@ class ToolUtilsTests {
private ToolCallback createMockToolCallback(String name, String result) {
ToolCallback callback = mock(ToolCallback.class);
ToolDefinition definition = ToolDefinition.builder()
ToolDefinition definition = DefaultToolDefinition.builder()
.name(name)
.description("Test tool")
.inputSchema("{}")
@@ -205,7 +206,7 @@ class ToolUtilsTests {
private ToolCallback createMockToolCallback(String name, RuntimeException error) {
ToolCallback callback = mock(ToolCallback.class);
ToolDefinition definition = ToolDefinition.builder()
ToolDefinition definition = DefaultToolDefinition.builder()
.name(name)
.description("Test tool")
.inputSchema("{}")

View File

@@ -51,7 +51,7 @@ import org.springframework.ai.chat.metadata.ChatResponseMetadata;
import org.springframework.ai.chat.metadata.DefaultUsage;
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.support.UsageCalculator;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
@@ -194,7 +194,8 @@ public class AnthropicChatModel implements ChatModel {
Usage currentChatResponseUsage = usage != null ? this.getDefaultUsage(completionResponse.usage())
: new EmptyUsage();
Usage accumulatedUsage = UsageUtils.getCumulativeUsage(currentChatResponseUsage, previousChatResponse);
Usage accumulatedUsage = UsageCalculator.getCumulativeUsage(currentChatResponseUsage,
previousChatResponse);
ChatResponse chatResponse = toChatResponse(completionEntity.getBody(), accumulatedUsage);
observationContext.setResponse(chatResponse);
@@ -256,7 +257,7 @@ public class AnthropicChatModel implements ChatModel {
Flux<ChatResponse> chatResponseFlux = response.flatMap(chatCompletionResponse -> {
AnthropicApi.Usage usage = chatCompletionResponse.usage();
Usage currentChatResponseUsage = usage != null ? this.getDefaultUsage(chatCompletionResponse.usage()) : new EmptyUsage();
Usage accumulatedUsage = UsageUtils.getCumulativeUsage(currentChatResponseUsage, previousChatResponse);
Usage accumulatedUsage = UsageCalculator.getCumulativeUsage(currentChatResponseUsage, previousChatResponse);
ChatResponse chatResponse = toChatResponse(chatCompletionResponse, accumulatedUsage);
if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse) && chatResponse.hasFinishReasons(Set.of("tool_use"))) {

View File

@@ -32,8 +32,8 @@ import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.method.MethodToolCallback;
import org.springframework.ai.tool.support.ToolDefinitions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
@@ -68,7 +68,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod).build())
.toolDefinition(ToolDefinitions.builder(toolMethod).build())
.toolMethod(toolMethod)
.build())
.call()
@@ -90,7 +90,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.toolDefinition(ToolDefinitions.builder(toolMethod)
.description("Get the weather in location")
.build())
.toolMethod(toolMethod)
@@ -117,7 +117,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(turnLightMethod)
.toolDefinition(ToolDefinitions.builder(turnLightMethod)
.description("Turn light on in the living room.")
.build())
.toolMethod(turnLightMethod)
@@ -145,7 +145,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.toolDefinition(ToolDefinitions.builder(toolMethod)
.description("Get the weather in location")
.build())
.toolMethod(toolMethod)
@@ -172,7 +172,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.toolDefinition(ToolDefinitions.builder(toolMethod)
.description("Get the weather in location")
.build())
.toolMethod(toolMethod)
@@ -203,7 +203,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
assertThatThrownBy(() -> ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.toolDefinition(ToolDefinitions.builder(toolMethod)
.description("Get the weather in location")
.build())
.toolMethod(toolMethod)
@@ -229,7 +229,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
.user("Turn light on in the living room.")
.toolCallbacks(MethodToolCallback.builder()
.toolMethod(toolMethod)
.toolDefinition(ToolDefinition.builder(toolMethod)
.toolDefinition(ToolDefinitions.builder(toolMethod)
.description("Can turn lights on in the Living Room")
.build())
.toolObject(targetObject)

View File

@@ -77,7 +77,7 @@ import org.springframework.ai.chat.metadata.EmptyUsage;
import org.springframework.ai.chat.metadata.PromptMetadata;
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.support.UsageCalculator;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
@@ -357,7 +357,8 @@ public class AzureOpenAiChatModel implements ChatModel {
// Accumulate the usage from the previous chat response
CompletionsUsage usage = chatCompletion.getUsage();
Usage currentChatResponseUsage = usage != null ? getDefaultUsage(usage) : new EmptyUsage();
Usage accumulatedUsage = UsageUtils.getCumulativeUsage(currentChatResponseUsage, previousChatResponse);
Usage accumulatedUsage = UsageCalculator.getCumulativeUsage(currentChatResponseUsage,
previousChatResponse);
return toChatResponse(chatCompletion, accumulatedUsage);
}).buffer(2, 1).map(bufferList -> {
ChatResponse chatResponse1 = bufferList.get(0);
@@ -365,7 +366,7 @@ public class AzureOpenAiChatModel implements ChatModel {
if (bufferList.size() == 2) {
ChatResponse chatResponse2 = bufferList.get(1);
if (chatResponse2 != null && chatResponse2.getMetadata() != null
&& !UsageUtils.isEmpty(chatResponse2.getMetadata().getUsage())) {
&& !UsageCalculator.isEmpty(chatResponse2.getMetadata().getUsage())) {
return toChatResponse(chatResponse1, chatResponse2.getMetadata().getUsage());
}
}
@@ -462,7 +463,7 @@ public class AzureOpenAiChatModel implements ChatModel {
if (chatCompletions.getUsage() != null) {
currentUsage = getDefaultUsage(chatCompletions.getUsage());
}
Usage cumulativeUsage = UsageUtils.getCumulativeUsage(currentUsage, previousChatResponse);
Usage cumulativeUsage = UsageCalculator.getCumulativeUsage(currentUsage, previousChatResponse);
return new ChatResponse(generations, from(chatCompletions, promptFilterMetadata, cumulativeUsage));
}

View File

@@ -43,6 +43,7 @@ import org.springframework.ai.deepseek.api.common.DeepSeekConstants;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.model.tool.*;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.support.UsageCalculator;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.http.ResponseEntity;
import org.springframework.retry.support.RetryTemplate;
@@ -179,7 +180,8 @@ public class DeepSeekChatModel implements ChatModel {
// Current usage
DeepSeekApi.Usage usage = completionEntity.getBody().usage();
Usage currentChatResponseUsage = usage != null ? getDefaultUsage(usage) : new EmptyUsage();
Usage accumulatedUsage = UsageUtils.getCumulativeUsage(currentChatResponseUsage, previousChatResponse);
Usage accumulatedUsage = UsageCalculator.getCumulativeUsage(currentChatResponseUsage,
previousChatResponse);
ChatResponse chatResponse = new ChatResponse(generations,
from(completionEntity.getBody(), accumulatedUsage));
@@ -256,7 +258,7 @@ public class DeepSeekChatModel implements ChatModel {
}).toList();
DeepSeekApi.Usage usage = chatCompletion2.usage();
Usage currentUsage = (usage != null) ? getDefaultUsage(usage) : new EmptyUsage();
Usage cumulativeUsage = UsageUtils.getCumulativeUsage(currentUsage, previousChatResponse);
Usage cumulativeUsage = UsageCalculator.getCumulativeUsage(currentUsage, previousChatResponse);
return new ChatResponse(generations, from(chatCompletion2, cumulativeUsage));
}

View File

@@ -39,7 +39,7 @@ 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.metadata.Usage;
import org.springframework.ai.chat.metadata.UsageUtils;
import org.springframework.ai.support.UsageCalculator;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
@@ -216,7 +216,7 @@ public class MistralAiChatModel implements ChatModel {
}).toList();
DefaultUsage usage = getDefaultUsage(completionEntity.getBody().usage());
Usage cumulativeUsage = UsageUtils.getCumulativeUsage(usage, previousChatResponse);
Usage cumulativeUsage = UsageCalculator.getCumulativeUsage(usage, previousChatResponse);
ChatResponse chatResponse = new ChatResponse(generations,
from(completionEntity.getBody(), cumulativeUsage));
@@ -298,7 +298,7 @@ public class MistralAiChatModel implements ChatModel {
if (chatCompletion2.usage() != null) {
DefaultUsage usage = getDefaultUsage(chatCompletion2.usage());
Usage cumulativeUsage = UsageUtils.getCumulativeUsage(usage, previousChatResponse);
Usage cumulativeUsage = UsageCalculator.getCumulativeUsage(usage, previousChatResponse);
return new ChatResponse(generations, from(chatCompletion2, cumulativeUsage));
}
else {

View File

@@ -25,6 +25,7 @@ import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.mistralai.api.MistralAiApi;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.boot.test.context.SpringBootTest;
@@ -106,7 +107,7 @@ public class MistralAiChatCompletionRequestTest {
private final ToolDefinition toolDefinition;
TestToolCallback(String name) {
this.toolDefinition = ToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolDefinition = DefaultToolDefinition.builder().name(name).inputSchema("{}").build();
}
@Override

View File

@@ -56,7 +56,7 @@ import org.springframework.ai.model.tool.DefaultToolCallingManager;
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.ToolCallbacks;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.beans.factory.annotation.Autowired;

View File

@@ -52,7 +52,7 @@ import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.ai.ollama.management.ModelManagementOptions;
import org.springframework.ai.ollama.management.OllamaModelManager;
import org.springframework.ai.ollama.management.PullModelStrategy;
import org.springframework.ai.tool.ToolCallbacks;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;

View File

@@ -26,6 +26,7 @@ import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
import static org.assertj.core.api.Assertions.assertThat;
@@ -167,7 +168,7 @@ class OllamaChatRequestTests {
private final ToolDefinition toolDefinition;
TestToolCallback(String name) {
this.toolDefinition = ToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolDefinition = DefaultToolDefinition.builder().name(name).inputSchema("{}").build();
}
@Override

View File

@@ -43,7 +43,7 @@ import org.springframework.ai.chat.metadata.DefaultUsage;
import org.springframework.ai.chat.metadata.EmptyUsage;
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.support.UsageCalculator;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
@@ -227,7 +227,8 @@ public class OpenAiChatModel implements ChatModel {
// Current usage
OpenAiApi.Usage usage = chatCompletion.usage();
Usage currentChatResponseUsage = usage != null ? getDefaultUsage(usage) : new EmptyUsage();
Usage accumulatedUsage = UsageUtils.getCumulativeUsage(currentChatResponseUsage, previousChatResponse);
Usage accumulatedUsage = UsageCalculator.getCumulativeUsage(currentChatResponseUsage,
previousChatResponse);
ChatResponse chatResponse = new ChatResponse(generations,
from(chatCompletion, rateLimit, accumulatedUsage));
@@ -321,7 +322,7 @@ public class OpenAiChatModel implements ChatModel {
// @formatter:on
OpenAiApi.Usage usage = chatCompletion2.usage();
Usage currentChatResponseUsage = usage != null ? getDefaultUsage(usage) : new EmptyUsage();
Usage accumulatedUsage = UsageUtils.getCumulativeUsage(currentChatResponseUsage,
Usage accumulatedUsage = UsageCalculator.getCumulativeUsage(currentChatResponseUsage,
previousChatResponse);
return new ChatResponse(generations, from(chatCompletion2, null, accumulatedUsage));
}
@@ -345,7 +346,7 @@ public class OpenAiChatModel implements ChatModel {
// This is the usage from the final Chat response for a
// given Chat request.
Usage usage = secondResponse.getMetadata().getUsage();
if (!UsageUtils.isEmpty(usage)) {
if (!UsageCalculator.isEmpty(usage)) {
// Store the usage from the final response to the
// penultimate response for accumulation.
return new ChatResponse(firstResponse.getResults(),

View File

@@ -27,6 +27,7 @@ import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.openai.api.tool.MockWeatherService;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.function.FunctionToolCallback;
@@ -166,7 +167,7 @@ class ChatCompletionRequestTests {
private final ToolDefinition toolDefinition;
TestToolCallback(String name) {
this.toolDefinition = ToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolDefinition = DefaultToolDefinition.builder().name(name).inputSchema("{}").build();
}
@Override

View File

@@ -69,7 +69,7 @@ import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionRequest.AudioPa
import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionRequest.AudioParameters.Voice;
import org.springframework.ai.openai.api.tool.MockWeatherService;
import org.springframework.ai.openai.testutils.AbstractIT;
import org.springframework.ai.tool.ToolCallbacks;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.beans.factory.annotation.Value;

View File

@@ -29,8 +29,8 @@ import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.openai.OpenAiTestConfiguration;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.method.MethodToolCallback;
import org.springframework.ai.tool.support.ToolDefinitions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
@@ -67,7 +67,7 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.toolDefinition(ToolDefinitions.builder(toolMethod)
.description("Get the weather in location")
.build())
.toolMethod(toolMethod)
@@ -92,7 +92,7 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.toolDefinition(ToolDefinitions.builder(toolMethod)
.description("Can turn lights on or off by room name")
.build())
.toolMethod(toolMethod)
@@ -120,7 +120,7 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.toolDefinition(ToolDefinitions.builder(toolMethod)
.description("Get the weather in location")
.build())
.toolMethod(toolMethod)
@@ -147,7 +147,7 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.toolDefinition(ToolDefinitions.builder(toolMethod)
.description("Get the weather in location")
.build())
.toolMethod(toolMethod)
@@ -176,7 +176,7 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
assertThatThrownBy(() -> ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.toolDefinition(ToolDefinitions.builder(toolMethod)
.description("Get the weather in location")
.build())
.toolMethod(toolMethod)
@@ -200,7 +200,7 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.toolDefinition(ToolDefinitions.builder(toolMethod)
.description("Can turn lights on in the Living Room")
.build())
.toolMethod(toolMethod)

View File

@@ -63,7 +63,7 @@ import org.springframework.ai.chat.metadata.ChatResponseMetadata;
import org.springframework.ai.chat.metadata.DefaultUsage;
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.support.UsageCalculator;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
@@ -415,7 +415,7 @@ public class VertexAiGeminiChatModel implements ChatModel, DisposableBean {
Usage currentUsage = (usage != null)
? new DefaultUsage(usage.getPromptTokenCount(), usage.getCandidatesTokenCount())
: new EmptyUsage();
Usage cumulativeUsage = UsageUtils.getCumulativeUsage(currentUsage, previousChatResponse);
Usage cumulativeUsage = UsageCalculator.getCumulativeUsage(currentUsage, previousChatResponse);
ChatResponse chatResponse = new ChatResponse(generations, toChatResponseMetadata(cumulativeUsage));
observationContext.setResponse(chatResponse);
@@ -528,7 +528,7 @@ public class VertexAiGeminiChatModel implements ChatModel, DisposableBean {
GenerateContentResponse.UsageMetadata usage = response.getUsageMetadata();
Usage currentUsage = (usage != null) ? getDefaultUsage(usage) : new EmptyUsage();
Usage cumulativeUsage = UsageUtils.getCumulativeUsage(currentUsage, previousChatResponse);
Usage cumulativeUsage = UsageCalculator.getCumulativeUsage(currentUsage, previousChatResponse);
ChatResponse chatResponse = new ChatResponse(generations, toChatResponseMetadata(cumulativeUsage));
return Flux.just(chatResponse);
});

View File

@@ -25,6 +25,7 @@ import org.springframework.ai.chat.prompt.Prompt;
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.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.util.json.schema.JsonSchemaGenerator;
import org.springframework.util.Assert;
@@ -75,7 +76,7 @@ public class VertexToolCallingManager implements ToolCallingManager {
ObjectNode openApiSchema = JsonSchemaConverter.convertToOpenApiSchema(jsonSchema);
JsonSchemaGenerator.convertTypeValuesToUpperCase(openApiSchema);
return ToolDefinition.builder()
return DefaultToolDefinition.builder()
.name(td.name())
.description(td.description())
.inputSchema(openApiSchema.toPrettyString())

View File

@@ -36,7 +36,7 @@ import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.ToolCallbacks;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.execution.DefaultToolExecutionExceptionProcessor;
import org.springframework.ai.tool.resolution.DelegatingToolCallbackResolver;

View File

@@ -23,11 +23,9 @@ import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import io.micrometer.observation.Observation;
@@ -46,28 +44,23 @@ import org.springframework.ai.chat.client.observation.ChatClientObservationDocum
import org.springframework.ai.chat.client.observation.DefaultChatClientObservationConvention;
import org.springframework.ai.chat.messages.AbstractMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
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.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.content.Media;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.StructuredOutputConverter;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.template.TemplateRenderer;
import org.springframework.ai.template.st.StTemplateRenderer;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.ToolCallbacks;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeType;
import org.springframework.util.StringUtils;

View File

@@ -26,6 +26,7 @@ import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.template.TemplateRenderer;
import org.springframework.ai.template.st.StTemplateRenderer;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.metadata.ToolMetadata;
@@ -419,12 +420,12 @@ class DefaultChatClientUtilsTests {
private final ToolMetadata toolMetadata;
TestToolCallback(String name) {
this.toolDefinition = ToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolDefinition = DefaultToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolMetadata = ToolMetadata.builder().build();
}
TestToolCallback(String name, boolean returnDirect) {
this.toolDefinition = ToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolDefinition = DefaultToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolMetadata = ToolMetadata.builder().returnDirect(returnDirect).build();
}

View File

@@ -39,6 +39,7 @@ import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.observation.conventions.AiProvider;
import org.springframework.ai.observation.conventions.SpringAiKind;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
import static org.assertj.core.api.Assertions.assertThat;
@@ -86,7 +87,7 @@ class DefaultChatClientObservationConventionTests {
@Override
public ToolDefinition getToolDefinition() {
return ToolDefinition.builder().name(name).inputSchema("{}").build();
return DefaultToolDefinition.builder().name(name).inputSchema("{}").build();
}
@Override

View File

@@ -29,7 +29,7 @@ import org.springframework.ai.integration.tests.tool.domain.Author;
import org.springframework.ai.integration.tests.tool.domain.Book;
import org.springframework.ai.integration.tests.tool.domain.BookService;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.tool.ToolCallbacks;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.method.MethodToolCallback;
import org.springframework.beans.factory.annotation.Autowired;

View File

@@ -37,7 +37,7 @@ 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.OpenAiChatModel;
import org.springframework.ai.tool.ToolCallbacks;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

View File

@@ -26,7 +26,7 @@ import java.util.Set;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.util.ToolUtils;
import org.springframework.ai.tool.support.ToolUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

View File

@@ -14,8 +14,9 @@
* limitations under the License.
*/
package org.springframework.ai.tool;
package org.springframework.ai.support;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
/**

View File

@@ -14,8 +14,10 @@
* limitations under the License.
*/
package org.springframework.ai.chat.metadata;
package org.springframework.ai.support;
import org.springframework.ai.chat.metadata.DefaultUsage;
import org.springframework.ai.chat.metadata.Usage;
import org.springframework.ai.chat.model.ChatResponse;
/**
@@ -23,9 +25,9 @@ import org.springframework.ai.chat.model.ChatResponse;
*
* @author Ilayaperumal Gopinathan
*/
public final class UsageUtils {
public final class UsageCalculator {
private UsageUtils() {
private UsageCalculator() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}

View File

@@ -16,7 +16,8 @@
package org.springframework.ai.tool.definition;
import org.springframework.ai.tool.util.ToolUtils;
import org.springframework.ai.tool.support.ToolUtils;
import org.springframework.ai.util.ParsingUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -66,7 +67,8 @@ public record DefaultToolDefinition(String name, String description, String inpu
public ToolDefinition build() {
if (!StringUtils.hasText(this.description)) {
this.description = ToolUtils.getToolDescriptionFromName(this.name);
Assert.hasText(this.name, "toolName cannot be null or empty");
this.description = ParsingUtils.reConcatenateCamelCase(this.name, " ");
}
return new DefaultToolDefinition(this.name, this.description, this.inputSchema);
}

View File

@@ -16,12 +16,6 @@
package org.springframework.ai.tool.definition;
import java.lang.reflect.Method;
import org.springframework.ai.tool.util.ToolUtils;
import org.springframework.ai.util.json.schema.JsonSchemaGenerator;
import org.springframework.util.Assert;
/**
* Definition used by the AI model to determine when and how to call the tool.
*
@@ -45,29 +39,4 @@ public interface ToolDefinition {
*/
String inputSchema();
/**
* Create a default {@link ToolDefinition} builder.
*/
static DefaultToolDefinition.Builder builder() {
return DefaultToolDefinition.builder();
}
/**
* Create a default {@link ToolDefinition} builder from a {@link Method}.
*/
static DefaultToolDefinition.Builder builder(Method method) {
Assert.notNull(method, "method cannot be null");
return DefaultToolDefinition.builder()
.name(ToolUtils.getToolName(method))
.description(ToolUtils.getToolDescription(method))
.inputSchema(JsonSchemaGenerator.generateForMethodInput(method));
}
/**
* Create a default {@link ToolDefinition} instance from a {@link Method}.
*/
static ToolDefinition from(Method method) {
return ToolDefinition.builder(method).build();
}
}

View File

@@ -27,11 +27,12 @@ import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.execution.DefaultToolCallResultConverter;
import org.springframework.ai.tool.execution.ToolCallResultConverter;
import org.springframework.ai.tool.metadata.ToolMetadata;
import org.springframework.ai.tool.util.ToolUtils;
import org.springframework.ai.tool.support.ToolUtils;
import org.springframework.ai.util.json.JsonParser;
import org.springframework.ai.util.json.schema.JsonSchemaGenerator;
import org.springframework.core.ParameterizedTypeReference;
@@ -203,7 +204,7 @@ public class FunctionToolCallback<I, O> implements ToolCallback {
public FunctionToolCallback<I, O> build() {
Assert.notNull(this.inputType, "inputType cannot be null");
var toolDefinition = ToolDefinition.builder()
var toolDefinition = DefaultToolDefinition.builder()
.name(this.name)
.description(StringUtils.hasText(this.description) ? this.description
: ToolUtils.getToolDescriptionFromName(this.name))

View File

@@ -18,7 +18,7 @@ package org.springframework.ai.tool.metadata;
import java.lang.reflect.Method;
import org.springframework.ai.tool.util.ToolUtils;
import org.springframework.ai.tool.support.ToolUtils;
import org.springframework.util.Assert;
/**

View File

@@ -19,7 +19,6 @@ package org.springframework.ai.tool.method;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -32,9 +31,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.metadata.ToolMetadata;
import org.springframework.ai.tool.util.ToolUtils;
import org.springframework.ai.tool.support.ToolDefinitions;
import org.springframework.ai.tool.support.ToolUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -88,7 +87,7 @@ public final class MethodToolCallbackProvider implements ToolCallbackProvider {
.filter(toolMethod -> toolMethod.isAnnotationPresent(Tool.class))
.filter(toolMethod -> !isFunctionalType(toolMethod))
.map(toolMethod -> MethodToolCallback.builder()
.toolDefinition(ToolDefinition.from(toolMethod))
.toolDefinition(ToolDefinitions.from(toolMethod))
.toolMetadata(ToolMetadata.from(toolMethod))
.toolMethod(toolMethod)
.toolObject(toolObject)

View File

@@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.ai.tool.util.ToolUtils;
import org.springframework.ai.tool.support.ToolUtils;
import org.springframework.ai.util.json.schema.JsonSchemaGenerator;
import org.springframework.ai.util.json.schema.SchemaType;
import org.springframework.context.ApplicationContext;

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2024 - 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.tool.support;
import java.lang.reflect.Method;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.util.json.schema.JsonSchemaGenerator;
import org.springframework.util.Assert;
/**
* Utility class for creating {@link ToolDefinition} builders and instances from Java
* {@link Method} objects.
* <p>
* This class provides static methods to facilitate the construction of
* {@link ToolDefinition} objects by extracting relevant metadata from Java reflection
* {@link Method} instances.
* </p>
*
* @author Mark Pollack
* @since 1.0.0
*/
public class ToolDefinitions {
/**
* Create a default {@link ToolDefinition} builder from a {@link Method}.
*/
public static DefaultToolDefinition.Builder builder(Method method) {
Assert.notNull(method, "method cannot be null");
return DefaultToolDefinition.builder()
.name(ToolUtils.getToolName(method))
.description(ToolUtils.getToolDescription(method))
.inputSchema(JsonSchemaGenerator.generateForMethodInput(method));
}
/**
* Create a default {@link ToolDefinition} instance from a {@link Method}.
*/
public static ToolDefinition from(Method method) {
return builder(method).build();
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.ai.tool.util;
package org.springframework.ai.tool.support;
import java.lang.reflect.Method;
import java.util.Arrays;

View File

@@ -16,7 +16,7 @@
@NonNullApi
@NonNullFields
package org.springframework.ai.tool.util;
package org.springframework.ai.tool.support;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

View File

@@ -29,6 +29,7 @@ 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.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.execution.ToolExecutionException;
import org.springframework.ai.tool.execution.ToolExecutionExceptionProcessor;
@@ -323,12 +324,12 @@ class DefaultToolCallingManagerTests {
private final ToolMetadata toolMetadata;
TestToolCallback(String name) {
this.toolDefinition = ToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolDefinition = DefaultToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolMetadata = ToolMetadata.builder().build();
}
TestToolCallback(String name, boolean returnDirect) {
this.toolDefinition = ToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolDefinition = DefaultToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolMetadata = ToolMetadata.builder().returnDirect(returnDirect).build();
}
@@ -354,7 +355,7 @@ class DefaultToolCallingManagerTests {
private final ToolDefinition toolDefinition;
FailingToolCallback(String name) {
this.toolDefinition = ToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolDefinition = DefaultToolDefinition.builder().name(name).inputSchema("{}").build();
}
@Override

View File

@@ -23,6 +23,7 @@ import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
import static org.assertj.core.api.Assertions.assertThat;
@@ -180,7 +181,7 @@ class ToolCallingChatOptionsTests {
private final ToolDefinition toolDefinition;
TestToolCallback(String name) {
this.toolDefinition = ToolDefinition.builder().name(name).inputSchema("{}").build();
this.toolDefinition = DefaultToolDefinition.builder().name(name).inputSchema("{}").build();
}
@Override