工具调用
工具调用(也称为 函数调用)是 AI 应用中的一种常用模式,允许模型与一组 API 或工具进行交互,从而增强其能力。
工具主要用于以下两类场景:
- 信息检索 此类工具可用于从外部来源获取信息,例如数据库、Web 服务、文件系统或搜索引擎。其目标是增强模型的知识,使模型能够回答原本无法回答的问题。因此,这类工具可用于增强检索生成(Retrieval Augmented Generation,RAG)场景。例如,工具可以用于获取指定位置的当前天气、检索最新新闻文章,或查询数据库中特定记录。
- 执行操作 此类工具可用于在软件系统中执行操作,例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。其目标是自动化原本需要人工干预或显式编程的任务。例如,工具可以用于为与聊天机器人交互的客户预订航班、在网页上填写表单,或在代码生成场景中基于自动化测试(TDD)生成 Java 类。
尽管我们通常将工具调用视为模型能力,但实际上工具调用逻辑是由客户端应用提供的。模型只能请求工具调用并提供输入参数,而应用程序负责根据输入参数执行工具调用并返回结果。模型不会直接访问任何作为工具提供的 API,这一点对安全性至关重要。
Spring AI 提供了便捷的 API,用于定义工具、解析模型的工具调用请求以及执行工具调用。以下章节将概述 Spring AI 中的工具调用能力。
快速开始
下面我们来看如何在 Spring AI 中开始使用工具调用。我们将实现两个简单的工具:一个用于信息检索,另一个用于执行操作。
- 信息检索工具:用于获取用户所在时区的当前日期和时间。
- 操作工具:用于为指定时间设置闹钟。
信息检索
AI 模型无法访问实时信息。任何假设模型能够知道当前日期或天气预报的问题,模型本身都无法回答。然而,我们可以提供一个工具来获取这些信息,并让模型在需要实时信息时调用该工具。
下面我们来实现一个工具,用于获取用户所在时区的当前日期和时间,放在 DateTimeTools 类中。该工具无需任何参数。Spring Framework 的 LocaleContextHolder 可以提供用户的时区信息。工具将定义为一个用 @Tool 注解的方法。为了帮助模型理解何时以及如何调用该工具,我们会提供详细的工具说明。
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();
}
}
接下来,让模型可以使用该工具。在这个示例中,我们将使用 ChatClient 与模型进行交互。通过 tools() 方法,将 DateTimeTools 的实例提供给模型。当模型需要获取当前日期和时间时,它会请求调用该工具。在内部,ChatClient 会执行工具调用并将结果返回给模型,模型随后会使用该工具调用的结果生成对原始问题的最终回答。
ChatModel chatModel = ...
String response = ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
System.out.println(response);
输出可能类似于:
Tomorrow is 2015-10-21.
你可以再次尝试问同样的问题,这次不向模型提供工具。输出可能类似于:
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.
没有工具时,模型无法回答该问题,因为它没有能力自行获取当前的日期和时间。
执行操作
AI 模型可以用来生成实现某些目标的计划。例如,模型可以生成前往丹麦旅行的计划。然而,模型自身无法执行这些计划,这时就需要工具(Tools):工具可以执行模型生成的计划。
在前面的例子中,我们使用了一个工具来获取当前日期和时间。在这个例子中,我们将定义第二个工具,用于在指定时间设置闹钟。目标是设置一个从现在起 10 分钟后的闹钟,因此我们需要向模型提供这两个工具,以完成该任务。
我们将新工具添加到之前的 DateTimeTools 类中。新工具接收一个参数,即 ISO-8601 格式的时间,然后在控制台打印一条消息,表示闹钟已设置。像之前一样,工具方法使用 @Tool 注解定义,同时提供详细说明,帮助模型理解何时以及如何使用该工具。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
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(description = "Set a user alarm for the given time, provided in ISO-8601 format")
void setAlarm(String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("Alarm set for " + alarmTime);
}
}
接下来,让模型可以使用这两个工具。我们使用 ChatClient 与模型交互,并通过 .tools() 方法将 DateTimeTools 实例提供给模型。当我们请求设置一个从现在起 10 分钟后的闹钟时,模型首先需要获取当前日期和时间,然后计算闹钟时间,最后使用闹钟工具设置闹钟。内部的 ChatClient 会处理模型发起的工具调用请求,并将工具执行结果返回给模型,以便生成最终的回答。
ChatModel chatModel = ...
String response = ChatClient.create(chatModel)
.prompt("Can you set an alarm 10 minutes from now?")
.tools(new DateTimeTools())
.call()
.content();
System.out.println(response);
在应用日志中,你可以看到闹钟已被设置在正确的时间。
概览
Spring AI 通过一套灵活的抽象机制支持工具调用,使你能够以统一的方式定义、解析和执行工具。本节概述了 Spring AI 中工具调用的主要概念和组件。

工具调用的主要操作流程:
- 提供工具给模型:当我们希望模型能够使用某个工具时,需要在聊天请求中包含该工具的定义。每个工具定义包括名称、描述以及输入参数的 schema。
- 模型发起工具调用:当模型决定调用某个工具时,它会发送一个响应,包含工具名称和符合定义 schema 的输入参数。
- 应用执行工具:应用程序负责根据工具名称识别并执行对应工具,并使用提供的输入参数。
- 处理工具调用结果:工具调用的结果由应用处理。
- 将结果返回给模型:应用将工具调用结果返回给模型。
- 生成最终响应:模型使用工具调用结果作为额外上下文生成最终响应。
工具是工具调用的基本构建块,由 ToolCallback 接口建模。Spring AI 支持从方法和函数中直接指定 ToolCallback,当然你也可以自定义 ToolCallback 实现以支持更多用例。
ChatModel 的实现会透明地将工具调用请求分发给对应的 ToolCallback 实现,并将工具调用结果返回给模型,从而生成最终响应。这个过程通过 ToolCallingManager 接口管理,它负责整个工具执行的生命周期。
ChatClient 和 ChatModel 都可以接受一组 ToolCallback 对象,将工具提供给模型,同时使用 ToolCallingManager 执行工具。
除了直接传入 ToolCallback 对象之外,你也可以传入一组工具名称,这些名称会通过 ToolCallbackResolver 接口动态解析为对应的工具。
接下来的章节将详细介绍这些概念和 API,包括如何自定义和扩展它们以支持更多用例。
方法作为工具
Spring AI 提供了两种内置方式,从方法中指定工具(即 ToolCallback):
- 声明式:使用
@Tool注解 - 编程式:使用底层的
MethodToolCallback实现
声明式定义:@Tool
你可以通过在方法上添加 @Tool 注解,将其转换为一个工具。
class DateTimeTools {
@Tool(description = "获取用户时区的当前日期和时间")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
@Tool 注解允许你为工具提供关键信息:
- name:工具名称。如果未提供,默认使用方法名。AI 模型通过此名称调用工具,因此同一类中不能有重复名称的工具,且在同一次聊天请求中工具名称必须唯一。
- description:工具描述,帮助模型理解何时以及如何调用该工具。强烈建议提供详细描述,否则模型可能无法正确使用工具。
- returnDirect:工具结果是否直接返回给客户端或传回模型。详见 Return Direct。
- resultConverter:用于将工具调用结果转换为字符串发送回模型的
ToolCallResultConverter实现。详见 Result Conversion。
该方法可以是静态方法或实例方法,且可以具有任何可见性(public、protected、包级别或 private)。包含该方法的类可以是顶级类或嵌套类,并且类的可见性也没有限制,只要在你计划实例化它的地方是可访问的即可。
@Tool 注解方法的类是 Spring Bean(例如使用 @Component 注解),Spring AI 就提供了对这些方法的 AOT(提前编译)支持。否则,你需要为 GraalVM 编译器提供必要的配置,例如,通过在类上添加 @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_METHODS) 注解。你可以为方法定义任意数量的参数(包括无参数),支持大多数类型(原始类型、POJO、枚举、列表、数组、映射等)。同样地,方法的返回值也支持大多数类型,包括 void。如果方法有返回值,则返回类型必须是可序列化的,因为结果会被序列化后发送回模型。
Spring AI 会自动为 @Tool 注解的方法生成输入参数的 JSON Schema,该 Schema 用于让模型理解如何调用工具并准备工具请求。你可以使用 @ToolParam 注解为输入参数提供额外信息,例如描述或是否为必填参数。默认情况下,所有输入参数都被视为必填。
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 = "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);
}
}
@ToolParam 注解允许你为工具参数提供关键信息:
- description:参数的描述,模型可通过它更好地理解如何使用该参数。例如参数应使用的格式、允许的值等。
- required:参数是否必填,默认所有参数都被视为必填。
如果参数使用 @Nullable 注解标记,则默认被视为可选,除非通过 @ToolParam 显式标记为必填。
除了 @ToolParam 注解,你还可以使用 Swagger 的 @Schema 注解或 Jackson 的 @JsonProperty 注解。有关更多信息,请参见 JSON Schema 相关内容。
将工具添加到 ChatClient
在使用声明式(declarative)方式时,你可以在调用 ChatClient 时,将工具类实例传递给 tools() 方法。这样添加的工具仅在该次特定聊天请求中可用。
ChatClient.create(chatModel)
.prompt("明天是星期几?")
.tools(new DateTimeTools())
.call()
.content();
在底层,ChatClient 会从工具类实例中每个带有 @Tool 注解的方法生成一个 ToolCallback,并将其传递给模型。如果你希望自行生成 ToolCallback,也可以使用 ToolCallbacks 工具类:
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
为 ChatClient 添加默认工具
在使用声明式(declarative)方式时,你可以通过将工具类实例传递给 defaultTools() 方法,为 ChatClient.Builder 添加默认工具。如果同时提供了默认工具和运行时工具,运行时工具会完全覆盖默认工具。
ChatClient.Builder 构建的所有 ChatClient 实例的所有聊天请求中共享。这对于在不同聊天请求中常用的工具非常有用,但如果不小心使用,也可能存在风险,可能会在不该使用的时候被访问到。ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new DateTimeTools())
.build();
为 ChatModel 添加工具
在使用声明式(declarative)方式时,你可以将工具类实例传递给用于调用 ChatModel 的 ToolCallingChatOptions 的 toolCallbacks() 方法。这样添加的工具只对该特定聊天请求可用。
ChatModel chatModel = ...
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build();
Prompt prompt = new Prompt("明天是星期几?", chatOptions);
chatModel.call(prompt);
为 ChatModel 添加默认工具
在使用声明式(declarative)方式时,你可以在构建 ChatModel 时,通过将工具类实例传递给用于创建该 ChatModel 的 ToolCallingChatOptions 的 toolCallbacks() 方法来添加默认工具。如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
ChatModel 实例执行的所有聊天请求中共享。它们适用于在不同聊天请求中常用的工具,但使用不当也可能带来风险,可能在不应该使用时被意外调用。ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build())
.build();
编程式定义:MethodToolCallback
你可以通过编程方式使用 MethodToolCallback 将一个方法转化为工具(Tool)。
class DateTimeTools {
String getCurrentDateTime() {
return LocalDateTime.now()
.atZone(LocaleContextHolder.getTimeZone().toZoneId())
.toString();
}
}
MethodToolCallback.Builder 允许你构建一个 MethodToolCallback 实例,并提供工具的关键信息:
- toolDefinition:
ToolDefinition实例,用于定义工具名称、描述以及输入参数的 JSON schema。可以通过ToolDefinition.Builder构建,必填。 - toolMetadata:
ToolMetadata实例,用于定义额外设置,如结果是否直接返回给客户端、使用的结果转换器等。可通过ToolMetadata.Builder构建。 - toolMethod:表示工具方法的
Method实例,必填。 - toolObject:包含工具方法的对象实例。如果方法是静态的,可以省略此参数。
- toolCallResultConverter:
ToolCallResultConverter实例,用于将工具调用的结果转换为发送给 AI 模型的字符串。如果未提供,将使用默认转换器DefaultToolCallResultConverter。
ToolDefinition.Builder 允许你构建一个 ToolDefinition 实例,并定义工具的名称、描述以及输入参数的 schema。
- name:工具名称,若未提供,则使用方法名。模型使用该名称调用工具,因此同一类中不允许出现同名工具,名称在特定聊天请求中必须唯一。
- description:工具描述,帮助模型理解何时及如何调用工具。强烈建议提供详细描述,否则可能导致模型未能正确调用工具。
- inputSchema:工具输入参数的 JSON schema。若未提供,schema 会根据方法参数自动生成。可以使用
@ToolParam注解提供额外信息(如描述、是否必填),默认所有参数均为必填。
ToolMetadata.Builder 允许你构建一个 ToolMetadata 实例,并为该工具定义额外的设置。
- returnDirect:结果是否直接返回给客户端,或返回给模型。
示例:
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinitions.builder(method)
.description("Get the current date and time in the user's timezone")
.build())
.toolMethod(method)
.toolObject(new DateTimeTools())
.build();
该方法可以是静态方法或实例方法,并且可以具有任意可见性(public、protected、包级私有或 private)。包含该方法的类可以是顶层类或嵌套类,并且同样可以具有任意可见性(只要在你打算实例化它的地方是可访问的即可)。
@Component 注解),Spring AI 就提供对工具方法的 AOT 编译的内置支持。否则,你需要为 GraalVM 编译器提供必要的配置,例如,通过在类上添加 @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_METHODS) 注解。方法可以定义任意数量的参数(包括无参数),支持大多数类型(原始类型、POJO、枚举、列表、数组、映射等)。方法的返回类型同样支持大多数类型,包括 void。如果方法有返回值,则返回类型必须是可序列化的,因为结果会被序列化并发送回模型。
如果方法是静态的,则可以省略 toolObject() 方法,因为静态方法不需要对象实例。
class DateTimeTools {
static String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinitions.builder(method)
.description("Get the current date and time in the user's timezone")
.build())
.toolMethod(method)
.build();
Spring AI 会自动为方法的输入参数生成 JSON Schema。该 Schema 用于让模型了解如何调用该工具并准备工具请求。你可以使用 @ToolParam 注解为输入参数提供额外信息,例如参数的描述或是否为必填参数。默认情况下,所有输入参数都被视为必填。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.ToolParam;
class DateTimeTools {
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);
}
}
@ToolParam 注解允许你为工具参数提供关键信息:
- description(描述):参数的描述信息,可用于帮助模型更好地理解如何使用该参数。例如,参数应使用的格式、允许的取值范围等。
- required(必填):指定参数是必填还是可选。默认情况下,所有参数都被视为必填。
如果某个参数被注解为 @Nullable,则默认视为可选,除非使用 @ToolParam 显式标记为必填。
除了 @ToolParam 注解,你还可以使用 Swagger 的 @Schema 注解或 Jackson 的 @JsonProperty 注解。更多信息请参见 JSON Schema。
向 ChatClient 和 ChatModel 添加工具
在使用编程方式(programmatic specification)时,你可以将 MethodToolCallback 实例传递给 ChatClient 的 toolCallbacks() 方法。该工具只会对添加它的特定聊天请求可用。
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("明天是星期几?")
.toolCallbacks(toolCallback)
.call()
.content();
向 ChatClient 添加默认工具
在使用编程方式(programmatic specification)时,你可以将 MethodToolCallback 实例传递给 ChatClient.Builder 的 defaultToolCallbacks() 方法来添加默认工具。如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
ChatClient.Builder 构建的所有 ChatClient 实例共享,适用于跨多个聊天请求经常使用的工具。但使用不当也可能带来风险,可能会在不应可用的场景下被访问。ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(toolCallback)
.build();
向 ChatModel 添加工具
在使用编程方式(programmatic specification)时,你可以将 MethodToolCallback 实例传递给用于调用 ChatModel 的 ToolCallingChatOptions 的 toolCallbacks() 方法。该工具只对添加它的特定聊天请求可用。
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build();
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);
向 ChatModel 添加默认工具
在使用编程方式(programmatic specification)时,你可以在创建 ChatModel 时,将 MethodToolCallback 实例传递给用于创建该模型的 ToolCallingChatOptions 的 toolCallbacks() 方法,从而添加默认工具。如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
ChatModel 实例执行的所有聊天请求中共享。它们适用于在不同聊天请求中常用的工具,但使用不当也可能带来风险,可能会在不该使用的场景下被调用。ToolCallback toolCallback = ...
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build())
.build();
方法工具的限制
以下类型目前不支持作为方法工具的参数或返回类型:
Optional类型- 异步类型(如
CompletableFuture、Future) - 响应式类型(如
Flow、Mono、Flux) - 函数式类型(如
Function、Supplier、Consumer)
函数式类型可以通过基于函数的工具规范方式(function-based tool specification)来支持。详见《Functions as Tools》章节。
函数作为工具
Spring AI 提供了内置支持,可以通过函数来指定工具,方式包括:
- 编程方式:使用底层的
FunctionToolCallback实现 - 动态方式:作为
@Bean在运行时解析
这两种方式都可以将函数定义为模型可调用的工具。
编程式定义:FunctionToolCallback
你可以通过编程方式构建 FunctionToolCallback,将函数类型(Function、Supplier、Consumer 或 BiFunction)转换为工具。
public class WeatherService implements Function<WeatherRequest, WeatherResponse> {
public WeatherResponse apply(WeatherRequest request) {
return new WeatherResponse(30.0, Unit.C);
}
}
public enum Unit { C, F }
public record WeatherRequest(String location, Unit unit) {}
public record WeatherResponse(double temp, Unit unit) {}
FunctionToolCallback.Builder 允许你构建一个 FunctionToolCallback 实例,并提供工具的关键信息:
- name:工具名称。AI 模型使用此名称在调用时识别工具。因此,在同一上下文中不允许两个工具重名。对于特定聊天请求,名称必须在所有可用工具中唯一。必填。
- toolFunction:表示工具方法的函数对象(Function、Supplier、Consumer 或 BiFunction)。必填。
- description:工具的描述,用于帮助模型理解何时以及如何调用该工具。如果未提供,将使用方法名作为工具描述。强烈建议提供详细描述,否则可能导致模型未能正确使用工具。
- inputType:函数输入类型。必填。
- inputSchema:工具输入参数的 JSON schema。如果未提供,将根据
inputType自动生成。可使用@ToolParam注解为输入参数提供额外信息,如描述或是否必填。默认所有输入参数均为必填。 - toolMetadata:
ToolMetadata实例,用于定义额外设置,例如结果是否直接返回客户端,以及使用的结果转换器。可通过ToolMetadata.Builder构建。 - toolCallResultConverter:
ToolCallResultConverter实例,用于将工具调用结果转换为发送给 AI 模型的字符串。如果未提供,将使用默认转换器(DefaultToolCallResultConverter)。
ToolMetadata.Builder 可用于构建 ToolMetadata 实例并定义工具的额外设置:
- returnDirect:工具结果是否直接返回给客户端,还是传回模型。详情见 Return Direct。
示例:
ToolCallback toolCallback = FunctionToolCallback
.builder("currentWeather", new WeatherService())
.description("Get the weather in location")
.inputType(WeatherRequest.class)
.build();
函数的输入和输出可以是 Void 或 POJO。输入和输出 POJO 必须是可序列化的,因为结果会被序列化并返回给模型。函数本身以及输入输出类型都必须是 public。
向 ChatClient 添加工具
在使用编程方式指定工具时,你可以将 FunctionToolCallback 实例传递给 ChatClient 的 toolCallbacks() 方法。该工具仅在被添加的特定聊天请求中可用。
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?")
.toolCallbacks(toolCallback)
.call()
.content();
向 ChatClient 添加默认工具
在使用编程方式指定工具时,你可以通过将 FunctionToolCallback 实例传递给 ChatClient.Builder 的 defaultToolCallbacks() 方法来添加默认工具。如果同时提供了默认工具和运行时工具,则运行时工具会完全覆盖默认工具。
ChatClient.Builder 构建的所有 ChatClient 实例执行的所有聊天请求中共享。它们适用于在不同聊天请求中常用的工具,但使用时需要小心,否则可能在不应该使用时被意外调用。ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(toolCallback)
.build();
向 ChatModel 添加工具
在使用编程方式指定工具时,你可以将 FunctionToolCallback 实例传递给 ToolCallingChatOptions 的 toolCallbacks() 方法。该工具仅在它被添加的特定聊天请求中可用。
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build();
Prompt prompt = new Prompt("What's the weather like in Copenhagen?", chatOptions);
chatModel.call(prompt);
向 ChatModel 添加默认工具
在使用编程方式指定工具时,你可以在创建 ChatModel 时,将 FunctionToolCallback 实例传递给用于创建 ChatModel 的 ToolCallingChatOptions 的 toolCallbacks() 方法。如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
ChatModel 实例执行的所有聊天请求中共享。这对于在不同聊天请求中常用的工具非常有用,但如果使用不当,也可能存在风险,可能会在不该可用时被调用。ToolCallback toolCallback = ...
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build())
.build();
动态定义:@Bean
与其以编程方式指定工具,你也可以将工具定义为 Spring Bean,并让 Spring AI 在运行时通过 ToolCallbackResolver 接口(使用 SpringBeanToolCallbackResolver 实现)动态解析它们。此方式允许你将任何 Function、Supplier、Consumer 或 BiFunction Bean 用作工具。Bean 名称将作为工具名称,Spring Framework 的 @Description 注解可用于提供工具描述,帮助模型理解何时以及如何调用该工具。如果未提供描述,则使用方法名作为工具描述。但强烈建议提供详细描述,因为这是模型理解工具用途和使用方法的关键。未提供良好描述可能导致模型在应使用工具时未调用,或调用不当。
@Configuration(proxyBeanMethods = false)
class WeatherTools {
WeatherService weatherService = new WeatherService();
@Bean
@Description("Get the weather in location")
Function<WeatherRequest, WeatherResponse> currentWeather() {
return weatherService;
}
}
工具的输入参数 JSON Schema 会自动生成。你可以使用 @ToolParam 注解提供输入参数的额外信息,例如描述或是否为必填。默认情况下,所有输入参数都被视为必填。
record WeatherRequest(
@ToolParam(description = "The name of a city or a country") String location,
Unit unit
) {}
这种工具指定方式的缺点是类型安全性无法保证,因为工具解析发生在运行时。为缓解此问题,你可以通过 @Bean 注解显式指定工具名称,并将其存储在常量中,这样在聊天请求中就可以使用该常量,而不是硬编码工具名称。
@Configuration(proxyBeanMethods = false)
class WeatherTools {
public static final String CURRENT_WEATHER_TOOL = "currentWeather";
@Bean(CURRENT_WEATHER_TOOL)
@Description("Get the weather in location")
Function<WeatherRequest, WeatherResponse> currentWeather() {
...
}
}
向 ChatClient 添加工具
在使用动态指定方式时,你可以将工具名称(即函数 Bean 的名称)传递给 ChatClient 的 toolNames() 方法。该工具只会对添加它的特定聊天请求可用。
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?")
.toolNames("currentWeather")
.call()
.content();
向 ChatClient 添加默认工具
在使用动态指定方式时,你可以通过将工具名称传递给 ChatClient.Builder 的 defaultToolNames() 方法来添加默认工具。如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
ChatClient.Builder 构建的所有 ChatClient 实例的所有聊天请求中共享。这对于在不同聊天请求中常用的工具非常有用,但如果使用不当,也可能带来风险,使工具在不应可用的情况下被使用。ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolNames("currentWeather")
.build();
向 ChatModel 添加工具
在使用动态指定方式时,你可以将工具名称传递给用于调用 ChatModel 的 ToolCallingChatOptions 的 toolNames() 方法。该工具仅在添加到的特定聊天请求中可用。
ChatModel chatModel = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolNames("currentWeather")
.build();
Prompt prompt = new Prompt("What's the weather like in Copenhagen?", chatOptions);
chatModel.call(prompt);
向 ChatModel 添加默认工具
在使用动态指定方式时,你可以在构建 ChatModel 时,将默认工具添加到 ChatModel,通过 ToolCallingChatOptions 实例的 toolNames() 方法传入工具名称。如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
ChatModel 实例执行的所有聊天请求中共享。它们适用于在不同聊天请求中常用的工具,但使用时需谨慎,否则可能在不应可用的情况下被使用。ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolNames("currentWeather")
.build())
.build();
函数工具的限制
以下类型目前不支持作为用作工具的函数的输入或输出类型:
- 原始类型(Primitive types)
Optional- 集合类型(如
List、Map、Array、Set) - 异步类型(如
CompletableFuture、Future) - 响应式类型(如
Flow、Mono、Flux)
原始类型和集合类型可以通过基于方法的工具规范方式支持。详情请参见《方法作为工具》。
工具规范
在 Spring AI 中,工具通过 ToolCallback 接口进行建模。在前面的章节中,我们已经介绍了如何使用 Spring AI 提供的内置支持,从方法和函数定义工具(参见《方法作为工具》和《函数作为工具》)。本节将更深入地探讨工具规范,以及如何自定义和扩展它以支持更多使用场景。
工具回调
ToolCallback 接口提供了一种定义可被 AI 模型调用的工具的方法,包括工具的定义和执行逻辑。当你希望从零开始定义一个工具时,这是主要需要实现的接口。例如,你可以基于 MCP 客户端(使用 Model Context Protocol)或 ChatClient 定义一个 ToolCallback,以构建模块化的智能代理应用程序。
该接口提供了以下方法:
public interface ToolCallback {
/**
* 供 AI 模型使用的工具定义,用于确定何时以及如何调用该工具。
*/
ToolDefinition getToolDefinition();
/**
* 提供有关如何处理该工具的附加信息的元数据。
*/
ToolMetadata getToolMetadata();
/**
* 使用给定输入执行工具,并返回结果以发送回 AI 模型。
*/
String call(String toolInput);
/**
* 使用给定输入和上下文执行工具,并返回结果以发送回 AI 模型。
*/
String call(String toolInput, ToolContext toolContext);
}
Spring AI 提供了针对方法工具(MethodToolCallback)和函数工具(FunctionToolCallback)的内置实现。
工具定义
ToolDefinition 接口为 AI 模型提供了工具可用性所需的信息,包括工具名称、描述以及输入参数的 schema。每个 ToolCallback 实现都必须提供一个 ToolDefinition 实例来定义工具。
该接口提供以下方法:
public interface ToolDefinition {
/**
* 工具名称。在提供给模型的工具集合中必须唯一。
*/
String name();
/**
* 工具描述,用于 AI 模型判断该工具的功能。
*/
String description();
/**
* 调用工具所需参数的 schema。
*/
String inputSchema();
}
ToolDefinition.Builder 可以使用默认实现(DefaultToolDefinition)来构建 ToolDefinition 实例:
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 会自动生成。如果你希望手动生成 ToolDefinition,可以使用该便捷构建器:
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinitions.from(method);
从方法生成的 ToolDefinition 默认会使用方法名作为工具名称和描述,并生成方法输入参数的 JSON schema。如果方法上使用了 @Tool 注解,则工具名称和描述会取自注解(如果设置了)。
如果你希望显式提供部分或全部属性,可以使用 ToolDefinition.Builder 构建自定义的 ToolDefinition:
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinitions.builder(method)
.name("currentDateTime")
.description("Get the current date and time in the user's timezone")
.inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
.build();
函数工具定义
当从函数构建工具时,ToolDefinition 也会自动生成。在使用 FunctionToolCallback.Builder 构建 FunctionToolCallback 实例时,可以提供工具名称、描述和输入 schema 来生成 ToolDefinition。更多详情请参见 函数作为工具。
JSON Schema
在向 AI 模型提供工具时,模型需要知道调用该工具时输入类型的 schema。该 schema 用于帮助模型理解如何调用工具并准备工具请求。Spring AI 内置了通过 JsonSchemaGenerator 类生成工具输入类型 JSON Schema 的支持。生成的 schema 会作为 ToolDefinition 的一部分提供。
ToolDefinition 以及如何向其传入输入 schema,请参见 工具定义。JsonSchemaGenerator 类在内部用于为方法或函数的输入参数生成 JSON schema,生成策略参考 方法作为工具 和 函数作为工具 中的描述。JSON schema 生成逻辑支持对输入参数使用一系列注解,以自定义生成的 schema。
本节介绍生成工具输入参数 JSON schema 时可自定义的两个主要选项:描述 和 必填状态。
描述(Description)
除了为工具本身提供描述外,还可以为工具的输入参数提供描述。描述可以用于说明参数应符合的格式、允许的值等信息,帮助模型理解输入 schema 以及如何使用参数。Spring AI 支持通过以下注解为输入参数生成描述:
@ToolParam(description = "...")(Spring AI)@JsonClassDescription(description = "...")(Jackson)@JsonPropertyDescription(description = "...")(Jackson)@Schema(description = "...")(Swagger)
此方式适用于方法和函数,也可以递归应用于嵌套类型。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.context.i18n.LocaleContextHolder;
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);
}
}
必填/可选(Required/Optional)
默认情况下,每个输入参数都是必填的,这要求 AI 模型在调用工具时必须提供对应值。但你可以通过以下注解将输入参数设为可选(按优先级顺序):
@ToolParam(required = false)(Spring AI)@JsonProperty(required = false)(Jackson)@Schema(required = false)(Swagger)@Nullable(Spring Framework)
同样,这种方式适用于方法和函数,也可以递归应用于嵌套类型。
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);
}
}
email 参数是可选的,这意味着模型在调用工具时可以不提供该值;如果参数为必填,模型必须提供值,否则可能会自行生成一个值,从而导致幻觉问题。结果转换
工具调用的结果会通过 ToolCallResultConverter 进行序列化,然后发送回 AI 模型。ToolCallResultConverter 接口提供了一种将工具调用结果转换为 String 对象的方式。
该接口提供如下方法:
@FunctionalInterface
public interface ToolCallResultConverter {
/**
* 将工具返回的对象转换为与给定类型兼容的字符串。
*/
String convert(@Nullable Object result, @Nullable Type returnType);
}
工具的返回结果必须是可序列化类型。默认情况下,结果会使用 Jackson 序列化为 JSON(DefaultToolCallResultConverter),但你可以通过提供自定义的 ToolCallResultConverter 实现来自定义序列化过程。
Spring AI 在方法工具和函数工具中都依赖 ToolCallResultConverter。
方法工具的结果转换
使用声明式方式构建方法工具时,可以通过在 @Tool 注解中设置 resultConverter() 属性,为该工具指定自定义的 ToolCallResultConverter:
class CustomerTools {
@Tool(description = "Retrieve customer information", resultConverter = CustomToolCallResultConverter.class)
Customer getCustomerInfo(Long id) {
return customerRepository.findById(id);
}
}
使用编程式方式时,可以通过 MethodToolCallback.Builder 的 resultConverter() 属性指定自定义的 ToolCallResultConverter。
详见 方法作为工具。
函数工具的结果转换
使用编程式方式构建函数工具时,可以通过 FunctionToolCallback.Builder 的 resultConverter() 属性为工具指定自定义的 ToolCallResultConverter。
详见 函数作为工具。
工具上下文
Spring AI 支持通过 ToolContext API 向工具传递额外的上下文信息。此功能允许你在工具执行时,除了 AI 模型传递的工具参数外,还可以使用用户提供的额外数据。

class CustomerTools {
@Tool(description = "Retrieve customer information")
Customer getCustomerInfo(Long id, ToolContext toolContext) {
return customerRepository.findById(id, toolContext.getContext().get("tenantId"));
}
}
ToolContext 会在调用 ChatClient 时被填充为用户提供的数据:
ChatModel chatModel = ...
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();
System.out.println(response);
ToolContext 中提供的任何数据都不会被发送给 AI 模型。同样,你也可以在直接调用 ChatModel 时定义工具上下文数据:
ChatModel chatModel = ...
ToolCallback[] customerTools = ToolCallbacks.from(new CustomerTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(customerTools)
.toolContext(Map.of("tenantId", "acme"))
.build();
Prompt prompt = new Prompt("Tell me more about the customer with ID 42", chatOptions);
chatModel.call(prompt);
如果在默认选项和运行时选项中都设置了 toolContext,最终的 ToolContext 会是两者的合并结果,其中运行时选项会覆盖默认选项的同名数据。
直接返回
默认情况下,工具调用的结果会作为响应返回给模型,然后模型可以使用该结果继续对话。
但是,有些情况下你可能希望将结果直接返回给调用方,而不是发送回模型。例如,如果你构建的代理依赖于 RAG 工具,你可能希望将结果直接返回给调用方,而不是发送回模型进行不必要的后处理。或者,某些工具的执行本身就应该结束代理的推理循环。
每个 ToolCallback 实现都可以定义工具调用的结果是直接返回给调用方,还是发送回模型。默认情况下,结果会发送回模型,但你可以针对每个工具修改此行为。
ToolCallingManager 负责管理工具执行的生命周期,并处理工具的 returnDirect 属性。如果该属性为 true,工具调用结果会直接返回给调用方;否则,结果会发送回模型。
returnDirect 属性都设置为 true,才能将结果直接返回给调用方,否则结果仍会发送回模型。
- 当我们希望让模型能够使用某个工具时,我们会在聊天请求中包含该工具的定义。如果我们希望工具执行的结果直接返回给调用方,而不是再发送回模型进行处理,就需要将
returnDirect属性设置为true。 - 当模型决定调用某个工具时,它会发送一条响应,内容包含工具名称以及根据工具定义的结构模式而生成的输入参数。
- 应用程序负责根据工具名称识别并执行对应的工具,并使用模型提供的输入参数来运行该工具。
- 工具执行完成后,应用程序会处理工具调用的结果。
- 随后,应用程序会将工具的执行结果直接返回给调用方,而不是将结果再发送回模型。
方法级 Return Direct
当使用声明式方式从方法构建工具时,你可以通过在 @Tool 注解中将 returnDirect 属性设置为 true,来标记该工具的结果应当直接返回给调用方。
class CustomerTools {
@Tool(description = "Retrieve customer information", returnDirect = true)
Customer getCustomerInfo(Long id) {
return customerRepository.findById(id);
}
}
如果使用的是编程式方式,你可以通过 ToolMetadata 接口设置 returnDirect 属性,并将其传递给 MethodToolCallback.Builder。
ToolMetadata toolMetadata = ToolMetadata.builder()
.returnDirect(true)
.build();
函数级 Return Direct
在使用编程式方式由函数构建工具时,你可以通过 ToolMetadata 接口设置 returnDirect 属性,并将其传递给 FunctionToolCallback.Builder。
ToolMetadata toolMetadata = ToolMetadata.builder()
.returnDirect(true)
.build();
工具执行
工具执行过程指的是使用提供的输入参数调用工具并返回结果。该过程由 ToolCallingManager 接口负责,它负责管理整个工具执行生命周期。
public interface ToolCallingManager {
/**
* 从模型的工具调用选项中解析工具定义。
*/
List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);
/**
* 执行模型请求的工具调用。
*/
ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);
}
如果你使用的是任何 Spring AI 的 Spring Boot Starter,DefaultToolCallingManager 就是 ToolCallingManager 接口的自动配置实现。你可以通过提供自定义的 ToolCallingManager bean 来调整工具执行行为:
@Bean
ToolCallingManager toolCallingManager() {
return ToolCallingManager.builder().build();
}
默认情况下,Spring AI 会在每个 ChatModel 实现内部透明地管理工具执行生命周期。但你也可以选择退出这种默认行为,自行控制工具的执行。本节将说明这两种场景。
框架控制的工具执行
当使用默认行为时,Spring AI 会自动拦截模型发起的任何工具调用请求,调用相应工具并将结果返回给模型。这一切都由各个 ChatModel 的实现通过 ToolCallingManager 在内部透明完成。

- 当我们希望让模型可以使用某个工具时,我们会在聊天请求(
Prompt)中包含该工具的定义,然后调用ChatModelAPI,将请求发送给 AI 模型。 - 当模型决定调用某个工具时,它会返回一个
ChatResponse,其中包含工具名称与根据定义的 schema 生成的输入参数。 - ChatModel 将工具调用请求转发给
ToolCallingManagerAPI。 - ToolCallingManager 负责识别要调用的工具,并使用提供的输入参数执行该工具。
- 工具执行的结果返回给 ToolCallingManager。
- ToolCallingManager 再将工具执行结果返回给 ChatModel。
- ChatModel 将工具执行结果(ToolResponseMessage)发送回 AI 模型。
- AI 模型使用工具调用结果作为额外上下文生成最终回答,并通过 ChatClient 将最终的
ChatResponse返回给调用者。
用于判断工具调用是否符合执行条件的逻辑由 ToolExecutionEligibilityPredicate 接口处理。默认情况下,工具执行的判定依据是:检查 ToolCallingChatOptions 的 internalToolExecutionEnabled 属性是否被设置为 true(默认值),以及 ChatResponse 是否包含任何工具调用。
public class DefaultToolExecutionEligibilityPredicate implements ToolExecutionEligibilityPredicate {
@Override
public boolean test(ChatOptions promptOptions, ChatResponse chatResponse) {
return ToolCallingChatOptions.isInternalToolExecutionEnabled(promptOptions)
&& chatResponse != null
&& chatResponse.hasToolCalls();
}
}
你可以在创建 ChatModel Bean 时提供自定义的 ToolExecutionEligibilityPredicate 实现。
用户控制的工具执行
在某些情况下,你可能希望亲自控制工具执行的生命周期。此时,可以将 ToolCallingChatOptions 的 internalToolExecutionEnabled 属性设置为 false 来实现。
当使用此选项调用 ChatModel 时,工具执行将由调用方自行处理,你将完全掌控工具执行的生命周期。你需要自行检查 ChatResponse 中是否包含工具调用,并使用 ToolCallingManager 来执行这些工具。
下面的示例展示了采用 “用户控制工具执行” 方式的最简实现:
ChatModel chatModel = ...
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(new CustomerTools())
.internalToolExecutionEnabled(false)
.build();
Prompt prompt = new Prompt("Tell me more about the customer with ID 42", chatOptions);
ChatResponse chatResponse = chatModel.call(prompt);
while (chatResponse.hasToolCalls()) {
ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, chatResponse);
prompt = new Prompt(toolExecutionResult.conversationHistory(), chatOptions);
chatResponse = chatModel.call(prompt);
}
System.out.println(chatResponse.getResult().getOutput().getText());
ToolCallingManager 来管理相关操作。这样可以充分利用 Spring AI 提供的工具执行支持。但如果你愿意,也可以完全实现自己的工具执行逻辑。下面的示例展示了 “用户控制工具执行” 方式与 ChatMemory API 结合使用的最简实现:
ToolCallingManager toolCallingManager = DefaultToolCallingManager.builder().build();
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
String conversationId = UUID.randomUUID().toString();
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(ToolCallbacks.from(new MathTools()))
.internalToolExecutionEnabled(false)
.build();
Prompt prompt = new Prompt(
List.of(new SystemMessage("You are a helpful assistant."), new UserMessage("What is 6 * 8?")),
chatOptions);
chatMemory.add(conversationId, prompt.getInstructions());
Prompt promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);
ChatResponse chatResponse = chatModel.call(promptWithMemory);
chatMemory.add(conversationId, chatResponse.getResult().getOutput());
while (chatResponse.hasToolCalls()) {
ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(promptWithMemory,
chatResponse);
chatMemory.add(conversationId, toolExecutionResult.conversationHistory()
.get(toolExecutionResult.conversationHistory().size() - 1));
promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);
chatResponse = chatModel.call(promptWithMemory);
chatMemory.add(conversationId, chatResponse.getResult().getOutput());
}
UserMessage newUserMessage = new UserMessage("What did I ask you earlier?");
chatMemory.add(conversationId, newUserMessage);
ChatResponse newResponse = chatModel.call(new Prompt(chatMemory.get(conversationId)));
异常处理
当工具调用失败时,会抛出 ToolExecutionException 异常,你可以捕获该异常来处理错误。
ToolExecutionExceptionProcessor 可用于处理 ToolExecutionException,并提供两种处理结果:
- 生成一条错误消息并发送回 AI 模型;
- 抛出异常,由调用方处理。
@FunctionalInterface
public interface ToolExecutionExceptionProcessor {
/**
* 将工具执行过程中抛出的异常转换为可发送给 AI 模型的字符串,
* 或者直接抛出异常,由调用方处理。
*/
String process(ToolExecutionException exception);
}
如果你使用了任意 Spring AI 的 Spring Boot Starter,那么 DefaultToolExecutionExceptionProcessor 会作为默认实现自动配置。
默认行为如下:
- RuntimeException:错误信息会发送回模型,由模型处理;
- 受检异常与 Error(如 IOException、OutOfMemoryError):始终抛出,不返回给模型。
- 构造函数允许设置
alwaysThrow属性。若设置为true,则所有错误都会直接抛出,而不会发送给模型。
你可以通过 spring.ai.tools.throw-exception-on-error 属性来控制默认异常处理器的行为:
| 配置项 | 描述 | 默认值 |
|---|---|---|
spring.ai.tools.throw-exception-on-error | 若为 true,工具调用错误将直接抛异常,由调用者处理;若为 false,错误将被转换为消息发送给 AI 模型,让模型处理。 | false |
例如:
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
return new DefaultToolExecutionExceptionProcessor(true);
}
ToolCallback 实现,请确保在工具逻辑的 call() 方法中发生错误时,抛出 ToolExecutionException。ToolExecutionExceptionProcessor 被默认的工具调用管理器 DefaultToolCallingManager 内部使用,用于处理工具执行过程中产生的异常。
有关工具执行生命周期的更多内容,请参阅 工具执行 章节。
工具解析
向模型传递工具的主要方式,是在调用 ChatClient 或 ChatModel 时提供一个或多个 ToolCallback,具体可参考 方法作为工具 和 函数作为工具 章节。
不过,Spring AI 也支持使用 ToolCallbackResolver 接口在运行时动态解析工具。
public interface ToolCallbackResolver {
/**
* 根据给定的工具名称解析对应的 {@link ToolCallback}。
*/
@Nullable
ToolCallback resolve(String toolName);
}
在这种方式下:
- 客户端:向
ChatClient或ChatModel提供 “工具名称”,而不是具体的ToolCallback实例。 - 服务端:通过一个
ToolCallbackResolver实现,将工具名称解析为实际的ToolCallback对象。
默认情况下,Spring AI 使用 DelegatingToolCallbackResolver,它依次委派给多个 ToolCallbackResolver 来解析工具:
- SpringBeanToolCallbackResolver
从 Spring 容器中解析工具,支持
Function、Supplier、Consumer、BiFunction等类型的 Bean。 参见 动态声明:@Bean 了解更多信息。 - StaticToolCallbackResolver
从静态的
ToolCallback列表中解析工具。 若你使用 Spring Boot 自动配置,该解析器会自动包含应用上下文中所有ToolCallback类型的 Bean。
如果你使用 Spring Boot 自动配置,可以通过提供一个自定义的 ToolCallbackResolver Bean 来定制工具解析逻辑:
@Bean
ToolCallbackResolver toolCallbackResolver(List<FunctionCallback> toolCallbacks) {
StaticToolCallbackResolver staticToolCallbackResolver = new StaticToolCallbackResolver(toolCallbacks);
return new DelegatingToolCallbackResolver(List.of(staticToolCallbackResolver));
}
ToolCallbackResolver 会被 ToolCallingManager 在内部使用,用于在运行时动态解析工具,支持:
- 框架托管的工具执行流程(Framework-Controlled Tool Execution)
- 用户托管的工具执行流程(User-Controlled Tool Execution)
可观察性
调用工具(Tool Calling)功能支持可观测性(observability),通过 spring.ai.tool 观测点可以测量执行时间并传递追踪信息。详见《工具调用可观测性》章节。
可选地,Spring AI 可以将工具调用的参数和结果作为 span 属性导出,但出于敏感性考虑,该功能默认关闭。详见《工具调用参数和结果数据》章节。
日志记录
工具调用功能的所有主要操作都会以 DEBUG 级别记录。你可以通过将 org.springframework.ai 包的日志级别设置为 DEBUG 来启用这些日志。