Tool的规范
视频讲解请参考:视频讲解
源码请参考:源码请参考
ToolCallback
AI模型调用的实例类接口。
scss
/**
*
* 表示一个工具的执行能被一个AI模型触发。
*/
public interface ToolCallback {
/**
* AI模型用工具的描述来决定什么时候和如何调用这个Tool
*/
ToolDefinition getToolDefinition();
/**
* 提供关于如何处理该工具的附加信息的元数据
*/
default ToolMetadata getToolMetadata() {
return ToolMetadata.builder().build();
}
/**
* 使用给定的参数一起执行工具并返回结果并发送给AI 模型。
*/
String call(String toolInput);
/**
*使用给定的参数一起执行工具并返回结果并发送给AI 模型。
*/
default String call(String toolInput, @Nullable ToolContext toolContext) {
if (toolContext != null && !toolContext.getContext().isEmpty()) {
logger.info("By default the tool context is not used, "
+ "override the method 'call(String toolInput, ToolContext toolcontext)' to support the use of tool context."
+ "Review the ToolCallback implementation for {}", getToolDefinition().name());
}
return call(toolInput);
}
}
Spring AI 有 FunctionToolCallback和MethodToolCallback 的两个实现。
ToolDefinition
ToolDefinition 接口提供了AI model 的所需的信息,包括tool名称、描述和输入schema。ToolCallback实现必须提供ToolDefinition 实例来定义tool。
java
public interface ToolDefinition {
/**
* The tool name. Unique within the tool set provided to a model.
* 工具名称,在提供给一个模型的集合中是唯一的
*/
String name();
/**
* The tool description, used by the AI model to determine what the tool does.
* 工具描述,给模型用于决定这个工具是做什么的
*/
String description();
/**
* The schema of the parameters used to call the tool.
* 用来调用tool的参数的结构
*/
String inputSchema();
/**
* Create a default {@link ToolDefinition} builder.
*/
static DefaultToolDefinition.Builder builder() {
return DefaultToolDefinition.builder();
}
}
JSONSchema
Schema用于理解如何调用tool,并准备调用tool的参数。Spring AI 通过JsonSchemaGenerator 类,提供了生成schema的支持。JSONSchema是作为ToolDefinition的一部分进行提供的。
除了为Tool 本身提供描述外,还可以对tool的输入参数进行提供描述。描述可用于提供有关输入参数的关键信息,例如参数应该是什么格式、允许什么值等。这对于帮助 model 理解输入 schema 以及如何使用它很有用。Spring AI 使用以下注解之一为输入参数生成描述提供内置支持:
ToolParam的注解
ini
@ToolParam(description = "结果的匹配分数,可以为空; 为空则返回查询的所有结果", required = false)
结果转换器
Tool call的结果使用ToolCallResultConverter序列化,然后发给AI model。
java
/**
* A functional interface to convert tool call results to a String that can be sent back
* to the AI model.
*
* @author Thomas Vitale
* @since 1.0.0
*/
@FunctionalInterface
public interface ToolCallResultConverter {
/**
* Given an Object returned by a tool, convert it to a String compatible with the
* given class type.
*/
String convert(@Nullable Object result, @Nullable Type returnType);
}
将tool的结果转换成String类型返回。默认使用的是DefaultToolCallResultConverter序列化JSON,但是你也可以提供自己的结果转换器,通过实现ToolCallResultConverter接口。
自定义结果转换器
方法Tool Call 结果转换
可以通过注解@Tool的 resultConverter()属性来为tool提供自定义的ToolCallResultConverter
java
public class CustomerTools {
@Tool(description = "获取客户信息, 输入参数是用户id,类型是long,必须输入"
,resultConverter = CustomToolCallResultConverter.class
)
public Customer getCustomerInfo(Long id){
//模拟通过数据库查询
Customer customer = new Customer();
customer.setId(id);
customer.setAge(28);
customer.setUserName("五五");
return customer;
}
}
java
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jetbrains.annotations.Nullable;
import org.springframework.ai.tool.execution.ToolCallResultConverter;
import java.lang.reflect.Type;
public class CustomToolCallResultConverter implements ToolCallResultConverter {
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* ToolCallResultConverter 接口的作用是将工具方法的返回值对象转换为字符串,
* 因为 AI 模型最终接收的是文本格式的结果。你需要将 Customer 对象序列化为 JSON 或其他可读格式。
* @param result
* @param returnType
* @return
*/
@Override
public String convert(@Nullable Object result, @Nullable Type returnType) {
if (result == null) {
return "No customer found";
}
// 如果结果是 Customer 类型,转换为格式化的字符串
if (result instanceof Customer customer) {
try {
// 方式1: 转换为 JSON 格式(推荐)
String json = objectMapper.writeValueAsString(customer);
return json;
// 方式2: 转换为人类可读的自然语言描述
// return String.format("Customer found - ID: %d, Name: %s, Age: %d",
// customer.getId(),
// customer.getUserName(),
// customer.getAge());
} catch (JsonProcessingException e) {
return "Error converting customer data: " + e.getMessage();
}
}
// 其他类型的默认处理
return result.toString();
}
}
编程式Tool Call 结果转换
通过设置 MethodToolCallback.Builder 的 toolCallResultConverter() 属性来为 tool 提供自定义的自定义结果返回类。
less
Method method = ReflectionUtils.findMethod(CustomerTools.class, "getCustomerInfo", Long.class);
MethodToolCallback customerToolCallback = MethodToolCallback.builder()
.toolDefinition(
ToolDefinition.builder()
.description("获取客户信息, 输入参数是用户id,类型是long,必须输入")
.inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
.name("getCustomerInfo")
.build()
)
.toolMethod(method)
.toolObject(new CustomerTools())
.toolCallResultConverter(new CustomToolCallResultConverter())
.build();
Tool结果直接返回
默认情况下,tool call的结果作为响应返回model。然后model可以使用结果继续对话。
在某些情况下,可能希望将结果直接返回给调用者,而不是将其发送回model。
代码实现,在返回tool执行结果的类中封装了是否直接返回,并判断是否直接返回。
组装的话是在类:org.springframework.ai.model.tool.ToolCallingManager中
在类:com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel

使用方式:
returnDirect = true
ini
public class CustomerTools {
@Tool(description = "获取客户信息, 输入参数是用户id,类型是long,必须输入"
,resultConverter = CustomToolCallResultConverter.class
,returnDirect = true //设置直接返回
)
public Customer getCustomerInfo(Long id){
//模拟通过数据库查询
Customer customer = new Customer();
customer.setId(id);
customer.setAge(28);
customer.setUserName("五五");
return customer;
}
}
如果是编程式的话,通过toolMetadata方式来配置
less
MethodToolCallback customerToolCallback = MethodToolCallback.builder()
.toolDefinition(
ToolDefinition.builder()
.description("获取客户信息, 输入参数是用户id,类型是long,必须输入")
.inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
.name("getCustomerInfo")
.build()
)
.toolMethod(method)
.toolObject(new CustomerTools())
.toolCallResultConverter(new CustomToolCallResultConverter())
.toolMetadata(ToolMetadata.builder()
.returnDirect(true) //设置直接返回
.build()
)
.build();
ReactAgent中使用工具
直接使用
通过tools方法设置ToolCallback工具类型。可以配置多个。
scss
/**
* 通过tools方法直接使用tool
*/
@Test
public void test7() throws GraphRunnerException {
ToolCallback weatherTool = FunctionToolCallback.builder("get weather",
new WeatherTool())
.description("Get weather for a given city")
.inputType(WeatherToolRequest.class)
.build();
ToolCallback searchTool = FunctionToolCallback.builder("search",
new SearchTool())
.description("Search for information")
.inputType(SearchToolInput.class)
.build();
ChatModel chatModel = CreateChatClient.createDashScopeChatModel();
ReactAgent agent = ReactAgent.builder()
.name("my_agent")
.tools(weatherTool,searchTool)
.model(chatModel)
.systemPrompt("You are a helpful assistant with access to weather and search tools.")
.saver(new MemorySaver())
.build();
AssistantMessage message = agent.call("北京的天气怎么样");
System.out.println(message.getText());
}
适用场景:
- 工具数量不是很多。
- 工具定义在编译时已知。
- 需要类型安全的工具定义。
方法工具
通过methodTools方法来传入带有注解@Tool注解方法的对象。这种方式简洁,直白。
java
public void test8() throws GraphRunnerException {
ChatModel chatModel = CreateChatClient.createDashScopeChatModel();
ReactAgent agent = ReactAgent.builder()
.methodTools(new DateTimeTools()) // 通过method tools指定tool
.name("my agent")
.model(chatModel)
.systemPrompt("You are a helpful assistant with date and time")
.build();
AssistantMessage message = agent.call("获取当前时间,并设置一个10分钟后的闹钟");
System.out.println(message.getText());
}
工具提供者
使用 ToolCallbackProvider 接口动态提供工具。这种方式适合需要根据运行时条件动态决定提供哪些工具的场景。
ini
public void test9() throws GraphRunnerException {
ToolCallback weatherTool = FunctionToolCallback.builder("get weather",
new WeatherTool())
.description("Get weather for a given city")
.inputType(WeatherToolRequest.class)
.build();
ToolCallback searchTool = FunctionToolCallback.builder("search",
new SearchTool())
.description("Search for information")
.inputType(SearchToolInput.class)
.build();
ToolCallbackProvider toolCallbackProvider = new CustomToolCallbackProvider(List.of(weatherTool, searchTool));
//动态控制有哪些工具
//ToolCallbackProvider toolCallbackProvider = new CustomToolCallbackProvider(List.of(weatherTool, searchTool),false);
ChatModel chatModel = CreateChatClient.createDashScopeChatModel();
ReactAgent agent = ReactAgent.builder()
.name("my agent")
.model(chatModel)
.toolCallbackProviders(toolCallbackProvider)
.systemPrompt("You are a helpful assistant with access to weather and search tools.")
.build();
AssistantMessage message = agent.call("北京的天气怎么样,今天发生了什么新闻");
System.out.println(message.getText());
}
工具名解析
通过toolNames()方法指定工具名称,配置resolver()方法提供的ToolCallbackResolver来解析工具。这种方式适合工具定义和工具使用分离的场景 。toolNames()方法必须与resolver()方法配合使用,否则抛出异常。
java
/**
* 工具名词解析方式使用Tool
*
*/
@Test
public void test10() throws GraphRunnerException {
ToolCallback weatherTool = FunctionToolCallback.builder("get weather",
new WeatherTool())
.description("Get weather for a given city")
.inputType(WeatherToolRequest.class)
.build();
ToolCallback searchTool = FunctionToolCallback.builder("search",
new SearchTool())
.description("Search for information")
.inputType(SearchToolInput.class)
.build();
StaticToolCallbackResolver resolver = new StaticToolCallbackResolver(List.of(weatherTool, searchTool));
ChatModel chatModel = CreateChatClient.createDashScopeChatModel();
ReactAgent agent = ReactAgent.builder()
.name("my agent")
.model(chatModel)
.description("An agent with multiple tools")
.instruction("You are a helpful assistant with access to calculator and search tools.")
.toolNames("get weather", "search")
.resolver(resolver)
.build();
AssistantMessage message = agent.call("北京天气怎么样?");
System.out.println(message);
}
注意点:工具可能动态变化,但是工具名词要保持稳定。
总结
| 方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| tools方式 | 工具数量少、定义简单 | 简单直接、类型安全 | 工具多时代码冗长 |
| methodTools方式 | 工具方法定义在类中 | 代码组织清晰、易于维护 | 需要创建一个工具类 |
| toolCallbackProviders | 可以动态的提供工具 | 灵活、支持运行时决策 | 需要实现对应的接口,代码复杂度提高 |
| toolNames和resolver | 工具定义和使用分离 | 解耦、支持配置化。tool name和具体的tool分离 | 必须配置resolver |