文章目录
- [1. 概述](#1. 概述)
- [2. 全局默认工具](#2. 全局默认工具)
-
- [2.1 defaultTools()](#2.1 defaultTools())
- [2.2 defaultToolCallbacks()](#2.2 defaultToolCallbacks())
-
- [2.2.1 ToolCallback 参数](#2.2.1 ToolCallback 参数)
- [2.2.2 ToolCallbackProvider 参数](#2.2.2 ToolCallbackProvider 参数)
- [2.3 defaultToolNames()](#2.3 defaultToolNames())
- [3. 运行时动态工具](#3. 运行时动态工具)
-
- [3.1 tools()](#3.1 tools())
- [3.2 其他方法](#3.2 其他方法)
- [4. 总结](#4. 总结)
-
- [4.1 是否需要配置全局默认工具?](#4.1 是否需要配置全局默认工具?)
- [4.2 函数型还是方法型工具?](#4.2 函数型还是方法型工具?)
- [4.3 生产级别的工具设计](#4.3 生产级别的工具设计)
-
- [4.3.1 工具注册策略对比](#4.3.1 工具注册策略对比)
- [4.3.2 生产级工具管理架构](#4.3.2 生产级工具管理架构)
- [4.3.3 生产级工具设计原则](#4.3.3 生产级工具设计原则)
- [4.3.4 工具最佳实践总结](#4.3.4 工具最佳实践总结)
1. 概述
从工具的生命周期来说,Spring AI 支持两种类型的工具,二者在创建时机、优先级、线程安全等方面存在显著差异,核心设计要点对比如下:
| 特性 | 全局默认工具 | 运行时动态工具 |
|---|---|---|
| 创建时机 | 应用启动 | 请求时 |
| 合并优先级 | 低(被覆盖) | 高(覆盖默认) |
| 线程安全 | 需考虑(共享) | 无需考虑(请求隔离) |
| 内存管理 | 常驻 | 请求结束回收 |
| 适用场景 | 通用能力 | 用户特定资源、会话状态 |
全局默认工具 :应用级生命周期,是在构建 ChatClient 时使用 defaultTool 相关方法传入的工具,应用关闭时销毁;
运行时动态工具 :请求/会话级生命周期,是在使用 call/stream 时使用 tool 相关方法传入的工具,请求结束后由GC回收。
2. 全局默认工具
ChatClient 提供了一组 defaultTool 相关方法,用于配置全局默认工具:

简单示例如下:
java
ChatClient chatClient = ChatClient.builder(chatModel)
// 默认工具配置
.defaultToolNames("searchBooks", "getWeather") // Bean名称
.defaultTools(new MyTools()) // @Tool对象
.defaultToolCallbacks(callback1, callback2) // 直接注册
.defaultToolCallbacks(provider) // Provider
.build();
2.1 defaultTools()
defaultTools() 方法将对象中标注了 @Tool 的方法自动解析为工具,无需手动构建 ToolCallback,适合快速将业务服务类转换为工具。
工具定义示例:
java
@Service
public class MyTools {
@Tool(description = "搜索书籍")
public Book searchBooks(@ToolParam(description = "书名关键词") String keyword) {
return bookService.search(keyword);
}
@Tool(description = "获取天气")
public Weather getWeather(@ToolParam(description = "城市") String city) {
return weatherService.get(city);
}
defaultTools() 方法本质是调用 defaultRequest 的 tools 方法,最终通过反射扫描 @Tool 注解方法并转换为 ToolCallback:
java
// defaultTools() 方法入口
@Override
public Builder defaultTools(Object... toolObjects) {
this.defaultRequest.tools(toolObjects);
return this;
}
// 底层调用 ToolCallbacks.from() 解析工具对象
@Override
public ChatClientRequestSpec tools(Object... toolObjects) {
Assert.notNull(toolObjects, "toolObjects cannot be null");
Assert.noNullElements(toolObjects, "toolObjects cannot contain null elements");
this.toolCallbacks.addAll(Arrays.asList(ToolCallbacks.from(toolObjects)));
return this;
}
// ToolCallbacks.from() 核心逻辑:通过 MethodToolCallbackProvider 提取 @Tool 方法
public static ToolCallback[] from(Object... sources) {
return MethodToolCallbackProvider.builder().toolObjects(sources).build().getToolCallbacks();
}
MethodToolCallbackProvider 的 getToolCallbacks() 方法会通过反射完成以下流程,最终生成 ToolCallback 实例:
-
遍历所有工具对象,若为
AOP代理对象则获取目标类,否则直接获取当前类; -
过滤出标注了
@Tool注解的方法,排除函数式接口方法、合成方法和桥接方法; -
为每个有效方法构建
MethodToolCallback,配置工具定义、元数据、结果转换器等; -
校验工具回调合法性(如避免重名),最终返回
ToolCallback数组。
最终所有工具会存储在 DefaultChatClientRequestSpec 的 toolCallbacks 列表中,请求时通过反射调用 toolMethod.invoke(toolObject, args) 执行工具。
适用场景:
-
快速将业务服务类转换为工具,无需手动构建
ToolCallback; -
适合方法级别的工具定义,追求开发效率与代码可读性。
2.2 defaultToolCallbacks()
defaultToolCallbacks() 是重载方法,支持直接传入 ToolCallback 对象或 ToolCallbackProvider,灵活适配不同工具定义场景,重载形式如下:
-
defaultToolCallbacks(ToolCallback... toolCallbacks):直接传入单个或多个ToolCallback对象; -
defaultToolCallbacks(List<ToolCallback> toolCallbacks):传入ToolCallback列表; -
defaultToolCallbacks(ToolCallbackProvider... toolCallbackProviders):传入ToolCallbackProvider,可以自定义提供工具。
2.2.1 ToolCallback 参数
直接传入 ToolCallback 对象,适合定义函数型工具,可完全控制工具逻辑、输入类型和元数据,示例如下:
java
// 构建 ToolCallback(函数型工具)
ToolCallback callback1 = FunctionToolCallback.builder("searchBooks", (request, context) -> {
return bookService.search(request.keyword());
})
.description("搜索书籍")
.inputType(BookSearchRequest.class)
.build();
ToolCallback callback2 = FunctionToolCallback.builder("getWeather", (request, context) -> {
return weatherService.get(request.city());
})
.description("获取天气")
.inputType(WeatherRequest.class)
.build();
方法入口与执行流程:
java
// 方法入口
@Override
public Builder defaultToolCallbacks(ToolCallback... toolCallbacks) {
this.defaultRequest.toolCallbacks(toolCallbacks);
return this;
}
@Override
public Builder defaultToolCallbacks(List<ToolCallback> toolCallbacks) {
this.defaultRequest.toolCallbacks(toolCallbacks);
return this;
}
// 底层存储逻辑:与 defaultTools() 结果存入同一列表
@Override
public ChatClientRequestSpec toolCallbacks(List<ToolCallback> toolCallbacks) {
Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
Assert.noNullElements(toolCallbacks, "toolCallbacks cannot contain null elements");
this.toolCallbacks.addAll(toolCallbacks);
return this;
}
执行流程:
- 通过
FunctionToolCallback.builder()构建工具对象 - 直接存入
defaultRequest.toolCallbacks列表 - 请求时无需额外解析,直接使用
适用场景:
- 完全控制工具的定义和行为,需自定义输入类型、元数据或结果转换;
- 工具逻辑复杂,需要访问
ToolContext; - 偏好函数式风格定义工具。
2.2.2 ToolCallbackProvider 参数
通过 ToolCallbackProvider 批量提供工具,适合工具按业务领域分组、动态提供工具集的场景,示例如下:
java
// ToolCallbackProvider 定义
public class MyToolProvider implements ToolCallbackProvider {
@Override
public ToolCallback[] getToolCallbacks() {
return new ToolCallback[] {
FunctionToolCallback.builder("tool1", ...).build(),
FunctionToolCallback.builder("tool2", ...).build(),
FunctionToolCallback.builder("tool3", ...).build()
};
}
}
// 使用方式
ToolCallbackProvider provider = new MyToolProvider();
chatClient.builder().defaultToolCallbacks(provider).build();
执行流程:
- 创建
ToolCallbackProvider实例并传入defaultToolCallbacks(); - 将
Provider存入defaultRequest.toolCallbackProviders列表; - 请求时,调用
provider.getToolCallbacks()懒加载获取ToolCallback数组; - 将获取到的工具合并到最终工具列表中执行。
适用场景:
-
批量提供相关工具(如数据库操作工具集、电商订单工具集);
-
工具按业务领域分组管理;
-
支持动态工具集(
Provider可按条件返回不同工具)。
2.3 defaultToolNames()
通过工具名称引用已定义的工具(如 Spring Bean 工具、ToolCallbackProvider 提供的工具),无需硬编码依赖工具实例,方法入口如下:
java
public Builder defaultToolNames(String... toolNames) {
this.defaultRequest.toolNames(toolNames);
return this;
}
应用启动时,defaultToolNames 传入的工具名称会存入 DefaultChatClientRequestSpec 的 toolNames 列表;当 ChatClient 发送请求时,会经历以下解析流程:
-
请求构建阶段,
DefaultChatClientUtils.toChatClientRequest()会合并defaultToolNames与请求级toolNames; -
ChatModel#call()方法执行时,ToolCallingManager会调用resolveToolDefinitions()解析工具名称; -
通过
DelegatingToolCallbackResolver依次从StaticToolCallbackResolver(存储Provider工具)和Spring容器(存储Bean工具)中获取对应ToolCallback; -
若未找到对应工具,会抛出
IllegalStateException异常。
异常示例:
java
2026-04-03T11:37:00.868+08:00 WARN 16552 --- [ai-chat-demo] [ main] o.s.a.m.tool.DefaultToolCallingManager : LLM may have adapted the tool name 'query', especially if the name was truncated due to length limits. If this is the case, you can customize the prefixing and processing logic using McpToolNamePrefixGenerator
Exception in thread "main" java.lang.IllegalStateException: No ToolCallback found for tool name: query
at org.springframework.ai.model.tool.DefaultToolCallingManager.resolveToolDefinitions(DefaultToolCallingManager.java:117)
at org.springframework.ai.zhipuai.ZhiPuAiChatModel.createRequest(ZhiPuAiChatModel.java:564)
at org.springframework.ai.zhipuai.ZhiPuAiChatModel.call(ZhiPuAiChatModel.java:251)
at org.springframework.ai.chat.client.advisor.ChatModelCallAdvisor.adviseCall(ChatModelCallAdvisor.java:56)
适用场景:
-
工具已在其他地方定义为
Spring Bean; -
按名称动态引用工具,避免硬编码依赖;
-
支持工具热替换(
Bean可动态更新)。
3. 运行时动态工具
运行时动态工具是在发起请求时配置的工具,属于请求/会话级生命周期,会继承全局默认工具的配置并可覆盖默认工具,ChatClient 提供的相关方法如下:

简单示例:
java
chatClient.prompt()
.user("查询信息")
// 运行时工具配置(与默认工具合并)
.toolNames("extraTool") // 额外Bean名称
.tools(new ExtraTools()) // 额外@Tool对象
.toolCallbacks(extraCallback) // 额外ToolCallback
.toolContext(Map.of("requestId", "req456")) // 上下文
.call().content();
3.1 tools()
tools() 方法用于在运行时传入 @Tool 注解对象,核心执行步骤如下:
java
// 第一步:创建请求规格,继承默认配置(默认工具、系统提示等)
ChatClient.ChatClientRequestSpec chatClientRequestSpec = deepSeekChatClient.prompt("几点了");
// 第二步:配置运行时动态工具,覆盖或补充默认工具
ChatClient.ChatClientRequestSpec clientRequestSpec = chatClientRequestSpec.tools(new DateTimeTools());
// 第三步:执行调用,发送请求到 AI 模型
ChatClient.CallResponseSpec callResponseSpec = clientRequestSpec.call();
// 第四步:提取响应内容
String content = callResponseSpec.content();
核心逻辑:
- 调用
prompt()方法时,会创建新的DefaultChatClientRequestSpec实例,复制defaultChatClientRequest中的默认配置(包括全局默认工具); - 后续调用
tools()方法时,会将运行时工具添加到新的请求规格中,与默认工具合并。 - 请求结束后,该请求规格及其中的工具实例会被
GC回收,下次请求需重新配置运行时工具。
3.2 其他方法
运行时动态工具的其他方法(toolNames()、toolCallbacks()、toolContext())与全局默认工具的对应方法逻辑一致,均用于配置运行时工具或上下文,差异仅在于生命周期(请求级 vs 应用级),此处不再赘述。
4. 总结
核心结论:全局默认工具适合通用能力,运行时动态工具适合上下文依赖;方法型工具适合快速开发,函数型工具适合精细控制;生产级设计需要兼顾权限、监控、审计与安全。
4.1 是否需要配置全局默认工具?
取决于应用场景,核心判断标准为"是否依赖用户上下文、是否可复用",具体推荐如下:
| 场景 | 推荐 | 原因 |
|---|---|---|
| 通用能力工具(天气查询、搜索、计算) | ✅ 全局默认 | 所有用户/请求共享,无需每次配置 |
| 业务领域工具(订单查询、库存管理) | ✅ 全局默认 | 业务系统核心能力,稳定复用 |
| 用户私有资源工具(个人文件、私有数据) | ❌ 运行时动态 | 需要用户上下文,请求隔离 |
| 会话状态工具(购物车、对话历史) | ❌ 运行时动态 | 状态随会话变化,不能共享 |
| 临时/一次性工具(特定任务处理) | ❌ 运行时动态 | 用完即弃,避免内存占用 |
4.2 函数型还是方法型工具?
Spring AI 支持两种工具创建方式,分别对应方法型和函数型工具,核心区别与推荐原则如下:
方法型工具(一个方法对应一个工具):
-
声明式:
@Tool+@ToolParam定义工具,框架自动扫描生成; -
编程式:普通
Java对象方法,手动获取方法对象、定义ToolDefinition生成工具。
函数型工具(一个函数对象对应一个工具):
-
声明式:通过
@Bean注解声明,框架自动解析注册; -
编程式:普通
Java函数对象,手动获取函数对象、定义ToolDefinition生成工具。
推荐原则:
| 优先选择 | 场景 |
|---|---|
| 方法型 | 业务服务类、团队协作、代码可读性优先 |
| 函数型 | 工具逻辑复杂、需要 ToolContext、自定义元数据 |
| 混合使用 | 简单工具用方法型,复杂工具用函数型 |
4.3 生产级别的工具设计
4.3.1 工具注册策略对比
生产环境中,工具注册推荐使用 ToolCallbackProvider,相比 Bean 名称注册更具灵活性和可扩展性,具体对比如下:
| 特性 | 方案一:Bean 名称 | 方案二:ToolCallbackProvider |
|---|---|---|
| 动态 CRUD | ❌ 不支持 | ✅ 支持 |
| 热更生效 | ❌ 需重启 | ✅ 实时生效 |
| 业务侵入性 | ❌ 需注册 Bean | ✅ 无侵入 |
| 分层解耦 | ❌ 耦合容器 | ✅ 完全解耦 |
| 生产级可用性 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
4.3.2 生产级工具管理架构
生产级工具管理需支持动态扩展、权限管控、实时监控和生命周期管理,推荐采用四层架构,具体如下:
text
┌─────────────────────────────────────────────────────────────────────┐
│ 生产级工具管理架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 工具注册中心 │ │
│ ├───────────────────────────────────────────────────────────────┤ │
│ │ ToolCallbackRegistry │ │
│ │ ├─ register(toolName, ToolCallback) // 注册 │ │
│ │ ├─ unregister(toolName) // 注销 │ │
│ │ ├─ resolve(toolName) // 解析 │ │
│ │ ├─ list() // 列表 │ │
│ │ └─ update(toolName, ToolCallback) // 更新 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 工具权限控制 │ │
│ ├───────────────────────────────────────────────────────────────┤ │
│ │ ToolAccessManager │ │
│ │ ├─ checkPermission(userId, toolName) // 权限检查 │ │
│ │ ├─ getAccessibleTools(userId) // 获取可用工具 │ │
│ │ └─ audit(userId, toolName, action) // 审计日志 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 工具执行监控 │ │
│ ├───────────────────────────────────────────────────────────────┤ │
│ │ ToolExecutionMonitor │ │
│ │ ├─ recordCall(toolName, duration, result) // 调用记录 │ │
│ │ ├─ getStatistics(toolName) // 统计数据 │ │
│ │ ├─ alertOnFailure(toolName, threshold) // 失败告警 │ │
│ │ └─ getSlowCalls(threshold) // 慢调用分析 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 工具生命周期管理 │ │
│ ├───────────────────────────────────────────────────────────────┤ │
│ │ ToolLifecycleManager │ │
│ │ ├─ init() // 初始化 │ │
│ │ ├─ healthCheck() // 健康检查 │ │
│ │ ├─ reload() // 热重载 │ │
│ │ └─ destroy() // 销毁 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
4.3.3 生产级工具设计原则
原则一:工具单一职责
每个工具只处理一件事,避免LLM调用混淆,降低故障风险:
java
// ❌ 错误:一个工具做多件事
@Tool(description = "订单操作")
public String orderOperation(String orderId, String action) {
if ("query".equals(action)) return query(orderId);
if ("cancel".equals(action)) return cancel(orderId);
if ("refund".equals(action)) return refund(orderId);
// LLM 容易混淆,调用出错
}
// ✅ 正确:每个工具单一职责
@Tool(description = "查询订单")
public Order queryOrder(@ToolParam(desc = "订单ID") String orderId) { ... }
@Tool(description = "取消订单")
public boolean cancelOrder(@ToolParam(desc = "订单ID") String orderId) { ... }
@Tool(description = "退款订单")
public boolean refundOrder(@ToolParam(desc = "订单ID") String orderId) { ... }
原则二:输入参数明确
工具参数需清晰描述,明确格式和要求,避免LLM无法正确传参:
java
// ❌ 错误:参数模糊
@Tool(description = "查询")
public Object query(String input) { ... } // LLM 不知道传什么
// ✅ 正确:参数清晰
@Tool(description = "根据订单ID查询订单详情")
public Order queryOrderById(
@ToolParam(description = "订单ID,格式:ORD-XXXXX", required = true)
String orderId
) { ... }
原则三:结果可解析
工具返回结果需精简、结构化,避免返回复杂对象,便于LLM理解与业务系统对接:
java
// ❌ 错误:返回复杂对象,LLM 难解析
@Tool(description = "查询用户")
public User queryUser(String userId) {
return userRepository.findById(userId); // 包含几十个字段
}
// ✅ 正确:返回结构化、精简结果
@Tool(description = "查询用户基本信息")
public UserInfo queryUserInfo(String userId) {
User user = userRepository.findById(userId);
return new UserInfo(user.getId(), user.getName(), user.getEmail()); // 只返回关键信息
}
原则四:幂等性设计
查询类工具需保证幂等(重复调用结果一致);非幂等工具需添加防重机制,避免重复操作导致异常:
java
// ✅ 正确:幂等工具,重复调用结果一致
@Tool(description = "查询订单状态")
public OrderStatus getOrderStatus(String orderId) {
return orderRepository.findStatus(orderId); // 只读,幂等
}
// ❌ 非幂等工具需谨慎(需添加防重机制)
@Tool(description = "发送通知")
public boolean sendNotification(String userId, String message) {
// 每次调用都会发送,需要防重机制
if (notificationCache.exists(userId, message)) {
return true; // 已发送,跳过
}
notificationService.send(userId, message);
notificationCache.mark(userId, message);
return true;
}
原则五:异常友好处理
工具异常需转换为LLM与用户可理解的文本,避免直接抛出技术异常中断业务流程:
java
// ✅ 正确:异常转换为友好消息
@Tool(description = "查询订单")
public String queryOrder(String orderId) {
try {
Order order = orderService.findById(orderId);
return formatOrder(order);
} catch (OrderNotFoundException e) {
return "订单不存在:" + orderId; // LLM 可理解的错误
} catch (Exception e) {
return "查询失败,请稍后重试";
}
}
// ❌ 错误:直接抛异常
@Tool(description = "查询订单")
public Order queryOrder(String orderId) {
return orderService.findById(orderId); // 异常会中断对话
}
原则六:敏感数据保护
用户敏感信息(手机号、邮箱、银行卡号等)需脱敏处理,避免数据泄露:
java
// ✅ 正确:敏感数据脱敏
@Tool(description = "查询用户信息")
public UserInfo queryUserInfo(String userId) {
User user = userService.findById(userId);
return new UserInfo(
user.getId(),
user.getName(),
maskEmail(user.getEmail()), // 邮箱脱敏:a***@gmail.com
maskPhone(user.getPhone()) // 电话脱敏:138****1234
);
}
4.3.4 工具最佳实践总结
| 最佳实践 | 说明 |
|---|---|
| 单一职责 | 每个工具只做一件事 |
| 参数清晰 | 使用 @ToolParam 详细描述参数 |
| 结果精简 | 只返回必要信息,避免复杂对象 |
| 幂等设计 | 重复调用结果一致,非幂等需防重 |
| 异常友好 | 异常转换为 LLM 可理解的文本 |
| 数据脱敏 | 保护敏感信息,避免泄露 |
| 权限控制 | 按用户/角色限制工具访问 |
| 调用监控 | 记录调用次数、耗时、成功率 |
| 审计日志 | 关键操作留痕,便于追溯 |
| 文档完善 | 工具描述准确,包含示例 |