Spring Boot-compatible Ollama integration providing ChatModel and EmbeddingModel implementations for running large language models locally with support for streaming, tool calling, model management, and observability.
Enable models to call external functions and tools.
Ollama supports tool/function calling with compatible models (Llama 3, Mistral, Qwen, etc.). Spring AI provides automatic tool execution, JSON Schema generation, and seamless integration with your Java methods.
Tool calling requires compatible models:
Check model documentation for tool support before use.
Create a service class with tool methods:
public class WeatherService {
public record Request(String location, String unit) {}
public record Response(String location, String temperature, String unit) {}
public Response getWeather(Request request) {
// Simulated weather lookup
String temp = switch (request.location().toLowerCase()) {
case "san francisco" -> "65";
case "tokyo" -> "50";
case "paris" -> "59";
default -> "72";
};
return new Response(
request.location(),
temp,
request.unit()
);
}
}Register tools using FunctionToolCallback:
WeatherService weatherService = new WeatherService();
OllamaChatOptions options = OllamaChatOptions.builder()
.model(OllamaModel.LLAMA3)
.toolCallbacks(List.of(
FunctionToolCallback.builder("getWeather", weatherService)
.description("Get current weather for a location")
.inputType(WeatherService.Request.class)
.build()
))
.build();
OllamaChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(ollamaApi)
.defaultOptions(options)
.build();String prompt = "What's the weather in San Francisco and Tokyo? Use Celsius.";
ChatResponse response = chatModel.call(new Prompt(prompt));
// Model automatically calls getWeather tool and includes results in response
System.out.println(response.getResult().getOutput().getText());
// Output: "The weather in San Francisco is 65°C and in Tokyo is 50°C"Tools are registered via ToolCallback (usually FunctionToolCallback).
// Basic tool registration
FunctionToolCallback weatherTool = FunctionToolCallback.builder(
"getWeather",
weatherService
)
.description("Get weather information for a location")
.inputType(WeatherService.Request.class)
.build();
OllamaChatOptions options = OllamaChatOptions.builder()
.toolCallbacks(List.of(weatherTool))
.build();Register multiple tools for the model to choose from:
OllamaChatOptions options = OllamaChatOptions.builder()
.model(OllamaModel.LLAMA3)
.toolCallbacks(List.of(
FunctionToolCallback.builder("getWeather", weatherService)
.description("Get current weather")
.inputType(WeatherRequest.class)
.build(),
FunctionToolCallback.builder("getTime", timeService)
.description("Get current time in a timezone")
.inputType(TimeRequest.class)
.build(),
FunctionToolCallback.builder("searchWeb", searchService)
.description("Search the web for information")
.inputType(SearchRequest.class)
.build()
))
.build();
// Model will choose appropriate tool(s) based on the prompt
String prompt = "What's the weather in London and what time is it there?";
ChatResponse response = chatModel.call(new Prompt(prompt));Enable only specific tools for a request:
// Register all tools at model level
OllamaChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(ollamaApi)
.defaultOptions(OllamaChatOptions.builder()
.model(OllamaModel.LLAMA3)
.toolCallbacks(List.of(weatherTool, timeTool, searchTool))
.build())
.build();
// Enable only weather tool for this request
OllamaChatOptions requestOptions = OllamaChatOptions.builder()
.toolNames("getWeather") // Only enable weather tool
.build();
ChatResponse response = chatModel.call(
new Prompt("What's the weather?", requestOptions)
);Share data between tool executions:
OllamaChatOptions options = OllamaChatOptions.builder()
.model(OllamaModel.LLAMA3)
.toolCallbacks(List.of(weatherTool))
.toolContext(Map.of(
"apiKey", "xyz123",
"userId", "user-456",
"maxResults", 10
))
.build();
// Tools can access context during execution
public class WeatherService {
public Response getWeather(Request request, Map<String, Object> context) {
String apiKey = (String) context.get("apiKey");
// Use apiKey for API calls
return fetchWeather(request.location(), apiKey);
}
}Control whether tools are executed automatically:
// Default: Auto-execute tools (recommended)
OllamaChatOptions options = OllamaChatOptions.builder()
.toolCallbacks(List.of(weatherTool))
.internalToolExecutionEnabled(true) // Default behavior
.build();
// Disable auto-execution (manual handling)
OllamaChatOptions options = OllamaChatOptions.builder()
.toolCallbacks(List.of(weatherTool))
.internalToolExecutionEnabled(false)
.build();
// Tool calls will be in response but not executed
ChatResponse response = chatModel.call(new Prompt("Get weather"));
// Check for tool calls in response and handle manuallyTool calls work with streaming responses:
OllamaChatOptions options = OllamaChatOptions.builder()
.model(OllamaModel.LLAMA3)
.toolCallbacks(List.of(weatherTool))
.build();
Flux<ChatResponse> stream = chatModel.stream(new Prompt(
"What's the weather in San Francisco?",
options
));
stream.subscribe(chunk -> {
String content = chunk.getResult().getOutput().getText();
if (content != null) {
System.out.print(content);
}
});
// Model will call tool during streaming and incorporate resultsDefine tools with complex parameter schemas:
public class SearchService {
public record Request(
String query,
List<String> domains,
int maxResults,
boolean includeImages,
Map<String, String> filters
) {}
public record Result(
String title,
String url,
String snippet,
List<String> images
) {}
public List<Result> search(Request request) {
// Complex search logic
return performSearch(request);
}
}
FunctionToolCallback searchTool = FunctionToolCallback.builder(
"searchWeb",
searchService
)
.description("Search the web with advanced filters")
.inputType(SearchService.Request.class) // Auto-generates schema
.build();Override tools for specific prompts:
// Model has default tools
OllamaChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(ollamaApi)
.defaultOptions(OllamaChatOptions.builder()
.model(OllamaModel.LLAMA3)
.toolCallbacks(List.of(weatherTool, timeTool))
.build())
.build();
// Add additional tool for this request only
OllamaChatOptions requestOptions = OllamaChatOptions.builder()
.toolCallbacks(List.of(specialTool))
.build();
// This request has access to weather, time, AND special tool
ChatResponse response = chatModel.call(
new Prompt("Use the special tool", requestOptions)
);Control which tools can be executed based on custom logic:
ToolExecutionEligibilityPredicate predicate = (tool, context) -> {
// Only allow weather tool during business hours
LocalTime now = LocalTime.now();
if (tool.getName().equals("getWeather")) {
return now.getHour() >= 9 && now.getHour() <= 17;
}
return true;
};
OllamaChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(ollamaApi)
.defaultOptions(OllamaChatOptions.builder()
.model(OllamaModel.LLAMA3)
.toolCallbacks(List.of(weatherTool))
.build())
.toolExecutionEligibilityPredicate(predicate)
.build();Examine tool calls in the response:
ChatResponse response = chatModel.call(new Prompt("Get weather"));
AssistantMessage message = response.getResult().getOutput();
// Check if message includes tool calls
List<ToolCall> toolCalls = message.getToolCalls();
if (!toolCalls.isEmpty()) {
for (ToolCall toolCall : toolCalls) {
String functionName = toolCall.name();
Map<String, Object> arguments = toolCall.arguments();
System.out.println("Called: " + functionName + " with " + arguments);
}
}Disable automatic execution and handle tools manually:
OllamaChatOptions options = OllamaChatOptions.builder()
.model(OllamaModel.LLAMA3)
.toolCallbacks(List.of(weatherTool))
.internalToolExecutionEnabled(false) // Disable auto-execution
.build();
ChatResponse response = chatModel.call(new Prompt("Get weather", options));
// Extract tool calls
AssistantMessage message = response.getResult().getOutput();
List<ToolCall> toolCalls = message.getToolCalls();
// Execute manually
for (ToolCall toolCall : toolCalls) {
String result = executeToolManually(toolCall);
// Create tool response message
ToolResponseMessage toolResponse = new ToolResponseMessage(
result,
toolCall.name()
);
// Continue conversation with tool results
ChatResponse nextResponse = chatModel.call(new Prompt(List.of(
message,
toolResponse
)));
}Tools are defined using JSON Schema. Spring AI auto-generates schemas from Java classes:
// Input class
public record WeatherRequest(
String location,
String unit
) {}
// Becomes JSON Schema:
{
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "Location to get weather for"
},
"unit": {
"type": "string",
"description": "Temperature unit (celsius or fahrenheit)"
}
},
"required": ["location", "unit"]
}Provide custom schemas if needed:
String customSchema = """
{
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"]
}
""";
FunctionToolCallback tool = FunctionToolCallback.builder(
"getWeather",
weatherService
)
.description("Get weather")
.schemaType(SchemaType.OPEN_API_SCHEMA)
.inputType(customSchema) // Use custom schema
.build();// Service implementation
public class WeatherService {
public record Request(String location, String unit) {}
public record Response(String location, double temperature, String unit, String conditions) {
@Override
public String toString() {
return String.format("%s: %.1f°%s, %s",
location, temperature, unit.substring(0, 1).toUpperCase(), conditions);
}
}
public Response getWeather(Request request) {
// Simulate weather data
return new Response(
request.location(),
getTemperature(request.location(), request.unit()),
request.unit(),
getConditions(request.location())
);
}
private double getTemperature(String location, String unit) {
double celsius = switch (location.toLowerCase()) {
case "san francisco" -> 18.3;
case "tokyo" -> 10.5;
case "paris" -> 15.2;
default -> 22.0;
};
return unit.equalsIgnoreCase("fahrenheit")
? (celsius * 9/5) + 32
: celsius;
}
private String getConditions(String location) {
return switch (location.toLowerCase()) {
case "san francisco" -> "Foggy";
case "tokyo" -> "Clear";
case "paris" -> "Partly Cloudy";
default -> "Sunny";
};
}
}
// Configuration
@Configuration
public class ToolConfig {
@Bean
public WeatherService weatherService() {
return new WeatherService();
}
@Bean
public OllamaChatModel chatModel(OllamaApi ollamaApi, WeatherService weatherService) {
return OllamaChatModel.builder()
.ollamaApi(ollamaApi)
.defaultOptions(OllamaChatOptions.builder()
.model(OllamaModel.LLAMA3)
.temperature(0.7)
.toolCallbacks(List.of(
FunctionToolCallback.builder("getWeather", weatherService)
.description("Get current weather for a location")
.inputType(WeatherService.Request.class)
.build()
))
.build())
.build();
}
}
// Usage
@Service
public class ChatService {
private final OllamaChatModel chatModel;
public String chat(String userMessage) {
ChatResponse response = chatModel.call(new Prompt(userMessage));
return response.getResult().getOutput().getText();
}
}
// Example queries:
// "What's the weather in San Francisco?"
// "Compare temperatures in Tokyo and Paris"
// "Is it warmer in San Francisco or Tokyo? Use Celsius"// Multiple services
public class WeatherService {
public record Request(String location, String unit) {}
public record Response(String location, double temp, String unit) {}
public Response getWeather(Request request) {
// Implementation
}
}
public class TimeService {
public record Request(String timezone) {}
public record Response(String timezone, String time, String date) {}
public Response getTime(Request request) {
// Implementation
}
}
public class CurrencyService {
public record Request(String from, String to, double amount) {}
public record Response(String from, String to, double result) {}
public Response convertCurrency(Request request) {
// Implementation
}
}
// Configuration with multiple tools
OllamaChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(ollamaApi)
.defaultOptions(OllamaChatOptions.builder()
.model(OllamaModel.LLAMA3)
.toolCallbacks(List.of(
FunctionToolCallback.builder("getWeather", weatherService)
.description("Get weather information")
.inputType(WeatherService.Request.class)
.build(),
FunctionToolCallback.builder("getTime", timeService)
.description("Get current time in a timezone")
.inputType(TimeService.Request.class)
.build(),
FunctionToolCallback.builder("convertCurrency", currencyService)
.description("Convert between currencies")
.inputType(CurrencyService.Request.class)
.build()
))
.build())
.build();
// Model chooses appropriate tools:
// "What's the weather in London and what time is it there?"
// "How much is 100 USD in EUR?"
// "Give me weather for Paris and convert 50 EUR to USD"tessl i tessl/maven-org-springframework-ai--spring-ai-ollama@1.1.1