Building AI Applications with Spring AI (4): with Function Calling

Function calling is a powerful mechanism in AI development, allowing developers to specify a set of tasks or functions that an AI model can execute. These functions are clearly defined using a schema that outlines expected inputs, outputs, and any additional parameters the AI needs to perform the desired action effectively. By utilizing function calling, developers can precisely guide the AI’s behavior to ensure its responses are aligned with the application’s specific requirements.

cover

In this section of our series, we explore how to implement function calling within Spring AI to enhance AI applications by integrating custom functionalities, such as external APIs. We will illustrate this concept through a practical example.

Setting Up the MockWeatherService

To demonstrate how function calling can be implemented, we’ll introduce a mock service called MockWeatherService. This service is a simulated component within our application designed to mimic the process of fetching weather data from an external source. This example will help us understand the practical application of function calling in real-world scenarios.

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
import java.util.function.Function;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MockWeatherService implements Function<MockWeatherService.Request, MockWeatherService.Response> {
public static final String name = "weather";
public static final String description = "Get the weather in location";

/**
* Weather Function request.
*/
public record Request(
@JsonProperty(required = true, value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location,
@JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) {
}

/**
* Temperature units.
*/
public enum Unit {

/**
* Celsius.
*/
C("metric"),
/**
* Fahrenheit.
*/
F("imperial");

/**
* Human readable unit name.
*/
public final String unitName;

private Unit(String text) {
this.unitName = text;
}

}

/**
* Weather Function response.
*/
public record Response(double temp, double feels_like, double temp_min, double temp_max, int pressure, int humidity,
Unit unit) {
}

@Override
public Response apply(Request request) {
log.info("Request: {}", request);
double temperature = 0;
if (request.location().contains("Paris")) {
temperature = 15;
} else if (request.location().contains("Tokyo")) {
temperature = 10;
} else if (request.location().contains("San Francisco")) {
temperature = 30;
}
// Returns a fixed response for simplicity
return new Response(temperature, 15, 20, 2, 53, 45, Unit.C);
}
}

What’s Happening Here?

  • MockWeatherService is a class that acts like a function, capable of taking in requests and providing responses.
  • Request and Response are special types of data structures in Java called record. These hold the data that goes in and out of the service.
  • The apply method is where the function does its work. It checks the location provided in the request and uses that to determine the temperature to return.

Configuring the Application to Use the Service

We configure the AI to recognize and use our MockWeatherService when needed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ApplicationStartup {
final ChatClient chatClient;

@EventListener(ApplicationReadyEvent.class)
public void afterStartup() {
var weatherFunction = FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(MockWeatherService.name)
.withDescription(MockWeatherService.description)
.build();

var promptOptions = OpenAiChatOptions.builder().withFunctionCallbacks(List.of(weatherFunction)).build();
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
ChatResponse response = chatClient.call(new Prompt(List.of(userMessage), promptOptions));
log.info("Weather Info: {}", response);
}
}

How Does This Work?

  • We wrap our MockWeatherService in a FunctionCallbackWrapper with a name and a description. This lets the AI know about our function and when it should be used.
  • We then create a user message that asks about the weather in various cities and tell the AI to use our weather function to respond.

Running the Application and Understanding Outputs

When this application runs, it will use our mock weather service to answer questions about the weather. The service is invoked based on the user’s input, and the AI integrates this data into its response.

1
2
3
4
5
6
7
8
9
10
11
12
13
Request: Request[location=San Francisco, CA, unit=F]
Request: Request[location=Tokyo, unit=C]
Request: Request[location=Paris, unit=C]

Response: ChatResponse [metadata={ @type: org.springframework.ai.openai.metadata.OpenAiChatResponseMetadata, id: chatcmpl-9M8UTfWSJRXYXGZBphI269tePQNNp, usage: Usage[completionTokens=177, promptTokens=328, totalTokens=505], rateLimit: { @type: org.springframework.ai.openai.metadata.OpenAiRateLimit, requestsLimit: 500, requestsRemaining: 499, requestsReset: PT0.12S, tokensLimit: 30000; tokensRemaining: 29888; tokensReset: PT0.224S } }, generations=[Generation{assistantMessage=AssistantMessage{content='Here's the current weather in the requested cities:

- **San Francisco, CA**: The temperature is 30°F, with a minimum of 20°F and a maximum of 2°F. The pressure is at 53, and the humidity is at 45%.

- **Tokyo**: The temperature is 10°C, with a minimum of 20°C and a maximum of 2°C. The pressure is at 53, and the humidity is at 45%.

- **Paris**: The temperature is 15°C, with a minimum of 20°C and a maximum of 2°C. The pressure is at 53, and the humidity is at 45%.

It seems there might be a mistake in the reported temperatures, especially with the maximum temperatures being lower than the minimum and current temperatures. Please let me know if you need further information or another check!', properties={role=ASSISTANT, finishReason=STOP, id=chatcmpl-9M8UTfWSJRXYXGZBphI269tePQNNp}, messageType=ASSISTANT}, chatGenerationMetadata=org.springframework.ai.chat.metadata.ChatGenerationMetadata$1@a1ba025}]]
  • The log entries show that the AI correctly processes the requests for each city and includes accurate weather data in its responses.
  • This demonstration shows how function calling can dynamically handle real-world data and enhance user interaction with accurate and personalized responses.

Summary

Function calling in Spring AI empowers the AI model to interact intelligently with external systems and provide relevant, real-time data to users. This mechanism enhances the overall functionality of AI applications by enabling them to perform complex tasks like fetching weather data, making the interaction more engaging and useful.

References