工具调用

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

工具主要用于:

  • 信息检索。此类工具可用于从外部源(如数据库、Web 服务、文件系统或 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 中工具调用的主要概念和组件的概述。

工具调用的主要操作序列
  1. 当我们想要使工具对模型可用时,我们在聊天请求中包含其定义。每个工具定义包括名称、描述和输入参数的架构。

  2. 当模型决定调用工具时,它会发送一个包含工具名称和按照定义架构建模的输入参数的响应。

  3. 应用程序负责使用工具名称来识别和执行具有提供的输入参数的工具。

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

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

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

工具是工具调用的构建块,它们由 ToolCallback 接口建模。Spring AI 提供了内置支持,用于从方法和函数指定 ToolCallback(s),但你始终可以定义自己的 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:工具结果是否应该直接返回给客户端或传回给模型。有关详细信息,请参见 Return Direct。

  • resultConverter:用于将工具调用结果转换为要发送回 AI 模型的 String 对象的 ToolCallResultConverter 实现。有关详细信息,请参见 Result Conversion。

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

注意:只要包含方法的类是 Spring bean(例如 @Component),Spring AI 就提供对 @Tool 注解方法的 AOT 编译的内置支持。否则,你需要向 GraalVM 编译器提供必要的配置。例如,通过用 @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_METHODS) 注解类。

你可以为方法定义任意数量的参数(包括无参数),支持大多数类型(基本类型、POJO、枚举、列表、数组、映射等)。同样,该方法可以返回大多数类型,包括 void。如果该方法返回值,则返回类型必须是可序列化类型,因为结果将被序列化并发送回模型。

注意:某些类型不受支持。有关详细信息,请参见 [_method_tool_limitations]。

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(s),可以使用 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 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:结果是否应该直接返回给客户端或传回给模型。有关详细信息,请参见 Return Direct。

Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolCallback toolCallback = MethodToolCallback.builder()
    .toolDefinition(ToolDefinition.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。如果该方法返回值,则返回类型必须是可序列化类型,因为结果将被序列化并发送回模型。

注意:某些类型不受支持。有关详细信息,请参见 [_method_tool_limitations]。

如果方法是静态的,你可以省略 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(ToolDefinition.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 实例传递给 tools() 方法来向 ChatClient 添加工具。 该工具仅对添加它们的特定聊天请求可用。

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

向 ChatClient 添加默认工具

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

警告:默认工具在所有由同一 ChatClient.Builder 构建的所有 ChatClient 实例执行的所有聊天请求之间共享。它们对于在不同聊天请求之间常用的工具很有用,但如果使用不当,也可能很危险,可能会在不应该时使它们可用。

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

向 ChatModel 添加工具

使用编程式规范方法时,你可以通过将 MethodToolCallback 实例传递给用于调用 ChatModel 的 ToolCallingChatOptions 的 toolCallbacks() 方法来向 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 实例传递给用于创建 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)。

功能类型支持使用基于函数的工具规范方法。有关详细信息,请参见 [_functions_as_tools]。

函数作为工具

Spring AI 提供了内置支持,用于从函数指定工具,无论是通过低级 FunctionToolCallback 实现程序化还是动态地作为 @Bean(s) 在运行时解析。

编程式规范: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:结果是否应该直接返回给客户端或传回给模型。有关详细信息,请参见 Return Direct。

ToolCallback toolCallback = FunctionToolCallback
    .builder("currentWeather", new WeatherService())
    .description("Get the weather in location")
    .inputType(WeatherRequest.class)
    .build();

函数输入和输出可以是 Void 或 POJO。输入和输出 POJO 必须是可序列化的,因为结果将被序列化并发送回模型。函数以及输入和输出类型必须是公共的。

注意:某些类型不受支持。有关详细信息,请参见 [_function_tool_limitations]。

向 ChatClient 添加工具

使用编程式规范方法时,你可以通过将 FunctionToolCallback 实例传递给 tools() 方法来向 ChatClient 添加工具。该工具仅对添加它们的特定聊天请求可用。

ToolCallback toolCallback = ...
ChatClient.create(chatModel)
    .prompt("What's the weather like in Copenhagen?")
    .tools(toolCallback)
    .call()
    .content();

向 ChatClient 添加默认工具

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

警告:默认工具在所有由同一 ChatClient.Builder 构建的所有 ChatClient 实例执行的所有聊天请求之间共享。它们对于在不同聊天请求之间常用的工具很有用,但如果使用不当,也可能很危险,可能会在不应该时使它们可用。

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

向 ChatModel 添加工具

使用编程式规范方法时,你可以通过将 FunctionToolCallback 实例传递给用于调用 ChatModel 的 ToolCallingChatOptions 的 toolCallbacks() 方法来向 ChatModel 添加工具。该工具仅对添加它们的特定聊天请求可用。

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;
	}

}

注意:某些类型不受支持。有关详细信息,请参见 [_function_tool_limitations]。

工具的输入参数的 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 名称)传递给 tools() 方法来向 ChatClient 添加工具。 该工具仅对添加它们的特定聊天请求可用。

ChatClient.create(chatModel)
    .prompt("What's the weather like in Copenhagen?")
    .tools("currentWeather")
    .call()
    .content();

向 ChatClient 添加默认工具

使用动态规范方法时,你可以通过将工具名称传递给 defaultTools() 方法来向 ChatClient.Builder 添加默认工具。 如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。

警告:默认工具在所有由同一 ChatClient.Builder 构建的所有 ChatClient 实例执行的所有聊天请求之间共享。它们对于在不同聊天请求之间常用的工具很有用,但如果使用不当,也可能很危险,可能会在不应该时使它们可用。

ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools("currentWeather")
    .build();

向 ChatModel 添加工具

使用动态规范方法时,你可以通过将工具名称传递给用于调用 ChatModel 的 ToolCallingChatOptions 的 toolNames() 方法来向 ChatModel 添加工具。该工具仅对添加它们的特定聊天请求可用。

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)。

基本类型和集合支持使用方法基于工具规范方法。有关详细信息,请参见 [_methods_as_tools]。

工具规范

在 Spring AI 中,工具通过 ToolCallback 接口建模。在前面的部分中,我们已经看到了如何使用 Spring AI 提供的内置支持从方法和函数指定工具(请参见 [_methods_as_tools] 和 [_functions_as_tools])。本节将深入探讨工具规范,以及如何自定义和扩展它以支持更多用例。

Tool Callback

ToolCallback 接口提供了一种方法来定义可以由 AI 模型调用的工具,包括定义和执行逻辑。它是当你想要从头定义工具时需要实现的主要接口。例如,你可以通过 MCP Client(使用模型上下文协议)或 ChatClient(构建模块代理应用程序)定义 ToolCallback。

该接口提供了以下方法:

public interface ToolCallback {

	/**
	 * 用于确定 AI 模型何时以及如何调用工具的定义。
	 */
	ToolDefinition getToolDefinition();

	/**
	 * 提供有关如何处理工具的附加信息。
	 */
	ToolMetadata getToolMetadata();

    /**
	 * 执行工具并返回结果以发送回 AI 模型。
	 */
	String call(String toolInput);

    /**
	 * 执行工具并返回结果以发送回 AI 模型。
	 */
	String call(String toolInput, ToolContext tooContext);

}

Spring AI 提供了内置实现工具方法(MethodToolCallback)和工具函数(FunctionToolCallback)。

Tool Definition

ToolDefinition 接口提供了 AI 模型了解工具可用性的必要信息,包括工具名称、描述和输入架构。每个 ToolCallback 实现必须提供 ToolDefinition 实例来定义工具。

该接口提供了以下方法:

public interface ToolDefinition {

	/**
	 * 工具名称。在提供给模型的工具集中是唯一的。
	 */
	String name();

	/**
	 * 用于确定 AI 模型工具的作用的工具描述。
	 */
	String description();

	/**
	 * 调用工具时使用的参数架构。
	 */
	String inputSchema();

}

注意:有关输入架构的更多详细信息,请参见 JSON Schema。

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();

Method Tool Definition

当从方法构建工具时,ToolDefinition 是自动生成的。如果你希望自己生成 ToolDefinition,可以使用此方便的构建器。

Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinition.from(method);

从方法生成的 ToolDefinition 包括方法名称作为工具名称、方法名称作为工具描述以及方法输入参数的 JSON 架构。如果方法被注解为 @Tool,工具名称和描述将从注解中获取。

注意:有关详细信息,请参见 [_methods_as_tools]。

如果你宁愿提供一些或所有属性显式地,你可以使用 ToolDefinition.Builder 来构建自定义 ToolDefinition 实例。

Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinition.builder(method)
    .name("currentDateTime")
    .description("Get the current date and time in the user's timezone")
    .inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
    .build();

Function Tool Definition

当从函数构建工具时,ToolDefinition 是自动生成的。当你使用 FunctionToolCallback.Builder 来构建 FunctionToolCallback 实例时,你可以提供工具名称、描述和输入架构,这些架构将用于生成 ToolDefinition。有关详细信息,请参见 [_functions_as_tools]。

JSON Schema

提供工具给 AI 模型时,模型需要知道工具的输入类型架构,以便理解如何调用工具和准备工具请求。Spring AI 提供了内置支持,用于通过 JsonSchemaGenerator 类生成工具的输入类型架构。架构作为 ToolDefinition 的一部分提供。

注意:有关 ToolDefinition 和如何传递输入架构的更多详细信息,请参见 Tool Definition。

JsonSchemaGenerator 类用于生成方法或函数的输入参数的 JSON 架构,使用 [_methods_as_tools] 和 [_functions_as_tools] 中描述的策略系列。JSON 架构生成逻辑支持一系列注解,你可以将这些注解用于方法和函数的输入参数,以自定义生成的架构。

本节描述了两个主要选项,你可以自定义这些选项,以生成工具输入参数的 JSON 架构:描述和必需状态。

Description

除了提供工具本身的描述外,还可以为工具的输入参数提供描述。描述可以用于提供有关输入参数的关键信息,例如参数应该采用什么格式、允许什么值等。这对于帮助模型理解输入架构和如何使用它非常有用。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);
    }

}

Required/Optional

默认情况下,每个输入参数都被视为必需的,这迫使 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 参数是可选的,这意味着模型可以调用工具而不提供值。如果参数是必需的,模型必须提供值。如果值不存在,模型可能会制作一个,导致幻觉。

Result Conversion

工具调用的结果使用 ToolCallResultConverter 序列化并发送回 AI 模型。ToolCallResultConverter 接口提供了一种方法,用于将工具调用结果转换为 String 对象。

该接口提供了以下方法:

@FunctionalInterface
public interface ToolCallResultConverter {

	/**
	 * 给定一个由工具返回的对象,将其转换为与给定类类型兼容的字符串。
	 */
	String convert(@Nullable Object result, @Nullable Type returnType);

}

结果必须是可序列化类型。默认情况下,结果序列化为 JSON 使用 Jackson(DefaultToolCallResultConverter),但你可以自定义序列化过程,通过提供自己的 ToolCallResultConverter 实现。

Spring AI 在方法和函数工具中依赖于 ToolCallResultConverter。

Method Tool Call Result Conversion

当使用声明式方法构建工具时,你可以通过设置 resultConverter() 属性来为工具提供自定义 ToolCallResultConverter。

class CustomerTools {

    @Tool(description = "Retrieve customer information", resultConverter = CustomToolCallResultConverter.class)
    Customer getCustomerInfo(Long id) {
        return customerRepository.findById(id);
    }

}

如果使用编程方法,你可以通过设置 resultConverter() 属性来为工具提供自定义 ToolCallResultConverter。

有关详细信息,请参见 [_methods_as_tools]。

Function Tool Call Result Conversion

当使用编程方法构建工具时,你可以通过设置 resultConverter() 属性来为工具提供自定义 ToolCallResultConverter。

有关详细信息,请参见 [_functions_as_tools]。

Tool Context

Spring AI 支持通过 ToolContext API 将附加上下文信息传递给工具。此功能允许你提供额外的、用户提供的数据,这些数据可以在工具执行过程中与工具参数传递的 AI 模型一起使用。

提供附加上下文信息给工具
class CustomerTools {

    @Tool(description = "Retrieve customer information")
    Customer getCustomerInfo(Long id, ToolContext toolContext) {
        return customerRepository.findById(id, toolContext.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 将是两个的合并, 其中运行时选项优先于默认选项。

Return Direct

默认情况下,工具调用结果作为响应发送回模型。然后,模型可以使用结果继续对话。

有些情况下,你宁愿直接将结果返回给调用者,而不是发送回模型。例如,如果你构建一个依赖于 RAG 工具的代理,你可能希望直接将结果返回给调用者,而不是发送回模型进行不必要的后处理。或者也许你有某些工具应该结束代理的推理循环。

每个 ToolCallback 实现可以定义工具调用结果是否应该直接返回给调用者或发送回模型。默认情况下,结果发送回模型。但你可以更改此行为每个工具。

ToolCallingManager,负责管理工具执行生命周期,负责处理与工具相关的 returnDirect 属性。如果属性设置为 true,则工具调用结果直接返回给调用者。否则,结果发送回模型。

注意:如果一次请求多个工具调用,则必须将 returnDirect 属性设置为 true 才能返回结果给调用者。否则,结果将发送回模型。

直接返回工具调用结果给调用者
  1. 当我们想要使工具对模型可用时,我们在聊天请求中包含其定义。如果我们要工具执行结果直接返回给调用者,我们将 returnDirect 属性设置为 true。

  2. 当模型决定调用工具时,它会发送一个包含工具名称和按照定义架构建模的输入参数的响应。

  3. 应用程序负责使用工具名称来识别和执行具有提供的输入参数的工具。

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

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

Method Return Direct

当使用声明式方法构建工具时,你可以通过设置 returnDirect 属性来将工具标记为直接返回结果给调用者。

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();

有关详细信息,请参见 [_methods_as_tools]。

Function Return Direct

当使用编程方法构建工具时,你可以通过设置 returnDirect 属性到 ToolMetadata 接口并传递给 FunctionToolCallback.Builder。

ToolMetadata toolMetadata = ToolMetadata.builder()
    .returnDirect(true)
    .build();

有关详细信息,请参见 [_functions_as_tools]。

工具执行

工具执行是调用工具并返回结果的过程。工具执行由 ToolCallingManager 接口处理,该接口负责管理工具执行生命周期。

public interface ToolCallingManager {

	/**
	 * 解析模型工具调用选项中的工具定义。
	 */
	List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);

	/**
	 * 执行模型请求的工具调用。
	 */
	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 实现透明地管理工具执行生命周期。但你可以选择退出此行为并控制工具执行。本节描述了这两种情况。

Framework-Controlled Tool Execution

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

Framework-controlled tool execution lifecycle
  1. 当我们想要使工具对模型可用时,我们在聊天请求中包含其定义。如果我们要工具执行结果直接返回给调用者,我们将 returnDirect 属性设置为 true。

  2. 当模型决定调用工具时,它会发送一个包含工具名称和按照定义架构建模的输入参数的响应。

  3. 应用程序负责使用工具名称来识别和执行具有提供的输入参数的工具。

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

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

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

警告:目前,与模型相关的工具执行内部消息交换不会暴露给用户。如果你需要访问这些消息,你应该使用用户控制的工具执行方法。

工具执行资格判断逻辑由 ToolExecutionEligibilityPredicate 接口处理。默认情况下,工具执行资格由检查 internalToolExecutionEnabled 属性是否设置为 true(默认值),以及 ChatResponse 是否包含任何工具调用来确定。

public class DefaultToolExecutionEligibilityPredicate implements ToolExecutionEligibilityPredicate {

	@Override
	public boolean test(ChatOptions promptOptions, ChatResponse chatResponse) {
		return ToolCallingChatOptions.isInternalToolExecutionEnabled(promptOptions) && chatResponse != null
				&& chatResponse.hasToolCalls();
	}

}

你可以提供自定义的 ToolExecutionEligibilityPredicate 实现,当你创建 ChatModel bean 时。

User-Controlled Tool Execution

有些情况下,你宁愿控制工具执行生命周期。你可以通过将 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)));

Exception Handling

当工具调用失败时,异常作为 ToolExecutionException 传播,可以捕获以处理错误。可以使用 ToolExecutionExceptionProcessor 来处理 ToolExecutionException 有两个结果:要么生成错误消息以发送回 AI 模型,要么抛出异常以由调用者处理。

@FunctionalInterface
public interface ToolExecutionExceptionProcessor {

	/**
	 * 将工具抛出的异常转换为可以发送回 AI 模型的字符串,或者抛出异常以由调用者处理。
	 */
	String process(ToolExecutionException exception);

}

如果你使用任何 Spring AI Spring Boot Starters,DefaultToolExecutionExceptionProcessor 是 ToolExecutionExceptionProcessor 接口的自动配置实现。默认情况下,错误消息发送回模型。DefaultToolExecutionExceptionProcessor 构造函数允许你将 alwaysThrow 属性设置为 true 或 false。如果 true,则抛出异常而不是发送错误消息回模型。

@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
    return new DefaultToolExecutionExceptionProcessor(true);
}

注意:如果你定义了自己的 ToolCallback 实现,请在 call() 方法的工具执行逻辑中抛出 ToolExecutionException 作为工具执行逻辑的一部分。

ToolExecutionExceptionProcessor 用于内部默认 ToolCallingManager(DefaultToolCallingManager)处理工具执行期间的异常。有关工具执行生命周期的更多详细信息,请参见 [_tool_execution]。

工具解析

传递工具到模型的主要方法是通过在调用 ChatClient 或 ChatModel 时提供 ToolCallback(s), 使用 [_methods_as_tools] 和 [_functions_as_tools] 中描述的策略之一。

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

public interface ToolCallbackResolver {

	/**
	 * 解析给定工具名称的 {@link ToolCallback}。
	 */
	@Nullable
	ToolCallback resolve(String toolName);

}

当使用此方法时:

  • 在客户端端,你提供工具名称而不是 ToolCallback(s) 给 ChatClient 或 ChatModel。

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

默认情况下,Spring AI 依赖于 DelegatingToolCallbackResolver,它将解析委托给一组 ToolCallbackResolver 实例:

  • SpringBeanToolCallbackResolver 解析从 Spring beans 类型为 Function、Supplier、Consumer 或 BiFunction 的工具。有关详细信息,请参见 [_dynamic_specification_bean]。

  • StaticToolCallbackResolver 解析从静态 ToolCallback 实例列表中解析工具。当使用 Spring Boot Autoconfiguration 时,此解析器自动配置为应用程序上下文中所有类型为 ToolCallback 的 bean。

如果你依赖 Spring Boot Autoconfiguration,你可以自定义解析逻辑,通过提供自定义 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。

Observability

工具调用包括 spring.ai.tool 观察支持,spring.ai.tool 观察测量完成时间和传播跟踪信息。有关详细信息,请参见 Tool Calling Observability。

可选地,Spring AI 可以将工具调用参数和结果作为跨度属性导出,默认情况下为敏感原因禁用。详细信息:Tool Call Arguments and Result Data。

Logging

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