跳到主要内容

工具调用

Deepseek 3.2 中英对照 Tool Calling

工具调用(也称为函数调用)是AI应用中的一种常见模式,它允许模型与一组API或工具进行交互,从而增强其能力。

工具主要用于:

  • 信息检索。此类别中的工具可用于从外部来源检索信息,例如数据库、网络服务、文件系统或网络搜索引擎。其目标是增强模型的知识,使其能够回答原本无法回答的问题。因此,它们可用于检索增强生成场景。例如,可以使用工具检索给定位置的当前天气、检索最新的新闻文章或查询数据库中的特定记录。

  • 执行操作。此类别中的工具可用于在软件系统中执行操作,例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。其目标是自动化原本需要人工干预或显式编程的任务。例如,在与聊天机器人交互的客户预订航班、填写网页上的表单或在代码生成场景中基于自动化测试实现 Java 类时,都可以使用工具。

尽管我们通常将工具调用称为模型能力,但实际上是由客户端应用程序来提供工具调用的逻辑。模型只能请求一个工具调用并提供输入参数,而应用程序则负责根据输入参数执行工具调用并返回结果。模型永远无法访问作为工具提供的任何API,这是一个关键的安全考量。

Spring AI 提供了便捷的 API,用于定义工具、解析来自模型的工具调用请求以及执行工具调用。以下各节概述了 Spring AI 中的工具调用功能。

备注

查看 Chat 模型对比 以了解哪些 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 中工具调用的核心概念与组件。

工具调用的主要操作序列

  1. 当我们需要让模型能够使用某个工具时,我们会在聊天请求中包含该工具的定义。每个工具定义由名称、描述和输入参数的 schema 组成。

  2. 当模型决定调用某个工具时,它会返回一个响应,其中包含工具名称以及根据定义的 schema 建模的输入参数。

  3. 应用程序负责根据工具名称识别工具,并使用提供的输入参数来执行该工具。

  4. 工具调用的结果由应用程序进行处理。

  5. 应用程序将工具调用的结果发送回模型。

  6. 模型将工具调用的结果作为额外上下文,生成最终响应。

工具是工具调用的基础构建块,它们通过 ToolCallback 接口进行建模。Spring AI 提供了内置支持,可以从方法和函数中指定 ToolCallback,但你始终可以定义自己的 ToolCallback 实现来支持更多用例。

ChatModel实现会透明地将工具调用请求分发给相应的ToolCallback实现,并将工具调用结果发送回模型,最终生成最终响应。它们通过ToolCallingManager接口来实现这一点,该接口负责管理工具执行的生命周期。

ChatClientChatModel 都接受一个 ToolCallback 对象列表,以便模型以及最终执行它们的 ToolCallingManager 能够使用这些工具。

除了直接传递 ToolCallback 对象之外,您还可以传递一个工具名称列表,这些名称将通过 ToolCallbackResolver 接口动态解析。

接下来的章节将详细介绍所有这些概念和API,包括如何自定义和扩展它们以支持更多使用场景。

方法作为工具

Spring AI 提供了两种内置方式来从方法中指定工具(即ToolCallback):

  • 通过声明式方式,使用 @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对象ToolCallResultConverter实现。详见结果转换

该方法既可以是静态方法也可以是实例方法,并且可以具有任意可见性(public、protected、包级私有或private)。包含该方法的类可以是顶级类或嵌套类,也可以具有任意可见性(只要在计划实例化它的地方可以访问即可)。

备注

只要包含这些方法的类是一个Spring bean(例如 @Component),Spring AI 就为 @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 添加工具

在使用声明式规范方法时,您可以将工具类实例传递给用于调用 ChatModelToolCallingChatOptionstoolCallbacks() 方法。这些工具将仅适用于添加它们的特定聊天请求。

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 添加默认工具,方法是将工具类实例传递给用于创建 ChatModelToolCallingChatOptions 实例的 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 来将方法转换为工具。

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:用于将工具调用结果转换为 String 对象以发送回 AI 模型的 ToolCallResultConverter 实例。若未提供,将使用默认转换器(DefaultToolCallResultConverter)。

ToolDefinition.Builder 允许您构建 ToolDefinition 实例,并定义工具名称、描述和输入模式:

  • name:工具的名称。如果未提供,将使用方法名称作为工具名。AI模型在调用工具时使用此名称进行识别。因此,在同一类中不允许存在两个同名工具。对于特定聊天请求中模型可用的所有工具,名称必须保持唯一。

  • description:工具的描述信息,模型可据此理解何时以及如何调用该工具。如果未提供,将使用方法名称作为工具描述。但强烈建议提供详细描述,因为这对模型理解工具的用途和使用方式至关重要。若描述不当,可能导致模型在该使用时未调用工具,或错误调用工具。

  • inputSchema:工具输入参数的JSON模式。如果未提供,将根据方法参数自动生成模式。您可以使用@ToolParam注解来提供输入参数的附加信息,例如描述、参数是否必需等。默认情况下,所有输入参数均被视为必需参数。详见JSON模式章节。

ToolMetadata.Builder 允许你构建一个 ToolMetadata 实例,并为工具定义额外的设置:

  • returnDirect: 此参数决定工具执行结果是否应直接返回给客户端,还是传回模型处理。更多详细信息请参见Return Direct章节。
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、package-private 或 private)。包含该方法的类可以是顶级类或嵌套类,同样可以具有任何可见性(只要在你计划实例化的位置可访问即可)。

备注

只要包含工具方法的类是Spring Bean(例如使用 @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 模式。该模式用于让模型理解如何调用工具并准备工具请求。@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

ChatClientChatModel 添加工具

在使用程序化规范方法时,你可以将 MethodToolCallback 实例传递给 ChatClienttoolCallbacks() 方法。该工具仅对添加它的特定聊天请求可用。

ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.toolCallbacks(toolCallback)
.call()
.content();

ChatClient 添加默认工具

使用编程式规范方法时,你可以通过向defaultToolCallbacks()方法传递MethodToolCallback实例,将默认工具添加到ChatClient.Builder中。如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。

注意

默认工具在所有由同一 ChatClient.Builder 构建的 ChatClient 实例执行的所有聊天请求之间共享。这些工具对于在不同聊天请求中常用的工具非常有用,但如果不小心使用,也可能带来风险,可能导致在不应该的情况下暴露这些工具。

ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(toolCallback)
.build();

ChatModel 添加工具

在使用编程式规范方法时,您可以将 MethodToolCallback 实例传递给 ToolCallingChatOptionstoolCallbacks() 方法,然后使用该选项调用 ChatModel。该工具仅在添加它的特定聊天请求中可用。

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 实例传递给用于创建 ChatModelToolCallingChatOptions 实例的 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,将函数式类型(FunctionSupplierConsumerBiFunction)转换为工具。

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: 代表工具方法的功能对象(FunctionSupplierConsumerBiFunction)。必需。

  • description: 工具的说明,模型可用其来理解何时以及如何调用该工具。如果未提供,将使用方法名称作为工具说明。但是,强烈建议提供详细的说明,因为这对于模型理解工具的用途和使用方法至关重要。未能提供良好的说明可能导致模型在该使用工具时未使用,或错误地使用它。

  • inputType: 函数输入的类型。必需。

  • inputSchema: 工具输入参数的 JSON 模式。如果未提供,将根据 inputType 自动生成模式。您可以使用 @ToolParam 注解来提供有关输入参数的附加信息,例如描述或参数是必需还是可选的。默认情况下,所有输入参数都被视为必需。详情请参阅 JSON 模式

  • toolMetadata: ToolMetadata 实例,用于定义其他设置,例如是否应将结果直接返回给客户端,以及要使用的结果转换器。您可以使用 ToolMetadata.Builder 类来构建它。

  • toolCallResultConverter: 用于将工具调用结果转换为 String 对象以发送回 AI 模型的 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 实例传递给 ChatClienttoolCallbacks() 方法。该工具仅在其被添加到的特定聊天请求中可用。

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 实例传递给 ToolCallingChatOptionstoolCallbacks() 方法。该工具将仅在其被添加到的特定聊天请求中可用。

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 添加默认工具

在使用程序化规范方法时,你可以通过在构建时向用于创建ChatModelToolCallingChatOptions实例的toolCallbacks()方法传递FunctionToolCallback实例,来向ChatModel添加默认工具。如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。

注意

默认工具在该 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实现)动态解析它们。这种选项使您能够将任何FunctionSupplierConsumerBiFunction 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模式将自动生成。你可以使用@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名称)传递给ChatClienttoolNames()方法。该工具仅对添加它的特定聊天请求可用。

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时所使用的ToolCallingChatOptionstoolNames()方法。该工具仅在添加到具体的聊天请求时可用。

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 添加默认工具

在使用动态规范方法时,你可以在构建时通过将工具名称传递给用于创建ChatModelToolCallingChatOptions实例的toolNames()方法,来向ChatModel添加默认工具。如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。

注意

默认工具在所有由该 ChatModel 实例执行的聊天请求之间共享。它们对于在不同聊天请求中常用的工具非常有用,但如果使用不当也可能带来风险,可能导致工具在不该出现的情况下被使用。

ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolNames("currentWeather")
.build())
.build();

函数工具限制

以下类型目前不支持作为工具使用函数的输入或输出类型:

  • 基本类型

  • Optional

  • 集合类型 (例如 ListMapArraySet)

  • 异步类型 (例如 CompletableFutureFuture)

  • 响应式类型 (例如 FlowMonoFlux)

原始类型和集合可通过基于方法的工具规范方式来支持。更多详情请参见 将方法作为工具

工具规范

在 Spring AI 中,工具通过 ToolCallback 接口进行建模。在前面的章节中,我们已经了解了如何利用 Spring AI 内置的支持,从方法和函数定义工具(参见方法作为工具函数作为工具)。本节将深入探讨工具规范,以及如何自定义和扩展它以支持更多使用场景。

工具回调

ToolCallback接口提供了一种定义AI模型可调用工具的方法,包含工具定义和执行逻辑。当需要从头开始定义工具时,这是需要实现的主要接口。例如,你可以从MCP客户端(使用模型上下文协议)或ChatClient(用于构建模块化智能体应用)定义ToolCallback

该接口提供以下方法:

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 Schema的支持。该模式作为ToolDefinition的一部分提供。

备注

有关 ToolDefinition 的更多详细信息,以及如何将输入模式传递给它,请参阅工具定义

JsonSchemaGenerator 类在底层用于为方法或函数的输入参数生成 JSON 模式,可采用 方法作为工具函数作为工具 中描述的任何策略。JSON 模式生成逻辑支持一系列注解,您可以在方法和函数的输入参数上使用这些注解来自定义生成的模式。

本节介绍在生成工具输入参数的 JSON 模式时,您可以自定义的两个主要选项:描述和必需状态。

描述

除了为工具本身提供描述外,你还可以为工具的输入参数提供描述。该描述可用于提供有关输入参数的关键信息,例如参数应采用何种格式、允许哪些值等。这有助于模型理解输入模式及如何使用它。Spring AI 内置支持使用以下注解之一生成输入参数的描述:

  • Spring AI 中的 @ToolParam(description = "…​")

  • Jackson 中的 @JsonClassDescription(description = "…​")

  • Jackson 中的 @JsonPropertyDescription(description = "…​")

  • Swagger 中的 @Schema(description = "…​")

这种方法适用于方法和函数,并且可以递归地用于嵌套类型。

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模型在调用工具时为其提供一个值。然而,您可以通过使用以下任一注解(按优先级顺序)将输入参数设为可选:

  • Spring AI 中的 @ToolParam(required = false)

  • Jackson 中的 @JsonProperty(required = false)

  • Swagger 中的 @Schema(required = false)

  • Spring Framework 中的 @Nullable

这种方法适用于方法和函数,并且你可以递归地将其用于嵌套类型。

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.BuilderresultConverter() 属性,为工具提供一个自定义的 ToolCallResultConverter 来使用。

函数工具调用结果转换

在通过编程方式从函数构建工具时,你可以通过设置 FunctionToolCallback.BuilderresultConverter() 属性,为工具提供一个自定义的 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,以便将结果直接返回给调用者。否则,结果将被发送回模型。

直接向调用者返回工具调用结果

  1. 当我们需要让模型能够使用某个工具时,会在聊天请求中包含其定义。如果我们希望工具执行的结果直接返回给调用者,需要将 returnDirect 属性设置为 true

  2. 当模型决定调用某个工具时,会发送一个包含工具名称和输入参数的响应,这些参数遵循已定义的架构。

  3. 应用程序负责根据工具名称识别工具,并使用提供的输入参数执行该工具。

  4. 工具调用的结果由应用程序处理。

  5. 应用程序将工具调用的结果直接发送给调用者,而不是将其返回给模型。

方法直接返回

当采用声明式方法构建工具时,你可以通过将@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();

更多详细信息请参阅方法作为工具

函数直接返回

在通过编程方式从函数构建工具时,你可以通过 ToolMetadata 接口设置 returnDirect 属性,并将其传递给 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 Starter,DefaultToolCallingManager 将是 ToolCallingManager 接口的自动配置实现。你可以通过提供自己的 ToolCallingManager 来定制工具执行行为。

@Bean
ToolCallingManager toolCallingManager() {
return ToolCallingManager.builder().build();
}

默认情况下,Spring AI 会在每个 ChatModel 实现内部透明地为你管理工具执行的生命周期。但你也可以选择退出此行为,自行控制工具的执行。本节将描述这两种情况。

框架控制的工具执行

当使用默认行为时,Spring AI 会自动拦截来自模型的任何工具调用请求,调用工具并将结果返回给模型。所有这些都是由每个 ChatModel 实现通过 ToolCallingManager 为您透明地完成的。

框架控制的工具执行生命周期

  1. 当我们需要使某个工具可供模型使用时,会将其定义包含在聊天请求(Prompt)中,并调用 ChatModel API 将该请求发送给 AI 模型。

  2. 当模型决定调用某个工具时,它会发送一个响应(ChatResponse),其中包含工具名称以及根据定义的模式建模的输入参数。

  3. ChatModel 将工具调用请求发送给 ToolCallingManager API。

  4. ToolCallingManager 负责识别要调用的工具,并使用提供的输入参数执行该工具。

  5. 工具调用的结果返回给 ToolCallingManager

  6. ToolCallingManager 将工具执行结果返回给 ChatModel

  7. ChatModel 将工具执行结果发送回 AI 模型(ToolResponseMessage)。

  8. AI 模型使用工具调用结果作为额外上下文生成最终响应,并通过 ChatClient 将其发送回调用方(ChatResponse)。

注意

目前,与模型之间关于工具执行的内部消息未向用户公开。如需访问这些消息,您应采用用户控制的工具执行方式。

工具调用是否具备执行资格的逻辑由 ToolExecutionEligibilityPredicate 接口处理。默认情况下,工具执行资格通过检查 ToolCallingChatOptionsinternalToolExecutionEnabled 属性是否设置为 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 实现。

用户控制的工具执行

在某些情况下,你可能更倾向于自行控制工具执行的生命周期。你可以通过将 ToolCallingChatOptionsinternalToolExecutionEnabled 属性设置为 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 的错误信息会被发送回模型,而受检异常和 Error(例如 IOExceptionOutOfMemoryError)则总是被抛出。DefaultToolExecutionExceptionProcessor 的构造函数允许你将 alwaysThrow 属性设置为 truefalse。如果为 true,则会抛出异常,而不是将错误信息发送回模型。

你可以使用 spring.ai.tools.throw-exception-on-error 属性来控制 DefaultToolExecutionExceptionProcessor bean 的行为:

属性描述默认值
spring.ai.tools.throw-exception-on-error如果为 true,工具调用错误将作为异常抛出,由调用者处理。如果为 false,错误将被转换为消息并发送回 AI 模型,允许其处理并响应错误。false
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
return new DefaultToolExecutionExceptionProcessor(true);
}
备注

如果你定义了自己的 ToolCallback 实现,请确保在 call() 方法的工具执行逻辑中发生错误时,抛出 ToolExecutionException

ToolExecutionExceptionProcessor 由默认的 ToolCallingManagerDefaultToolCallingManager)内部使用,用于处理工具执行过程中的异常。关于工具执行生命周期的更多详细信息,请参阅工具执行

工具解析

将工具传递给模型的主要方法是在调用 ChatClientChatModel 时提供 ToolCallback(回调函数),具体可使用 方法即工具函数即工具 章节中描述的任一策略。

然而,Spring AI 也支持在运行时通过 ToolCallbackResolver 接口动态解析工具。

public interface ToolCallbackResolver {

/**
* Resolve the {@link ToolCallback} for the given tool name.
*/
@Nullable
ToolCallback resolve(String toolName);

}

在使用此方法时:

  • 在客户端,你将工具名称提供给 ChatClientChatModel,而不是 ToolCallback

  • 在服务端,ToolCallbackResolver 的实现负责将工具名称解析为相应的 ToolCallback 实例。

默认情况下,Spring AI 依赖于一个 DelegatingToolCallbackResolver,该解析器将工具解析委托给一系列 ToolCallbackResolver 实例:

  • SpringBeanToolCallbackResolver 从类型为 FunctionSupplierConsumerBiFunction 的 Spring 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));
}

ToolCallbackResolverToolCallingManager 内部使用,用于在运行时动态解析工具,同时支持框架控制的工具执行用户控制的工具执行

可观测性

工具调用包含可观测性支持,通过spring.ai.tool观察点来测量完成时间并传播追踪信息。详情参见工具调用可观测性

可选地,Spring AI可以将工具调用参数和结果导出为跨度属性,出于敏感性考虑默认禁用。详细信息请参阅:工具调用参数与结果数据

日志记录

工具调用功能的所有主要操作均在 DEBUG 级别进行日志记录。您可以通过为 org.springframework.ai 包设置日志级别为 DEBUG 来启用日志记录。