Skip to content

Commit df3a3b4

Browse files
committed
Advancing Tool Support - Part 1
* Defined new APIs consolidating the “tool” naming as opposed to the current “function”, aligning with the industry and solving the confusion between “function tool” and “Java Function”: ToolCallback and ToolCallingChatOptions. They extend the current ones to ensure backward compatibility, but FunctionCallback and FunctionCallingOptions can be considered deprecated. * Enhanced support for methods as tools, introducing support for declarative Tool-annotated methods via MethodToolCallback and MethodToolCallbackProvider (deprecating the existing MethodInvokingFunctionCallback). * Improved tool execution logic with granular support for returning the result directly to the client and exception handling. * Improved JSON Schema generation and parsing logic, consolidating the usage of the victools/jsonschema-generator library and dropping the non-maintained Jackson JSON Schema Module. This makes it possible to use tools with input lists/arrays, which the latter library was not supporting. * Extended ChatClient API with new methods tools() and toolCallbacks(). The existing functions() methods can be considered deprecated. Relates to gh-2049 Signed-off-by: Thomas Vitale <ThomasVitale@users.noreply.github.com>
1 parent 329e6c0 commit df3a3b4

File tree

48 files changed

+4609
-13
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4609
-13
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2023-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.aot;
18+
19+
import org.springframework.ai.tool.execution.DefaultToolCallResultConverter;
20+
import org.springframework.aot.hint.MemberCategory;
21+
import org.springframework.aot.hint.RuntimeHints;
22+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
23+
import org.springframework.lang.NonNull;
24+
import org.springframework.lang.Nullable;
25+
26+
/**
27+
* Registers runtime hints for the tool calling APIs.
28+
*
29+
* @author Thomas Vitale
30+
*/
31+
public class ToolRuntimeHints implements RuntimeHintsRegistrar {
32+
33+
@Override
34+
public void registerHints(@NonNull RuntimeHints hints, @Nullable ClassLoader classLoader) {
35+
var mcs = MemberCategory.values();
36+
hints.reflection().registerType(DefaultToolCallResultConverter.class, mcs);
37+
}
38+
39+
}

spring-ai-core/src/main/java/org/springframework/ai/chat/client/ChatClient.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -215,6 +215,12 @@ interface ChatClientRequestSpec {
215215

216216
<T extends ChatOptions> ChatClientRequestSpec options(T options);
217217

218+
ChatClientRequestSpec tools(String... toolNames);
219+
220+
ChatClientRequestSpec tools(Object... toolObjects);
221+
222+
ChatClientRequestSpec toolCallbacks(FunctionCallback... toolCallbacks);
223+
218224
/**
219225
* @deprecated use {@link #functions(FunctionCallback...)} instead.
220226
*/
@@ -293,6 +299,12 @@ interface Builder {
293299

294300
Builder defaultSystem(Consumer<PromptSystemSpec> systemSpecConsumer);
295301

302+
Builder defaultTools(String... toolNames);
303+
304+
Builder defaultTools(Object... toolObjects);
305+
306+
Builder defaultToolCallbacks(FunctionCallback... toolCallbacks);
307+
296308
/**
297309
* @deprecated use {@link #defaultFunctions(FunctionCallback...)} instead.
298310
*/

spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
3232
import io.micrometer.observation.Observation;
3333
import io.micrometer.observation.ObservationRegistry;
3434
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
35+
import org.springframework.ai.tool.ToolCallbacks;
3536
import reactor.core.publisher.Flux;
3637
import reactor.core.scheduler.Schedulers;
3738

@@ -782,10 +783,9 @@ public Builder mutate() {
782783
builder.defaultOptions(this.chatOptions);
783784
}
784785

785-
// workaround to set the missing fields.
786-
builder.defaultRequest.getMessages().addAll(this.messages);
787-
builder.defaultRequest.getFunctionCallbacks().addAll(this.functionCallbacks);
788-
builder.defaultRequest.getToolContext().putAll(this.toolContext);
786+
builder.addMessages(this.messages);
787+
builder.addToolCallbacks(this.functionCallbacks);
788+
builder.addToolContext(this.toolContext);
789789

790790
return builder;
791791
}
@@ -836,6 +836,30 @@ public <T extends ChatOptions> ChatClientRequestSpec options(T options) {
836836
return this;
837837
}
838838

839+
@Override
840+
public ChatClientRequestSpec tools(String... toolNames) {
841+
Assert.notNull(toolNames, "toolNames cannot be null");
842+
Assert.noNullElements(toolNames, "toolNames cannot contain null elements");
843+
this.functionNames.addAll(List.of(toolNames));
844+
return this;
845+
}
846+
847+
@Override
848+
public ChatClientRequestSpec tools(Object... toolObjects) {
849+
Assert.notNull(toolObjects, "toolObjects cannot be null");
850+
Assert.noNullElements(toolObjects, "toolObjects cannot contain null elements");
851+
this.functionCallbacks.addAll(Arrays.asList(ToolCallbacks.from(toolObjects)));
852+
return this;
853+
}
854+
855+
@Override
856+
public ChatClientRequestSpec toolCallbacks(FunctionCallback... toolCallbacks) {
857+
Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
858+
Assert.noNullElements(toolCallbacks, "toolCallbacks cannot contain null elements");
859+
this.functionCallbacks.addAll(Arrays.asList(toolCallbacks));
860+
return this;
861+
}
862+
839863
@Override
840864
public <I, O> ChatClientRequestSpec function(String name, String description,
841865
java.util.function.Function<I, O> function) {
@@ -888,10 +912,7 @@ public <I, O> ChatClientRequestSpec function(String name, String description, @N
888912
}
889913

890914
public ChatClientRequestSpec functions(String... functionBeanNames) {
891-
Assert.notNull(functionBeanNames, "functionBeanNames cannot be null");
892-
Assert.noNullElements(functionBeanNames, "functionBeanNames cannot contain null elements");
893-
this.functionNames.addAll(List.of(functionBeanNames));
894-
return this;
915+
return tools(functionBeanNames);
895916
}
896917

897918
public ChatClientRequestSpec functions(FunctionCallback... functionCallbacks) {

spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClientBuilder.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,10 +30,12 @@
3030
import org.springframework.ai.chat.client.DefaultChatClient.DefaultChatClientRequestSpec;
3131
import org.springframework.ai.chat.client.advisor.api.Advisor;
3232
import org.springframework.ai.chat.client.observation.ChatClientObservationConvention;
33+
import org.springframework.ai.chat.messages.Message;
3334
import org.springframework.ai.chat.model.ChatModel;
3435
import org.springframework.ai.chat.model.ToolContext;
3536
import org.springframework.ai.chat.prompt.ChatOptions;
3637
import org.springframework.ai.model.function.FunctionCallback;
38+
import org.springframework.ai.tool.ToolCallbacks;
3739
import org.springframework.core.io.Resource;
3840
import org.springframework.lang.Nullable;
3941
import org.springframework.util.Assert;
@@ -147,6 +149,24 @@ public Builder defaultSystem(Consumer<PromptSystemSpec> systemSpecConsumer) {
147149
return this;
148150
}
149151

152+
@Override
153+
public Builder defaultTools(String... toolNames) {
154+
this.defaultRequest.functions(toolNames);
155+
return this;
156+
}
157+
158+
@Override
159+
public Builder defaultTools(Object... toolObjects) {
160+
this.defaultRequest.functions(ToolCallbacks.from(toolObjects));
161+
return this;
162+
}
163+
164+
@Override
165+
public Builder defaultToolCallbacks(FunctionCallback... toolCallbacks) {
166+
this.defaultRequest.functions(toolCallbacks);
167+
return this;
168+
}
169+
150170
public <I, O> Builder defaultFunction(String name, String description, java.util.function.Function<I, O> function) {
151171
this.defaultRequest.function(name, description, function);
152172
return this;
@@ -173,4 +193,17 @@ public Builder defaultToolContext(Map<String, Object> toolContext) {
173193
return this;
174194
}
175195

196+
void addMessages(List<Message> messages) {
197+
this.defaultRequest.messages(messages);
198+
}
199+
200+
void addToolCallbacks(List<FunctionCallback> toolCallbacks) {
201+
Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
202+
this.defaultRequest.toolCallbacks(toolCallbacks.toArray(FunctionCallback[]::new));
203+
}
204+
205+
void addToolContext(Map<String, Object> toolContext) {
206+
this.defaultRequest.toolContext(toolContext);
207+
}
208+
176209
}

0 commit comments

Comments
 (0)