Skip to content

Commit 8f5d7df

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 2f14597 commit 8f5d7df

File tree

26 files changed

+412
-174
lines changed

26 files changed

+412
-174
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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+
### 4. Options Configuration
92+
93+
Before:
94+
```java
95+
FunctionCallingOptions.builder()
96+
.model(modelName)
97+
.function("weatherFunction")
98+
.build()
99+
```
100+
101+
After:
102+
```java
103+
ToolCallingChatOptions.builder()
104+
.model(modelName)
105+
.tools("weatherFunction")
106+
.build()
107+
```
108+
109+
### 5. Default Functions in ChatClient Builder
110+
111+
Before:
112+
```java
113+
ChatClient.builder(chatModel)
114+
.defaultFunctions(FunctionCallback.builder()
115+
.function("getCurrentWeather", new MockWeatherService())
116+
.description("Get the weather in location")
117+
.inputType(MockWeatherService.Request.class)
118+
.build())
119+
.build()
120+
```
121+
122+
After:
123+
```java
124+
ChatClient.builder(chatModel)
125+
.defaultTools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
126+
.description("Get the weather in location")
127+
.inputType(MockWeatherService.Request.class)
128+
.build())
129+
.build()
130+
```
131+
132+
### 6. Spring Bean Configuration
133+
134+
Before:
135+
```java
136+
@Bean
137+
public FunctionCallback weatherFunctionInfo() {
138+
return FunctionCallback.builder()
139+
.function("WeatherInfo", new MockWeatherService())
140+
.description("Get the current weather")
141+
.inputType(MockWeatherService.Request.class)
142+
.build();
143+
}
144+
```
145+
146+
After:
147+
```java
148+
@Bean
149+
public ToolCallback weatherFunctionInfo() {
150+
return FunctionToolCallback.builder("WeatherInfo", new MockWeatherService())
151+
.description("Get the current weather")
152+
.inputType(MockWeatherService.Request.class)
153+
.build();
154+
}
155+
```
156+
157+
## Breaking Changes
158+
159+
1. The `method()` configuration in function callbacks has been replaced with a more explicit method tool configuration using `ToolDefinition` and `MethodToolCallback`.
160+
161+
2. When using method-based callbacks, you now need to explicitly find the method using `ReflectionUtils` and provide it to the builder.
162+
163+
3. For non-static methods, you must now provide both the method and the target object:
164+
```java
165+
MethodToolCallback.builder()
166+
.toolDefinition(ToolDefinition.builder(toolMethod)
167+
.description("Description")
168+
.build())
169+
.toolMethod(toolMethod)
170+
.toolObject(targetObject)
171+
.build()
172+
```
173+
174+
## Deprecated Methods
175+
176+
The following methods are deprecated and will be removed in a future release:
177+
178+
- `ChatClient.Builder.defaultFunctions(String...)`
179+
- `ChatClient.Builder.defaultFunctions(FunctionCallback...)`
180+
- `ChatClient.RequestSpec.functions()`
181+
182+
Use their `tools` counterparts instead.
183+
184+
## Additional Notes
185+
186+
1. The new API provides better separation between tool definition and implementation.
187+
2. Tool definitions can be reused across different implementations.
188+
3. The builder pattern has been simplified for common use cases.
189+
4. Better support for method-based tools with improved error handling.
190+
191+
## Timeline
192+
193+
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
@@ -49,8 +49,8 @@
4949
import org.springframework.ai.converter.ListOutputConverter;
5050
import org.springframework.ai.converter.MapOutputConverter;
5151
import org.springframework.ai.model.Media;
52-
import org.springframework.ai.model.function.FunctionCallback;
53-
import org.springframework.ai.model.function.FunctionCallingOptions;
52+
import org.springframework.ai.model.tool.ToolCallingChatOptions;
53+
import org.springframework.ai.tool.function.FunctionToolCallback;
5454
import org.springframework.beans.factory.annotation.Autowired;
5555
import org.springframework.beans.factory.annotation.Value;
5656
import org.springframework.boot.SpringBootConfiguration;
@@ -259,7 +259,7 @@ void multiModalityPdfTest() throws IOException {
259259
List.of(new Media(new MimeType("application", "pdf"), pdfData)));
260260

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

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

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

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

339337
var promptOptions = AnthropicChatOptions.builder()
340338
.model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName())
341-
.functionCallbacks(List.of(FunctionCallback.builder()
342-
.function("getCurrentWeather", new MockWeatherService())
339+
.functionCallbacks(List.of(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
343340
.description(
344341
"Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.")
345342
.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
@@ -42,7 +42,7 @@
4242
import org.springframework.ai.chat.model.ChatResponse;
4343
import org.springframework.ai.converter.BeanOutputConverter;
4444
import org.springframework.ai.converter.ListOutputConverter;
45-
import org.springframework.ai.model.function.FunctionCallback;
45+
import org.springframework.ai.tool.function.FunctionToolCallback;
4646
import org.springframework.beans.factory.annotation.Autowired;
4747
import org.springframework.beans.factory.annotation.Value;
4848
import org.springframework.boot.test.context.SpringBootTest;
@@ -212,8 +212,7 @@ void functionCallTest() {
212212
// @formatter:off
213213
String response = ChatClient.create(this.chatModel).prompt()
214214
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
215-
.functions(FunctionCallback.builder()
216-
.function("getCurrentWeather", new MockWeatherService())
215+
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
217216
.inputType(MockWeatherService.Request.class)
218217
.build())
219218
.call()
@@ -231,8 +230,7 @@ void functionCallWithGeneratedDescription() {
231230
// @formatter:off
232231
String response = ChatClient.create(this.chatModel).prompt()
233232
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
234-
.functions(FunctionCallback.builder()
235-
.function("getCurrentWeatherInLocation", new MockWeatherService())
233+
.tools(FunctionToolCallback.builder("getCurrentWeatherInLocation", new MockWeatherService())
236234
.inputType(MockWeatherService.Request.class)
237235
.build())
238236
.call()
@@ -249,8 +247,7 @@ void defaultFunctionCallTest() {
249247

250248
// @formatter:off
251249
String response = ChatClient.builder(this.chatModel)
252-
.defaultFunctions(FunctionCallback.builder()
253-
.function("getCurrentWeather", new MockWeatherService())
250+
.defaultTools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
254251
.description("Get the weather in location")
255252
.inputType(MockWeatherService.Request.class)
256253
.build())
@@ -272,8 +269,7 @@ void streamFunctionCallTest() {
272269
// @formatter:off
273270
Flux<String> response = ChatClient.create(this.chatModel).prompt()
274271
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
275-
.functions(FunctionCallback.builder()
276-
.function("getCurrentWeather", new MockWeatherService())
272+
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
277273
.description("Get the weather in location")
278274
.inputType(MockWeatherService.Request.class)
279275
.build())

0 commit comments

Comments
 (0)