跳到主要内容
Spring AI 工具调用(Tool Calling)详解 | 极客日志
Java AI java
Spring AI 工具调用(Tool Calling)详解 综述由AI生成 Spring AI 中的 Tool Calling(工具调用)功能,旨在解决大模型缺乏实时信息和无法执行操作的问题。内容涵盖 Tool Calling 的原理、三种工具定义方式(@Tool 注解、MethodToolCallback、@Bean)、工具规范(ToolDefinition、JSON Schema)、参数描述(@ToolParam)、上下文传递(ToolContext)及结果转换等核心机制。文章还提供了实战案例演示天气查询工具的构建,并总结了常见问题排查指南,帮助开发者掌握如何在 Spring AI 中集成外部工具以扩展模型能力。
黑客 发布于 2026/3/24 更新于 2026/5/1 8.6K 浏览Spring AI 工具调用(Tool Calling)详解
1. 什么是 Tool Calling?
官方定义
Spring AI 官方文档的描述:
Tool calling (also known as function calling) is a common pattern in AI applications allowing a model to interact with a set of APIs, or tools, augmenting its capabilities.
— Spring AI 官方文档 - Tool Calling
翻译:工具调用 (也称为函数调用)是 AI 应用中的一种常见模式,它允许模型与一组 API(即工具)进行交互,从而扩展模型的能力 。
通俗理解
大模型(比如 DeepSeek、GPT)本质上就是一个"文字处理器"——它只能根据已有的训练数据来生成文字。它不能上网、不能查数据库、不能调接口、不能操作你的系统 。
但是,如果你给它一套"工具",告诉它"需要的时候可以用这些工具",它就能在对话过程中主动请求调用工具 ,拿到结果后再生成最终回答。
生活比喻
想象你在和一个非常聪明的朋友聊天,但他被关在一个没有窗户的房间 里:
你: "今天北京天气怎么样?"
朋友: "我不知道,我看不到外面的天气..."
—— 现在你给他一部手机(工具)——
你: "今天北京天气怎么样?"
朋友: (拿起手机,打开天气 App,查到 "北京:晴,25°C" )
朋友: "北京今天天气晴朗,气温 25°C,适合出门!"
工具调用就是给大模型一部"手机" ——让它在需要的时候能"打电话"去获取信息或执行操作。
2. 为什么需要 Tool Calling?
大模型的两大局限
局限 说明 举例 没有实时信息 模型的知识截止于训练数据 问"今天几号",它不知道 不能执行操作 模型只能生成文字,不能"动手" 让它"帮我发个邮件",它做不到
Tool Calling 解决的两类问题
Spring AI 官方文档将工具分为两大类:
1. 信息获取型(Information Retrieval)
工具用于从外部数据源获取信息 ,弥补模型知识的不足。
查询当前时间、天气
搜索数据库中的记录
调用外部 API 获取实时数据
在 RAG(检索增强生成)场景中检索文档
2. 执行动作型(Taking Action)
工具用于在系统中执行操作 ,实现任务自动化。
提交表单、触发工作流
预订机票、设置闹钟
一个关键的安全概念
Even though we typically refer to tool calling as a model capability, it is actually up to the client application to provide the tool calling logic. The model can only request a tool call and provide the input arguments, whereas the application is responsible for executing the tool call. The model never gets access to any of the APIs provided as tools.
翻译:虽然我们说"模型调用工具",但实际上模型并没有直接访问任何 API 的权限 。模型只能"请求"调用工具并提供参数,而真正执行工具的是你的应用程序 。
这就像:你的朋友(模型)说"帮我查一下北京天气",但实际上是你 (应用程序)去查的,然后把结果告诉他。模型本身永远不会直接接触 你的 API、数据库或任何外部系统——这是一个重要的安全保障 。
3. Tool Calling 的工作原理
完整的 6 步流程 ┌─────────┐ ┌──────────┐ ┌──────────┐
│ 你的 │ │ Spring │ │ AI 模型 │
│ 应用 │ │ AI │ │ (DeepSeek│
│ 程序 │ │ 框架 │ │ /GPT) │
└────┬────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ ① 发送请求 + 工具定义 │
│ "今天几号?" │
│ 工具:getCurrentDateTime () │
│─────────────────────────────>│ 发送给模型
│──────────────────────────────>│
│ │ │
│ ② 模型决定调用工具 │
│ "我需要调用 │
│ getCurrentDateTime ()" │
│<──────────────────────────────│
│ │ │
│ ③ 框架执行工具 │
│ 调用 getCurrentDateTime () │
│<─────────────────────────────│
│ │ │
│ ④ 返回工具结果 │
│ "2025 -02 -17 T15:30 :00 " │
│─────────────────────────────>│
│ │ │
│ ⑤ 把结果发给模型 │
│──────────────────────────────>│
│ │ │
│ ⑥ 模型生成最终回答 │
│ " 今天是 2025 年 2 月 17 日" │
│<──────────────────────────────│
│ │ │
│ 返回最终答案 │
│<─────────────────────────────│
│ │ │
用文字总结这 6 步 步骤 谁在做 做什么 ① 你的应用 → Spring AI 发送用户问题 + 工具定义(工具名、描述、参数格式) ② AI 模型 分析问题,决定需要调用哪个工具,返回工具名 + 参数 ③ Spring AI 框架 根据工具名找到对应的工具实现,执行它 ④ 你的工具方法 返回执行结果(比如当前时间) ⑤ Spring AI 框架 → AI 模型 把工具结果发回给模型 ⑥ AI 模型 利用工具结果生成最终的自然语言回答
重点 :整个过程中,步骤 ③ 和 ④ 是你需要写的代码 (定义工具方法),其余步骤 Spring AI 框架自动帮你完成。
4. 快速入门:第一个工具
4.1 信息获取型工具——获取当前时间 AI 模型不知道"现在是几点",但我们可以写一个工具告诉它:
import java.time.LocalDateTime;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime () {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
@Tool 注解:告诉 Spring AI'这个方法是一个工具'
description:告诉 AI 模型"这个工具是干什么的"——这个描述非常重要 ,模型根据它来判断什么时候该调用这个工具
方法体:实际的工具逻辑——返回当前时间
ChatModel chatModel = ...;
String response = ChatClient.create(chatModel)
.prompt("What day is tomorrow?" )
.tools(new DateTimeTools ())
.call()
.content();
System.out.println(response);
I am an AI and do not have access to real - time information. Please provide the current date so I can accurately determine what day tomorrow will be.
4.2 执行动作型工具——设置闹钟 import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime () {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
@Tool(description = "Set a user alarm for the given time, provided in ISO-8601 format")
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);
}
}
String response = ChatClient.create(chatModel)
.prompt("Can you set an alarm 10 minutes from now?" )
.tools(new DateTimeTools ())
.call()
.content();
先调用 getCurrentDateTime() → 知道现在是几点
再调用 setAlarm("2025-02-17T15:40:00") → 设置 10 分钟后的闹钟
模型自己规划了工具调用顺序 ——它知道要先获取当前时间,才能计算 10 分钟后是几点。
5. 定义工具的三种方式 Spring AI 提供了三种方式来定义工具,适用于不同场景:
5.1 声明式:@Tool 注解(最常用,推荐) class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime () {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
属性 说明 默认值 name工具名称,模型用这个名字来调用工具 方法名 description工具描述,非常重要 ,模型根据描述判断何时使用 方法名(建议手动写) returnDirect是否把结果直接返回给用户(而不是回传给模型) falseresultConverter自定义结果转换器 默认 JSON 序列化
可以是 public、protected、package-private 或 private
可以是静态方法或实例方法
参数类型支持:基本类型、POJO、枚举、List、数组、Map 等
返回类型支持:大部分类型,包括 void
不支持 :Optional、CompletableFuture、Mono、Flux 等异步/响应式类型
5.2 编程式:MethodToolCallback(灵活,适合第三方类) 当你无法修改工具类的源码(比如第三方库),或者需要更精细地控制工具定义时,可以使用编程式:
class WeatherTools {
String getCurrentWeatherByCityName (String cityName) {
return "晴天,25°C" ;
}
}
Method method = ReflectionUtils.findMethod(WeatherTools.class, "getCurrentWeatherByCityName" , String.class);
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinitions.builder(method).description("根据给定的城市名称,获取城市当前的天气" ).build())
.toolMethod(method)
.toolObject(new WeatherTools ())
.build();
MethodToolCallback.Builder 的关键属性 :
属性 说明 是否必须 toolDefinition工具定义(名称 + 描述 + 参数 Schema) ✅ 必须 toolMethod工具方法的 Method 反射对象 ✅ 必须 toolObject工具方法所在对象的实例(静态方法可省略) 实例方法必须 toolMetadata额外配置(如 returnDirect) 可选 toolCallResultConverter自定义结果转换器 可选
5.3 动态式:@Bean + Function(Spring Bean 方式) 把工具定义为 Spring Bean,Spring AI 在运行时自动解析:
@Configuration(proxyBeanMethods = false)
class WeatherToolConfig {
@Bean
@Description("Get the weather in location")
Function<WeatherRequest, WeatherResponse> currentWeather () {
return request -> new WeatherResponse (30.0 , Unit.C);
}
}
public record WeatherRequest (@ToolParam(description = "The name of a city or a country") String location, Unit unit) {}
public record WeatherResponse (double temp, Unit unit) {}
public enum Unit { C, F }
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?" )
.toolNames("currentWeather" )
.call()
.content();
Bean 名称就是工具名称
@Description 注解提供工具描述
入参和出参必须是 public 的 POJO ,不支持基本类型和集合类型
这种方式类型安全性较弱(运行时解析)
三种方式对比 声明式 @Tool 编程式 MethodToolCallback 动态式 @Bean 简单程度 ⭐⭐⭐ 最简单 ⭐ 最复杂 ⭐⭐ 中等 适用场景 自己写的工具类 第三方库的方法 需要 Spring Bean 注入的场景 参数类型 支持丰富 支持丰富 仅支持 POJO 类型安全 ✅ 编译时 ✅ 编译时 ❌ 运行时 需要修改源码 需要加注解 不需要 不需要 推荐程度 ✅ 首选 特定场景使用 特定场景使用
6. 工具规范(Tool Specification) 在前面的章节中,我们学习了如何通过 @Tool、MethodToolCallback、@Bean 三种方式来定义工具。这一节深入讲解 Spring AI 工具系统的底层设计 ——即这些工具在框架内部是如何被描述、组织和转换的。
官方文档的说法:In Spring AI, tools are modeled via the ToolCallback interface.
翻译:在 Spring AI 中,工具通过 ToolCallback 接口进行建模。
6.1 ToolCallback——工具的核心接口 所有工具最终都是一个 ToolCallback 。不管你用 @Tool 注解还是手动构建 MethodToolCallback,最终都会变成一个实现了 ToolCallback 接口的对象。
public interface ToolCallback {
ToolDefinition getToolDefinition () ;
ToolMetadata getToolMetadata () ;
String call (String toolInput) ;
String call (String toolInput, ToolContext toolContext) ;
}
通俗理解 :ToolCallback 就是一个"工具包装盒",它包含两部分信息:
工具说明书 (ToolDefinition)——名字、描述、参数格式,给模型看的
工具本体 (call 方法)——实际执行逻辑,给应用程序调用的
实现类 对应的工具定义方式 MethodToolCallback方法作为工具(@Tool 或编程式) FunctionToolCallback函数作为工具(@Bean + Function)
6.2 ToolDefinition——工具的"说明书" ToolDefinition 是工具的定义信息,它决定了模型看到的工具长什么样 。
public interface ToolDefinition {
String name () ;
String description () ;
String inputSchema () ;
}
自动生成 vs 手动构建 自动生成 :当你使用 @Tool 注解时,Spring AI 会自动从方法签名中提取这三项信息:
@Tool(description = "获取当前时间")
String getCurrentDateTime () {
...
}
手动构建 :你也可以用 ToolDefinition.Builder 手动指定:
ToolDefinition toolDefinition = ToolDefinition.builder()
.name("currentWeather" )
.description("Get the weather in location" )
.inputSchema("""
{ "type": "object", "properties": {
"location": { "type": "string" },
"unit": { "type": "string", "enum": ["C", "F"] }
}, "required": ["location", "unit"] }
""" )
.build();
从方法自动生成 ToolDefinition
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime" );
ToolDefinition toolDefinition = ToolDefinitions.from(method);
ToolDefinition toolDefinition = ToolDefinitions.builder(method)
.name("currentDateTime" )
.description("Get the current date and time in the user's timezone" )
.inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
.build();
6.3 JSON Schema——模型理解参数的关键 当你给模型提供一个工具时,模型需要知道参数长什么样才能正确调用 。Spring AI 通过 JsonSchemaGenerator 类自动为工具参数生成 JSON Schema 。
什么是 JSON Schema? JSON Schema 就是一份"数据格式说明"。比如下面这个工具方法:
@Tool(description = "设置闹钟")
void setAlarm (@ToolParam(description = "ISO-8601 格式的时间") String time) {
...
}
Spring AI 会自动生成这样的 JSON Schema:
{
"type" : "object" ,
"properties" : {
"time" : {
"type" : "string" ,
"description" : "ISO-8601 格式的时间"
}
} ,
"required" : [ "time" ]
}
模型看到这个 Schema 后就知道:调用 setAlarm 需要传一个 time 参数,类型是字符串,格式应该是 ISO-8601。
JSON Schema 的生成规则 你写的代码 Schema 中的效果 String time"type": "string"int count"type": "integer"boolean flag"type": "boolean"List<String> tags"type": "array", "items": {"type": "string"}enum Unit { C, F }"type": "string", "enum": ["C", "F"]@ToolParam(description = "...")"description": "..."@ToolParam(required = false)不在 "required" 数组中
描述参数的四种注解 你可以用以下注解来为参数添加描述(优先级从高到低):
注解 来源 适用场景 @ToolParam(description = "...")Spring AI 推荐 ,方法参数描述@JsonClassDescription("...")Jackson POJO 类级别描述 @JsonPropertyDescription("...")Jackson POJO 字段级别描述 @Schema(description = "...")Swagger 项目已引入 Swagger 时
标记可选参数的四种注解 默认所有参数都是必填(required),用以下注解可标记为可选(优先级从高到低):
注解 来源 @ToolParam(required = false)Spring AI @JsonProperty(required = false)Jackson @Schema(required = false)Swagger @NullableSpring Framework
这些注解支持递归 ——如果你的参数是一个嵌套的 POJO,内层字段也可以用这些注解来描述。
6.4 Result Conversion——工具结果的转换 工具执行完毕后,结果需要转换成字符串 才能发回给模型。Spring AI 通过 ToolCallResultConverter 接口来处理这个转换。
@FunctionalInterface
public interface ToolCallResultConverter {
String convert (@Nullable Object result, @Nullable Type returnType) ;
}
默认行为 默认使用 DefaultToolCallResultConverter,它会把结果序列化为 JSON 字符串 (通过 Jackson)。
@Tool(description = "查询客户信息")
Customer getCustomerInfo (Long id) {
return new Customer (42L , "张三" , "[email protected] " );
}
自定义结果转换器 如果默认的 JSON 转换不满足需求(比如你想返回 XML 格式、或者只返回部分字段),可以自定义:
public class SimpleCustomerConverter implements ToolCallResultConverter {
@Override
public String convert (Object result, Type returnType) {
if (result instanceof Customer c) {
return "客户姓名:" + c.getName() + ",邮箱:" + c.getEmail();
}
return result.toString();
}
}
@Tool(description = "查询客户信息", resultConverter = SimpleCustomerConverter.class)
Customer getCustomerInfo (Long id) {
return customerRepository.findById(id);
}
什么时候需要自定义? 场景 是否需要自定义 返回简单对象(String、数字、POJO) ❌ 默认 JSON 就够了 返回的对象太大,想只取部分字段 ✅ 自定义,减少 Token 消耗 想返回特定格式(如 Markdown 表格) ✅ 自定义 返回敏感信息需要脱敏 ✅ 自定义
6.5 工具规范的整体结构图 ToolCallback(工具的完整表示)
┌──────────────────────────────┐
│ │
├───────────────┤ getToolDefinition () │
│ │ → 工具的"说明书" │
│ │ │
├────────────┤ getToolMetadata () │
│ │ → 额外配置(returnDirect 等) │
│ │ │
│ │ │
│ │ call (input) │
│ │ call (input, context)│
│ │ → 实际执行工具 │
│ └──────────────────────────────┘
│ │
│ │ ToolMetadata
│ │ ┌──────────────────┐
│ │ └───────────→│ returnDirect │ 是否直接返回结果给用户
│ │ └──────────────────┘
│ │
│ │ ToolDefinition(给模型看的)
│ │ ┌──────────────────┐
│ │ └──────────────→│ name () │ 工具名称
│ │ │ description () │ 工具描述
│ │ │ inputSchema () │ 参数的 JSON Schema
│ │ └──────────────────┘
│ │ JsonSchemaGenerator 自动从方法签名生成
│ │ 工具执行后 ──→ ToolCallResultConverter ──→ 字符串结果 ──→ 发回模型
7. 使用工具的两种 API 定义好工具后,你可以通过 ChatClient 或 ChatModel 来使用它们。
7.1 通过 ChatClient(推荐,更高层抽象)
声明式工具(@Tool)
ChatClient.create(chatModel)
.prompt("What day is tomorrow?" )
.tools(new DateTimeTools ())
.call()
.content();
编程式工具(MethodToolCallback) ToolCallback toolCallback = ...;
ChatClient.create(chatModel)
.prompt("What day is tomorrow?" )
.toolCallbacks(toolCallback)
.call()
.content();
动态式工具(@Bean) ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?" )
.toolNames("currentWeather" )
.call()
.content();
设置默认工具 ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new DateTimeTools ())
.build();
chatClient.prompt("What day is tomorrow?" ).call().content();
注意 :如果同时设置了默认工具和请求级工具,请求级工具会完全覆盖默认工具 (不是合并)。
7.2 通过 ChatModel(更底层)
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools ());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build();
Prompt prompt = new Prompt ("What day is tomorrow?" , chatOptions);
chatModel.call(prompt);
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolNames("currentWeather" )
.build();
Prompt prompt = new Prompt ("What's the weather?" , chatOptions);
chatModel.call(prompt);
ChatClient vs ChatModel 使用工具时的区别 ChatClient ChatModel 抽象层次 高层,更简洁 底层,更灵活 传声明式工具 .tools(new XxxTools())ToolCallbacks.from(...) + ToolCallingChatOptions传编程式工具 .toolCallbacks(callback)ToolCallingChatOptions.builder().toolCallbacks(...)传动态式工具 .toolNames("name")ToolCallingChatOptions.builder().toolNames(...)推荐 ✅ 日常开发首选 需要精细控制时使用
8. @ToolParam——告诉模型参数怎么填 当工具方法有参数时,你需要告诉模型每个参数的含义。@ToolParam 注解就是干这个的。
基本用法 class DateTimeTools {
@Tool(description = "Set a user alarm for the given time")
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);
}
}
属性 说明 默认值 description参数描述,告诉模型这个参数的格式、含义、允许值等 无 required参数是否必填 true
可选参数 默认情况下,所有参数都是必填的。如果某个参数是可选的:
class CustomerTools {
@Tool(description = "Update customer information")
void updateCustomerInfo (Long id, String name, @ToolParam(required = false) String email // ← 可选参数
) {
System.out.println("Updated info for customer with id: " + id);
}
}
void updateCustomerInfo (Long id, String name, @Nullable String email) {
...
}
参数描述的替代注解 注解 来源 用法 @ToolParam(description = "...")Spring AI 推荐 @Schema(description = "...")Swagger 如果项目已引入 Swagger @JsonPropertyDescription("...")Jackson 函数式工具的 POJO 字段
为什么参数描述很重要? 模型在决定如何调用工具时,完全依赖 你提供的工具描述和参数描述。如果描述不清楚,模型可能:
不该调用工具时却调用了
该调用工具时没调用
传了错误格式的参数
建议 :工具描述和参数描述要写得尽量详细和准确 。不要写"时间",要写"Time in ISO-8601 format, e.g. 2025-02-17T15:30:00"。
9. ToolContext——给工具传递额外上下文
什么场景需要? 有时候,工具需要一些模型不知道的信息 ——比如当前登录用户的 ID、租户标识等。这些信息不应该让模型来提供(模型也提供不了),而是由你的应用程序传递。
用法 class CustomerTools {
@Tool(description = "Retrieve customer information")
Customer getCustomerInfo (Long id, ToolContext toolContext) {
String tenantId = toolContext.getContext().get("tenantId" );
return customerRepository.findById(id, tenantId);
}
}
调用时通过 .toolContext() 传入上下文数据 :
String response = ChatClient.create(chatModel)
.prompt("Tell me more about the customer with ID 42" )
.tools(new CustomerTools ())
.toolContext(Map.of("tenantId" , "acme" ))
.call()
.content();
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(ToolCallbacks.from(new CustomerTools ()))
.toolContext(Map.of("tenantId" , "acme" ))
.build();
Prompt prompt = new Prompt ("Tell me about customer 42" , chatOptions);
chatModel.call(prompt);
重要注意事项 ⚠️ 如果你的工具方法声明了 ToolContext 参数,调用时就必须通过 .toolContext() 提供上下文数据,否则会报错 :
java.lang.IllegalArgumentException: ToolContext is required by the method as an argument
这正是你之前遇到的那个错误!如果你的工具方法不需要额外上下文,就不要声明 ToolContext 参数。
10. returnDirect——工具结果直接返回给用户
默认行为 默认情况下,工具的执行结果会先发回给模型 ,让模型基于结果生成最终回答:
用户 → 模型 → 调用工具 → 工具结果 → 回传给模型 → 模型生成回答 → 用户
什么时候需要 returnDirect? 有些场景你不需要模型再"加工"工具结果,想直接把结果返回给用户:
RAG 场景 :工具检索到的文档直接返回,不需要模型再处理
终止推理循环 :工具执行后就结束对话,不需要模型继续思考
用法 class CustomerTools {
@Tool(description = "Retrieve customer information", returnDirect = true)
Customer getCustomerInfo (Long id) {
return customerRepository.findById(id);
}
}
设置 returnDirect = true 后,工具结果跳过模型 ,直接返回给调用方。
11. 工具执行的两种模式
11.1 框架控制模式(默认) 这是默认行为——Spring AI 框架自动处理一切:
① 你发请求 + 工具定义
② 模型返回工具调用请求
③ 框架自动执行工具 ← 自动的
④ 框架把结果发回模型 ← 自动的
⑤ 模型生成最终回答
你只需要写工具方法,其他的 Spring AI 全部帮你搞定。大多数场景用这个就够了。
11.2 用户控制模式 如果你需要在工具执行的过程中插入自定义逻辑(比如审批、日志、人工确认),可以关闭框架自动执行:
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(new CustomerTools ())
.internalToolExecutionEnabled(false )
.build();
Prompt prompt = new Prompt ("Tell me about customer 42" , chatOptions);
ChatResponse chatResponse = chatModel.call(prompt);
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();
while (chatResponse.hasToolCalls()) {
System.out.println("模型请求调用工具,正在执行..." );
ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, chatResponse);
prompt = new Prompt (toolExecutionResult.conversationHistory(), chatOptions);
chatResponse = chatModel.call(prompt);
}
System.out.println(chatResponse.getResult().getOutput().getText());
框架控制(默认) 用户控制 设置 默认就是 internalToolExecutionEnabled(false)工具执行 框架自动执行 你手动调用 ToolCallingManager 适用场景 大多数场景 需要人工审批、自定义日志、复杂流程 代码量 少 多
12. 异常处理
工具执行失败怎么办? 当工具方法抛出异常时,Spring AI 通过 ToolExecutionExceptionProcessor 来处理。默认行为:
RuntimeException :将错误信息发送回模型,让模型基于错误信息继续对话
检查异常和 Error (如 IOException、OutOfMemoryError):直接抛出,由调用方处理
配置异常处理行为
spring:
ai:
tools:
throw-exception-on-error: true
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor () {
return new DefaultToolExecutionExceptionProcessor (true );
}
什么时候选哪种? 策略 适用场景 默认(发回模型) 模型可以"重试"或"换一种方式"完成任务 始终抛出 不希望模型继续处理错误,而是让应用程序统一处理
13. 工具解析(Tool Resolution) 前面我们学的都是手动传工具 ——通过 .tools(new XxxTools()) 或 .toolCallbacks(callback) 把工具传给 ChatClient。但 Spring AI 还支持另一种方式:通过工具名称动态解析 。
13.1 什么是工具解析? 工具解析就是:你只告诉 Spring AI 工具的名字 ,框架会自动根据名字找到对应的 ToolCallback 。
chatClient.prompt("今天天气如何?" )
.toolNames("currentWeather" )
.call()
.content();
Spring AI 会在运行时通过 ToolCallbackResolver 接口来解析这个名称,找到对应的工具。
13.2 ToolCallbackResolver 接口 public interface ToolCallbackResolver {
@Nullable
ToolCallback resolve (String toolName) ;
}
13.3 默认的解析策略 Spring AI 默认使用 DelegatingToolCallbackResolver,它委托给两个子解析器:
DelegatingToolCallbackResolver
├── SpringBeanToolCallbackResolver
│ → 从 Spring 容器中查找 Function / Supplier/ Consumer/ BiFunction 类型的 Bean
│ → 对应 @Bean + Function 动态式工具
└── StaticToolCallbackResolver
→ 从静态的 ToolCallback 列表中查找
→ 自动收集应用上下文中所有 ToolCallback 类型的 Bean
13.4 使用场景 场景 用哪种方式 工具类就在你的代码里 .tools(new XxxTools()) 手动传工具是 Spring Bean(@Bean + Function) .toolNames("beanName") 按名称解析工具注册为 ToolCallback Bean .toolNames("toolName") 按名称解析需要在运行时动态决定用哪些工具 自定义 ToolCallbackResolver
13.5 自定义工具解析器 @Bean
ToolCallbackResolver toolCallbackResolver (List<FunctionCallback> toolCallbacks) {
StaticToolCallbackResolver staticResolver = new StaticToolCallbackResolver (toolCallbacks);
return new DelegatingToolCallbackResolver (List.of(staticResolver));
}
总结 :工具解析是 toolNames() 方式的底层支撑。大多数场景下你不需要关心它——直接 .tools(new XxxTools()) 手动传就够了。当你使用 @Bean 动态式工具或需要运行时动态注册工具时,才会用到工具解析机制。
14. 实战案例:天气查询工具 以你的项目代码为例,完整演示从工具定义到调用的全过程。
场景 用户问"北京今天天气怎么样?",我们提供一个天气查询工具让模型调用。
方式一:声明式(@Tool) public class WeatherTools {
@Tool(description = "根据给定的城市名称,获取城市当前的天气")
String getCurrentWeatherByCityName (@ToolParam(description = "城市名称,例如:北京、上海、广州") String cityName) {
switch (cityName) {
case "北京" : return "北京今天天气:晴空万里" ;
case "上海" : return "上海今天天气:电闪雷鸣" ;
case "广州" : return "广州今天天气:细雨蒙蒙" ;
default : return "没有该城市的天气信息" ;
}
}
}
@RequestMapping("/chat")
@RestController
public class ChatController {
private ChatClient chatClient;
public ChatController (DashScopeChatModel chatModel) {
this .chatClient = ChatClient.builder(chatModel).build();
}
@RequestMapping("/call")
public String call (String message) {
return chatClient.prompt()
.user(message)
.tools(new WeatherTools ())
.call()
.content();
}
}
方式二:编程式(MethodToolCallback) 当 WeatherTools 类上没有 @Tool 注解时,用编程式构建:
@RequestMapping("/call2")
public String call2 (String message) {
Method method = ReflectionUtils.findMethod(WeatherTools.class, "getCurrentWeatherByCityName" , String.class);
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinitions.builder(method).description("根据给定的城市名称,获取城市当前的天气" ).build())
.toolMethod(method)
.toolObject(new WeatherTools ())
.build();
return chatClient.prompt()
.user(message)
.toolCallbacks(toolCallback)
.call()
.content();
}
方式三:通过 ChatModel + ToolCallingChatOptions @RequestMapping("/callByTool")
public String callByTool (String message) {
ToolCallback[] weatherTools = ToolCallbacks.from(new WeatherTools ());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(weatherTools)
.build();
Prompt prompt = new Prompt (message, chatOptions);
return chatModel.call(prompt).getResult().getOutput().getText();
}
15. 常见问题与排坑
问题一:ToolContext is required by the method as an argument java.lang.IllegalArgumentException: ToolContext is required by the method as an argument
原因 :工具方法声明了 ToolContext 参数,但调用时没有通过 .toolContext() 提供上下文数据。
@Tool(description = "Get current time")
String getCurrentDateTime () {
return LocalDateTime.now().toString();
}
chatClient.prompt()
.user(message)
.tools(new MyTools ())
.toolContext(Map.of("key" , "value" ))
.call()
.content();
问题二:模型不调用工具
工具描述不够清楚 — 模型不知道什么时候该用这个工具
用户的问题和工具描述不匹配 — 模型判断不需要这个工具
模型不支持工具调用 — 有些小模型不支持 function calling
优化 @Tool(description = "...") 的描述,写得更具体
确认你使用的模型支持工具调用(如 DeepSeek、GPT-4、通义千问等)
问题三:同名工具冲突 规则 :同一次请求中,工具名称必须唯一 。如果你传了两个同名的工具,会报错。
chatClient.prompt()
.tools(new DateTimeTools (), new AnotherDateTimeTools ())
.call()
.content();
解决方案 :通过 @Tool(name = "customName") 给工具取不同的名字。
问题四:默认工具 vs 请求级工具 重要规则 :如果同时设置了默认工具和请求级工具,请求级工具会完全覆盖默认工具 ,不是合并!
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new DateTimeTools ())
.build();
chatClient.prompt()
.tools(new WeatherTools ())
.call()
.content();
问题五:方法的参数类型不支持
Optional
CompletableFuture、Future
Mono、Flux(响应式类型)
Function、Supplier、Consumer(函数式类型)
Function as Tool(@Bean 方式)不支持的类型 :
基本类型(int、String 等,必须包装成 POJO)
集合类型(List、Map、Set 等)
16. 总结
核心概念速查表 概念 说明 Tool Calling 让 AI 模型在对话中调用外部工具(API/方法)来扩展能力 @Tool 声明式定义工具的注解,最简单常用 MethodToolCallback 编程式定义工具,适合第三方类 @Bean + Function 动态式定义工具,通过 Spring Bean 管理 ToolCallback 工具的核心接口,包含工具定义 + 执行逻辑 ToolDefinition 工具的"说明书"——名称、描述、参数 JSON Schema JSON Schema 工具参数的格式描述,由 JsonSchemaGenerator 自动生成 ToolCallResultConverter 工具结果转换器,默认将结果序列化为 JSON 字符串 @ToolParam 描述工具参数的注解,帮助模型理解怎么传参 ToolContext 给工具传递应用层上下文(如用户 ID、租户 ID) returnDirect 工具结果跳过模型,直接返回给用户 ToolCallingManager 管理工具执行生命周期的核心接口 ToolCallbackResolver 工具解析器,根据名称动态查找 ToolCallback
选择工具定义方式的决策树 你能修改工具类的源码吗?
├── 能 → 用 @Tool 注解(声明式)✅ 推荐
└── 不能 → 工具方法的参数是 POJO 吗?
├── 是 → 用 @Bean + Function(动态式)
└── 不是 → 用 MethodToolCallback(编程式)
一句话总结
Tool Calling = 给大模型装上"手和脚"。你定义工具(写 Java 方法),模型决定何时调用(基于工具描述),Spring AI 负责串联整个流程(自动执行工具、传递结果)。三者分工明确,各司其职。
17. 参考资料
官方文档
相关博客 相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online