在AI应用开发中,如何让大语言模型与现有业务系统无缝对接是个关键挑战。Spring AI提供的工具调用(Tool Calling)功能,特别是将函数作为工具的能力,为这个问题提供了优雅的解决方案。想象一下,你的AI助手不仅能回答问题,还能实时查询天气、调用企业API或操作数据库——这正是函数工具调用的魅力所在。
我在实际项目中多次使用这套机制,发现它比传统的API调用方式更符合AI开发范式。不同于常规的REST接口调用,Spring AI的工具调用允许开发者将业务逻辑封装为标准函数,然后像"插件"一样注入到AI对话流程中。这种设计既保持了代码的整洁性,又为AI模型提供了灵活的外部能力扩展。
函数工具调用本质上是将Java函数(Function、Supplier等)转化为AI模型能够理解和执行的"工具"。当模型需要外部能力时(比如查询实时数据),会自动触发这些预注册的函数,并将结果整合到对话上下文中。
这种机制的核心价值在于:
Spring AI提供了两种主要的集成模式:
编程式规范(FunctionToolCallback)
声明式规范(@Bean动态解析)
这两种方式不是互斥的——在实际项目中,我经常混合使用。比如用@Bean定义基础工具集,再通过编程方式按需添加会话特定的工具。
让我们深入分析WeatherService示例(虽然原文不完整,但根据行业实践可以合理还原):
java复制public class WeatherService implements Function<WeatherRequest, WeatherResponse> {
@Override
public WeatherResponse apply(WeatherRequest request) {
// 实际调用天气API的逻辑
return externalWeatherApi.fetch(request.getCity());
}
}
// 注册为工具
FunctionToolCallback weatherTool = FunctionToolCallback.builder()
.name("getCurrentWeather")
.description("获取指定城市的当前天气情况")
.function(new WeatherService())
.build();
关键配置项说明:
重要提示:函数输入输出类型必须能被Jackson序列化,因为Spring AI内部使用JSON格式传递参数
除了标准的Function,Spring AI还支持多种函数式接口:
| 函数类型 | 适用场景 | 示例 |
|---|---|---|
| Supplier | 无需参数的只读操作 | 获取服务器时间 |
| Consumer | 有输入无输出的操作 | 发送通知消息 |
| BiFunction | 需要两个参数的计算 | 计算两地距离 |
| Runnable | 无参数无返回的副作用操作 | 清理临时文件 |
在实际项目中,我建议优先使用Function,因为它的输入输出明确,最符合工具调用的语义。
完整的工具使用流程通常包括:
典型代码示例:
java复制// 1. 定义函数
Function<StockQuery, StockInfo> stockService = query ->
stockApi.getPrice(query.getSymbol());
// 2. 构建工具
FunctionToolCallback stockTool = FunctionToolCallback.builder()
.name("getStockPrice")
.description("查询股票实时价格")
.function(stockService)
.build();
// 3. 注册工具
ChatClient client = AiClient.create()
.withTools(stockTool)
.build();
// 4. 使用(模型会自动判断何时调用)
ChatResponse response = client.generate(
"请帮我看看AAPL的最新股价");
对于更复杂的生产环境,推荐使用声明式风格:
java复制@Bean
public FunctionToolCallback weatherTool() {
return FunctionToolCallback.builder()
.name("weatherService")
.function(new WeatherService())
.build();
}
这种方式的优势在于:
Spring AI的工具解析器会按以下顺序查找工具:
这种设计带来了极大的灵活性。比如可以实现:
在application.yml中配置默认工具:
yaml复制spring:
ai:
tool:
default-tools: weatherService,stockService
enabled: true
我建议的配置原则:
与ChatClient集成时,可以灵活控制工具使用策略:
java复制ChatClient client = AiClient.builder()
.defaultTools("timeService") // 默认工具
.optionalTools(weatherTool) // 可选工具
.toolChoice("auto") // 控制策略
.build();
工具调用策略选项:
通过工具组合可以实现复杂业务逻辑:
java复制FunctionToolCallback paymentTool = ...;
FunctionToolCallback inventoryTool = ...;
ChatClient client = AiClient.builder()
.withTools(paymentTool, inventoryTool)
.build();
// 模型可以自动组合工具调用
String response = client.generate(
"用户想购买2件商品A,请处理订单");
这种模式下,模型可能会:
工具调用可能失败,必须实现健壮的错误处理:
java复制FunctionToolCallback retryTool = FunctionToolCallback.builder()
.function((RetryRequest req) -> {
int attempts = 0;
while (attempts++ < req.getMaxRetries()) {
try {
return externalService.call(req);
} catch (Exception e) {
Thread.sleep(req.getDelay());
}
}
throw new RuntimeException("操作失败");
})
.build();
我总结的最佳实践:
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 工具未被调用 | 描述信息不准确 | 优化description字段 |
| 参数解析失败 | 类型不匹配 | 检查函数签名和实际参数 |
| 工具名称冲突 | 重复的name值 | 使用全局唯一的工具名称 |
| 性能瓶颈 | 工具执行耗时过长 | 实现异步调用或超时控制 |
properties复制logging.level.org.springframework.ai=DEBUG
java复制ToolTester.testTool(weatherTool)
.withInput("{\"city\":\"北京\"}")
.expectOutputContains("temperature");
java复制ChatResponse response = client.generate(
"请直接调用getCurrentWeather工具查询上海天气");
我在项目中实测发现,合理的缓存可以将工具调用耗时降低80%以上。例如天气工具可以这样优化:
java复制@Cacheable(value = "weather", key = "#request.city")
public WeatherResponse apply(WeatherRequest request) {
// 原始API调用
}
在某电商项目中,我们实现了以下工具集:
这种架构使得客服AI能够处理90%的常规咨询,准确率比传统规则引擎提高了40%。
工具链:将多个工具串联形成工作流
text复制用户咨询 → 地址识别工具 → 天气查询 → 行程建议生成
动态工具:根据上下文注册临时工具
java复制// 在会话中临时添加工具
chatSession.addTool(oneTimePasswordTool);
工具编排:使用Spring Integration管理复杂流程
对于敏感操作,必须实现安全控制:
java复制Function<PaymentRequest, Receipt> paymentTool = request -> {
SecurityUtils.validate(request.getToken());
return paymentService.process(request);
};
java复制FunctionToolCallback.builder()
.validator(new PaymentValidator()) // 自定义校验器
.build();
java复制function = request -> {
auditLog.log(request);
return realService.call(request);
};
在金融类项目中,我们还会为关键工具添加二次确认机制,通过额外的AI检查或人工审核确保操作安全。