Skip to content

Commit f36b73c

Browse files
committed
refactor: migrate from functions to tools terminology
Refactors the test codebase to use tools instead of functions. - Rename FunctionCallback to FunctionToolCallback - Rename FunctionCallingOptions to ToolCallingChatOptions - Update API methods from functions() to tools() - Deprecate function-related methods in favor of tool alternatives - Refactor MethodToolCallback implementation with improved builder pattern - Update all tests to use new tool-based APIs - Add funcs to tools migration guide Signed-off-by: Christian Tzolov <[email protected]>
1 parent ff287a5 commit f36b73c

File tree

40 files changed

+624
-346
lines changed

40 files changed

+624
-346
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
# Migrating from FunctionCallback to ToolCallback API
2+
3+
This guide helps you migrate from the deprecated FunctionCallback API to the new ToolCallback API in Spring AI.
4+
5+
## Overview of Changes
6+
7+
The Spring AI project is moving from "functions" to "tools" terminology to better align with industry standards. This involves several API changes while maintaining backward compatibility through deprecated methods.
8+
9+
## Key Changes
10+
11+
1. `FunctionCallback``ToolCallback`
12+
2. `FunctionCallback.builder().functions()``FunctionToolCallback.builder()`
13+
3. `FunctionCallback.builder().method()``MethodToolCallback.builder()`
14+
4. `FunctionCallingOptions``ToolCallingChatOptions`
15+
5. Method names from `functions()``tools()`
16+
17+
## Migration Examples
18+
19+
### 1. Basic Function Callback
20+
21+
Before:
22+
```java
23+
FunctionCallback.builder()
24+
.function("getCurrentWeather", new MockWeatherService())
25+
.description("Get the weather in location")
26+
.inputType(MockWeatherService.Request.class)
27+
.build()
28+
```
29+
30+
After:
31+
```java
32+
FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
33+
.description("Get the weather in location")
34+
.inputType(MockWeatherService.Request.class)
35+
.build()
36+
```
37+
38+
### 2. ChatClient Usage
39+
40+
Before:
41+
```java
42+
String response = ChatClient.create(chatModel)
43+
.prompt()
44+
.user("What's the weather like in San Francisco?")
45+
.functions(FunctionCallback.builder()
46+
.function("getCurrentWeather", new MockWeatherService())
47+
.description("Get the weather in location")
48+
.inputType(MockWeatherService.Request.class)
49+
.build())
50+
.call()
51+
.content();
52+
```
53+
54+
After:
55+
```java
56+
String response = ChatClient.create(chatModel)
57+
.prompt()
58+
.user("What's the weather like in San Francisco?")
59+
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
60+
.description("Get the weather in location")
61+
.inputType(MockWeatherService.Request.class)
62+
.build())
63+
.call()
64+
.content();
65+
```
66+
67+
### 3. Method-Based Function Callbacks
68+
69+
Before:
70+
```java
71+
FunctionCallback.builder()
72+
.method("getWeatherInLocation", String.class, Unit.class)
73+
.description("Get the weather in location")
74+
.targetClass(TestFunctionClass.class)
75+
.build()
76+
```
77+
78+
After:
79+
```java
80+
var toolMethod = ReflectionUtils.findMethod(
81+
TestFunctionClass.class, "getWeatherInLocation", String.class, Unit.class);
82+
83+
MethodToolCallback.builder()
84+
.toolDefinition(ToolDefinition.builder(toolMethod)
85+
.description("Get the weather in location")
86+
.build())
87+
.toolMethod(toolMethod)
88+
.build()
89+
```
90+
91+
And you can use the same `ChatClient#tools()` API to register method-based tool callbackes:
92+
93+
```java
94+
String response = ChatClient.create(chatModel)
95+
.prompt()
96+
.user("What's the weather like in San Francisco?")
97+
.tools(MethodToolCallback.builder()
98+
.toolDefinition(ToolDefinition.builder(toolMethod)
99+
.description("Get the weather in location")
100+
.build())
101+
.toolMethod(toolMethod)
102+
.build())
103+
.call()
104+
.content();
105+
```
106+
107+
### 4. Options Configuration
108+
109+
Before:
110+
```java
111+
FunctionCallingOptions.builder()
112+
.model(modelName)
113+
.function("weatherFunction")
114+
.build()
115+
```
116+
117+
After:
118+
```java
119+
ToolCallingChatOptions.builder()
120+
.model(modelName)
121+
.tools("weatherFunction")
122+
.build()
123+
```
124+
125+
### 5. Default Functions in ChatClient Builder
126+
127+
Before:
128+
```java
129+
ChatClient.builder(chatModel)
130+
.defaultFunctions(FunctionCallback.builder()
131+
.function("getCurrentWeather", new MockWeatherService())
132+
.description("Get the weather in location")
133+
.inputType(MockWeatherService.Request.class)
134+
.build())
135+
.build()
136+
```
137+
138+
After:
139+
```java
140+
ChatClient.builder(chatModel)
141+
.defaultTools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
142+
.description("Get the weather in location")
143+
.inputType(MockWeatherService.Request.class)
144+
.build())
145+
.build()
146+
```
147+
148+
### 6. Spring Bean Configuration
149+
150+
Before:
151+
```java
152+
@Bean
153+
public FunctionCallback weatherFunctionInfo() {
154+
return FunctionCallback.builder()
155+
.function("WeatherInfo", new MockWeatherService())
156+
.description("Get the current weather")
157+
.inputType(MockWeatherService.Request.class)
158+
.build();
159+
}
160+
```
161+
162+
After:
163+
```java
164+
@Bean
165+
public ToolCallback weatherFunctionInfo() {
166+
return FunctionToolCallback.builder("WeatherInfo", new MockWeatherService())
167+
.description("Get the current weather")
168+
.inputType(MockWeatherService.Request.class)
169+
.build();
170+
}
171+
```
172+
173+
## Breaking Changes
174+
175+
1. The `method()` configuration in function callbacks has been replaced with a more explicit method tool configuration using `ToolDefinition` and `MethodToolCallback`.
176+
177+
2. When using method-based callbacks, you now need to explicitly find the method using `ReflectionUtils` and provide it to the builder.
178+
179+
3. For non-static methods, you must now provide both the method and the target object:
180+
```java
181+
MethodToolCallback.builder()
182+
.toolDefinition(ToolDefinition.builder(toolMethod)
183+
.description("Description")
184+
.build())
185+
.toolMethod(toolMethod)
186+
.toolObject(targetObject)
187+
.build()
188+
```
189+
190+
## Deprecated Methods
191+
192+
The following methods are deprecated and will be removed in a future release:
193+
194+
- `ChatClient.Builder.defaultFunctions(String...)`
195+
- `ChatClient.Builder.defaultFunctions(FunctionCallback...)`
196+
- `ChatClient.RequestSpec.functions()`
197+
198+
Use their `tools` counterparts instead.
199+
200+
## @Tool tool definition path.
201+
202+
Now you can use the method-level annothation (`@Tool`) to register tools with Spring AI
203+
204+
```java
205+
public class Home {
206+
207+
@Tool(description = "Turn light On or Off in a room.")
208+
public void turnLight(String roomName, boolean on) {
209+
// ...
210+
logger.info("Turn light in room: {} to: {}", roomName, on);
211+
}
212+
}
213+
214+
Home homeAutomation = new HomeAutomation();
215+
216+
String response = ChatClient.create(this.chatModel).prompt()
217+
.user("Turn the light in the living room On.")
218+
.tools(homeAutomation)
219+
.call()
220+
.content();
221+
222+
```
223+
224+
225+
## Additional Notes
226+
227+
1. The new API provides better separation between tool definition and implementation.
228+
2. Tool definitions can be reused across different implementations.
229+
3. The builder pattern has been simplified for common use cases.
230+
4. Better support for method-based tools with improved error handling.
231+
232+
## Timeline
233+
234+
The deprecated methods will be maintained for backward compatibility in the current major version but will be removed in the next major release. It's recommended to migrate to the new API as soon as possible.

models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelIT.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@
4747
import org.springframework.ai.converter.ListOutputConverter;
4848
import org.springframework.ai.converter.MapOutputConverter;
4949
import org.springframework.ai.model.Media;
50-
import org.springframework.ai.model.function.FunctionCallback;
51-
import org.springframework.ai.model.function.FunctionCallingOptions;
50+
import org.springframework.ai.model.tool.ToolCallingChatOptions;
51+
import org.springframework.ai.tool.function.FunctionToolCallback;
5252
import org.springframework.beans.factory.annotation.Autowired;
5353
import org.springframework.beans.factory.annotation.Value;
5454
import org.springframework.boot.SpringBootConfiguration;
@@ -258,7 +258,7 @@ void multiModalityPdfTest() throws IOException {
258258
List.of(new Media(new MimeType("application", "pdf"), pdfData)));
259259

260260
var response = this.chatModel.call(new Prompt(List.of(userMessage),
261-
FunctionCallingOptions.builder().model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName()).build()));
261+
ToolCallingChatOptions.builder().model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName()).build()));
262262

263263
assertThat(response.getResult().getOutput().getText()).containsAnyOf("Spring AI", "portable API");
264264
}
@@ -273,8 +273,7 @@ void functionCallTest() {
273273

274274
var promptOptions = AnthropicChatOptions.builder()
275275
.model(AnthropicApi.ChatModel.CLAUDE_3_OPUS.getName())
276-
.functionCallbacks(List.of(FunctionCallback.builder()
277-
.function("getCurrentWeather", new MockWeatherService())
276+
.functionCallbacks(List.of(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
278277
.description(
279278
"Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.")
280279
.inputType(MockWeatherService.Request.class)
@@ -306,8 +305,7 @@ void streamFunctionCallTest() {
306305

307306
var promptOptions = AnthropicChatOptions.builder()
308307
.model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName())
309-
.functionCallbacks(List.of(FunctionCallback.builder()
310-
.function("getCurrentWeather", new MockWeatherService())
308+
.functionCallbacks(List.of(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
311309
.description(
312310
"Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.")
313311
.inputType(MockWeatherService.Request.class)
@@ -337,8 +335,7 @@ void streamFunctionCallUsageTest() {
337335

338336
var promptOptions = AnthropicChatOptions.builder()
339337
.model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName())
340-
.functionCallbacks(List.of(FunctionCallback.builder()
341-
.function("getCurrentWeather", new MockWeatherService())
338+
.functionCallbacks(List.of(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
342339
.description(
343340
"Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.")
344341
.inputType(MockWeatherService.Request.class)

models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
import org.springframework.ai.chat.model.ChatResponse;
4141
import org.springframework.ai.converter.BeanOutputConverter;
4242
import org.springframework.ai.converter.ListOutputConverter;
43-
import org.springframework.ai.model.function.FunctionCallback;
43+
import org.springframework.ai.tool.function.FunctionToolCallback;
4444
import org.springframework.beans.factory.annotation.Autowired;
4545
import org.springframework.beans.factory.annotation.Value;
4646
import org.springframework.boot.test.context.SpringBootTest;
@@ -211,8 +211,7 @@ void functionCallTest() {
211211
// @formatter:off
212212
String response = ChatClient.create(this.chatModel).prompt()
213213
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
214-
.functions(FunctionCallback.builder()
215-
.function("getCurrentWeather", new MockWeatherService())
214+
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
216215
.inputType(MockWeatherService.Request.class)
217216
.build())
218217
.call()
@@ -230,8 +229,7 @@ void functionCallWithGeneratedDescription() {
230229
// @formatter:off
231230
String response = ChatClient.create(this.chatModel).prompt()
232231
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
233-
.functions(FunctionCallback.builder()
234-
.function("getCurrentWeatherInLocation", new MockWeatherService())
232+
.tools(FunctionToolCallback.builder("getCurrentWeatherInLocation", new MockWeatherService())
235233
.inputType(MockWeatherService.Request.class)
236234
.build())
237235
.call()
@@ -248,8 +246,7 @@ void defaultFunctionCallTest() {
248246

249247
// @formatter:off
250248
String response = ChatClient.builder(this.chatModel)
251-
.defaultFunctions(FunctionCallback.builder()
252-
.function("getCurrentWeather", new MockWeatherService())
249+
.defaultTools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
253250
.description("Get the weather in location")
254251
.inputType(MockWeatherService.Request.class)
255252
.build())
@@ -271,8 +268,7 @@ void streamFunctionCallTest() {
271268
// @formatter:off
272269
Flux<String> response = ChatClient.create(this.chatModel).prompt()
273270
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
274-
.functions(FunctionCallback.builder()
275-
.function("getCurrentWeather", new MockWeatherService())
271+
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
276272
.description("Get the weather in location")
277273
.inputType(MockWeatherService.Request.class)
278274
.build())

0 commit comments

Comments
 (0)