1. Spring AI工具调用实战:让AI拥有"超能力"的完整指南
作为一名长期从事企业级Java开发的工程师,我最近在探索如何将大语言模型(LLM)的能力整合到Spring生态系统中。Spring AI的出现为我们提供了一种优雅的解决方案,特别是其工具调用(Tool Calling)功能,可以让AI模型突破自身限制,真正具备与外部世界交互的能力。
1.1 为什么需要工具调用?
大语言模型虽然强大,但存在几个关键限制:
- 知识时效性问题:模型的训练数据有截止日期,无法获取最新信息
- 执行能力局限:只能生成文本,无法执行实际操作系统
- 计算精度不足:复杂数学计算容易出错
- 私有数据隔离:无法访问企业内部或用户个人数据
工具调用技术正是为了解决这些问题而生。通过这项技术,我们可以:
- 让AI查询实时数据(如天气、股票)
- 执行具体操作(如发送邮件、设置提醒)
- 进行精确计算
- 与企业内部系统集成
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 项目架构与技术选型
2.1 整体架构设计
我们的项目采用分层架构设计:
code复制spring-ai-jc/
├── src/main/java/org/example/
│ ├── controller/ # 表现层
│ ├── exception/ # 异常处理
│ ├── tools/ # 工具实现层
│ └── config/ # 配置类
├── src/main/resources/
│ └── application.yml # 配置文件
└── pom.xml # 依赖管理
2.2 技术栈详解
我们选择的技术栈充分考虑了企业级应用的需求:
| 技术 | 版本 | 选择理由 |
|---|---|---|
| Spring Boot | 3.5.10 | 提供快速开发能力,完善的生态系统 |
| Spring AI | 1.1.0-SNAPSHOT | 官方支持的AI集成方案,与Spring生态无缝衔接 |
| Java | 17 | LTS版本,稳定性高,企业级应用首选 |
| WebFlux | 3.5.10 | 响应式编程模型,适合高并发场景 |
| JUnit 5 | 5.10.0 | 现代测试框架,支持参数化测试和扩展模型 |
2.3 开发环境准备
在实际开发中,我推荐以下环境配置:
- JDK安装:
bash复制# 使用SDKMAN管理多版本JDK
sdk install java 17.0.10-tem
sdk use java 17.0.10-tem
- IDE配置:
- IntelliJ IDEA 2023.3+(社区版或终极版)
- 安装Lombok插件
- 配置Git版本控制
- API密钥管理:
建议使用环境变量或配置中心管理敏感信息,避免硬编码:
bash复制# 在~/.bashrc或~/.zshrc中添加
export SPRING_AI_OPENAI_API_KEY='your-api-key'
3. 核心实现详解
3.1 工具类开发实践
3.1.1 日期时间工具实现
DateTimeTools类展示了如何实现一个完整的工具类:
java复制@Component
public class DateTimeTools {
private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern("HH:mm:ss");
@Tool(description = "获取用户所在时区的当前日期和时间,格式为ISO-8601")
public String getCurrentDateTime() {
ZoneId zoneId = ZoneId.systemDefault();
LocalDateTime now = LocalDateTime.now(zoneId);
return now.format(DateTimeFormatter.ISO_DATE_TIME);
}
@Tool(description = "计算两个日期之间的工作日天数(排除周末)")
public long calculateWorkingDays(
@ToolParam(description = "开始日期,格式yyyy-MM-dd") String startDate,
@ToolParam(description = "结束日期,格式yyyy-MM-dd") String endDate) {
LocalDate start = LocalDate.parse(startDate, DATE_FORMATTER);
LocalDate end = LocalDate.parse(endDate, DATE_FORMATTER);
return start.datesUntil(end)
.filter(date -> !isWeekend(date))
.count();
}
private boolean isWeekend(LocalDate date) {
DayOfWeek day = date.getDayOfWeek();
return day == DayOfWeek.SATURDAY || day == DayOfWeek.SUNDAY;
}
}
关键点说明:
- 使用
@Component让Spring管理工具类生命周期 @Tool注解的description要详细描述功能和使用场景- 日期处理使用Java 8的Time API,线程安全且功能强大
- 添加了工作日计算等实用功能,超越基础实现
3.1.2 天气查询工具优化
实际项目中,我们通常会接入真实的天气API服务。以下是优化后的WeatherTools实现:
java复制@Component
@RequiredArgsConstructor
public class WeatherTools {
private final RestTemplate restTemplate;
private final CacheManager cacheManager;
@Tool(description = "获取指定城市的当前天气信息,包括温度、天气状况、湿度、风速等")
public String getCurrentWeather(
@ToolParam(description = "城市名称,例如:北京、上海") String city) {
// 检查缓存
Cache cache = cacheManager.getCache("weather");
Cache.ValueWrapper cached = cache.get(city);
if (cached != null) {
return (String) cached.get();
}
// 调用真实API
String apiUrl = "https://api.weather.com/v3/wx/conditions/current";
Map<String, String> params = Map.of(
"city", city,
"apiKey", weatherApiKey
);
WeatherResponse response = restTemplate.getForObject(
apiUrl + "?city={city}&apiKey={apiKey}",
WeatherResponse.class,
params);
// 处理响应并缓存
String result = formatWeather(response);
cache.put(city, result);
return result;
}
// 更详细的天气数据模型
@Data
private static class WeatherResponse {
private Temperature temperature;
private Precipitation precipitation;
private Wind wind;
private String phrase;
@Data
static class Temperature {
private Double value;
private String unit;
}
// 其他内部类...
}
}
企业级优化:
- 引入Spring Cache抽象,减少API调用次数
- 使用RestTemplate进行HTTP调用
- 定义详细的响应模型类,提高代码可读性
- 添加了完整的JavaDoc和参数校验
3.2 控制器层实现
ToolController是连接前端和AI能力的桥梁,我们采用响应式编程模型:
java复制@RestController
@RequestMapping("/api/tools")
@RequiredArgsConstructor
public class ToolController {
private final ChatClient chatClient;
private final DateTimeTools dateTimeTools;
private final WeatherTools weatherTools;
@PostMapping(value = "/query", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<AiResponse>> handleQuery(
@RequestBody @Valid UserQueryRequest request) {
return Mono.fromCallable(() -> {
String response = chatClient.prompt()
.system("你是一个智能助手,可以使用多种工具帮助用户解决问题")
.user(request.getQuestion())
.tools(dateTimeTools, weatherTools)
.call()
.content();
return ResponseEntity.ok(new AiResponse(response));
}).subscribeOn(Schedulers.boundedElastic());
}
// 流式响应端点
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamResponse(
@RequestParam String question) {
return chatClient.prompt()
.system("你是一个有帮助的助手")
.user(question)
.tools(dateTimeTools, weatherTools)
.stream()
.content();
}
@Data
private static class UserQueryRequest {
@NotBlank
private String question;
}
@Data
@AllArgsConstructor
private static class AiResponse {
private String answer;
}
}
关键设计决策:
- 使用响应式编程处理并发请求
- 添加参数校验确保输入安全
- 提供常规JSON和流式两种响应方式
- 封装标准的请求/响应模型
- 异常处理通过@ControllerAdvice统一管理
4. 高级特性实现
4.1 多工具组合调用
在实际业务场景中,经常需要多个工具协同工作。我们通过优化系统提示词和工具描述来实现智能调度:
java复制public String handleComplexQuery(String question) {
return chatClient.prompt()
.system("""
你是一个高级智能助手,可以协调使用多个工具解决问题。
请遵循以下原则:
1. 分析用户问题的核心需求
2. 选择最合适的工具组合
3. 合理安排工具调用顺序
4. 整合结果给出完整回答
""")
.user(question)
.tools(dateTimeTools, weatherTools, calculatorTools, alarmTools)
.call()
.content();
}
典型调用场景:
- 用户问:"明天北京天气如何?现在几点了?"
- AI依次调用:
- getCurrentWeather("北京")
- getCurrentDateTime()
- 整合两个工具的结果生成回复
4.2 工具调用监控
在生产环境中,我们需要监控工具调用的性能和成功率:
java复制@Aspect
@Component
@RequiredArgsConstructor
public class ToolMonitoringAspect {
private final MeterRegistry meterRegistry;
@Around("@annotation(org.springframework.ai.tool.annotation.Tool)")
public Object monitorToolCall(ProceedingJoinPoint joinPoint) throws Throwable {
String toolName = joinPoint.getSignature().getName();
Timer.Sample sample = Timer.start(meterRegistry);
try {
Object result = joinPoint.proceed();
sample.stop(meterRegistry.timer("tool.calls", "name", toolName, "status", "success"));
return result;
} catch (Exception e) {
sample.stop(meterRegistry.timer("tool.calls", "name", toolName, "status", "failure"));
throw e;
}
}
}
监控指标包括:
- 调用次数
- 成功率
- 响应时间分布
- 异常统计
5. 生产环境最佳实践
5.1 性能优化策略
- 工具缓存:对频繁调用且结果变化不频繁的工具添加缓存
java复制@Cacheable(value = "weather", key = "#city")
public String getCurrentWeather(String city) {
// API调用
}
- 异步执行:使用CompletableFuture并行执行独立工具
java复制CompletableFuture<String> weatherFuture = CompletableFuture.supplyAsync(
() -> weatherTools.getCurrentWeather("北京"));
CompletableFuture<String> timeFuture = CompletableFuture.supplyAsync(
() -> dateTimeTools.getCurrentDateTime());
CompletableFuture.allOf(weatherFuture, timeFuture).join();
String weather = weatherFuture.get();
String time = timeFuture.get();
- 连接池配置:优化HTTP工具的连接参数
yaml复制spring:
ai:
openai:
timeout:
connect: 5s
read: 30s
5.2 安全防护措施
- 输入校验:对所有工具参数进行严格校验
java复制@Tool(description = "获取天气信息")
public String getWeather(
@ToolParam(description = "城市名称") @Pattern(regexp = "^[\\u4e00-\\u9fa5a-zA-Z]+$") String city) {
// 实现
}
- 权限控制:基于Spring Security实现工具访问控制
java复制@PreAuthorize("hasRole('WEATHER_ACCESS')")
@Tool(description = "获取天气信息")
public String getWeather(String city) {
// 实现
}
- 敏感数据过滤:在响应中过滤敏感信息
java复制@ControllerAdvice
public class ResponseSanitizer implements ResponseBodyAdvice<Object> {
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
// 实现敏感信息过滤逻辑
}
}
6. 扩展与集成
6.1 企业级扩展方向
- 数据库操作工具:
java复制@Tool(description = "查询用户信息")
public List<User> queryUsers(
@ToolParam(description = "查询条件") String condition) {
return userRepository.findByCondition(condition);
}
- 工作流集成:
java复制@Tool(description = "启动审批流程")
public String startApproval(
@ToolParam(description = "流程类型") String processType,
@ToolParam(description = "业务ID") String businessId) {
return workflowEngine.startProcess(processType, businessId);
}
- 消息通知工具:
java复制@Tool(description = "发送系统通知")
public String sendNotification(
@ToolParam(description = "接收人") String receiver,
@ToolParam(description = "消息内容") String content) {
notificationService.send(receiver, content);
return "通知发送成功";
}
6.2 监控与告警集成
- Prometheus监控:
java复制@Bean
public MeterRegistryCustomizer<PrometheusMeterRegistry> configureMetrics() {
return registry -> registry.config().commonTags("application", "spring-ai-demo");
}
- 日志审计:
java复制@Aspect
@Component
public class ToolAuditAspect {
private final AuditLogger auditLogger;
@AfterReturning(pointcut = "@annotation(org.springframework.ai.tool.annotation.Tool)",
returning = "result")
public void auditToolCall(JoinPoint joinPoint, Object result) {
String toolName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
auditLogger.logToolCall(toolName, args, result);
}
}
7. 常见问题排查
在实际项目落地过程中,我们遇到了不少挑战,以下是典型问题及解决方案:
7.1 工具不被调用问题
症状:AI没有按预期调用工具
排查步骤:
- 检查工具描述是否足够详细
- 验证系统提示词是否明确指示使用工具
- 检查模型是否支持工具调用功能
- 确认工具方法签名正确
解决方案:
java复制// 优化后的工具描述示例
@Tool(description = """
获取指定城市的当前天气信息。
当用户询问天气、温度、是否下雨等问题时使用此工具。
参数city为城市中文名称,如北京、上海。
返回包含温度、天气状况、湿度的完整描述。
""")
public String getWeather(String city) { ... }
7.2 性能问题优化
症状:工具调用响应慢
优化方案:
- 添加缓存层
- 实现异步调用
- 优化网络连接
- 设置合理超时
配置示例:
yaml复制spring:
ai:
openai:
timeout:
connect: 5s
read: 30s
connection:
max-per-route: 20
max-total: 100
7.3 安全性加固
风险点:
- 未经校验的工具参数
- 敏感数据泄露
- 过度权限
防护措施:
- 输入参数校验
- 输出内容过滤
- 基于角色的访问控制
- 请求频率限制
8. 项目演进路线
根据我们的实践经验,Spring AI工具调用项目可以分三个阶段推进:
8.1 初级阶段(POC验证)
- 实现基础工具(日期、计算器)
- 验证技术可行性
- 建立开发规范
8.2 中级阶段(业务集成)
- 集成企业API
- 实现组合工具调用
- 添加监控告警
- 性能优化
8.3 高级阶段(平台化)
- 开发工具市场
- 实现动态工具注册
- 构建编排引擎
- 智能化工具推荐
9. 经验总结与建议
经过多个项目的实践,我们总结了以下关键经验:
- 工具描述要具体:清晰的描述能显著提高工具调用准确率
- 参数说明要完整:包括格式、示例和约束条件
- 错误处理要友好:提供可操作的错误信息
- 监控要全面:覆盖性能、成功率和业务指标
- 安全要前置:从设计阶段就考虑安全问题
对于刚开始接触Spring AI工具调用的团队,我建议:
- 从简单工具开始,逐步增加复杂度
- 建立完善的测试体系
- 重视文档和示例
- 关注社区最佳实践
10. 资源推荐
10.1 学习资料
10.2 开发工具
- IntelliJ IDEA - 智能Java IDE
- Postman - API测试工具
- Prometheus - 监控系统
10.3 实用库
- Lombok - 减少样板代码
- Guava - Google核心Java库
- Resilience4j - 容错库
在实际项目中,我们发现Spring AI的工具调用功能确实能显著扩展AI应用的能力边界。通过合理的架构设计和持续优化,这套方案已经在我们多个生产环境中稳定运行,处理日均百万级的工具调用请求。
