Skip to content

Advancing Tool Support - Part 5 #2162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

ThomasVitale
Copy link
Contributor

  • Introduced new @ToolParam annotation for defining a description for tool parameters and marking them as (non)required.
  • Improved the JSON Schema generation for tools, solving inconsistencies between methods and functions, and ensuring a predictable outcome.
  • Added support for returning tool results directly to the user instead of passing them back to the model. Introduced new ToolExecutionResult API to propagate this information.
  • Consolidated naming of tool-related options in ToolCallingChatOptions.
  • Fixed varargs issue in ChatClient when passing ToolCallback[].
  • Introduced new documentation for the tool calling capabilities in Spring AI, and deprecated the old one.
  • Bumped jsonschema dependency to 4.37.0.

Relates to gh-2049

* Introduced new ToolParam annotation for defining a description for tool parameters and marking them as (non)required.
* Improved the JSON Schema generation for tools, solving inconsistencies between methods and functions, and ensuring a predictable outcome.
* Added support for returning tool results directly to the user instead of passing them back to the model. Introduced new ToolExecutionResult API to propagate this information.
* Consolidated naming of tool-related options in ToolCallingChatOptions.
* Fixed varargs issue in ChatClient when passing ToolCallback[].
* Introduced new documentation for the tool calling capabilities in Spring AI, and deprecated the old one.
* Bumped jsonschema dependency to 4.37.0.

Relates to spring-projectsgh-2049

Signed-off-by: Thomas Vitale <ThomasVitale@users.noreply.github.com>
@ThomasVitale ThomasVitale marked this pull request as draft February 2, 2025 21:05
var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response);
if (toolExecutionResult.returnDirect()) {
// Return tool execution result directly to the client.
return ChatResponse.builder()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a given tool is marked with the option returnDirect, the tool call result is sent back to the user directly, wrapping each tool call result in a Generation object.

That is especially useful when building agents. For example, if I have a RAG tool, I might want to get the result back directly instead of having the main agent model post-process the RAG answer. Or I might want certain tools to end the reasoning loop of an agent.

@@ -216,9 +216,9 @@ interface ChatClientRequestSpec {

ChatClientRequestSpec tools(String... toolNames);

ChatClientRequestSpec tools(Object... toolObjects);
ChatClientRequestSpec tools(FunctionCallback... toolCallbacks);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Java compiler complaints when trying to pass an array of FunctionCallback and requires casting. In this way, we keep the same API, but it's more explicit and no casting is required.

@@ -53,12 +53,12 @@ public interface ToolCallingChatOptions extends FunctionCallingOptions {
/**
* Names of the tools to register with the ChatModel.
*/
Set<String> getTools();
Set<String> getToolNames();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having the same API (tools()) in ChatClient and ToolCallingChatOptions, but with very different behaviours, didn't seem good.

Now, the ChatClient keeps having one single API with overloaded methods to support different use cases: tools().
ToolCallingChatOptions, instead, reflects the two types of tool-related information it stores: toolNames() and toolCallbacks().

@tzolov what do you think?

@@ -39,7 +38,7 @@ public interface ToolCallingManager {
/**
* Execute the tool calls requested by the model.
*/
List<Message> executeToolCalls(Prompt prompt, ChatResponse chatResponse);
ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I introduced this to propagate back whether the "returnDirect" option is enabled. Even without that use case, I think it's still a better return type than what I used before (List<Message>). It's more easily understandable.

@Target({ ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ToolParam {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the naming, I followed the most used convention in Spring Framework (@RequestParam @CookieParam...).

I made it tool-specific, but this could actually be used also when using Structured Outputs (as well as the entire JSON schema generation logic in JsonSchemaGenerator). But if we want that, we need to come up with a better name).

* to work well in the context of tool calling and structured outputs, aiming at ensuring
* consistency and robustness across different model providers.
* <p>
* Metadata such as descriptions and required properties can be specified using one of the
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the main inconsistency I fixed in this PR. Methods and functions were treated differently, with functions having undocumented broader support for annotations. Now there's parity of behaviour between them when it comes to description and required properties.

@tzolov tzolov added this to the 1.0.0-M6 milestone Feb 4, 2025
@tzolov tzolov self-assigned this Feb 4, 2025
@tzolov tzolov marked this pull request as ready for review February 4, 2025 12:00
@tzolov
Copy link
Contributor

tzolov commented Feb 4, 2025

Rebased and merged at 560baaa

@tzolov tzolov closed this Feb 4, 2025
@markpollack
Copy link
Member

related to epic #2207

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants