Spring AI 工具调用(Tool Calling)实战
在大模型应用开发中,一个核心挑战是如何让语言模型“走出”自身的知识边界,与外部系统进行交互。这就是 工具调用(Tool Calling) 的意义所在。
工具调用 是指语言模型根据用户输入判断是否需要调用某个外部 API 或执行某项操作,并生成结构化的调用请求(包含工具名和参数),由客户端应用程序负责执行该调用并将结果返回给模型,最终由模型整合信息并生成自然语言响应。
它主要应用于两大场景:
- 信息检索:从数据库、网络服务、文件系统等外部来源获取实时或私有数据,扩展模型的知识能力。
- 采取行动:触发系统内的具体操作,如发送邮件、设置提醒、创建任务等,实现自动化流程。
本文将结合完整的代码示例,带你深入掌握 Spring AI 中实现工具调用的两种方式 —— 方法作为工具 和 函数作为工具,并通过一个天气查询系统的实战案例,全面解析其工作原理与最佳实践。
虽然我们常说“模型调用了工具”,但实际上,模型本身并不执行任何外部逻辑。真正的执行责任在于客户端应用程序。整个过程是一个循环的协作流程:
- 应用程序构建
ChatRequest,并将可用的 工具定义(Tool Definition) 注入其中; - 请求被发送至大模型;
- 模型分析用户意图,若需调用工具,则返回一个结构化指令(含工具名称和参数);
- 客户端接收到响应后,解析出工具调用请求;
- 工具调度器根据工具名称查找对应的工具实现并执行;
- 执行结果被封装并再次发送回模型;
- 模型结合工具返回的结果生成最终的人类可读回复;
- 若模型仍需调用其他工具,则重复上述流程,直到完成所有步骤。
这个过程可能涉及多次往返通信(multi-turn),确保复杂任务的顺利完成。
方法一:使用 @Tool 注解定义工具
Spring AI 支持通过注解的方式将普通 Java 方法转化为可被模型识别的“工具”。
示例:时间与闹钟工具
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.context.i18n.LocaleContextHolder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class LocalDateTimeTool {
@Tool(description = "Get the current date and time in the user's timezone")
public String getCurrentDateTime() {
System.out.println("Get the current date!");
return LocalDateTime.now()
.atZone(LocaleContextHolder.getTimeZone().toZoneId())
.toString();
}
@Tool(description = "Set a user alarm for the given time, provided in ISO-8601 format")
public void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("Alarm set for " + alarmTime);
}
}
注解说明:
| 注解 | 参数 | 说明 |
|---|---|---|
@Tool |
name |
工具名称,默认为方法名 |
description |
必须提供!帮助模型理解何时调用此工具 | |
returnDirect |
是否直接返回结果给用户(默认 false) |
|
resultConverter |
自定义结果转字符串逻辑 | |
@ToolParam |
description |
参数描述,强烈建议填写 |
required |
是否必填 |
控制器调用示例:
@GetMapping("/annotate/setAlarm")
public String setAlarm(String userInput) {
return chatClient.prompt()
.tools(new LocalDateTimeTool()) // 注册整个类中的 @Tool 方法
.user(userInput)
.call()
.content();
}
测试请求:
GET /tool/annotate/setAlarm?userInput=请帮我设置10分钟后的闹钟
模型会自动提取时间并格式化为 ISO-8601 字符串传入 setAlarm() 方法。
使用限制
当使用方法作为工具时,以下类型不能用于参数或返回值:
Optional<T>- 异步类型(如
CompletableFuture,Future) - 响应式类型(如
Mono,Flux,Flow) - 函数式接口(如
Function,Supplier,Consumer)
方法二:使用函数式编程定义工具
Spring AI 还支持以 Function<I, O> 形式定义工具,更适合无状态的服务逻辑。
示例:天气查询服务
public class WeatherService implements Function<WeatherService.WeatherRequest, WeatherService.WeatherResponse> {
public record WeatherRequest(String location, Unit unit) {}
public record WeatherResponse(double temp, Unit unit) {}
public enum Unit { C, F }
@Override
public WeatherResponse apply(WeatherRequest request) {
System.out.println("获取{" + request.location + "}的天气");
double temperature = Math.random() * 50; // 模拟随机温度
return new WeatherResponse(temperature, Unit.C);
}
}
方式 A:手动构建 FunctionToolCallback
适用于灵活注册、动态管理工具场景。
@GetMapping("/method/getWeather")
public String getWeather(String userInput) {
FunctionToolCallback<WeatherService.WeatherRequest, WeatherService.WeatherResponse> callback =
FunctionToolCallback.builder("currentWeather", new WeatherService())
.description("Get the weather in location")
.inputType(WeatherService.WeatherRequest.class)
.build();
return chatClient.prompt()
.toolCallbacks(callback)
.user(userInput)
.call()
.content();
}
方式 B:通过 @Bean 注册工具(推荐)
利用 Spring IoC 容器统一管理工具实例,更符合我们的开发习惯。
@Configuration(proxyBeanMethods = false)
public class WeatherTools {
public static final String CURRENT_WEATHER_TOOL = "currentWeather";
@Bean(CURRENT_WEATHER_TOOL)
@Description("Get the weather in location")
public Function<WeatherRequest, WeatherResponse> currentWeather() {
return new WeatherService();
}
}
注意:
@Description注解用于提供工具描述,若不加则默认使用 Bean 名称作为描述。
调用控制器:
@GetMapping("/spring/getWeather")
public String getWeatherBySpring(String userInput) {
return chatClient.prompt()
.toolNames(WeatherTools.CURRENT_WEATHER_TOOL) // 仅传入 Bean 名称
.user(userInput)
.call()
.content();
}
测试请求:
GET /tool/spring/getWeather?userInput=上海今天热吗?
模型会正确解析出地点“上海”,调用 currentWeather 工具,并结合返回温度生成人性化回答。
函数工具的类型限制
以下类型不支持作为函数工具的输入或输出:
- 原始类型(primitive types,如
int,double) Optional<T>- 集合类型(如
List,Map,Set, 数组) - 异步/响应式类型
✅正确做法是使用 Record 或 POJO 封装参数和返回值,如上文的 WeatherRequest 和 WeatherResponse。
总结对比
| 特性 | 方法工具(@Tool) | 函数工具(Function) |
|---|---|---|
| 定义方式 | 类中带注解的方法 | Function<I, O> 实现 |
| 注册方式 | .tools(instance) |
.toolCallbacks() 或 .toolNames(beanName) |
| 适合场景 | 多个相关功能组合(如时间处理) | 单一职责服务(如天气、翻译) |
| 管理方式 | 手动实例化 | 可交由 Spring 容器管理(@Bean) |
| 类型限制 | 不支持异步/响应式等 | 不支持原始类型、集合等 |
最佳实践建议
- 优先使用
@Bean + Function模式:便于依赖注入、单元测试和统一管理; - 务必填写 description:高质量的描述能显著提升模型选择工具的准确性;
- 合理命名工具和参数:语义清晰有助于模型理解和推理;
- 避免副作用过重的操作:工具应尽量保持幂等性和安全性;
- 记录日志便于调试:观察模型是否准确识别了工具调用时机;
- 考虑错误处理机制:工具执行失败时应有兜底策略。
结语
Spring AI 对工具调用的抽象极大简化了大模型与现实世界的连接。无论是将已有方法快速暴露为工具,还是构建新的函数式服务,Spring AI 都提供了简洁而强大的支持。
本文使用 @Tool 注解创建了时间工具和闹钟功能,利用 Function + @Bean 构建了可复用的天气查询服务,介绍了工具调用背后的多轮交互机制和常见类型限制陷阱。
更多推荐



所有评论(0)