Bootiful Spring Boot 3.4: Spring AI

Engineering | Josh Long | November 24, 2024 | ...

I love Spring AI. It’s an amazing project designed to bring the patterns and practices of AI engineering to the Spring Boot developer. It’s got clean idiomatic abstractions that’ll make any Sring developer feel right at home, and it has a ton of integrations with all manner of different vector stores, embedding models, transcription models, image modes, and chat models.

The new release, m4, builds upon Spring Boot 3.4 and adds a ton of new features. As usual, I can’t hope to look at all the new features, but the release notes do an exquisite job.

  • there’s new Amazon Bedrock Convertse support
  • there’s been a ton of work done to support more expressive function calling, both in Java and Kotlin
  • there’s the first cut of support for the ideas taking shape in the AI community around "advanced and modular RAG". RAG, of course, is retrieval augmented generation and refers to using data from a system or service to inform the response generated by a chat model. These ideas are articulated in this paepr and this one, and the building blocks are taking root in this new release. Amazing!
  • updates across the board to various vector store integrations and chat models
  • there’saeven a comprehensive chat model comparison page in the documentation
  • vector storage and embedding improvements, including enhancements to vector stores like Azure and Milvus

The evolving nature of the functional callback support has so enamored me. Spring AI aims to make connecting your AI models with your data and business logic easy. Remember: the name of the game here is integration. Most people aren’t going to build their models. They’re going to integrate existing ones into their business logic and services. And where does all that stuff live? In Spring, of course. Spring AI is a natural! And it keeps getting easier. In this release, there’s new support for describing and then letting models invoke functions if they decide they’ve got the need to do so.

Here’s a simple example demonstrating the definition of a FunctionCallback and the Spring AI ChatClient, which is your first port of call for all interactions with a Sprign AI ChatModel.

package com.example.bootiful_34.ai;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
class AiConfiguration {

	@Bean
	ChatClient chatClient(ChatClient.Builder builder, FunctionCallback weatherFunctionCallback) {
		return builder.defaultFunctions(weatherFunctionCallback).build();
	}

	@Bean
	FunctionCallback weatherFunctionCallback() {
		return FunctionCallback.builder()
			.description("returns the weather for a given city")
			.function("getCurrentWeatherForACity",
					(WeatherRequest wr) -> new WeatherResponse(wr.city(),
							wr.city().equalsIgnoreCase("san francisco") ? 68.0f : 72.0f))
			.inputType(WeatherRequest.class)
			.build();
	}

}

record WeatherRequest(String city) {
}

record WeatherResponse(String city, float temperature) {
}

It’s a pretty trivial example: given a WeatherRequest specifying a city, we make up and return some temperature. In this case, I’ve got a hardcoded case for San Francisco.

We put all of this to work in the test, knowing that the model won’t know the current weather in a given city, and so we will have to defer to a function that we provide. It knows about the nature of the function because we’ve given it a description when we configured the FunctionCallback. It knows that the city parameter is Stringand that San Francisco is a city, so it passes the stringSan Francisco` to our function, allowing us to provide the expected response. We verify as much with the test, asserting that the response is the hardcoded magic number.

package com.example.bootiful_34.ai;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class AiConfigurationTest {

	@Test
	void functionCallbacks(@Autowired ChatClient cc) {
		var weatherInSf = cc.prompt("give me the weather for the city of san francisco").call().content();
		Assertions.assertNotNull(weatherInSf);
		Assertions.assertTrue(weatherInSf.contains("68"));
	}

}

And just like that, we’ve given our AI model the ability to ask questions about our systems and services and to support a more agentic workflow. Easy!

Get the Spring newsletter

Stay connected with the Spring newsletter

Subscribe

Get ahead

VMware offers training and certification to turbo-charge your progress.

Learn more

Get support

Tanzu Spring offers support and binaries for OpenJDK™, Spring, and Apache Tomcat® in one simple subscription.

Learn more

Upcoming events

Check out all the upcoming events in the Spring community.

View all