Tool Calling
Tool Calling 让 AI 模型能够调用 Java 方法,实现与外部系统(数据库、API、文件系统等)的交互——模型根据用户意图自动决定调用哪个方法,Spring AI 执行后将结果返回给模型,形成"模型决策 → 本地执行 → 模型总结"的闭环。
1. 使用 ChatClient 的工具 API
ChatClient 提供四组工具相关方法,支持 Builder 级默认值(全局生效)和 Request 级覆盖(单次调用生效)。
1.1 注册工具对象
tools() 接受标注了 @Tool 注解的对象,Spring AI 自动扫描 @Tool 方法并转换为工具回调。
// 全局注册:Builder 级别,所有请求可用
@Bean
public ChatClient chatClient(ChatClient.Builder builder, WeatherTools weatherTools) {
return builder
.defaultTools(weatherTools)
.defaultSystem("你是一个天气助手")
.build();
}
// 单次注册:Request 级别,仅本次调用可用
String response = chatClient.prompt()
.user("北京今天天气怎么样?")
.tools(new CalculatorTools())
.call()
.content();
1.2 按名称启用工具
toolNames() 按工具名称白名单过滤,仅启用名称匹配的工具。适合工具对象已全局注册、但某次调用只需其中部分工具的场景。
// WeatherTools 中定义了 getWeather、getForecast 两个方法
chatClient.prompt()
.user("北京天气怎么样?")
.tools(new WeatherTools())
.toolNames("getWeather") // 仅启用 getWeather,getForecast 不可用
.call()
.content();
1.3 直接传入回调
toolCallbacks() 直接传入 ToolCallback 实例,适合 Lambda 创建的工具或需要细粒度控制的场景。
ToolCallback weatherCallback = FunctionToolCallback
.builder("getWeather", (Function<String, String>) city ->
city + ":晴,25°C")
.description("获取指定城市的天气")
.inputType(String.class)
.build();
chatClient.prompt()
.user("北京天气怎么样?")
.toolCallbacks(weatherCallback)
.call()
.content();
1.4 传递业务上下文
toolContext() 向工具传递业务数据(用户 ID、会话信息等),工具侧通过 ToolContext 参数获取。
chatClient.prompt()
.user("查询我的订单状态")
.tools(new OrderTools())
.toolContext(Map.of("userId", "12345", "sessionId", "abc-def"))
.call()
.content();
1.5 四组方法的合并规则
| 方法对 | 合并行为 |
|---|---|
tools() / defaultTools() | 累加 |
toolNames() / defaultToolNames() | 覆盖(Request 级替换 Builder 级) |
toolCallbacks() / defaultToolCallbacks() | 累加 |
toolContext() / defaultToolContext() | 累加 |
2. 用 @Tool 注解定义工具方法
在 Bean 的 public 方法上标注 @Tool,即可将其暴露为 AI 可调用的工具。
public class WeatherTools {
@Tool(description = "获取指定城市的实时天气信息")
public String getWeather(
@ToolParam(description = "城市名称,如北京、上海") String city) {
return city + ":晴,25°C,湿度 60%";
}
@Tool(description = "获取指定城市未来 N 天的天气预报")
public String getForecast(
@ToolParam(description = "城市名称") String city,
@ToolParam(description = "预报天数") int days) {
return city + "未来" + days + "天以晴天为主,温度 22-28°C";
}
}
2.1 @Tool 配置项
| 配置项 | 默认值 | 用途 |
|---|---|---|
name | 方法名 | 工具的唯一标识,与 toolNames() 配合使用 |
description | 方法名 | 工具用途描述,直接影响模型决策准确性(建议必填) |
returnDirect | false | 设为 true 则跳过模型二次总结,直接返回工具结果 |
2.2 @ToolParam 配置项
| 配置项 | 默认值 | 用途 |
|---|---|---|
description | "" | 参数描述,帮助模型理解如何填参(建议必填) |
required | true | 参数是否必填 |
2.3 直接返回结果
当工具结果就是用户需要的最终答案时,设置 returnDirect = true 跳过模型的二次加工:
@Tool(description = "执行数学计算", returnDirect = true)
public double calculate(@ToolParam(description = "数学表达式") String expression) {
return evaluate(expression);
}
3. 无注解场景:用 Lambda 创建工具
不依赖 @Tool 注解,通过 Lambda 表达式直接创建工具回调,适合已有 Function Bean 或需要动态注册的场景。
- Function
- Supplier(无入参)
- BiFunction(带上下文)
ToolCallback callback = FunctionToolCallback
.builder("getWeather", (Function<WeatherRequest, WeatherResponse>) req ->
new WeatherResponse(req.city(), "晴,25°C"))
.description("获取指定城市的实时天气")
.inputType(WeatherRequest.class)
.build();
ToolCallback callback = FunctionToolCallback
.builder("getCurrentTime", (Supplier<String>) () -> Instant.now().toString())
.description("获取当前时间")
.build();
BiFunction<OrderRequest, ToolContext, OrderResponse> queryOrders = (req, ctx) -> {
String userId = (String) ctx.getContext().get("userId");
return orderService.query(userId, req.orderId());
};
ToolCallback callback = FunctionToolCallback
.builder("queryOrders", queryOrders)
.description("根据用户ID查询订单状态")
.inputType(OrderRequest.class)
.build();
Lambda 创建的回调通过 toolCallbacks() 注册:
chatClient.prompt()
.user("查询我的订单")
.toolCallbacks(callback)
.toolContext(Map.of("userId", "12345"))
.call()
.content();
4. 完整示例
完整示例:ToolCallingExample.java
public class ToolCallingExample {
static class WeatherTools {
@Tool(description = "获取指定城市的实时天气信息")
public String getWeather(
@ToolParam(description = "城市名称") String city) {
return city + ":晴,25°C,湿度 60%,风力 3 级";
}
@Tool(description = "获取指定城市未来 N 天的天气预报")
public String getForecast(
@ToolParam(description = "城市名称") String city,
@ToolParam(description = "预报天数") int days) {
return city + "未来" + days + "天天气:以晴为主,22-28°C";
}
}
public static void main(String[] args) {
OllamaApi ollamaApi = OllamaApi.builder()
.baseUrl("http://localhost:11434")
.build();
OllamaChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(ollamaApi)
.defaultOptions(OllamaOptions.builder().model("qwen3:8b").build())
.build();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new WeatherTools())
.defaultSystem("你是一个天气助手,可以查询各地的天气信息")
.build();
// 使用 tools() + toolContext()
String response = chatClient.prompt()
.user("北京今天的天气怎么样?")
.toolContext(Map.of("userId", "12345", "vip", true))
.call()
.content();
System.out.println(response);
// 使用 toolNames() 限制可用工具
String response2 = chatClient.prompt()
.user("查询北京今天天气和未来3天的预报")
.toolNames("getWeather", "getForecast")
.call()
.content();
System.out.println(response2);
}
}
5. 模型兼容性
| 平台 | 支持情况 |
|---|---|
| OpenAI | 全系列支持 |
| Ollama | 需使用支持 tool 调用的模型(如 qwen3、llama3.1 系列),部分小型模型不支持 |
| Anthropic | 全系列支持 |
| DeepSeek | 需确认模型版本 |
6. 异常处理
Spring AI 1.0.1 引入了可配置的工具调用异常处理机制,通过 ToolCallingProperties 控制异常行为。
6.1 默认模式:错误回传模型
默认情况下,工具执行异常不会被直接抛出,而是转换为错误消息返回给 AI 模型,由模型自主决定如何处理(如重试、向用户道歉、尝试替代方案)。
// 默认行为:异常被转换为消息回传给模型
chatClient.prompt()
.user("查询北京的天气")
.tools(new WeatherTools()) // 假设此工具内部抛出了异常
.call()
.content(); // 模型可能会回应 "抱歉,天气查询服务暂时不可用"
6.2 配置为直接抛异常
通过 spring.ai.tools.throw-exception-on-error=true,工具调用异常将直接抛出给调用方处理。
spring:
ai:
tools:
throw-exception-on-error: true
// 配置后,异常直接抛出
try {
chatClient.prompt()
.user("查询北京的天气")
.tools(new WeatherTools())
.call()
.content();
} catch (ToolExecutionException e) {
// 调用方自行处理异常
log.error("工具执行失败", e);
}
6.3 自定义异常处理器
通过实现 ToolExecutionExceptionProcessor 接口,可以对特定异常进行选择性处理。
public class CustomToolExceptionProcessor extends DefaultToolExecutionExceptionProcessor {
@Override
public String process(ToolExecutionException exception) {
if (exception.getCause() instanceof TimeoutException) {
// 超时异常直接抛出,不交给模型处理
throw exception;
}
// 其他异常按默认逻辑回传模型
return super.process(exception);
}
}
6.4 配置项
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
spring.ai.tools.throw-exception-on-error | boolean | false | 工具调用异常是否直接抛出 |
spring.ai.tools.observations.include-content | boolean | false | 可观测性是否包含工具调用内容 |
7. 工具调用循环控制(1.1.3 新增)
ToolCallAdvisor 负责管理工具调用的循环流程——模型给出工具调用建议 → Spring AI 执行工具 → 将结果反馈给模型 → 检查是否还需要调用工具。
7.1 对话历史控制
通过 conversationHistoryEnabled 选项控制工具调用循环中传递的上下文:
true(默认):传递完整对话历史,模型能看到所有上下文false:仅传递 System Message 和最后一条工具响应,避免上下文膨胀
ToolCallAdvisor advisor = ToolCallAdvisor.builder()
.toolCallingManager(toolCallingManager)
.conversationHistoryEnabled(false)
.build();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(advisor)
.build();
7.2 流式工具调用(1.1.3 新增)
1.1.5 完整实现了流式工具调用支持,adviseStream() 方法在流式 ChatClient 调用中自动执行工具调用循环。
ToolCallAdvisor advisor = ToolCallAdvisor.builder()
.toolCallingManager(toolCallingManager)
.build();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(advisor)
.build();
// 流式调用中自动执行工具循环
chatClient.prompt()
.user("查询天气并翻译成英文")
.stream()
.content()
.doOnNext(System.out::print)
.blockLast();