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):

  1. Define tools as Java methods annotated with @Tool
  2. Provide clear descriptions to help the AI choose the right tool
  3. Let the AI automatically call tools when needed
  4. 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

1
2
3
4
5
6
7
8
9
10
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-agent application
  • 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:

1
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:

1
2
3
4
5
<!-- 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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:

  1. Converts city names to coordinates using the Geocoding API
  2. Fetches weather data from Open-Meteo API
  3. Returns formatted temperature forecasts
  4. Handles errors gracefully

Update ChatService with Tools

Add the tools to ChatService:

src/main/java/com/example/ai/agent/service/ChatService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
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:

1
./mvnw spring-boot:test-run

Test with REST API:

1
2
3
4
5
6
# 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.

Get Weather Forecast

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:

  1. AI analyzes the question: Recognizes it needs current date and weather data
  2. AI calls getCurrentDateTime: Gets today’s date to calculate “tomorrow”
  3. AI calls getWeather: Fetches forecast for Paris on the calculated date
  4. 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:

1
2
3
4
5
6
7
8
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."

Booking problem

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:

1
2
docker stop ai-agent-postgres
docker rm ai-agent-postgres

(Optional) To remove all data and start fresh:

1
docker volume prune

Commit Changes

1
2
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.

Challenges and Solutions - Part 4

Learn More

Let’s continue building intelligent Java applications with Spring AI!

Comments