Core model interfaces and abstractions for Spring AI framework providing portable API for chat, embeddings, images, audio, and tool calling across multiple AI providers
Complete framework for enabling AI models to invoke Java methods and functions, including tool definitions, annotations for declarative tool creation, callbacks, resolution, execution, and comprehensive error handling.
Core interface for executing tools/functions.
public interface ToolCallback {
/**
* Get the tool definition describing the tool's interface.
*
* @return the tool definition
*/
ToolDefinition getToolDefinition();
/**
* Get the tool metadata with execution hints.
*
* @return the tool metadata
*/
ToolMetadata getToolMetadata();
/**
* Execute the tool with JSON input.
*
* @param toolInput the JSON-encoded tool arguments
* @return the JSON-encoded tool result
*/
String call(String toolInput);
/**
* Execute the tool with JSON input and context.
*
* @param toolInput the JSON-encoded tool arguments
* @param toolContext the execution context
* @return the JSON-encoded tool result
*/
String call(String toolInput, ToolContext toolContext);
}Provider of tool callbacks for discovery.
public interface ToolCallbackProvider {
/**
* Get all tool callbacks provided by this provider.
*
* @return list of tool callbacks
*/
List<ToolCallback> getToolCallbacks();
}Static provider with a fixed list of tools.
public class StaticToolCallbackProvider implements ToolCallbackProvider {
/**
* Construct a StaticToolCallbackProvider with tool callbacks.
*
* @param toolCallbacks the list of tool callbacks
*/
public StaticToolCallbackProvider(List<ToolCallback> toolCallbacks);
@Override
public List<ToolCallback> getToolCallbacks();
}Marks a method as a tool/function callable by AI models.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tool {
/**
* The tool name. Defaults to method name.
*
* @return the tool name
*/
String name() default "";
/**
* Description of what the tool does.
* Used by AI to understand when to call the tool.
* Defaults to empty string if not specified.
*
* @return the tool description
*/
String description() default "";
/**
* Whether to return the tool result directly to the user.
* If true, bypasses further AI processing.
*
* @return true to return directly
*/
boolean returnDirect() default false;
/**
* Custom result converter for formatting tool output.
*
* @return the result converter class
*/
Class<? extends ToolCallResultConverter> resultConverter()
default DefaultToolCallResultConverter.class;
}Annotates tool method parameters with metadata.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ToolParam {
/**
* Whether this parameter is required.
*
* @return true if required
*/
boolean required() default true;
/**
* Description of the parameter for AI understanding.
*
* @return the parameter description
*/
String description() default "";
}Defines a tool's interface contract.
public interface ToolDefinition {
/**
* Get the tool name.
*
* @return the tool name
*/
String name();
/**
* Get the tool description.
*
* @return the tool description
*/
String description();
/**
* Get the JSON schema for tool input parameters.
*
* @return the JSON schema string
*/
String inputSchema();
/**
* Create a new tool definition builder.
*
* @return a new builder
*/
static Builder builder();
}Record implementation of ToolDefinition.
public record DefaultToolDefinition(
String name,
String description,
String inputSchema
) implements ToolDefinition {
/**
* Builder for DefaultToolDefinition.
*/
public static class Builder {
public Builder name(String name);
public Builder description(String description);
public Builder inputSchema(String inputSchema);
public DefaultToolDefinition build();
}
}Metadata for tool execution behavior.
public interface ToolMetadata {
/**
* Whether to return tool result directly without further AI processing.
* When true, the tool result is sent directly to the user without
* passing through the AI model for additional processing.
*
* @return true to return directly, false to pass through AI model
*/
boolean returnDirect();
/**
* Create a new metadata builder.
*
* @return a new builder for constructing ToolMetadata
*/
static Builder builder();
/**
* Builder for constructing ToolMetadata instances.
*/
interface Builder {
/**
* Set whether to return tool result directly.
*
* @param returnDirect true to return directly
* @return this builder
*/
Builder returnDirect(boolean returnDirect);
/**
* Build the ToolMetadata instance.
*
* @return the constructed ToolMetadata
*/
ToolMetadata build();
}
}Record implementation of ToolMetadata.
public record DefaultToolMetadata(boolean returnDirect) implements ToolMetadata {
}Wraps a Java function as a tool.
public class FunctionToolCallback<I, O> implements ToolCallback {
/**
* Construct a FunctionToolCallback wrapping a function.
*
* @param definition the tool definition
* @param function the function to wrap (input -> output)
* @param objectMapper the Jackson ObjectMapper for JSON serialization
*/
public FunctionToolCallback(
ToolDefinition definition,
Function<I, O> function,
ObjectMapper objectMapper
);
@Override
public ToolDefinition getToolDefinition();
@Override
public ToolMetadata getToolMetadata();
@Override
public String call(String toolInput);
@Override
public String call(String toolInput, ToolContext toolContext);
}Wraps a Java method as a tool.
public class MethodToolCallback implements ToolCallback {
/**
* Construct a MethodToolCallback wrapping a method.
*
* @param bean the object instance containing the method
* @param method the method to wrap
* @param definition the tool definition
* @param metadata the tool metadata
*/
public MethodToolCallback(
Object bean,
Method method,
ToolDefinition definition,
ToolMetadata metadata
);
@Override
public ToolDefinition getToolDefinition();
@Override
public ToolMetadata getToolMetadata();
@Override
public String call(String toolInput);
@Override
public String call(String toolInput, ToolContext toolContext);
}Provider that scans beans for @Tool annotated methods.
public class MethodToolCallbackProvider implements ToolCallbackProvider {
/**
* Construct a MethodToolCallbackProvider scanning a bean for @Tool methods.
*
* @param bean the bean to scan for @Tool annotations
*/
public MethodToolCallbackProvider(Object bean);
@Override
public List<ToolCallback> getToolCallbacks();
}Resolves tool callbacks by name.
public interface ToolCallbackResolver {
/**
* Resolve a tool callback by name.
*
* @param toolName the tool name
* @return the tool callback, or null if not found
*/
ToolCallback resolve(String toolName);
}Resolver with a static list of tools.
public class StaticToolCallbackResolver implements ToolCallbackResolver {
/**
* Construct a StaticToolCallbackResolver with tool callbacks.
*
* @param toolCallbacks the list of tool callbacks
*/
public StaticToolCallbackResolver(List<ToolCallback> toolCallbacks);
@Override
public ToolCallback resolve(String toolName);
}Resolver that finds tools in Spring ApplicationContext.
public class SpringBeanToolCallbackResolver implements ToolCallbackResolver {
/**
* Construct a SpringBeanToolCallbackResolver.
*
* @param applicationContext the Spring application context
*/
public SpringBeanToolCallbackResolver(ApplicationContext applicationContext);
@Override
public ToolCallback resolve(String toolName);
}Resolver that delegates to multiple resolvers.
public class DelegatingToolCallbackResolver implements ToolCallbackResolver {
/**
* Construct a DelegatingToolCallbackResolver.
*
* @param resolvers the list of resolvers to delegate to
*/
public DelegatingToolCallbackResolver(List<ToolCallbackResolver> resolvers);
@Override
public ToolCallback resolve(String toolName);
}Helper for resolving types in tool definitions and parameter extraction.
public class TypeResolverHelper {
/**
* Resolve the actual type from a generic type parameter.
* Useful for extracting concrete types from parameterized interfaces.
*
* @param type the type to resolve
* @param contextClass the context class containing the type
* @return the resolved type
*/
public static Type resolveType(Type type, Class<?> contextClass);
/**
* Extract parameter types from a method.
* Returns the actual parameter types considering generic type resolution.
*
* @param method the method to extract parameters from
* @return array of resolved parameter types
*/
public static Type[] getParameterTypes(Method method);
/**
* Check if a type is a primitive or wrapper type.
*
* @param type the type to check
* @return true if the type is primitive or a wrapper
*/
public static boolean isPrimitiveOrWrapper(Class<?> type);
}Converts tool results to strings.
public interface ToolCallResultConverter {
/**
* Convert a tool result object to string.
*
* @param result the result object
* @return the string representation
*/
String apply(Object result);
}Default converter that uses JSON serialization.
public class DefaultToolCallResultConverter implements ToolCallResultConverter {
@Override
public String apply(Object result);
}Processes exceptions during tool execution.
public interface ToolExecutionExceptionProcessor {
/**
* Process an exception into a string message.
*
* @param throwable the exception
* @return the error message string
*/
String processException(Throwable throwable);
}Default processor that formats exception messages.
public class DefaultToolExecutionExceptionProcessor implements ToolExecutionExceptionProcessor {
@Override
public String processException(Throwable throwable);
}Exception thrown during tool execution.
public class ToolExecutionException extends RuntimeException {
/**
* Construct a ToolExecutionException with message.
*
* @param message the error message
*/
public ToolExecutionException(String message);
/**
* Construct a ToolExecutionException with message and cause.
*
* @param message the error message
* @param cause the underlying cause
*/
public ToolExecutionException(String message, Throwable cause);
}Utility class for creating ToolDefinition instances from Java Method objects using reflection.
public final class ToolDefinitions {
/**
* Create a ToolDefinition builder from a Method.
* Extracts tool name, description, and input schema from the method.
* Analyzes @Tool and @ToolParam annotations to build the definition.
*
* @param method the method to create a tool definition for
* @return a builder for the tool definition with extracted metadata
*/
public static DefaultToolDefinition.Builder builder(Method method);
/**
* Create a complete ToolDefinition from a Method.
* Convenience method that creates and builds in one step.
* Equivalent to calling builder(method).build().
*
* @param method the method to create a tool definition for
* @return the complete tool definition ready for use
*/
public static ToolDefinition from(Method method);
/**
* Create a ToolDefinition from a Function with explicit schema.
* Useful when wrapping lambda functions or method references.
*
* @param name the tool name
* @param description the tool description
* @param inputSchema the JSON schema for input parameters
* @return the complete tool definition
*/
public static ToolDefinition from(String name, String description, String inputSchema);
/**
* Create a ToolDefinition from a Function with input class.
* Automatically generates JSON schema from the input class.
*
* @param name the tool name
* @param description the tool description
* @param inputClass the input parameter class
* @return the complete tool definition
*/
public static ToolDefinition from(String name, String description, Class<?> inputClass);
}General tool utilities for working with tool callbacks and definitions.
public final class ToolUtils {
/**
* Extract tool name from a method.
* Uses @Tool annotation name if present, otherwise uses method name.
*
* @param method the method to extract name from
* @return the tool name
*/
public static String getToolName(Method method);
/**
* Extract tool description from a method.
* Uses @Tool annotation description if present.
*
* @param method the method to extract description from
* @return the tool description
*/
public static String getToolDescription(Method method);
/**
* Check if a method is annotated with @Tool.
*
* @param method the method to check
* @return true if the method has @Tool annotation
*/
public static boolean isToolMethod(Method method);
/**
* Create a ToolCallback from a method and bean instance.
*
* @param bean the object instance containing the method
* @param method the method to wrap as a tool
* @return a ToolCallback wrapping the method
*/
public static ToolCallback createToolCallback(Object bean, Method method);
/**
* Validate that a tool callback has a valid definition.
* Checks that name, description, and schema are properly defined.
*
* @param toolCallback the tool callback to validate
* @throws IllegalArgumentException if validation fails
*/
public static void validateToolCallback(ToolCallback toolCallback);
}import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
@Component
public class WeatherTools {
@Tool(description = "Get current weather for a city")
public String getCurrentWeather(
@ToolParam(description = "The city name") String city,
@ToolParam(description = "Temperature unit", required = false) String unit
) {
// Implementation
return "{\"temperature\": 72, \"condition\": \"sunny\", \"city\": \"" + city + "\"}";
}
@Tool(description = "Get weather forecast for next N days")
public String getWeatherForecast(
@ToolParam(description = "The city name") String city,
@ToolParam(description = "Number of days") int days
) {
// Implementation
return "{\"forecast\": [...]}";
}
}import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
@Service
public class ToolChatService {
private final ChatModel chatModel;
private final WeatherTools weatherTools;
public String chatWithTools(String userMessage) {
// Create tool provider
MethodToolCallbackProvider toolProvider =
new MethodToolCallbackProvider(weatherTools);
// Configure options with tools
ChatOptions options = ChatOptions.builder()
.toolCallbacks(toolProvider.getToolCallbacks())
.build();
// Create prompt
Prompt prompt = new Prompt(userMessage, options);
// Call chat model - it can now use the weather tools
ChatResponse response = chatModel.call(prompt);
return response.getResult().getOutput().getText();
}
}import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.ai.tool.definition.ToolDefinition;
import com.fasterxml.jackson.databind.ObjectMapper;
// Define input/output classes
record CalculatorInput(double a, double b, String operation) {}
record CalculatorOutput(double result) {}
// Create function
Function<CalculatorInput, CalculatorOutput> calculator = input -> {
double result = switch (input.operation()) {
case "add" -> input.a() + input.b();
case "subtract" -> input.a() - input.b();
case "multiply" -> input.a() * input.b();
case "divide" -> input.a() / input.b();
default -> throw new IllegalArgumentException("Unknown operation");
};
return new CalculatorOutput(result);
};
// Create tool definition
ToolDefinition definition = ToolDefinition.builder()
.name("calculator")
.description("Perform basic arithmetic operations")
.inputSchema(generateJsonSchema(CalculatorInput.class))
.build();
// Wrap as tool
FunctionToolCallback<CalculatorInput, CalculatorOutput> tool =
new FunctionToolCallback<>(definition, calculator, new ObjectMapper());import org.springframework.ai.tool.resolution.*;
@Configuration
public class ToolConfig {
@Bean
public ToolCallbackResolver toolResolver(
ApplicationContext context,
List<ToolCallback> staticTools
) {
// Create multiple resolvers
SpringBeanToolCallbackResolver springResolver =
new SpringBeanToolCallbackResolver(context);
StaticToolCallbackResolver staticResolver =
new StaticToolCallbackResolver(staticTools);
// Combine with delegating resolver
return new DelegatingToolCallbackResolver(
List.of(springResolver, staticResolver)
);
}
}@Component
public class DirectReturnTools {
@Tool(
description = "Get current time",
returnDirect = true // Return directly to user, skip AI processing
)
public String getCurrentTime() {
return LocalDateTime.now().toString();
}
@Tool(
description = "Get user account balance",
returnDirect = true
)
public String getBalance(@ToolParam(description = "User ID") String userId) {
// Query database
return "{\"balance\": 1234.56, \"currency\": \"USD\"}";
}
}public class CustomResultConverter implements ToolCallResultConverter {
@Override
public String apply(Object result) {
if (result instanceof Map) {
// Custom formatting for maps
return formatAsKeyValue((Map<?, ?>) result);
}
// Default JSON serialization
return new ObjectMapper().writeValueAsString(result);
}
private String formatAsKeyValue(Map<?, ?> map) {
return map.entrySet().stream()
.map(e -> e.getKey() + ": " + e.getValue())
.collect(Collectors.joining("\n"));
}
}
@Tool(
description = "Get system info",
resultConverter = CustomResultConverter.class
)
public Map<String, String> getSystemInfo() {
return Map.of(
"os", System.getProperty("os.name"),
"java", System.getProperty("java.version")
);
}@Component
public class SafeTools {
@Tool(description = "Divide two numbers")
public String divide(
@ToolParam(description = "Numerator") double a,
@ToolParam(description = "Denominator") double b
) {
try {
if (b == 0) {
throw new ToolExecutionException("Cannot divide by zero");
}
double result = a / b;
return "{\"result\": " + result + "}";
} catch (Exception e) {
throw new ToolExecutionException("Division failed", e);
}
}
}@Component
public class ContextualTools {
@Tool(description = "Process user data")
public String processUserData(
@ToolParam(description = "Data to process") String data
) {
// Access tool context if needed
// Context is passed via ToolContext parameter in call method
return "{\"processed\": true}";
}
}
// When calling manually
ToolContext context = new ToolContext(Map.of(
"userId", "user_123",
"sessionId", "session_456"
));
String result = tool.call(toolInput, context);@Configuration
public class ToolScanConfig {
@Bean
public ToolCallbackProvider multipleToolProviders(
WeatherTools weatherTools,
CalculatorTools calculatorTools,
DatabaseTools databaseTools
) {
List<ToolCallback> allTools = new ArrayList<>();
// Scan each bean
allTools.addAll(new MethodToolCallbackProvider(weatherTools).getToolCallbacks());
allTools.addAll(new MethodToolCallbackProvider(calculatorTools).getToolCallbacks());
allTools.addAll(new MethodToolCallbackProvider(databaseTools).getToolCallbacks());
return new StaticToolCallbackProvider(allTools);
}
}@Service
public class ToolExecutionService {
private final ChatModel chatModel;
private final ToolCallbackResolver toolResolver;
public String executeWithTools(String userMessage) {
// Initial call
ChatResponse response = chatModel.call(new Prompt(userMessage));
// Check for tool calls
while (response.hasToolCalls()) {
List<Message> messages = new ArrayList<>();
messages.add(response.getResult().getOutput());
// Execute each tool call
for (AssistantMessage.ToolCall toolCall :
response.getResult().getOutput().getToolCalls()) {
// Resolve and execute tool
ToolCallback tool = toolResolver.resolve(toolCall.name());
String result = tool.call(toolCall.arguments());
// Add tool response
messages.add(new ToolResponseMessage(List.of(
new ToolResponseMessage.ToolResponse(
toolCall.id(),
toolCall.name(),
result
)
)));
}
// Continue conversation with tool results
response = chatModel.call(new Prompt(messages));
}
return response.getResult().getOutput().getText();
}
}@Component
public class DatabaseTools {
@Tool(description = "Search products in database")
public String searchProducts(
@ToolParam(description = "Search query") String query,
@ToolParam(description = "Maximum results", required = false) Integer limit,
@ToolParam(description = "Category filter", required = false) String category
) {
// Set defaults
int maxResults = limit != null ? limit : 10;
// Query database
List<Product> products = productRepository.search(query, category, maxResults);
// Convert to JSON
return objectMapper.writeValueAsString(products);
}
@Tool(description = "Get product details by ID")
public String getProduct(
@ToolParam(description = "Product ID") String productId
) {
Product product = productRepository.findById(productId)
.orElseThrow(() -> new ToolExecutionException("Product not found"));
return objectMapper.writeValueAsString(product);
}
}