Skip to content

Commit 9d8b240

Browse files
Replaced FunctionCallbacks with ToolCallbacks
1 parent 614a017 commit 9d8b240

File tree

9 files changed

+126
-86
lines changed

9 files changed

+126
-86
lines changed

orchestration/src/test/java/com/sap/ai/sdk/orchestration/spring/OrchestrationChatModelTest.java

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
import org.springframework.ai.chat.messages.AssistantMessage.ToolCall;
4343
import org.springframework.ai.chat.model.ChatResponse;
4444
import org.springframework.ai.chat.prompt.Prompt;
45-
import org.springframework.ai.tool.function.FunctionToolCallback;
45+
import org.springframework.ai.tool.ToolCallbacks;
4646
import reactor.core.publisher.Flux;
4747

4848
@WireMockTest
@@ -152,13 +152,7 @@ void testToolCallsWithoutExecution() throws IOException {
152152
.withBodyFile("toolCallsResponse.json")
153153
.withHeader("Content-Type", "application/json")));
154154

155-
defaultOptions.setToolCallbacks(
156-
List.of(
157-
FunctionToolCallback.builder(
158-
"CurrentWeather", new MockWeatherService()) // (1) function name and instance
159-
.description("Get the weather in location") // (2) function description
160-
.inputType(MockWeatherService.Request.class) // (3) function input type
161-
.build()));
155+
defaultOptions.setToolCallbacks(List.of(ToolCallbacks.from(new WeatherMethod())));
162156
defaultOptions.setInternalToolExecutionEnabled(false);
163157
val prompt = new Prompt("What is the weather in Potsdam and in Toulouse?", defaultOptions);
164158
val result = client.call(prompt);
@@ -169,10 +163,12 @@ void testToolCallsWithoutExecution() throws IOException {
169163
ToolCall toolCall2 = toolCalls.get(1);
170164
assertThat(toolCall1.type()).isEqualTo("function");
171165
assertThat(toolCall2.type()).isEqualTo("function");
172-
assertThat(toolCall1.name()).isEqualTo("CurrentWeather");
173-
assertThat(toolCall2.name()).isEqualTo("CurrentWeather");
174-
assertThat(toolCall1.arguments()).isEqualTo("{\"location\": \"Potsdam\", \"unit\": \"C\"}");
175-
assertThat(toolCall2.arguments()).isEqualTo("{\"location\": \"Toulouse\", \"unit\": \"C\"}");
166+
assertThat(toolCall1.name()).isEqualTo("getCurrentWeather");
167+
assertThat(toolCall2.name()).isEqualTo("getCurrentWeather");
168+
assertThat(toolCall1.arguments())
169+
.isEqualTo("{\"arg0\": {\"location\": \"Potsdam\", \"unit\": \"C\"}}");
170+
assertThat(toolCall2.arguments())
171+
.isEqualTo("{\"arg0\": {\"location\": \"Toulouse\", \"unit\": \"C\"}}");
176172

177173
try (var request1InputStream = fileLoader.apply("toolCallsRequest.json")) {
178174
final String request1 = new String(request1InputStream.readAllBytes());
@@ -202,13 +198,7 @@ void testToolCallsWithExecution() throws IOException {
202198
.withBodyFile("toolCallsResponse2.json")
203199
.withHeader("Content-Type", "application/json")));
204200

205-
defaultOptions.setToolCallbacks(
206-
List.of(
207-
FunctionToolCallback.builder(
208-
"CurrentWeather", new MockWeatherService()) // (1) function name and instance
209-
.description("Get the weather in location") // (2) function description
210-
.inputType(MockWeatherService.Request.class) // (3) function input type
211-
.build()));
201+
defaultOptions.setToolCallbacks(List.of(ToolCallbacks.from(new WeatherMethod())));
212202
val prompt = new Prompt("What is the weather in Potsdam and in Toulouse?", defaultOptions);
213203
val result = client.call(prompt);
214204

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/MockWeatherService.java renamed to orchestration/src/test/java/com/sap/ai/sdk/orchestration/spring/WeatherMethod.java

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
package com.sap.ai.sdk.app.services;
1+
package com.sap.ai.sdk.orchestration.spring;
22

3-
import com.sap.ai.sdk.app.services.MockWeatherService.Request;
4-
import com.sap.ai.sdk.app.services.MockWeatherService.Response;
5-
import java.util.function.Function;
63
import javax.annotation.Nonnull;
4+
import org.springframework.ai.tool.annotation.Tool;
5+
import org.springframework.ai.tool.annotation.ToolParam;
76

8-
/** Function for tool calls in Spring AI */
9-
public class MockWeatherService implements Function<Request, Response> {
7+
public class WeatherMethod {
108

119
/** Unit of temperature */
1210
public enum Unit {
1311
/** Celsius */
12+
@SuppressWarnings("unused")
1413
C,
1514
/** Fahrenheit */
15+
@SuppressWarnings("unused")
1616
F
1717
}
1818

@@ -32,14 +32,10 @@ public record Request(String location, Unit unit) {}
3232
*/
3333
public record Response(double temp, Unit unit) {}
3434

35-
/**
36-
* Apply the function
37-
*
38-
* @param request the request
39-
* @return the response
40-
*/
4135
@Nonnull
42-
public Response apply(@Nonnull Request request) {
43-
return new Response(30.0, Unit.C);
36+
@SuppressWarnings("unused")
37+
@Tool(description = "Get the weather in location")
38+
Response getCurrentWeather(@ToolParam @Nonnull Request request) {
39+
return new Response(30, request.unit);
4440
}
4541
}

orchestration/src/test/resources/__files/toolCallsResponse.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@
2424
"id": "call_LOyP7EVdeqFlGEmVzmPdCVey",
2525
"type": "function",
2626
"function": {
27-
"name": "CurrentWeather",
28-
"arguments": "{\"location\": \"Potsdam\", \"unit\": \"C\"}"
27+
"name": "getCurrentWeather",
28+
"arguments": "{\"arg0\": {\"location\": \"Potsdam\", \"unit\": \"C\"}}"
2929
}
3030
},
3131
{
3232
"id": "call_bwFjnXCfCO4N3f0bMtFMlNSg",
3333
"type": "function",
3434
"function": {
35-
"name": "CurrentWeather",
36-
"arguments": "{\"location\": \"Toulouse\", \"unit\": \"C\"}"
35+
"name": "getCurrentWeather",
36+
"arguments": "{\"arg0\": {\"location\": \"Toulouse\", \"unit\": \"C\"}}"
3737
}
3838
}
3939
]
@@ -65,16 +65,16 @@
6565
"id": "call_LOyP7EVdeqFlGEmVzmPdCVey",
6666
"type": "function",
6767
"function": {
68-
"name": "CurrentWeather",
69-
"arguments": "{\"location\": \"Potsdam\", \"unit\": \"C\"}"
68+
"name": "getCurrentWeather",
69+
"arguments": "{\"arg0\": {\"location\": \"Potsdam\", \"unit\": \"C\"}}"
7070
}
7171
},
7272
{
7373
"id": "call_bwFjnXCfCO4N3f0bMtFMlNSg",
7474
"type": "function",
7575
"function": {
76-
"name": "CurrentWeather",
77-
"arguments": "{\"location\": \"Toulouse\", \"unit\": \"C\"}"
76+
"name": "getCurrentWeather",
77+
"arguments": "{\"arg0\": {\"location\": \"Toulouse\", \"unit\": \"C\"}}"
7878
}
7979
}
8080
]

orchestration/src/test/resources/__files/toolCallsResponse2.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@
1414
"id": "call_LOyP7EVdeqFlGEmVzmPdCVey",
1515
"type": "function",
1616
"function": {
17-
"name": "CurrentWeather",
18-
"arguments": "{\"location\": \"Potsdam\", \"unit\": \"C\"}"
17+
"name": "getCurrentWeather",
18+
"arguments": "{\"arg0\": {\"location\": \"Potsdam\", \"unit\": \"C\"}}"
1919
}
2020
},
2121
{
2222
"id": "call_bwFjnXCfCO4N3f0bMtFMlNSg",
2323
"type": "function",
2424
"function": {
25-
"name": "CurrentWeather",
26-
"arguments": "{\"location\": \"Toulouse\", \"unit\": \"C\"}"
25+
"name": "getCurrentWeather",
26+
"arguments": "{\"arg0\": {\"location\": \"Toulouse\", \"unit\": \"C\"}}"
2727
}
2828
}
2929
]

orchestration/src/test/resources/toolCallsRequest.json

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,34 @@
1919
"type": "function",
2020
"function": {
2121
"description": "Get the weather in location",
22-
"name": "CurrentWeather",
22+
"name": "getCurrentWeather",
2323
"parameters": {
2424
"$schema": "https://json-schema.org/draft/2020-12/schema",
2525
"additionalProperties": false,
2626
"type": "object",
2727
"properties": {
28-
"location": {
29-
"type": "string"
30-
},
31-
"unit": {
32-
"type": "string",
33-
"enum": [
34-
"C",
35-
"F"
28+
"arg0": {
29+
"type": "object",
30+
"properties": {
31+
"location": {
32+
"type": "string"
33+
},
34+
"unit": {
35+
"type": "string",
36+
"enum": [
37+
"C",
38+
"F"
39+
]
40+
}
41+
},
42+
"required": [
43+
"location",
44+
"unit"
3645
]
3746
}
3847
},
3948
"required": [
40-
"location",
41-
"unit"
49+
"arg0"
4250
]
4351
},
4452
"strict": false

orchestration/src/test/resources/toolCallsRequest2.json

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@
1818
"tool_calls": [
1919
{
2020
"function": {
21-
"name": "CurrentWeather",
22-
"arguments": "{\"location\": \"Potsdam\", \"unit\": \"C\"}"
21+
"name": "getCurrentWeather",
22+
"arguments": "{\"arg0\": {\"location\": \"Potsdam\", \"unit\": \"C\"}}"
2323
},
2424
"id": "call_LOyP7EVdeqFlGEmVzmPdCVey",
2525
"type": "function"
2626
},
2727
{
2828
"function": {
29-
"name": "CurrentWeather",
30-
"arguments": "{\"location\": \"Toulouse\", \"unit\": \"C\"}"
29+
"name": "getCurrentWeather",
30+
"arguments": "{\"arg0\": {\"location\": \"Toulouse\", \"unit\": \"C\"}}"
3131
},
3232
"id": "call_bwFjnXCfCO4N3f0bMtFMlNSg",
3333
"type": "function"
@@ -51,26 +51,34 @@
5151
"type": "function",
5252
"function": {
5353
"description": "Get the weather in location",
54-
"name": "CurrentWeather",
54+
"name": "getCurrentWeather",
5555
"parameters": {
5656
"$schema": "https://json-schema.org/draft/2020-12/schema",
5757
"additionalProperties": false,
5858
"type": "object",
5959
"properties": {
60-
"location": {
61-
"type": "string"
62-
},
63-
"unit": {
64-
"type": "string",
65-
"enum": [
66-
"C",
67-
"F"
60+
"arg0": {
61+
"type": "object",
62+
"properties": {
63+
"location": {
64+
"type": "string"
65+
},
66+
"unit": {
67+
"type": "string",
68+
"enum": [
69+
"C",
70+
"F"
71+
]
72+
}
73+
},
74+
"required": [
75+
"location",
76+
"unit"
6877
]
6978
}
7079
},
7180
"required": [
72-
"location",
73-
"unit"
81+
"arg0"
7482
]
7583
},
7684
"strict": false

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOrchestrationService.java

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import org.springframework.ai.chat.model.ChatResponse;
1616
import org.springframework.ai.chat.prompt.Prompt;
1717
import org.springframework.ai.chat.prompt.PromptTemplate;
18-
import org.springframework.ai.tool.function.FunctionToolCallback;
18+
import org.springframework.ai.tool.ToolCallbacks;
1919
import org.springframework.stereotype.Service;
2020
import reactor.core.publisher.Flux;
2121

@@ -91,22 +91,16 @@ public ChatResponse masking() {
9191
}
9292

9393
/**
94-
* Register a function that will be called when the user asks for the weather. <a
95-
* href="https://docs.spring.io/spring-ai/reference/api/tools.html#_programmatic_specification_functiontoolcallback">Spring
96-
* AI Function Calling</a>
94+
* Turn a method into a tool by annotating it with @Tool. <a
95+
* href="https://docs.spring.io/spring-ai/reference/api/tools.html#_methods_as_tools">Spring AI
96+
* Tool Method Declarative Specification</a>
9797
*
9898
* @return the assistant response object
9999
*/
100100
@Nonnull
101101
public ChatResponse toolCalling(final boolean internalToolExecutionEnabled) {
102102
final OrchestrationChatOptions options = new OrchestrationChatOptions(config);
103-
options.setToolCallbacks(
104-
List.of(
105-
FunctionToolCallback.builder(
106-
"CurrentWeather", new MockWeatherService()) // (1) function name and instance
107-
.description("Get the weather in location") // (2) function description
108-
.inputType(MockWeatherService.Request.class) // (3) function input type
109-
.build()));
103+
options.setToolCallbacks(List.of(ToolCallbacks.from(new WeatherMethod())));
110104
options.setInternalToolExecutionEnabled(internalToolExecutionEnabled);
111105

112106
val prompt = new Prompt("What is the weather in Potsdam and in Toulouse?", options);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.sap.ai.sdk.app.services;
2+
3+
import javax.annotation.Nonnull;
4+
import org.springframework.ai.tool.annotation.Tool;
5+
import org.springframework.ai.tool.annotation.ToolParam;
6+
7+
class WeatherMethod {
8+
9+
/** Unit of temperature */
10+
enum Unit {
11+
/** Celsius */
12+
@SuppressWarnings("unused")
13+
C,
14+
/** Fahrenheit */
15+
@SuppressWarnings("unused")
16+
F
17+
}
18+
19+
/**
20+
* Request for the weather
21+
*
22+
* @param location the city
23+
* @param unit the unit of temperature
24+
*/
25+
record Request(String location, Unit unit) {}
26+
27+
/**
28+
* Response for the weather
29+
*
30+
* @param temp the temperature
31+
* @param unit the unit of temperature
32+
*/
33+
record Response(double temp, Unit unit) {}
34+
35+
@Nonnull
36+
@SuppressWarnings("unused")
37+
@Tool(description = "Get the weather in location")
38+
Response getCurrentWeather(@ToolParam @Nonnull final Request request) {
39+
final int temperature = request.location.hashCode() % 30;
40+
return new Response(temperature, request.unit);
41+
}
42+
}

sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationTest.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,12 @@ void testToolCallingWithoutExecution() {
6767
ToolCall toolCall2 = toolCalls.get(1);
6868
assertThat(toolCall1.type()).isEqualTo("function");
6969
assertThat(toolCall2.type()).isEqualTo("function");
70-
assertThat(toolCall1.name()).isEqualTo("CurrentWeather");
71-
assertThat(toolCall2.name()).isEqualTo("CurrentWeather");
72-
assertThat(toolCall1.arguments()).isEqualTo("{\"location\": \"Potsdam\", \"unit\": \"C\"}");
73-
assertThat(toolCall2.arguments()).isEqualTo("{\"location\": \"Toulouse\", \"unit\": \"C\"}");
70+
assertThat(toolCall1.name()).isEqualTo("getCurrentWeather");
71+
assertThat(toolCall2.name()).isEqualTo("getCurrentWeather");
72+
assertThat(toolCall1.arguments())
73+
.isEqualTo("{\"arg0\": {\"location\": \"Potsdam\", \"unit\": \"C\"}}");
74+
assertThat(toolCall2.arguments())
75+
.isEqualTo("{\"arg0\": {\"location\": \"Toulouse\", \"unit\": \"C\"}}");
7476
}
7577

7678
@Test

0 commit comments

Comments
 (0)