|
此版本仍在开发中,尚未被视为稳定版。如需最新的快照版本,请使用 Spring AI 1.1.3! |
工具调用
工具调用(也称为函数调用)是 AI 应用中的一种常见模式,允许模型与一组 API 或工具进行交互,从而增强其能力。
工具主要用于:
-
信息检索。此类别中的工具可用于从外部来源(如数据库、Web 服务、文件系统或网络搜索引擎)检索信息。其目标是增强模型的知识,使其能够回答原本无法回答的问题。因此,它们可用于检索增强生成(RAG)场景。例如,可以使用工具检索给定位置的当前天气、获取最新新闻文章,或查询数据库以获取特定记录。
-
采取行动。此类别中的工具可用于在软件系统中执行操作,例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。其目标是自动化那些原本需要人工干预或显式编程的任务。例如,工具可用于为与聊天机器人交互的客户预订航班、填写网页上的表单,或在代码生成场景中基于自动化测试(TDD)实现一个 Java 类。
尽管我们通常将工具调用称为模型的一种能力,但实际上由客户端应用程序负责提供工具调用的逻辑。模型只能请求工具调用并提供输入参数,而应用程序则负责根据这些输入参数执行工具调用并返回结果。模型永远无法访问作为工具提供的任何 API,这是一个关键的安全考量。
Spring AI 提供了便捷的 API,用于定义工具、解析来自模型的工具调用请求,并执行这些工具调用。以下章节将概述 Spring AI 中的工具调用功能。
| 查看 聊天模型对比 以了解哪些 AI 模型支持工具调用。 |
| 请遵循指南,从已弃用的 FunctionCallback 迁移到 ToolCallback API。 |
快速开始
让我们看看如何在 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 模型可用于生成实现特定目标的计划。例如,模型可以生成预订丹麦之旅的计划。然而,该模型不具备执行该计划的能力。这正是工具发挥作用的地方:它们可用于执行模型生成的计划。
在前面的示例中,我们使用了一个工具来确定当前的日期和时间。在本示例中,我们将定义第二个工具,用于在特定时间设置闹钟。目标是将闹钟设置为从现在起 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 中工具调用的主要概念和组件。
-
当我们要将一个工具提供给模型使用时,我们需要在聊天请求中包含该工具的定义。每个工具定义包括名称、描述以及输入参数的模式。
-
当模型决定调用一个工具时,它会发送一个响应,包含工具名称和依据定义的模式建模的输入参数。
-
该应用负责使用工具名称来识别并执行带有提供的输入参数的工具。
-
工具调用的结果由应用程序处理。
-
该应用将工具调用结果发送回模型。
-
该模型使用工具调用结果作为额外背景生成最终响应。
工具是工具调用的构建块,它们由 ToolCallback 接口建模。Spring AI 提供了内置支持,用于从方法和函数中指定 ToolCallback,但您始终可以定义自己的 ToolCallback 实现以支持更多用例。
ChatModel 个实现透明地将工具调用请求分派给相应的 ToolCallback 实现,并将工具调用结果发送回模型,模型最终将生成最终响应。它们使用 ToolCallingManager 接口来完成此操作,该接口负责管理工具执行的生命周期。
ChatClient 和 ChatModel 都接受一个 ToolCallback 对象列表,以便将这些工具提供给模型以及最终执行它们的 ToolCallingManager。
除了直接传递ToolCallback对象外,您还可以传递工具名称列表,这些名称将使用ToolCallbackResolver接口动态解析。
以下章节将深入探讨所有这些概念和 API 的详细信息,包括如何自定义和扩展它们以支持更多用例。
作为工具的方法
Spring AI 提供了两种内置方式,用于从方法中指定工具(即 ToolCallback(s)):
-
声明式地,使用
@Tool注解 -
以编程方式,使用底层的
MethodToolCallback实现。
声明式规范:@Tool
你可以通过使用 @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();
}
}
@Tool 注解允许您提供有关该工具的关键信息:
-
name: 工具的名称。如果未提供,将使用方法名称。AI 模型在调用工具时使用此名称来识别它。因此,不允许在同一个类中存在两个同名的工具。对于特定的聊天请求,该名称在模型可用的所有工具中必须是唯一的。 -
description:工具的说明,模型可利用该说明理解何时以及如何调用此工具。若未提供,则将使用方法名作为工具说明。然而,强烈建议提供详细的描述,因为这对于模型理解工具的用途及使用方式至关重要。若未能提供良好的描述,可能导致模型在应当使用时未调用该工具,或错误地使用它。 -
returnDirect:工具结果应直接返回给客户端还是传回模型。详见 直接返回 获取更多详情。 -
resultConverter:用于将工具调用结果转换为要发送回 AI 模型的String object的ToolCallResultConverter实现。有关更多详情,请参阅 结果转换。
该方法可以是静态的或实例的,并且可以具有任何可见性(public、protected、包私有或 private)。包含该方法的类可以是顶层类或嵌套类,也可以具有任何可见性(只要在您计划实例化它的地方可访问即可)。
Spring AI 为包含在 Spring Bean(例如 @Component)中的类里带有 @Tool 注解的方法提供了内置的 AOT 编译支持。否则,您需要向 GraalVM 编译器提供必要的配置,例如通过使用该类的 @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_METHODS) 注解。 |
您可以为方法定义任意数量的参数(包括无参数),支持大多数类型(基本类型、POJO、枚举、列表、数组、映射等)。同样,该方法可以返回大多数类型,包括void。如果方法返回值,则返回类型必须是可序列化的类型,因为结果将被序列化并发送回模型。
| 某些类型不受支持。请参阅 方法工具限制 以获取更多详细信息。 |
Spring AI 将自动为带有 @Tool 注解的方法的输入参数生成 JSON 模式。该模式用于帮助模型理解如何调用工具并准备工具请求。@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
使用声明式规范方法时,您可以在调用 ChatClient 时将工具类实例传递给 tools() 方法。此类工具仅对添加它们的特定聊天请求可用。
ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
在底层,ChatClient 会从工具类实例中每个标注了 @Tool 的方法生成一个 ToolCallback,并将其传递给模型。如果您更倾向于自行生成 ToolCallback,可以使用 ToolCallbacks 工具类。
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
添加默认工具到ChatClient
使用声明式规范方法时,您可以通过将工具类实例传递给 defaultTools() 方法,向 ChatClient.Builder 添加默认工具。
如果同时提供了默认工具和运行时工具,则运行时工具将完全覆盖默认工具。
默认工具在所有由同一个ChatClient.Builder构建的ChatClient实例所执行的所有聊天请求之间共享。它们适用于在不同聊天请求中常用的工具,但如果使用不当也可能带来危险,可能导致在不应该可用的情况下被暴露。 |
ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new DateTimeTools())
.build();
添加工具到ChatModel
使用声明式规范方法时,您可以将工具类实例传递给您用于调用 ChatModel 的 ToolCallingChatOptions 的 toolCallbacks() 方法。此类工具仅对添加了它们的特定聊天请求可用。
ChatModel 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);
添加默认工具到ChatModel
使用声明式规范方法时,您可以在构造时通过将工具类实例传递给用于创建 ChatModel 的 ToolCallingChatOptions 实例的 toolCallbacks() 方法,将默认工具添加到 ChatModel。
如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
默认工具在该 ChatModel 实例执行的所有聊天请求之间共享。它们适用于在不同聊天请求中常用的工具,但如果使用不当也可能带来风险,导致在不应可用时仍被暴露。 |
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build())
.build();
编程式规范:MethodToolCallback
您可以通过以编程方式构建一个 MethodToolCallback 将方法转变为工具。
class DateTimeTools {
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
MethodToolCallback.Builder 允许您构建一个 MethodToolCallback 实例,并提供有关该工具的关键信息:
-
toolDefinition:定义工具名称、描述和输入模式的ToolDefinition实例。您可以使用ToolDefinition.Builder类来构建它。必填项。 -
toolMetadata:ToolMetadata实例,用于定义其他设置,例如结果是否应直接返回给客户端,以及要使用的结果转换器。您可以使用ToolMetadata.Builder类来构建它。 -
toolMethod:代表工具方法的Method实例。必填。 -
toolObject: 包含工具方法的对象实例。如果该方法是静态的,则可以省略此参数。 -
toolCallResultConverter: 用于将工具调用结果转换为发回给 AI 模型的String对象的ToolCallResultConverter实例。如果未提供,将使用默认转换器(DefaultToolCallResultConverter)。
ToolDefinition.Builder 允许您构建一个 ToolDefinition 实例,并定义工具名称、描述和输入模式:
-
name: 工具的名称。如果未提供,将使用方法名称。AI 模型在调用工具时使用此名称来识别它。因此,不允许在同一个类中存在两个同名的工具。对于特定的聊天请求,该名称在模型可用的所有工具中必须是唯一的。 -
description:工具的说明,模型可利用该说明理解何时以及如何调用此工具。若未提供,则将使用方法名作为工具说明。然而,强烈建议提供详细的描述,因为这对于模型理解工具的用途及使用方式至关重要。若未能提供良好的描述,可能导致模型在应当使用时未调用该工具,或错误地使用它。 -
inputSchema:工具输入参数的 JSON 架构。如果未提供,将基于方法参数自动生成该架构。您可以使用@ToolParam注解来提供有关输入参数的附加信息,例如描述、参数是必填还是可选等。默认情况下,所有输入参数均视为必填。有关更多详细信息,请参阅 JSON Schema。
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)。包含该方法的类可以是顶层类或嵌套类,也可以具有任何可见性(只要在您计划实例化它的地方可访问即可)。
Spring AI 为工具方法的 AOT 编译提供了内置支持,前提是包含这些方法的类是一个 Spring Bean(例如:@Component)。否则,您需要向 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 模式。该模式用于帮助模型理解如何调用工具并准备工具请求。@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
当使用编程式规范方法时,您可以将 MethodToolCallback 实例传递给 ChatClient 的 toolCallbacks() 方法。
该工具仅对其所添加的特定聊天请求可用。
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.toolCallbacks(toolCallback)
.call()
.content();
添加默认工具到ChatClient
当使用编程式配置方法时,您可以通过将 MethodToolCallback 实例传递给 defaultToolCallbacks() 方法,向 ChatClient.Builder 添加默认工具。
如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
默认工具在所有由同一个ChatClient.Builder构建的ChatClient实例所执行的所有聊天请求之间共享。它们适用于在不同聊天请求中常用的工具,但如果使用不当也可能带来危险,可能导致在不应该可用的情况下被暴露。 |
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(toolCallback)
.build();
添加工具到ChatModel
当使用编程式规范方法时,您可以将 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
当使用编程式规范方法时,您可以在构造时通过将 MethodToolCallback 实例传递给用于创建 ChatModel 的 ToolCallingChatOptions 实例的 toolCallbacks() 方法,向 ChatModel 添加默认工具。
如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
默认工具在该 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)。
支持使用基于函数的工具规范方法来定义函数式类型。有关更多详细信息,请参阅 作为工具的函数。
函数作为工具
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 架构。如果未提供,将根据inputType自动生成该架构。您可以使用@ToolParam注解为输入参数提供额外信息,例如描述或说明该参数是必填还是可选。默认情况下,所有输入参数均视为必填。有关更多详情,请参阅 JSON Schema。 -
toolMetadata:ToolMetadata实例,用于定义其他设置,例如结果是否应直接返回给客户端,以及要使用的结果转换器。您可以使用ToolMetadata.Builder类来构建它。 -
toolCallResultConverter: 用于将工具调用结果转换为发回给 AI 模型的String对象的ToolCallResultConverter实例。如果未提供,将使用默认转换器(DefaultToolCallResultConverter)。
ToolMetadata.Builder 允许您构建一个 ToolMetadata 实例,并为该工具定义其他设置:
-
returnDirect:工具结果应直接返回给客户端还是传回模型。详见 直接返回 获取更多详情。
ToolCallback toolCallback = FunctionToolCallback
.builder("currentWeather", new WeatherService())
.description("Get the weather in location")
.inputType(WeatherRequest.class)
.build();
函数的输入和输出可以是 Void 或 POJO。输入和输出的 POJO 必须是可序列化的,因为结果将被序列化并发送回模型。函数以及输入和输出类型必须是公开的。
| 某些类型不受支持。有关更多详细信息,请参阅 函数工具限制。 |
添加工具到ChatClient
当使用编程式规范方法时,您可以将 FunctionToolCallback 实例传递给 ChatClient 的 toolCallbacks() 方法。该工具仅对添加了它的特定聊天请求可用。
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?")
.toolCallbacks(toolCallback)
.call()
.content();
添加默认工具到ChatClient
当使用编程式配置方法时,您可以通过将 FunctionToolCallback 实例传递给 defaultToolCallbacks() 方法,向 ChatClient.Builder 添加默认工具。
如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
默认工具在所有由同一个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
当使用编程式规范方法时,您可以在构造时通过将 FunctionToolCallback 实例传递给用于创建 ChatModel 的 ToolCallingChatOptions 实例的 toolCallbacks() 方法,向 ChatModel 添加默认工具。
如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
默认工具在该 ChatModel 实例执行的所有聊天请求之间共享。它们适用于在不同聊天请求中常用的工具,但如果使用不当也可能带来风险,导致在不应可用时仍被暴露。 |
ToolCallback toolCallback = ...
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build())
.build();
动态规范:@Bean
除了通过编程方式指定工具外,您还可以将工具定义为 Spring beans,并让 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 注解来提供关于输入参数的额外信息,例如描述或参数是否为必填或可选。默认情况下,所有输入参数都被视为必填。有关更多详细信息,请参阅 JSON Schema。
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 名称)传递给 toolNames() 的 ChatClient 方法。
该工具仅对其添加到的特定聊天请求可用。
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?")
.toolNames("currentWeather")
.call()
.content();
添加默认工具到ChatClient
使用动态规范方法时,可以通过将工具名称传递给defaultToolNames()方法来向ChatClient.Builder添加默认工具。
如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
默认工具在所有由同一个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 的 ToolCallingChatOptions 实例的 toolNames() 方法,在构造时将默认工具添加到 ChatModel 中。
如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
默认工具在该 ChatModel 实例执行的所有聊天请求之间共享。它们适用于在不同聊天请求中常用的工具,但如果使用不当也可能带来风险,导致在不应可用时仍被暴露。 |
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolNames("currentWeather")
.build())
.build();
功能工具限制
以下类型目前不支持作为用作工具的函数的输入或输出类型:
-
基本类型
-
Optional -
集合类型(例如
List,Map,Array,Set) -
异步类型(例如
CompletableFuture、Future) -
响应式类型(例如
Flow、Mono、Flux)。
基于方法的工具规范方法支持基本类型和集合。有关更多详细信息,请参阅 方法作为工具。
工具规范
在 Spring AI 中,工具是通过 ToolCallback 接口建模的。在前面的章节中,我们已经了解了如何使用 Spring AI 提供的内置支持从方法和函数定义工具(参见 方法作为工具 和 函数作为工具)。本节将深入探讨工具规范,以及如何自定义和扩展它以支持更多用例。
工具回调
ToolCallback 接口提供了一种定义可被 AI 模型调用的工具的方法,包括定义和执行逻辑。当你想要从头定义一个工具时,这是需要实现的主要接口。例如,你可以从 MCP 客户端(使用模型上下文协议)定义一个 ToolCallback,或者定义一个 ChatClient(以构建模块化的代理应用程序)。
该接口提供以下方法:
public interface ToolCallback {
/**
* Definition used by the AI model to determine when and how to call the tool.
*/
ToolDefinition getToolDefinition();
/**
* Metadata providing additional information on how to handle the tool.
*/
ToolMetadata getToolMetadata();
/**
* Execute tool with the given input and return the result to send back to the AI model.
*/
String call(String toolInput);
/**
* Execute tool with the given input and context, and return the result to send back to the AI model.
*/
String call(String toolInput, ToolContext tooContext);
}
Spring AI 为工具方法(MethodToolCallback)和工具函数(FunctionToolCallback)提供了内置实现。
工具定义
ToolDefinition 接口为 AI 模型提供了了解工具可用性所需的信息,包括工具名称、描述和输入模式。每个 ToolCallback 实现必须提供一个 ToolDefinition 实例来定义该工具。
该接口提供以下方法:
public interface ToolDefinition {
/**
* The tool name. Unique within the tool set provided to a model.
*/
String name();
/**
* The tool description, used by the AI model to determine what the tool does.
*/
String description();
/**
* The schema of the parameters used to call the tool.
*/
String inputSchema();
}
| 参见 JSON 模式 以获取有关输入模式的更多详细信息。 |
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 模式。如果方法带有 @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 实例时,您可以提供工具名称、描述和输入模式,这些将用于生成 ToolDefinition。有关更多详细信息,请参阅 函数作为工具。
JSON Schema
当向 AI 模型提供工具时,模型需要了解调用工具的输入类型的模式。该模式用于理解如何调用工具并准备工具请求。Spring AI 通过 JsonSchemaGenerator 类为工具的输入类型生成 JSON 模式提供了内置支持。该模式作为 ToolDefinition 的一部分提供。
参见 工具定义 以获取关于 ToolDefinition 的更多详细信息以及如何将输入模式传递给它。 |
JsonSchemaGenerator 类在底层用于为方法或函数的输入参数生成 JSON 模式,使用 Methods as Tools 和 Functions as Tools 中描述的任何策略。JSON 模式生成逻辑支持一系列注解,您可以在方法和函数的输入参数上使用这些注解来自定义生成的模式。
本节描述了在为工具的输入参数生成 JSON 模式时可以自定义的两个主要选项:描述和必需状态。
<description> </description>
除了为工具本身提供描述外,您还可以为工具的输入参数提供描述。描述可以用于提供关于输入参数的关键信息,例如参数应该采用什么格式、允许哪些值等等。这有助于帮助模型理解输入模式以及如何使用它。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);
}
}
必填/可选
默认情况下,每个输入参数都被视为必需的,这强制 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 {
/**
* Given an Object returned by a tool, convert it to a String compatible with the
* given class type.
*/
String convert(@Nullable Object result, @Nullable Type returnType);
}
结果必须是可序列化类型。默认情况下,结果使用 Jackson (DefaultToolCallResultConverter) 序列化为 JSON,但你可以通过提供自己的 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。 -
当模型决定调用一个工具时,它会发送一个响应,包含工具名称和依据定义的模式建模的输入参数。
-
该应用负责使用工具名称来识别并执行带有提供的输入参数的工具。
-
工具调用的结果由应用程序处理。
-
应用程序将工具调用结果直接发送给调用者,而不是将其发送回模型。
方法返回直接
在使用声明式方法从方法构建工具时,您可以通过将 @Tool 注解的 returnDirect 属性设置为 true,来标记工具将结果直接返回给调用者。
class CustomerTools {
@Tool(description = "Retrieve customer information", returnDirect = true)
Customer getCustomerInfo(Long id) {
return customerRepository.findById(id);
}
}
如果使用编程方式,您可以通过 returnDirect 接口设置 ToolMetadata 属性,并将其传递给 MethodToolCallback.Builder。
ToolMetadata toolMetadata = ToolMetadata.builder()
.returnDirect(true)
.build();
请参阅 方法作为工具 以了解更多详细信息。
函数直接返回
在使用编程方式从函数构建工具时,您可以通过 returnDirect 接口设置 ToolMetadata 属性,并将其传递给 FunctionToolCallback.Builder。
ToolMetadata toolMetadata = ToolMetadata.builder()
.returnDirect(true)
.build();
有关详细信息,请参阅 函数作为工具。
工具执行
工具执行是使用提供的输入参数调用工具并返回结果的过程。工具执行由 ToolCallingManager 接口处理,该接口负责管理工具执行生命周期。
public interface ToolCallingManager {
/**
* Resolve the tool definitions from the model's tool calling options.
*/
List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);
/**
* Execute the tool calls requested by the model.
*/
ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);
}
如果您正在使用任何 Spring AI Spring Boot Starters,DefaultToolCallingManager 是 ToolCallingManager 接口的自动配置实现。您可以通过提供自己的 ToolCallingManager bean 来自定义工具执行行为。
@Bean
ToolCallingManager toolCallingManager() {
return ToolCallingManager.builder().build();
}
默认情况下,Spring AI 在每个 ChatModel 实现内部为您透明地管理工具执行生命周期。但您可以选择退出此行为并自行控制工具执行。本节描述了这两种情况。
框架控制的工具执行
当使用默认行为时,Spring AI 会自动拦截来自模型的任何工具调用请求,调用工具并将结果返回给模型。所有这些操作都由每个 ChatModel 实现使用 ToolCallingManager 透明地为您完成。
-
当我们想要让工具对模型可用时,我们在聊天请求(
Prompt)中包含其定义,并调用ChatModelAPI,该 API 将请求发送到 AI 模型。 -
当模型决定调用工具时,它会发送一个响应(
ChatResponse),其中包含工具名称和根据定义的 schema 建模的输入参数。 -
ChatModel将工具调用请求发送到ToolCallingManagerAPI。 -
ToolCallingManager负责识别要调用的工具,并使用提供的输入参数执行它。 -
工具调用的结果返回到
ToolCallingManager。 -
ToolCallingManager将工具执行结果返回给ChatModel。 -
ChatModel将工具执行结果发送回 AI 模型(ToolResponseMessage)。 -
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 的自定义实现。
Advisor-Controlled Tool Execution with ToolCallAdvisor
作为框架控制的工具执行的替代方案,您可以使用 ToolCallAdvisor 来将工具调用实现为 advisor chain 的一部分。这种方法提供了几个优势:
-
可观测性: 链中的其他通知器可以拦截并观察每个工具调用迭代
-
与聊天记忆的集成:与聊天记忆顾问无缝协作,用于对话历史管理
-
Extensibility: The advisor can be extended to customize the tool calling behavior
ToolCallAdvisor 实现了工具调用循环,并禁用了模型的内部工具执行。当模型请求工具调用时,advisor 执行该工具并将结果发送回模型,持续进行直到不再需要工具调用。
var toolCallAdvisor = ToolCallAdvisor.builder()
.toolCallingManager(toolCallingManager)
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
.build();
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(toolCallAdvisor)
.build();
String response = chatClient.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
配置选项
ToolCallAdvisor.Builder 支持以下配置选项:
-
toolCallingManager: 用于执行工具调用的ToolCallingManager实例。如果未提供,则使用默认实例。 -
advisorOrder: 顾问在链中应用的顺序。必须介于BaseAdvisor.HIGHEST_PRECEDENCE和BaseAdvisor.LOWEST_PRECEDENCE之间。 -
conversationHistoryEnabled: 控制顾问在工具调用迭代期间是否在内部维护对话历史。默认为true。
会话历史管理
默认情况下(conversationHistoryEnabled=true),ToolCallAdvisor 在工具调用迭代期间会在内部维护完整的对话历史。每次后续的 LLM 调用都会包含所有先前的消息。
使用 .disableInternalConversationHistory() 方法来禁用内部对话历史管理。当禁用时,只有最后一个工具响应消息会被传递到下一次迭代。当与已经管理对话历史的 Chat Memory 顾问集成时,这非常有用:
var toolCallAdvisor = ToolCallAdvisor.builder()
.toolCallingManager(toolCallingManager)
.disableInternalConversationHistory() // Let ChatMemory handle history
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
.build();
var chatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory)
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 200) // Before ToolCallAdvisor
.build();
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(chatMemoryAdvisor, toolCallAdvisor)
.build();
直接返回
ToolCallAdvisor 支持“直接返回”功能,允许工具绕过 LLM 并直接将结果返回给客户端。当工具执行具有 returnDirect=true 时,顾问会跳出工具调用循环并直接返回工具结果。
关于 ToolCallAdvisor 的更多详细信息,请参阅 Recursive Advisors - ToolCallAdvisor。
用户控制的工具执行
有些情况下,您更希望自己控制工具执行的生命周期。您可以通过将 internalToolExecutionEnabled 的 ToolCallingChatOptions 属性设置为 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 {
/**
* Convert an exception thrown by a tool to a String that can be sent back to the AI
* model or throw an exception to be handled by the caller.
*/
String process(ToolExecutionException exception);
}
如果您正在使用任何 Spring AI Spring Boot Starters,DefaultToolExecutionExceptionProcessor 是 ToolExecutionExceptionProcessor 接口的自动配置实现。默认情况下,RuntimeException 的错误消息会发送回模型,而检查异常和错误(例如,IOException、OutOfMemoryError)始终会被抛出。DefaultToolExecutionExceptionProcessor 构造函数允许您将 alwaysThrow 属性设置为 true 或 false。如果 true,将抛出异常而不是将错误消息发送回模型。
您可以使用 `spring.ai.tools.throw-exception-on-error 属性来控制 DefaultToolExecutionExceptionProcessor bean 的行为:
| <property> </property> | <description> </description> | 默认 |
|---|---|---|
|
如果为 |
|
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
return new DefaultToolExecutionExceptionProcessor(true);
}
如果您定义了自己的 ToolCallback 实现,请确保在 call() 方法中作为工具执行逻辑的一部分发生错误时抛出 ToolExecutionException。 |
默认 ToolCallingManager (DefaultToolCallingManager) 内部使用 ToolExecutionExceptionProcessor 来处理工具执行期间的异常。有关工具执行生命周期的更多详细信息,请参阅 工具执行。
工具解析
将工具传递给模型的主要方法是在调用 ToolCallback 或 ChatModel 时提供 ChatClient,
使用 Methods as Tools 和 Functions as Tools 中描述的策略之一。
然而,Spring AI 也支持使用 ToolCallbackResolver 接口在运行时动态解析工具。
public interface ToolCallbackResolver {
/**
* Resolve the {@link ToolCallback} for the given tool name.
*/
@Nullable
ToolCallback resolve(String toolName);
}
当使用此方法时:
-
在客户端,您需要将工具名称提供给
ChatClient或ChatModel,而不是ToolCallback。 -
在服务器端,
ToolCallbackResolver实现负责将工具名称解析为相应的ToolCallback实例。
默认情况下,Spring AI 依赖于一个 DelegatingToolCallbackResolver,该 DelegatingToolCallbackResolver 将工具解析委托给一系列 ToolCallbackResolver 实例:
-
SpringBeanToolCallbackResolver从类型为Function、Supplier、Consumer或BiFunction的 Spring beans 中解析工具。有关更多详细信息,请参阅 动态规范:@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));
}
工具参数增强
Spring AI 提供了一个实用工具,用于动态增强工具输入模式,通过添加额外的参数。这使得能够从模型中捕获额外信息——例如推理或元数据——而无需修改底层工具实现。
常见用例包括:
-
内部思考/推理:在执行工具之前捕获模型的逐步推理
-
内存增强: 提取见解以存储在长期记忆中
-
分析与跟踪:收集元数据、用户意图或使用模式
-
多代理协调:传递代理标识符或协调信号
快速开始
定义增强参数 为 Java Record:
public record AgentThinking(
@ToolParam(description = "Your reasoning for calling this tool", required = true)
String innerThought,
@ToolParam(description = "Confidence level (low, medium, high)", required = false)
String confidence
) {}
用 AugmentedToolCallbackProvider 包装你的工具:
AugmentedToolCallbackProvider<AgentThinking> provider = AugmentedToolCallbackProvider
.<AgentThinking>builder()
.toolObject(new MyTools()) // Your @Tool annotated class
.argumentType(AgentThinking.class)
.argumentConsumer(event -> {
AgentThinking thinking = event.arguments();
log.info("Tool: {} | Reasoning: {}", event.toolDefinition().name(), thinking.innerThought());
})
.removeExtraArgumentsAfterProcessing(true)
.build();
与 ChatClient 一起使用:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(provider)
.build();
LLM 会看到包含您额外字段的增强模式。您的消费者接收 AgentThinking 记录,而原始工具仅接收其预期的参数。
可观测性
工具调用包括可观察性支持,使用 spring.ai.tool 观察来测量完成时间并传播跟踪信息。请参阅 工具调用可观察性。
可选地,Spring AI 可以将工具调用参数和结果导出为 span 属性,出于敏感原因默认禁用。详情:工具调用参数和结果数据。