工具调用

工具调用(也称为函数调用)是 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 中工具调用的主要概念和组件。

The main sequence of actions for tool calling
  1. 当我们需要使工具可供模型使用时,我们会在聊天请求中包含其定义。每个工具定义包括名称、描述和输入参数的架构。

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

  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:用于将工具调用结果转换为 String 对象以发送回 AI 模型的 ToolCallResultConverter 实现。有关更多详细信息,请参阅结果转换

该方法可以是静态方法或实例方法,并且可以具有任何可见性(公共、受保护、包私有或私有)。包含该方法的类可以是顶级类或嵌套类,并且也可以具有任何可见性(只要它在您打算实例化它的地方可访问)。

只要包含方法的类是 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 模式

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

当使用声明式规范方法时,您可以通过将工具类实例传递给用于创建 ChatModelToolCallingChatOptions 实例的 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:用于将工具调用结果转换为 String 对象以发送回 AI 模型的 ToolCallResultConverter 实例。如果未提供,将使用默认转换器(DefaultToolCallResultConverter)。

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

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

  • description:工具的描述,模型可以使用它来理解何时以及如何调用工具。如果未提供,方法名称将用作工具描述。但是,强烈建议提供详细描述,因为这对模型理解工具的目的和如何使用至关重要。未能提供好的描述可能导致模型在应该使用工具时没有使用,或者使用不正确。

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

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

该方法可以是静态方法或实例方法,并且可以具有任何可见性(公共、受保护、包私有或私有)。包含该方法的类可以是顶级类或嵌套类,并且也可以具有任何可见性(只要它在您打算实例化它的地方可访问)。

只要包含方法的类是 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 模式

将工具添加到 ChatClientChatModel

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

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 实例传递给用于调用 ChatModelToolCallingChatOptionstoolCallbacks() 方法。该工具将仅对它所添加的特定聊天请求可用。

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

  • 异步类型(例如 CompletableFutureFuture

  • 响应式类型(例如 FlowMonoFlux

  • 函数式类型(例如 FunctionSupplierConsumer)。

函数式类型通过基于函数的工具规范方法受支持。有关更多详细信息,请参阅函数作为工具

函数作为工具

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

当使用编程规范方法时,您可以通过将 FunctionToolCallback 实例传递给用于创建 ChatModelToolCallingChatOptions 实例的 toolCallbacks() 方法来在构建时向 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 模式

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

当使用动态规范方法时,您可以将工具名称传递给用于调用 ChatModelToolCallingChatOptionstoolNames() 方法。该工具将仅对它所添加的特定聊天请求可用。

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 模式

当向 AI 模型提供工具时,模型需要知道用于调用工具的输入类型的模式。该模式用于理解如何调用工具并准备工具请求。Spring AI 通过 JsonSchemaGenerator 类提供了内置支持,用于为工具生成输入类型的 JSON 模式。该模式作为 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 序列化为 JSON(DefaultToolCallResultConverter),但您可以通过提供自己的 ToolCallResultConverter 实现来自定义序列化过程。

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

方法工具调用结果转换

当使用声明式方法从方法构建工具时,您可以通过设置 @Tool 注解的 resultConverter() 属性来为该工具提供自定义的 ToolCallResultConverter

class CustomerTools {

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

}

如果使用编程方法,您可以通过设置 MethodToolCallback.BuilderresultConverter() 属性来为工具提供自定义的 ToolCallResultConverter

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

函数工具调用结果转换

当使用编程方法从函数构建工具时,您可以通过设置 FunctionToolCallback.BuilderresultConverter() 属性来为工具提供自定义的 ToolCallResultConverter

有关更多详细信息,请参阅函数作为工具

工具上下文

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

Providing additional contextual info to tools
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 才能将结果直接返回给调用者。否则,结果将发送回模型。
Returning tool call results directly to the caller
  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,DefaultToolCallingManagerToolCallingManager 接口的自动配置实现。您可以通过提供自己的 ToolCallingManager bean 来定制工具执行行为。

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

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

框架控制的工具执行

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

Framework-controlled tool execution lifecycle
  1. 当我们需要使工具可供模型使用时,我们会在聊天请求 (Prompt) 中包含其定义并调用 ChatModel API,该 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 Starter,DefaultToolExecutionExceptionProcessorToolExecutionExceptionProcessor 接口的自动配置实现。默认情况下,RuntimeException 的错误消息会发送回模型,而检查异常和错误(例如 IOExceptionOutOfMemoryError)总是抛出。DefaultToolExecutionExceptionProcessor 构造函数允许您将 alwaysThrow 属性设置为 truefalse。如果为 true,则会抛出异常,而不是将错误消息发送回模型。

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

财产 描述 默认值

spring.ai.tools.throw-exception-on-error

如果为 true,则工具调用错误将作为异常抛出,由调用者处理。如果为 false,则错误将转换为消息并发送回 AI 模型,允许其处理并响应错误。

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

}

当使用此方法时:

  • 在客户端,您将工具名称而不是 ToolCallback 传递给 ChatClientChatModel

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

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

  • SpringBeanToolCallbackResolver 从类型为 FunctionSupplierConsumerBiFunction 的 Spring bean 解析工具。有关更多详细信息,请参阅动态规范:@Bean

  • StaticToolCallbackResolverToolCallback 实例的静态列表解析工具。当使用 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 来启用日志记录。

© . This site is unofficial and not affiliated with VMware.