This issue presents ideas and suggestions for consolidating and advancing tool support in Spring AI, before reaching the first GA version.
Tool Registration
Tools are modelled via the ToolCallback
API. By default, Spring AI supports registering tools from methods and from functional objects.
Methods
In the context of Java, a tool can naturally be modelled as a method. Spring AI support registering methods as tools via the MethodToolCallback
API.
Declarative tool registration is supported via the @Tool
annotation on methods.
class MyTools {
@Tool(description = "Get the list of books written by the given author available in the library")
List<Book> booksByAuthor(String author) {
return bookService.getBooksByAuthor(new Author(author));
}
}
@GetMapping("/chat")
String chat(String authorName) {
return chatClient.prompt()
.user("What books written by %s are available in the library?".formatted(authorName))
.tools(new MyTools())
.call()
.content();
}
Functions
In special cases, there might be a wish for modelling tools as functional objects (Function
/BiFunction
/Supplier
/Consumer
). Spring AI support registering functional objects as tools via the FunctionToolCallback
API.
@GetMapping("/chat")
String chat(String authorName) {
return chatClient.prompt()
.user("What books written by %s are available in the library?".formatted(authorName))
.tools(FunctionToolCallback.builder("booksByAuthor", author -> bookService().getBooksByAuthor(author))
.description("Get the list of books written by the given author available in the library")
.inputType(BookService.Author.class)
.build())
.call()
.content();
}
Tool Execution
By default, tool execution is performed transparently by a ChatModel
object using the ToolCallingManager
API. In case of errors while executing tools, a ToolCallExceptionConverter
is used to process the exception.
If you need full control over the tool execution, for example by asking the user permission before each tool execution, you can disable the internal tool execution feature and manage the tool calling workflow yourself using the ToolCallingManager
API directly.
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(ToolCallbacks.from(tools))
.internalToolExecutionEnabled(false)
.build();
Prompt prompt = new Prompt("What's the weather in Copenhagen?", chatOptions);
ChatResponse chatResponse = chatModel.call(prompt);
if (chatResponse.hasToolCalls()) {
List<Message> messages = toolCallingManager.executeToolCalls(prompt, chatResponse);
}
Prompt secondPrompt = new Prompt(messages, chatOptions);
ChatResponse secondChatResponse = chatModel.call(secondPrompt);
Tool Resolution
Instead of providing tools as ToolCallback
s to ChatModel or ChatClient, you have the option to pass just tool names and let the framework resolve them into ToolCallback
s dynamically.
By default, Spring AI support dynamic tool resolution from the following:
- Functional objects registered as beans with the Spring context (
Function
/BiFunction
/Supplier
/Consumer
). ToolCallback
s registered as beans with the Spring context.
Additional resolution strategies can be provided via the ToolCallbackResolver
API.
Example:
@Configuration(proxyBeanMethods = false)
class Functions {
@Bean
@Description("Get the list of books written by the given author available in the library")
public Function<Author, List<Book>> booksByAuthor(BookService bookService) {
return author -> bookService.getBooksByAuthor(author);
}
}
Tasks
- https://github.com/spring-projects/spring-ai/pull/2064
- https://github.com/spring-projects/spring-ai/pull/2085
- https://github.com/spring-projects/spring-ai/pull/2121
- https://github.com/spring-projects/spring-ai/pull/2148
- https://github.com/spring-projects/spring-ai/pull/2162
Comment From: hansdesmet
This would make tooling great !
LangChain4J also has a @Tool
annotation with the same purpose:
https://docs.langchain4j.dev/tutorials/tools/#high-level-tool-api
Comment From: Yougoss
Is there a plan to support Function parameters properties description? to make ai better understand how to get the corresponding parameters from the context.
From openai doc, there is a description field for each function parameters property
Comment From: ogbozoyan
When a chat model determines that a certain tool needs to be called
It's not quite clear how the model realizes that it's “time” to call in the tool
Is it possible to predefine the flow, when model is going to call a method, it checks with @Description
from the user if he wants it and on positive answer starts to execute the method ?
Comment From: ThomasVitale
@Yougoss there is already some partial support based on whether you're using methods or functions, and whether it's a nested object or not. I've been working on improving that logic to ensure consistency of behaviour across all those scenarios, and writing documentation for it. You can see the draft PR here: https://github.com/spring-projects/spring-ai/pull/2162 Feel free to share your feedback on the solution!
The idea implemented in that PR is such that descriptions and required properties can be specified using one of the following supported annotations:
@ToolParam(required = ..., description = ...)
(from Spring AI)@JsonProperty(required = ...)
(from Jackson)@Schema(required = ..., description = ...)
(from Swagger)@Nullable
(from Spring Framework)
Notice that this is just a proposal. The final solution might differ.