跳到主要内容
版本:Next

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 可调用的工具。

WeatherTools.java
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方法名工具用途描述,直接影响模型决策准确性(建议必填
returnDirectfalse设为 true 则跳过模型二次总结,直接返回工具结果

2.2 @ToolParam 配置项

配置项默认值用途
description""参数描述,帮助模型理解如何填参(建议必填
requiredtrue参数是否必填

2.3 直接返回结果

当工具结果就是用户需要的最终答案时,设置 returnDirect = true 跳过模型的二次加工:

@Tool(description = "执行数学计算", returnDirect = true)
public double calculate(@ToolParam(description = "数学表达式") String expression) {
return evaluate(expression);
}

3. 无注解场景:用 Lambda 创建工具

不依赖 @Tool 注解,通过 Lambda 表达式直接创建工具回调,适合已有 Function Bean 或需要动态注册的场景。

ToolCallback callback = FunctionToolCallback
.builder("getWeather", (Function<WeatherRequest, WeatherResponse>) req ->
new WeatherResponse(req.city(), "晴,25°C"))
.description("获取指定城市的实时天气")
.inputType(WeatherRequest.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 调用的模型(如 qwen3llama3.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-errorbooleanfalse工具调用异常是否直接抛出
spring.ai.tools.observations.include-contentbooleanfalse可观测性是否包含工具调用内容

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.3 完整实现了流式工具调用支持,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();