Getting Started with Spring AI Function Calling
This article will show you how to use Spring AI support for Java function calling with the OpenAI chat model. The Spring AI function calling feature lets us connect the LLM capabilities with external APIs or systems. OpenAI’s models are trained to know when to call a function. We will work on implementing a Java function that takes the call arguments from the AI model and sends the result back. Our main goal is to connect to the third-party APIs to provide these results. Then the AI model uses the provided results to complete the conversation.
This article is the second part of a series describing some of the AI project’s most notable features. Before reading on, I recommend checking out my introduction to Spring AI, which is available here. The first part describes such features as prompts, structured output, chat memory, and built-in advisors. Additionally, it demonstrates the capability to switch between the most popular AI chat model API providers.
Source Code
If you would like to try it by yourself, you may always take a look at my source code. To do that, you must clone my sample GitHub repository. Then you should only follow my instructions.
Problem
Whenever I create a new article or example related to AI, I like to define the problem I’m trying to solve. The problem we will solve in this exercise is visible in the following prompt template. I’m asking the AI model about the value of my stock wallet. However, the model doesn’t know how many shares I have, and can’t get the latest stock prices. Since the OpenAI model is trained on a static dataset it does not have direct access to the online services or APIs.
So, in this case, we should provide private data with our wallet structure and “connect” our model with a public API that returns live stock market data. Let’s see how we tackle this challenge with Spring AI function calling.
Create Spring Functions
WalletService Supplier
We will begin with a source code. Then we will visualize the whole process on the diagram. Spring AI supports different ways of registering a function to call. You can read more about it in the Spring AI docs here. We will choose the way based on plain Java functions defined as beans in the Spring application context. This approach allows us to use interfaces from the java.util.function
package such as Function
, Supplier
, or Consumer
. Our first function takes no input, so it implements the Supplier interface. It just returns a list of shares that we currently have. It obtains such information from the database through the Spring Data WalletRepository
bean.
public class WalletService implements Supplier<WalletResponse> {
private WalletRepository walletRepository;
public WalletService(WalletRepository walletRepository) {
this.walletRepository = walletRepository;
}
@Override
public WalletResponse get() {
return new WalletResponse((List<Share>) walletRepository.findAll());
}
}
JavaInformation about the number of owned shares is stored in the share
table. Each row contains a company name and the quantity of that company shares.
@Entity
public class Share {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String company;
private int quantity;
// ... GETTERS/SETTERS
}
JavaThe Spring Boot application launches an embedded, in-memory database and inserts test data into the stock table. Our wallet contains the most popular companies on the U.S. stock market, including Amazon, Meta, and Microsoft.
insert into share(id, company, quantity) values (1, 'AAPL', 100);
insert into share(id, company, quantity) values (2, 'AMZN', 300);
insert into share(id, company, quantity) values (3, 'META', 300);
insert into share(id, company, quantity) values (4, 'MSFT', 400);
insert into share(id, company, quantity) values (5, 'NVDA', 200);
SQLStockService Function
Our second function takes an input argument and returns an output. Therefore, it implements the Function
interface. It must interact with live stock market API to get the current price of a given company share. We use the api.twelvedata.com
service to access stock exchange quotes. The function returns a current price wrapped by the StockResponse
object.
public class StockService implements Function<StockRequest, StockResponse> {
private static final Logger LOG = LoggerFactory.getLogger(StockService.class);
@Autowired
RestTemplate restTemplate;
@Value("${STOCK_API_KEY}")
String apiKey;
@Override
public StockResponse apply(StockRequest stockRequest) {
StockData data = restTemplate.getForObject("https://api.twelvedata.com/time_series?symbol={0}&interval=1min&outputsize=1&apikey={1}",
StockData.class,
stockRequest.company(),
apiKey);
DailyStockData latestData = data.getValues().get(0);
LOG.info("Get stock prices: {} -> {}", stockRequest.company(), latestData.getClose());
return new StockResponse(Float.parseFloat(latestData.getClose()));
}
}
JavaHere are the Java records for request and response objects.
public record StockRequest(String company) { }
public record StockResponse(Float price) { }
JavaTo summarize, the first function accesses the database to get owned shares quantity, while the second function communicates public API to get the current price of a company share.
Spring AI Function Calling Flow
Architecture
Here’s the diagram that visualizes the flow of our application. The Spring AI Prompt
object must contain references to our function beans. This allows the OpenAI model to recognize when a function should be called. However, the model does not call the function directly but only generates JSON used to call the function on the application side. Each function must provide a name, description, and signature (as JSON schema) to let the model know what arguments it expects. We have two functions. The StockService function returns a list of owned company shares, while the second function takes a single company name as the argument. This is where the magic happens. The chat model detects that it should call the WalletService
function for each object in the list returned by the StockService
function. The final response combines results received from both our functions.
Implementation
To implement the flow visualized above we must register our functions as Spring beans. The method name determines the name of the bean in the Spring context. Each bean declaration should also contain a description, which helps the model to understand when to call the function. The WalletResponse
function is registered under the numberOfShares
name, while the StockService
function under the latestStockPrices
name. The WalletService
doesn’t take any input arguments, but injects the WalletRepository
bean to interact with the database.
@Bean
@Description("Number of shares for each company in my portfolio")
public Supplier<WalletResponse> numberOfShares(WalletRepository walletRepository) {
return new WalletService(walletRepository);
}
@Bean
@Description("Latest stock prices")
public Function<StockRequest, StockResponse> latestStockPrices() {
return new StockService();
}
JavaFinally, let’s take a look at the REST controller implementation. It exposes the GET /wallet
endpoint that communicates with the OpenAI chat model. When creating a prompt we should register both our functions using the OpenAiChatOptions
class and its function
method. The reference contains only the function @Bean
name.
@RestController
@RequestMapping("/wallet")
public class WalletController {
private final ChatClient chatClient;
public WalletController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
@GetMapping
String calculateWalletValue() {
PromptTemplate pt = new PromptTemplate("""
What’s the current value in dollars of my wallet based on the latest stock daily prices ?
""");
return this.chatClient.prompt(pt.create(
OpenAiChatOptions.builder()
.function("numberOfShares")
.function("latestStockPrices")
.build()))
.call()
.content();
}
}
JavaRun and Test the Spring AI Application
Before running the app, we must export OpenAI and Twelvedata API keys as environment variables.
export STOCK_API_KEY=<YOUR_TWELVEDATA_API_KEY>
export OPEN_AI_TOKEN=<YOUR_OPENAI_TOKEN>
ShellSessionWe must create an account on the Twelvedata platform to obtain its API key. The Twelvedata platform provides API to get the latest stock prices.
Of course, we must have an API key on the OpenAI platform. Once you create an account there you should go to that page. Then choose the name for your token and copy it after creation.
Then, we run our Spring AI app using the following Maven command:
mvn spring-boot:run
ShellSessionAfter running the app, we can call the /wallet
endpoint to calculate our stock portfolio.
curl http://localhost:8080/wallet
ShellSessionHere’s the response returned by OpenAI for the provided test data and the current stock market prices.
Then, let’s switch to the application logs. We can see that the StockService
function was called five times – once for every company in the wallet. After we added the SimpleLoggerAdvisor
advisor to the and set the property logging.level.org.springframework.ai
to DEBUG
, we can observe detailed logs with requests and responses from the OpenAI chat model.
Final Thoughts
In this article, we analyzed the Spring AI integration with function support in AI models. OpenAI’s function calling is a powerful feature that enhances how AI models interact with external tools, APIs, and structured data. It makes AI more interactive and practical for real-world applications. Spring AI provides a flexible way to register and invoke such functions. However, it still requires attention from developers, who need to define clear function schemas and handle edge cases.
Leave a Reply