In Part 3 of this series, we enhanced our AI agent with domain-specific knowledge through RAG, allowing it to answer questions based on company documents. However, we discovered another critical limitation: when asked about real-time information like weather forecasts or current dates, the agent couldn't provide accurate answers.
AI models are trained on historical data with a knowledge cutoff date. They don't have access to real-time information like current weather, today's date, live flight prices, or currency exchange rates. This makes them unable to help with time-sensitive tasks that require up-to-date information.
In this post, we'll add tool calling (also known as function calling) to our AI agent, allowing it to access real-time information and take actions by calling external APIs and services.
Overview of the Solution
The Real-Time Information Challenge
AI models are trained on historical data and don't have access to:
- Current date and time
- Weather forecasts
- Live flight prices
- Currency exchange rates
- Real-time inventory or availability
They might provide outdated or incorrect information based on their training data cutoff.
We'll solve this with tool calling (function calling):
- Define tools as Java methods annotated with
@Tool - Provide clear descriptions to help the AI choose the right tool
- Let the AI automatically call tools when needed
- Return real-time data to ground AI responses
Why Tool Calling?
Tool calling enables two key capabilities:
- Information Retrieval: Access external data sources (weather APIs, databases, web services) to augment the AI's knowledge with real-time information
- Taking Action: Execute operations (send emails, book flights, create records) to automate tasks that require system integration
Architecture Overview
User Question
↓
AI Model (Amazon Bedrock)
↓
[Decides which tool to call]
↓
[DateTimeService] ← Get current date/time
[WeatherService] ← Get weather forecast
↓
Response (with real-time data)
Prerequisites
Before you start, ensure you have:
- Completed Part 3 of this series with the working
ai-agentapplication - Java 21 JDK installed (Amazon Corretto 21)
- Maven 3.6+ installed
- Docker Desktop running (for Testcontainers with PostgreSQL/PGVector)
- AWS CLI configured with access to Amazon Bedrock
Navigate to your project directory from Part 3:
cd ai-agent
Tool Calling
Add WebClient Dependency
We'll use Spring WebFlux's WebClient for non-blocking HTTP calls to external APIs.
Open pom.xml and add this dependency to the <dependencies> section:
<!-- WebClient for HTTP calls -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
Create DateTimeService
Create a tool that provides the current date and time in any timezone:
mkdir -p src/main/java/com/example/ai/agent/tool
cat <<'EOF' > src/main/java/com/example/ai/agent/tool/DateTimeService.java
package com.example.ai.agent.tool;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
@Service
public class DateTimeService {
@Tool(description = """
Get current date and time in specified timezone.
Parameters:
- timeZone: e.g., 'UTC', 'America/New_York', 'Europe/London'
Returns: ISO format (YYYY-MM-DDTHH:MM:SS)
Use this when users mention relative dates like "next week" or "tomorrow".
""")
public String getCurrentDateTime(String timeZone) {
return ZonedDateTime.now(ZoneId.of(timeZone))
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}
EOF
@Tool Annotation: Marks methods as callable tools for the AI. The description helps the AI understand when and how to use it.
Create WeatherService
Create a tool that fetches weather forecasts from Open-Meteo API:
cat <<'EOF' > src/main/java/com/example/ai/agent/tool/WeatherService.java
package com.example.ai.agent.tool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@Service
public class WeatherService {
private static final Logger logger = LoggerFactory.getLogger(WeatherService.class);
private final WebClient webClient;
public WeatherService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.build();
}
@Tool(description = """
Get weather forecast for a city on a specific date.
Requires:
- city: City name (e.g., 'London', 'Paris', 'New York')
- date: Date in YYYY-MM-DD format
Returns: Weather forecast with minimum and maximum temperatures.
Examples:
- getWeather("London", "2025-11-10")
- getWeather("Paris", "2025-11-15")
Use this tool when users ask about weather conditions for travel planning.
""")
public String getWeather(String city, String date) {
if (city == null || city.trim().isEmpty()) {
return "Error: City parameter is required";
}
try {
LocalDate.parse(date);
} catch (Exception e) {
return "Error: Invalid date format. Use YYYY-MM-DD";
}
logger.info("Fetching weather for city: {}, date: {}", city, date);
try {
// Get city coordinates
String encodedCity = URLEncoder.encode(city.trim(), StandardCharsets.UTF_8);
String geocodingUrl = "https://geocoding-api.open-meteo.com/v1/search?name=" + encodedCity + "&count=1";
Map<?, ?> geocodingResponse = webClient.get()
.uri(geocodingUrl)
.retrieve()
.bodyToMono(Map.class)
.timeout(Duration.ofSeconds(15))
.block();
List<?> results = Collections.emptyList();
if (geocodingResponse != null && geocodingResponse.containsKey("results")) {
Object resultsObj = geocodingResponse.get("results");
if (resultsObj instanceof List) {
results = (List<?>) resultsObj;
}
}
if (results.isEmpty()) {
return "Error: City not found: " + city;
}
var location = (Map<?, ?>) results.get(0);
var latitude = ((Number) location.get("latitude")).doubleValue();
var longitude = ((Number) location.get("longitude")).doubleValue();
var cityName = (String) location.get("name");
var country = location.get("country") != null ? (String) location.get("country") : "";
// Get weather data
String weatherUrl = String.format(
"https://api.open-meteo.com/v1/forecast?latitude=%s&longitude=%s&daily=temperature_2m_max,temperature_2m_min&timezone=auto&start_date=%s&end_date=%s",
latitude, longitude, date, date
);
Map<?, ?> weatherResponse = webClient.get()
.uri(weatherUrl)
.retrieve()
.bodyToMono(Map.class)
.timeout(Duration.ofSeconds(15))
.block();
if (weatherResponse == null) {
return "Error: No response from weather service";
}
var dailyData = (Map<?, ?>) weatherResponse.get("daily");
var dailyUnits = (Map<?, ?>) weatherResponse.get("daily_units");
if (dailyData == null || dailyUnits == null) {
return "Error: Invalid weather data format";
}
var maxTempList = (List<?>) dailyData.get("temperature_2m_max");
var minTempList = (List<?>) dailyData.get("temperature_2m_min");
if (maxTempList == null || minTempList == null || maxTempList.isEmpty() || minTempList.isEmpty()) {
return "Error: No temperature data for date: " + date;
}
var maxTemp = ((Number) maxTempList.get(0)).doubleValue();
var minTemp = ((Number) minTempList.get(0)).doubleValue();
var unit = (String) dailyUnits.get("temperature_2m_max");
String locationDisplay = cityName + (country.isEmpty() ? "" : ", " + country);
String formattedUnit = unit.replace("°", " deg ");
logger.info("Retrieved weather for {}: min: {}{}, max: {}{}",
locationDisplay, minTemp, formattedUnit, maxTemp, formattedUnit);
return String.format(
"Weather for %s on %s:\nMin: %.1f%s, Max: %.1f%s",
locationDisplay, date, minTemp, formattedUnit, maxTemp, formattedUnit
);
} catch (Exception e) {
logger.error("Error fetching weather for city: {}, date: {}", city, date, e);
return "Error: Unable to fetch weather data - " + e.getMessage();
}
}
}
EOF
The WeatherService:
- Converts city names to coordinates using the Geocoding API
- Fetches weather data from Open-Meteo API
- Returns formatted temperature forecasts
- Handles errors gracefully
Update ChatService with Tools
Add the tools to ChatService:
src/main/java/com/example/ai/agent/service/ChatService.java
...
private final ChatMemoryService chatMemoryService;
public static final String SYSTEM_PROMPT = """
You are a helpful AI Agent for travel and expenses.
Guidelines:
1. Use markdown tables for structured data
2. If unsure, say "I don't know"
3. Use provided context for company policies
4. Use tools for dynamic data (flights, weather, bookings, currency)
""";
public ChatService(ChatMemoryService chatMemoryService,
VectorStore vectorStore,
DateTimeService dateTimeService,
WeatherService weatherService,
ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder
.defaultSystem(SYSTEM_PROMPT)
.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore).build()) // RAG for policies
.defaultTools(dateTimeService, weatherService) // Custom tools
.build();
this.chatMemoryService = chatMemoryService;
}
...
Testing Tools
Let's test the tool calling with real-time information:
./mvnw spring-boot:test-run
Test with REST API:
# Test current date/time and weather forecast
curl -X POST http://localhost:8080/api/chat/message \
-H "Content-Type: application/json" \
-d '{"prompt": "I would like to travel to Paris next week from Monday to Friday. What is the weather forecast?", "userId": "alice"}' \
--no-buffer
# Response: "Weather for Paris, France on 2025-11-11: Min: 8.7 deg C, Max: 14.3 deg C"
✅ Success! The agent now has access to real-time information through tools.
You can also test in the UI at http://localhost:8080 - ask about weather, dates, or travel planning questions that require real-time data.
How Tool Calling Works
When you ask "What is the weather tomorrow in Paris?", here's what happens:
- AI analyzes the question: Recognizes it needs current date and weather data
- AI calls getCurrentDateTime: Gets today's date to calculate "tomorrow"
- AI calls getWeather: Fetches forecast for Paris on the calculated date
- AI synthesizes response: Combines tool results into a natural language answer
The AI automatically decides which tools to call, in what order, and with what parameters—all based on the tool descriptions you provided.
Let's continue the chat:
curl -X POST http://localhost:8080/api/chat/message \
-H "Content-Type: application/json" \
-d '{
"prompt": "Please find me inbound and outbound flights and accommodations for a trip from London to Paris next week, From Monday to Friday. I travel alone, prefer BA flights in the first part of the day, and choose accommodation which is the most expensive, but comply with our travel policy. Give me a travel itinerary with flights, accommodation, prices and weather forecast for each day of the travel.",
"userId": "alice"
}' \
--no-buffer
# Response: "I understand you'd like a complete travel itinerary for your London to Paris trip next week (Monday-Friday), but I don't have access to flight booking systems or hotel reservation platforms to search for specific BA flights or accommodations."
❌ Problem discovered: The agent doesn't have access to flight booking systems or hotel reservation platforms to search for specific BA flights or accommodations, and we cannot integrate every required system via APIs like we did with the weather system. We need a more flexible way.
We'll address this limitation in the next part of the series! Stay tuned!
Cleanup
To stop the application, press Ctrl+C in the terminal where it's running.
The PostgreSQL container will continue running (due to withReuse(true)). If necessary, stop and remove it:
docker stop ai-agent-postgres
docker rm ai-agent-postgres
(Optional) To remove all data and start fresh:
docker volume prune
Commit Changes
git add .
git commit -m "Add tool calling with date/time and weather"
Conclusion
In this post, we've added real-time capabilities to our AI agent through tool calling:
- DateTimeService: Provides current date and time in any timezone
- WeatherService: Fetches weather forecasts from Open-Meteo API
- Automatic tool selection: AI decides which tools to call based on user questions
- Real-time data: Agent can now answer time-sensitive questions accurately
Our AI agent now has the complete foundation for production use: memory (Part 2), knowledge (Part 3), and real-time information access (Part 4). It can remember conversations, answer policy questions, and provide up-to-date information—all essential capabilities for real-world applications.
What's Next
We need to give the AI Agent the ability to connect to APIs on the Internet or in the Intranet in a flexible way.
Learn More
- Spring AI Tool Calling Documentation
- Amazon Bedrock User Guide
- Open-Meteo Weather API
- Part 1: Create an AI Agent
- Part 2: Add Memory
- Part 3: Add Knowledge
Let's continue building intelligent Java applications with Spring AI!



Top comments (0)