文章目录
- [1. 前言](#1. 前言)
- [2. 工具提供者](#2. 工具提供者)
-
- [2.1 ToolCallbackProvider](#2.1 ToolCallbackProvider)
- [2.2 StaticToolCallbackProvider](#2.2 StaticToolCallbackProvider)
- [2.3 MethodToolCallbackProvider](#2.3 MethodToolCallbackProvider)
- [2.4 AugmentedToolCallbackProvider](#2.4 AugmentedToolCallbackProvider)
- [2.5 McpToolCallbackProvider](#2.5 McpToolCallbackProvider)
- [3. 案例演示](#3. 案例演示)
-
- [3.1 工具硬编码问题](#3.1 工具硬编码问题)
- [3.2 定义工具来源](#3.2 定义工具来源)
- [3.3 工具函数](#3.3 工具函数)
- [3.4 实现 ToolCallbackProvider](#3.4 实现 ToolCallbackProvider)
- [3.5 ChatClient 配置](#3.5 ChatClient 配置)
- [3.6 测试](#3.6 测试)
1. 前言
在之前我们学习了创建方法型、函数型工具的多种方式,以及通过 ChatClient 进行全局默认、运行时动态配置,也了解了工具执行和解析的核心流程和源码。
以上都是在代码中直接定义工具,属于本地硬编码式的工具集成方案 ------无论是基于 @Tool 注解的业务方法封装、函数式接口实现的工具 Bean,还是手动构建的 ToolCallback 实例,所有工具的定义、逻辑、注册都与应用代码强耦合,必须随项目一起编译、部署、重启才能生效。
但在真实的生产级 AI 应用、微服务架构中,这种本地硬编码的方式存在明显局限:
- 工具需要跨服务、跨应用共享时,无法复用统一的工具能力;
- 业务迭代需要新增、修改、下线工具时,必须重启服务,影响线上稳定性;
- 对接第三方服务、远程
API、外部系统能力时,本地编码实现成本高、维护繁琐; - 企业级场景下,需要中心化管理、权限控制、动态调度
AI工具,硬编码无法满足。
为此,Spring AI 提供了更灵活、更贴近生产实践的「非编码式/远程式工具集成体系」 ,彻底打破本地代码的限制,支持对接远程工具、动态注册工具、中心化管理工具,这也是构建企业级 AI 智能体的核心能力。
接下来,我们将重点讲解 Spring AI 工具生态的 ToolCallbackProvider 。
2. 工具提供者
2.1 ToolCallbackProvider
用于批量管理和提供 ToolCallback实例的核心接口,充当工具工厂 或工具仓库 的角色。它的核心职责将不同来源的工具定义转换为 ToolCallback 数组,供 AI 模型调用。
接口定义:
java
package org.springframework.ai.tool;
public interface ToolCallbackProvider {
// 返回工具回调数组,核心方法
ToolCallback[] getToolCallbacks();
// 静态工厂方法:从工具回调列表创建提供者
static ToolCallbackProvider from(List<? extends FunctionCallback> toolCallbacks) {
return new StaticToolCallbackProvider(toolCallbacks);
}
// 同上
static ToolCallbackProvider from(ToolCallback... toolCallbacks) {
return new StaticToolCallbackProvider(toolCallbacks);
}
}
设计意图:
- 解耦工具定义与注册 :工具可以在不同来源定义(配置文件、数据库、
MCP、注解),统一通过Provider注册 - 延迟加载 :
getToolCallbacks()方法在实际需要时才被调用 - 可组合性 :多个
Provider可以同时注册,工具自动合并
Spring AI 提供了5 种官方实现,覆盖不同工具来源与增强场景:
StaticToolCallbackProvider:静态工具回调MethodToolCallbackProvider:从@Tool注解方法生成AugmentedToolCallbackProvider: 带增强参数的包装器SyncMcpToolCallbackProvider:MCP同步工具发现AsyncMcpToolCallbackProvider:MCP异步工具发现

2.2 StaticToolCallbackProvider
ToolCallbackProvider 接口的最基础实现,用于:
- 持有静态工具集:在构造时确定工具列表,运行时不可变
- 简化工具注册:提供简单的
API将已构建好的ToolCallback包装成Provider
内部维护了一个不可变的 final 工具列表:
java
private final ToolCallback[] toolCallbacks;
源码:
java
public class StaticToolCallbackProvider implements ToolCallbackProvider {
private final ToolCallback[] toolCallbacks;
// 构造方法1: 接受可变参数数组
public StaticToolCallbackProvider(ToolCallback... toolCallbacks) {
Assert.notNull(toolCallbacks, "ToolCallbacks must not be null");
this.toolCallbacks = toolCallbacks;
}
// 构造方法2: 接受 List
public StaticToolCallbackProvider(List<? extends ToolCallback> toolCallbacks) {
Assert.noNullElements(toolCallbacks, "toolCallbacks cannot contain null elements");
this.toolCallbacks = toolCallbacks.toArray(new ToolCallback[0]);
}
@Override
public ToolCallback[] getToolCallbacks() {
return this.toolCallbacks;
}
}
2.3 MethodToolCallbackProvider
基于方法注解的工具提供者,核心能力是扫描并自动将带有 @Tool 注解的方法转换为 ToolCallback 实例,是 Spring AI 工具注册体系中最常用的实现之一。
核心流程:
- 反射获取类上声明了
@Tool的方法对象 - 排除
Function/Supplier/Consumer返回类型的方法 - 每个方法对象创建
MethodToolCallback实例 - 返回
ToolCallback[]列表
核心属性和构造方法:
java
public final class MethodToolCallbackProvider implements ToolCallbackProvider {
private static final Logger logger = LoggerFactory.getLogger(MethodToolCallbackProvider.class);
private final List<Object> toolObjects; // 存储 @Tool 注解方法所在的对象
// 私有构造,通过 Builder 创建
private MethodToolCallbackProvider(List<Object> toolObjects) {
Assert.notNull(toolObjects, "toolObjects cannot be null");
Assert.noNullElements(toolObjects, "toolObjects cannot contain null elements");
assertToolAnnotatedMethodsPresent(toolObjects); // 验证存在 @Tool 方法
this.toolObjects = toolObjects;
validateToolCallbacks(getToolCallbacks()); // 验证无重名工具
}
}
核心方法 getToolCallbacks() :
java
@Override
public ToolCallback[] getToolCallbacks() {
var toolCallbacks = this.toolObjects.stream()
// 1. 遍历每个工具对象
.map(toolObject -> Stream
// 2. 获取该对象声明的所有方法
.of(ReflectionUtils.getDeclaredMethods(
// 处理 AOP 代理对象
AopUtils.isAopProxy(toolObject)
? AopUtils.getTargetClass(toolObject)
: toolObject.getClass()))
// 3. 过滤:带有 @Tool 注解
.filter(this::isToolAnnotatedMethod)
// 4. 过滤:排除 Function/Supplier/Consumer 返回类型
.filter(toolMethod -> !isFunctionalType(toolMethod))
// 5. 过滤:排除编译器生成的方法
.filter(ReflectionUtils.USER_DECLARED_METHODS::matches)
// 6. 为每个方法创建 MethodToolCallback
.map(toolMethod -> MethodToolCallback.builder()
.toolDefinition(ToolDefinitions.from(toolMethod))
.toolMetadata(ToolMetadata.from(toolMethod))
.toolMethod(toolMethod)
.toolObject(toolObject)
.toolCallResultConverter(ToolUtils.getToolCallResultConverter(toolMethod))
.build())
.toArray(ToolCallback[]::new))
// 7. 扁平化为单一流
.flatMap(Stream::of)
.toArray(ToolCallback[]::new);
// 8. 验证无重名工具
validateToolCallbacks(toolCallbacks);
return toolCallbacks;
}
2.4 AugmentedToolCallbackProvider
是 Spring AI 中工具输入增强的核心实现,核心能力是在不修改原始工具方法签名的前提下,为工具追加输入 Schema 扩展字段,实现工具调用上下文信息传递、元数据记录等高级功能,是构建复杂智能体应用的关键组件之一。
常见应用场景:
- 内部思考 / 推理:捕获模型执行工具前的分步推理逻辑
- 记忆增强:提取关键信息并存储至长期记忆
- 分析与追踪:收集元数据、用户意图或使用模式
- 多智能体协作:传递智能体标识或协作指令
核心组件:
AugmentedToolCallbackProvider<T>:包装工具对象 / 提供者,为所有工具添加指定的记录类型参数AugmentedToolCallback<T>:包装单个ToolCallback实例AugmentedArgumentEvent<T>:包含工具定义、原始输入和增强参数,供消费者使用ToolInputSchemaAugmenter:底层的模式修改工具类
核心属性和构造函数:
java
public class AugmentedToolCallbackProvider<T extends Record> implements ToolCallbackProvider {
// 委托提供者
// 作为被增强的对象,持有原始的 ToolCallbackProvider
private final ToolCallbackProvider delegate;
// 后处理开关
// 作用: 控制扩展参数在消费后是否从输入中移除
private final boolean removeExtraArgumentsAfterProcessing;
// 参数消费者
// 作用: 处理 LLM 传入的扩展参数的回调函数
// 触发时机: 在工具实际执行之前
private Consumer<AugmentedArgumentEvent<T>> argumentConsumer;
// 扩展参数类型
// 定义要注入到工具中的额外参数的结构
// - 必须是 Record 类型 - 保证不可变性和可序列化
// 至少包含一个字段 - 空 Record 无意义
private final Class<T> argumentType;
public AugmentedToolCallbackProvider(Object toolObject, Class<T> argumentType,
Consumer<AugmentedArgumentEvent<T>> argumentConsumer, boolean removeExtraArgumentsAfterProcessing) {
this(MethodToolCallbackProvider.builder().toolObjects(toolObject).build(), argumentType, argumentConsumer,
removeExtraArgumentsAfterProcessing);
}
public AugmentedToolCallbackProvider(ToolCallbackProvider delegate, Class<T> argumentType,
Consumer<AugmentedArgumentEvent<T>> argumentConsumer, boolean removeExtraArgumentsAfterProcessing) {
this.delegate = delegate;
this.argumentType = argumentType;
this.argumentConsumer = argumentConsumer;
this.removeExtraArgumentsAfterProcessing = removeExtraArgumentsAfterProcessing;
}
配置项 removeExtraArgumentsAfterProcessing 控制增强参数是否传递给原始工具:
true(默认):调用工具前移除增强参数,原始工具看不到扩展参数,适用于会话ID、租户ID等上下文信息场景false:保留输入中的增强参数,原始工具可访问,适用于原始工具也需要处理的扩展参数
核心方法将普通的 ToolCallback 包装为增强版的 AugmentedToolCallback :
java
@Override
public ToolCallback[] getToolCallbacks() {
return Arrays.stream(this.delegate.getToolCallbacks())
.map(toolCallback -> new AugmentedToolCallback<T>(toolCallback, this.argumentType, this.argumentConsumer,
this.removeExtraArgumentsAfterProcessing))
.toArray(ToolCallback[]::new);
}
将增强参数定义为 Java 记录类:
java
public record AgentThinking(
@ToolParam(description = "调用该工具的推理过程", required = true)
String innerThought,
@ToolParam(description = "置信度(低/中/高)", required = false)
String confidence
) {}
通过 AugmentedToolCallbackProvider 包装你的工具:
java
AugmentedToolCallbackProvider<AgentThinking> provider = AugmentedToolCallbackProvider
.<AgentThinking>builder()
.toolObject(new MyTools()) // 带@Tool注解的工具类
.argumentType(AgentThinking.class)
.argumentConsumer(event -> {
AgentThinking thinking = event.arguments();
log.info("工具:{} | 推理:{}", event.toolDefinition().name(), thinking.innerThought());
})
.removeExtraArgumentsAfterProcessing(true)
.build();
在聊天客户端中使用:
java
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(provider)
.build();
大语言模型会识别包含新增字段的增强后模式;消费者会接收 AgentThinking 记录对象,而原始工具仅接收其预期的参数。
2.5 McpToolCallbackProvider
MCP 相关的 ToolCallbackProvider 是 spring-ai-mcp 模块提供的,SyncMcpToolCallbackProvider、AsyncMcpToolCallbackProvider 是 Spring AI 中 MCP (Model Context Protocol) 工具集成的核心组件,负责将 MCP Server 提供的工具转换为 Spring AI 的 ToolCallback。
示例 1 ,直接使用 Spring Bean 注册,自动配置
java
@Bean
public ToolCallbackProvider myTools() {
List<ToolCallback> tools = List.of(
FunctionToolCallback.builder("tool1", fn1).build(),
FunctionToolCallback.builder("tool2", fn2).build()
);
return ToolCallbackProvider.from(tools);
}
java
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(ToolCallbackProvider.from(callback1, callback2))
.build();
3. 案例演示
3.1 工具硬编码问题
在之前的案例中,我们定义好工具之后,还需要显式的将工具对象传递给 ChatClient :

否则,该工具不会被加载:

解决方案 :通过 ToolCallbackProvider 实现动态工具加载。
3.2 定义工具来源
Tool Calling 的本质是函数调用,无论如何都需要提供 ToolCallback 工具对象,但是我们可以通过动态加载工具信息,然后再实例化对象,并可以提供一个注册表,实现对工具的动态管理。这里将工具信息存放到本地文件,实际开发可以选择数据库、配置中心。
创建一个工具信息类:
java
/**
* 工具配置(对应 tools.json)
*/
@Data
public class AiToolInfo {
private String name; // 工具名称
private String description; // 工具描述
private String implClass; // 工具实现类
private String inputType; // 输入参数类型(Function 需要,Supplier 不需要)
private String type; // 类型:function(默认)或 supplier
private Integer status; // 1=启用 0=禁用
}
在 resources/tools 目录下创建 tools.json 文件:
json
[
{
"name": "query_weather",
"description": "查询指定城市的天气信息,参数:city-城市名称",
"implClass": "com.example.aichatdemo.file.WeatherTool",
"inputType": "com.example.aichatdemo.file.WeatherInput",
"type": "function",
"status": 1
},
{
"name": "get_time",
"description": "获取当前日期和时间",
"implClass": "com.example.aichatdemo.file.TimeTool",
"type": "supplier",
"status": 1
}
]
3.3 工具函数
提供一个天气查询工具:
java
public class WeatherTool implements Function<WeatherInput, String> {
@Override
public String apply(WeatherInput input) {
return "城市: " + input.getCity() + ", 天气: 晴朗, 温度: 25°C";
}
}
@Data
public class WeatherInput {
private String city;
}
提供一个时间查询工具:
java
public class TimeTool implements Supplier<String> {
@Override
public String get() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
}
3.4 实现 ToolCallbackProvider
自定义 ToolCallbackProvider 实现类:
java
/**
* 基于配置文件的工具回调提供器
* <p>
* 功能说明:
* 从 tools.json 配置文件动态加载工具定义,并构建对应的 ToolCallback 对象。
* 支持两种类型的函数工具:
* - Function<REQ, RES>: 有输入参数的函数工具(如天气查询,需要传入城市名称)
* - Supplier<RES>: 无输入参数的函数工具(如获取当前时间)
* <p>
* 工作流程:
* 1. 启动时从 tools.json 加载工具配置列表
* 2. 过滤启用的工具(status=1)
* 3. 反射实例化工具实现类
* 4. 自动推断工具类型和输入参数类型,构建 FunctionToolCallback
* <p>
* 配置示例(tools.json)- inputType 和 type 自动推断,无需配置:
* <pre>
* [
* {
* "name": "query_weather",
* "description": "查询指定城市的天气信息",
* "implClass": "com.example.aichatdemo.file.WeatherTool",
* "status": 1
* },
* {
* "name": "get_time",
* "description": "获取当前日期和时间",
* "implClass": "com.example.aichatdemo.file.TimeTool",
* "status": 1
* }
* ]
* </pre>
*
* @author TD
* @see ToolCallbackProvider Spring AI 工具回调提供器接口
* @see FunctionToolCallback 函数工具回调实现
* @see AiToolInfo 工具配置信息实体
*/
@Slf4j
@Component
public class FileToolCallbackProvider implements ToolCallbackProvider {
/**
* 默认 Schema 类型,用于生成工具输入参数的 JSON Schema
*/
private static final SchemaType DEFAULT_SCHEMA_TYPE = SchemaType.JSON_SCHEMA;
/**
* 当前使用的 Schema 类型
*/
private final SchemaType schemaType;
/**
* JSON 解析器,用于解析 tools.json 配置文件
*/
private final ObjectMapper mapper = new ObjectMapper();
/**
* 构造器
*
* @param schemaType 可选的 Schema 类型,为空时使用默认值 JSON_SCHEMA
*/
public FileToolCallbackProvider(@Nullable SchemaType schemaType) {
this.schemaType = schemaType != null ? schemaType : DEFAULT_SCHEMA_TYPE;
}
/**
* 获取所有工具回调
* <p>
* 实现逻辑:
* 1. 加载配置文件中的工具定义
* 2. 遍历并过滤启用的工具
* 3. 构建每个工具的 ToolCallback
* 4. 返回 ToolCallback 数组供 ChatClient 使用
*
* @return ToolCallback 数组,包含所有启用的工具回调
*/
@Override
public ToolCallback[] getToolCallbacks() {
List<ToolCallback> callbacks = new ArrayList<>();
// 从配置文件加载工具定义
List<AiToolInfo> toolInfos = loadConfig();
log.info("配置文件加载 {} 个工具定义", toolInfos.size());
// 遍历所有工具配置
for (AiToolInfo info : toolInfos) {
// 跳过未启用的工具(status != 1)
if (info.getStatus() != 1) continue;
try {
// 构建工具回调对象
ToolCallback callback = buildCallback(info);
callbacks.add(callback);
log.info("加载工具: {}", info.getName());
} catch (Exception e) {
// 单个工具加载失败不影响其他工具
log.warn("加载工具失败: {}", info.getName(), e);
}
}
return callbacks.toArray(ToolCallback[]::new);
}
/**
* 根据工具配置信息构建 ToolCallback
* <p>
* 构建逻辑:
* - Supplier 类型:无输入参数,使用空 schema "{}"
* - Function 类型:有输入参数,自动生成 JSON Schema
*
* @param info 工具配置信息
* @return 构建好的 ToolCallback 对象
* @throws Exception 类加载或实例化失败时抛出异常
*/
private ToolCallback buildCallback(AiToolInfo info) throws Exception {
String toolName = info.getName();
String description = info.getDescription();
Integer status = info.getStatus();
// 仅加载启用的工具
if (status == 1) {
// 通过反射实例化工具实现类
Object instance = Class.forName(info.getImplClass())
.getDeclaredConstructor()
.newInstance();
// 处理 Supplier 类型工具(无参数,如获取当前时间)
if (instance instanceof Supplier<?> supplier) {
return FunctionToolCallback.builder(toolName, supplier)
.description(description)
.inputSchema("{}") // Supplier 无参数,使用空 schema
.build();
}
// 处理 Function 类型工具(有参数,如查询天气需要城市名称)
if (instance instanceof Function<?, ?> function) {
// 加载输入参数类型
Class<?> inputTypeClass = Class.forName(info.getInputType());
// 转换为 ResolvableType 用于生成 Schema
ResolvableType toolInputType = ResolvableType.forClass(inputTypeClass);
return FunctionToolCallback.builder(toolName, function)
.description(description)
.inputSchema(generateSchema(toolInputType)) // 自动生成参数的 JSON Schema
.inputType(ParameterizedTypeReference.forType(inputTypeClass))
.build();
}
// 不支持其他类型
throw new RuntimeException("不支持的类型: " + instance.getClass().getName());
}
throw new RuntimeException("当前工具未启用");
}
/**
* 根据输入类型生成 JSON Schema
* <p>
* JSON Schema 用于告诉大模型工具参数的结构,例如:
* <pre>
* {
* "type": "object",
* "properties": {
* "city": { "type": "string" }
* },
* "required": ["city"]
* }
* </pre>
*
* @param toolInputType 工具输入参数的类型
* @return JSON Schema 字符串
*/
private String generateSchema(ResolvableType toolInputType) {
// OPEN_API_SCHEMA 格式需要特殊处理(类型值大写)
if (this.schemaType == SchemaType.OPEN_API_SCHEMA) {
return JsonSchemaGenerator.generateForType(
toolInputType.getType(),
JsonSchemaGenerator.SchemaOption.UPPER_CASE_TYPE_VALUES
);
}
// 默认使用 JSON_SCHEMA 格式
return JsonSchemaGenerator.generateForType(toolInputType.getType());
}
/**
* 从配置文件加载工具定义列表
* <p>
* 加载顺序:
* 1. 尝试从项目相对路径加载(开发环境)
* 2. 尝试从 ClassPath 加载(打包后的运行环境)
*
* @return 工具配置信息列表,加载失败时返回空列表
*/
private List<AiToolInfo> loadConfig() {
// 可能的配置文件路径(支持不同运行环境)
String[] possiblePaths = {
"src/main/resources/tools/tools.json", // 根目录运行
"ai-chat-demo/src/main/resources/tools/tools.json" // 子模块运行
};
// 尝试从文件系统加载
for (String path : possiblePaths) {
File file = new File(path);
if (file.exists()) {
try {
return mapper.readValue(file, new TypeReference<List<AiToolInfo>>() {});
} catch (Exception e) {
log.warn("解析失败: {}", e.getMessage());
}
}
}
// 尝试从 ClassPath 加载(适用于 jar 包运行环境)
try {
return mapper.readValue(
new org.springframework.core.io.ClassPathResource("tools/tools.json").getInputStream(),
new TypeReference<List<AiToolInfo>>() {}
);
} catch (Exception e) {
log.warn("读取 tools.json 失败: {}", e.getMessage());
return new ArrayList<>();
}
}
}
提示:这里没有将工具对象保存到本地内存,每次大模型请求时,都会重新从文件中查找工具信息,创建工具对象,你可以使用内存进行缓存:
java
private final ToolCallback[] toolCallbacks;
3.5 ChatClient 配置
构建 ChatClient 时注入 FileToolCallbackProvider :
java
@Configuration
public class ChatClientConfig {
/**
* 创建智谱AI ChatClient,注入YML配置的工具
*/
@Bean("zhiPuAiChatClient")
public ChatClient zhiPuAiChatClient(ZhiPuAiChatModel zhiPuAiChatModel, FileToolCallbackProvider fileToolCallbackProvider) {
return ChatClient.builder(zhiPuAiChatModel)
.defaultToolCallbacks(fileToolCallbackProvider)
.build();
}
}
3.6 测试
提供一个对话测试接口:
java
@RestController
@RequestMapping("/chat")
@RequiredArgsConstructor
public class ChatController {
private final ChatClient chatClient;
@GetMapping
public String chat(@RequestParam String message) {
return chatClient.prompt().user(message).call().content();
}
}
启动时会执行加载:

调用访问接口进行对话:
java
http://localhost:8080/chat?message=几点了?
再次执行了工具加载并返回工具结果:


时间工具改为禁用状态:

再次请求只加载了天气工具:

无法再查看时间:
