收藏本站,收获最前沿的人工智能与编程资讯!!

Spring AI 工具调用

技术文档 13℃ 0

工具调用(也称为函数调用)是人工智能应用中的一种常用模式,允许模型与一组应用程序接口或工具进行交互,从而增强其能力。

工具主要用于:

信息检索

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

执行操作

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

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

Spring AI 提供便捷的应用程序接口来定义工具、解析来自模型的工具调用请求并执行工具调用。以下章节概述了 Spring AI 中的工具调用功能。

查看聊天模型对比以了解哪些人工智能模型支持工具调用。

按照指南从已弃用的 FunctionCallback 迁移到 ToolCallback 应用程序接口。

快速开始

让我们看看如何在 Spring AI 中开始使用工具调用。我们将实现两个简单的工具:一个用于信息检索,一个用于执行操作。信息检索工具将用于获取用户时区的当前日期和时间。操作工具将用于为指定时间设置闹钟。

信息检索

人工智能模型无法访问实时信息。任何需要了解当前日期或天气预报等信息的问题,模型都无法直接回答。但是,我们可以提供一个能够检索此类信息的工具,并让模型在需要访问实时信息时调用该工具。

让我们在 DateTimeTools 类中实现一个获取用户时区当前日期和时间的工具。该工具无需参数。Spring 框架的 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.

没有该工具,模型不知道如何回答问题,因为它无法确定当前日期和时间。

执行操作

人工智能模型可用于生成实现特定目标的计划。例如,模型可以生成前往丹麦旅行的预订计划。但是,模型没有执行该计划的能力。这就是工具的用武之地:它们可用于执行模型生成的计划。

在上一个示例中,我们使用了一个工具来确定当前日期和时间。在本例中,我们将定义第二个工具,用于在特定时间设置闹钟。目标是设置10分钟后的闹钟,因此我们需要为模型提供这两个工具来完成此任务。

我们将新工具添加到之前的同一个 DateTimeTools 类中。新工具将接受单个参数,即 ISO-8601 格式的时间。然后,该工具将在控制台打印一条消息,指示已为给定时间设置闹钟。与之前一样,该工具定义为带有 @Tool 注解的方法,我们还使用该注解提供详细描述,帮助模型理解何时以及如何使用该工具。

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;

class DateTimeTools {

    @Tool(description = "Get the current date and time in the user's timezone")
    String getCurrentDateTime() {
        return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
    }

    @Tool(description = "Set a user alarm for the given time, provided in ISO-8601 format")
    void setAlarm(String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("Alarm set for " + alarmTime);
    }

}

接下来,让模型可以使用这两个工具。我们将使用 ChatClient 与模型交互。我们将通过 tools() 方法传递 DateTimeTools 实例,为模型提供这些工具。当我们要求设置10分钟后的闹钟时,模型首先需要知道当前日期和时间。然后,它将使用当前日期和时间计算闹钟时间。最后,它将使用闹钟工具设置闹钟。在内部,ChatClient 将处理来自模型的任何工具调用请求,并将任何工具调用执行结果发送回模型,以便模型生成最终响应。

ChatModel chatModel = ...

String response = ChatClient.create(chatModel)
        .prompt("Can you set an alarm 10 minutes from now?")
        .tools(new DateTimeTools())
        .call()
        .content();

System.out.println(response);

在应用程序日志中,你可以检查闹钟是否已设置在正确的时间。

概述

Spring AI 通过一组灵活的抽象层支持工具调用,允许你以一致的方式定义、解析和执行工具。本节概述了 Spring AI 中工具调用的主要概念和组件。

工具调用的主要操作序列

当我们希望模型可以使用某个工具时,我们将其定义包含在聊天请求中。每个工具定义包括名称、描述和输入参数的模式。

当模型决定调用工具时,它会发送一个包含工具名称和遵循定义模式的输入参数的响应。

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

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

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

模型使用工具调用结果作为附加上下文生成最终响应。

工具是工具调用的构建块,它们由 ToolCallback 接口建模。Spring AI 内置支持从方法和函数指定 ToolCallback,但你始终可以定义自己的 ToolCallback 实现以支持更多用例。

ChatModel 实现透明地将工具调用请求分派到相应的 ToolCallback 实现,并将工具调用结果发送回模型,最终由模型生成最终响应。它们使用 ToolCallingManager 接口完成此操作,该接口负责管理工具执行生命周期。

ChatClient 和 ChatModel 都接受 ToolCallback 对象列表,使模型可以使用这些工具,并接受最终执行它们的 ToolCallingManager。

除了直接传递 ToolCallback 对象外,你还可以传递工具名称列表,这些名称将使用 ToolCallbackResolver 接口动态解析。

以下章节将详细介绍所有这些概念和应用程序接口,包括如何自定义和扩展它们以支持更多用例。

方法作为工具

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:工具的名称。如果未提供,将使用方法名。人工智能模型在调用工具时使用此名称识别工具。因此,同一个类中不允许有两个同名工具。对于特定的聊天请求,该名称在模型可用的所有工具中必须唯一。

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

returnDirect:工具结果是否应直接返回给客户端还是传回给模型。

resultConverter:用于将工具调用结果转换为字符串对象以发送回人工智能模型的 ToolCallResultConverter 实现。

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

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

你可以为方法定义任意数量的参数(包括无参数),支持大多数类型(基本类型、普通Java对象、枚举、列表、数组、映射等)。同样,该方法可以返回大多数类型,包括 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。

向 ChatClient 添加工具

使用声明式指定方法时,你可以在调用 ChatClient 时将工具类实例传递给 tools() 方法。此类工具仅对添加它们的特定聊天请求可用。

ChatClient.create(chatModel)
    .prompt("What day is tomorrow?")
    .tools(new DateTimeTools())
    .call()
    .content();

在底层,ChatClient 将从工具类实例中的每个 @Tool 注解方法生成 ToolCallback,并将它们传递给模型。如果你更喜欢自己生成 ToolCallback,可以使用 ToolCallbacks 工具类。

ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());

向 ChatClient 添加默认工具

使用声明式指定方法时,你可以通过将工具类实例传递给 defaultTools() 方法,向 ChatClient.Builder 添加默认工具。如果同时提供默认工具和运行时工具,运行时工具将完全覆盖默认工具。

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

ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools(new DateTimeTools())
    .build();

向 ChatModel 添加工具

使用声明式指定方法时,你可以将工具类实例传递给用于调用 ChatModel 的 ToolCallingChatOptions 的 toolCallbacks() 方法。此类工具仅对添加它们的特定聊天请求可用。

ChatModel chatModel = ...
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolCallbacks(dateTimeTools)
    .build();
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);

向 ChatModel 添加默认工具

使用声明式指定方法时,你可以在构造 ChatModel 时,将工具类实例传递给用于创建 ChatModel 的 ToolCallingChatOptions 实例的 toolCallbacks() 方法,从而添加默认工具。如果同时提供默认工具和运行时工具,运行时工具将完全覆盖默认工具。

默认工具在该 ChatModel 实例执行的所有聊天请求中共享。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用也可能有风险,可能会在不应该时使工具可用。

ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatModel chatModel = OllamaChatModel.builder()
    .ollamaApi(OllamaApi.builder().build())
    .defaultOptions(ToolCallingChatOptions.builder()
            .toolCallbacks(dateTimeTools)
            .build())
    .build();

编程式指定:MethodToolCallback

你可以通过编程方式构建 MethodToolCallback,将方法转换为工具。

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

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

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

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

inputSchema:工具输入参数的 JSON 模式。如果未提供,将根据方法参数自动生成模式。你可以使用 @ToolParam 注解提供有关输入参数的附加信息,例如描述或参数是否为必填项。默认情况下,所有输入参数都被视为必填项。

ToolMetadata.Builder 允许你构建 ToolMetadata 实例并定义工具的附加设置:

returnDirect:工具结果是否应直接返回给客户端还是传回给模型。

Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolCallback toolCallback = MethodToolCallback.builder()
    .toolDefinition(ToolDefinitions.builder(method)
            .description("Get the current date and time in the user's timezone")
            .build())
    .toolMethod(method)
    .toolObject(new DateTimeTools())
    .build();

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

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

你可以为方法定义任意数量的参数(包括无参数),支持大多数类型(基本类型、普通Java对象、枚举、列表、数组、映射等)。同样,该方法可以返回大多数类型,包括 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。

向 ChatClient 和 ChatModel 添加工具

使用编程式指定方法时,你可以将 MethodToolCallback 实例传递给 ChatClient 的 toolCallbacks() 方法。该工具仅对添加它的特定聊天请求可用。

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

向 ChatClient 添加默认工具

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

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

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

向 ChatModel 添加工具

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

ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolCallbacks(toolCallback)
    .build();
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);

向 ChatModel 添加默认工具

使用编程式指定方法时,你可以在构造 ChatModel 时,将 MethodToolCallback 实例传递给用于创建 ChatModel 的 ToolCallingChatOptions 实例的 toolCallbacks() 方法,从而添加默认工具。如果同时提供默认工具和运行时工具,运行时工具将完全覆盖默认工具。

默认工具在该 ChatModel 实例执行的所有聊天请求中共享。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用也可能有风险,可能会在不应该时使工具可用。

ToolCallback toolCallback = ...
ChatModel chatModel = OllamaChatModel.builder()
    .ollamaApi(OllamaApi.builder().build())
    .defaultOptions(ToolCallingChatOptions.builder()
            .toolCallbacks(toolCallback)
            .build())
    .build();

方法工具限制

以下类型目前不支持作为工具方法的参数或返回类型:

Optional

异步类型(例如 CompletableFuture、Future)

响应式类型(例如 Flow、Mono、Flux)

函数类型(例如 Function、Supplier、Consumer)。

函数类型使用基于函数的工具指定方法支持。

函数作为工具

Spring AI 内置支持从函数指定工具,既可以通过底层的 FunctionToolCallback 实现以编程方式指定,也可以作为运行时解析的 @Bean 动态指定。

编程式指定:FunctionToolCallback

你可以通过编程方式构建 FunctionToolCallback,将函数类型(Function、Supplier、Consumer 或 BiFunction)转换为工具。

public class WeatherService implements Function{
    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:工具的名称。人工智能模型在调用工具时使用此名称识别工具。因此,同一上下文中不允许有两个同名工具。对于特定的聊天请求,该名称在模型可用的所有工具中必须唯一。必填项。

toolFunction:表示工具方法的函数对象(Function、Supplier、Consumer 或 BiFunction)。必填项。

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

inputType:函数输入的类型。必填项。

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

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

toolCallResultConverter:用于将工具调用结果转换为字符串对象以发送回人工智能模型的 ToolCallResultConverter 实例。如果未提供,将使用默认转换器(DefaultToolCallResultConverter)。

ToolMetadata.Builder 允许你构建 ToolMetadata 实例并定义工具的附加设置:

returnDirect:工具结果是否应直接返回给客户端还是传回给模型。

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

函数的输入和输出可以是 Void 或普通Java对象。输入和输出普通Java对象必须是可序列化的,因为结果将被序列化并发送回模型。函数以及输入和输出类型必须是 public。

某些类型不受支持。

向 ChatClient 添加工具

使用编程式指定方法时,你可以将 FunctionToolCallback 实例传递给 ChatClient 的 toolCallbacks() 方法。该工具仅对添加它的特定聊天请求可用。

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

向 ChatClient 添加默认工具

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

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

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

向 ChatModel 添加工具

使用编程式指定方法时,你可以将 FunctionToolCallback 实例传递给 ToolCallingChatOptions 的 toolCallbacks() 方法。该工具仅对添加它的特定聊天请求可用。

ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolCallbacks(toolCallback)
    .build();
Prompt prompt = new Prompt("What's the weather like in Copenhagen?", chatOptions);
chatModel.call(prompt);

向 ChatModel 添加默认工具

使用编程式指定方法时,你可以在构造 ChatModel 时,将 FunctionToolCallback 实例传递给用于创建 ChatModel 的 ToolCallingChatOptions 实例的 toolCallbacks() 方法,从而添加默认工具。如果同时提供默认工具和运行时工具,运行时工具将完全覆盖默认工具。

默认工具在该 ChatModel 实例执行的所有聊天请求中共享。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用也可能有风险,可能会在不应该时使工具可用。

ToolCallback toolCallback = ...
ChatModel chatModel = OllamaChatModel.builder()
    .ollamaApi(OllamaApi.builder().build())
    .defaultOptions(ToolCallingChatOptions.builder()
            .toolCallbacks(toolCallback)
            .build())
    .build();

动态指定:@Bean

除了以编程方式指定工具外,你还可以将工具定义为 Spring bean,并让 Spring AI 在运行时使用 ToolCallbackResolver 接口(通过 SpringBeanToolCallbackResolver 实现)动态解析它们。此选项允许你将任何 Function、Supplier、Consumer 或 BiFunction bean 用作工具。bean 名称将用作工具名称,Spring 框架的 @Description 注解可用于为工具提供描述,模型使用该描述理解何时以及如何调用工具。如果你不提供描述,将使用方法名作为工具描述。但是,强烈建议提供详细描述,因为这对于模型理解工具的用途和使用方法至关重要。未能提供良好的描述可能导致模型在应该使用工具时不使用或错误使用。

@Configuration(proxyBeanMethods = false)
class WeatherTools {

    WeatherService weatherService = new WeatherService();

	@Bean
	@Description("Get the weather in location")
	FunctioncurrentWeather() {
		return weatherService;
	}

}

某些类型不受支持。

工具输入参数的 JSON 模式将自动生成。你可以使用 @ToolParam 注解提供有关输入参数的附加信息,例如描述或参数是否为必填项。默认情况下,所有输入参数都被视为必填项。

record WeatherRequest(@ToolParam(description = "The name of a city or a country") String location, Unit unit) {}

这种工具指定方法的缺点是不能保证类型安全,因为工具解析是在运行时完成的。为了缓解这个问题,你可以使用 @Bean 注解显式指定工具名称并将值存储在常量中,这样你就可以在聊天请求中使用它,而不是硬编码工具名称。

@Configuration(proxyBeanMethods = false)
class WeatherTools {

    public static final String CURRENT_WEATHER_TOOL = "currentWeather";

	@Bean(CURRENT_WEATHER_TOOL)
	@Description("Get the weather in location")
	FunctioncurrentWeather() {
		...
	}

}

向 ChatClient 添加工具

使用动态指定方法时,你可以将工具名称(即函数 bean 名称)传递给 ChatClient 的 toolNames() 方法。该工具仅对添加它的特定聊天请求可用。

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

向 ChatClient 添加默认工具

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

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

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

向 ChatModel 添加工具

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

ChatModel chatModel = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolNames("currentWeather")
    .build();
Prompt prompt = new Prompt("What's the weather like in Copenhagen?", chatOptions);
chatModel.call(prompt);

向 ChatModel 添加默认工具

使用动态指定方法时,你可以在构造 ChatModel 时,将工具名称传递给用于创建 ChatModel 的 ToolCallingChatOptions 实例的 toolNames() 方法,从而添加默认工具。如果同时提供默认工具和运行时工具,运行时工具将完全覆盖默认工具。

默认工具在该 ChatModel 实例执行的所有聊天请求中共享。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用也可能有风险,可能会在不应该时使工具可用。

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

函数工具限制

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

基本类型

Optional

集合类型(例如 List、Map、Array、Set)

异步类型(例如 CompletableFuture、Future)

响应式类型(例如 Flow、Mono、Flux)。

基本类型和集合使用基于方法的工具指定方法支持。

工具规范

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

工具回调

ToolCallback 接口提供了一种定义可被人工智能模型调用的工具的方法,包括定义和执行逻辑。当你想从头开始定义工具时,它是要实现的主要接口。例如,你可以从 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 接口为人工智能模型提供了解工具可用性所需的信息,包括工具名称、描述和输入模式。每个 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();

}

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

向人工智能模型提供工具时,模型需要知道调用工具的输入类型模式。该模式用于理解如何调用工具并准备工具请求。Spring AI 内置支持通过 JsonSchemaGenerator 类为工具的输入类型生成 JSON 模式。该模式作为 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);
    }

}

必填/可选

默认情况下,每个输入参数都被视为必填项,这迫使人工智能模型在调用工具时为其提供值。但是,你可以使用以下注解之一使输入参数成为可选项,优先级按此顺序:

来自 Spring AI 的 @ToolParam(required = false)

来自 Jackson 的 @JsonProperty(required = false)

来自 Swagger 的 @Schema(required = false)

来自 Spring 框架的 @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 序列化,然后发送回人工智能模型。ToolCallResultConverter 接口提供了一种将工具调用结果转换为字符串对象的方法。

该接口提供以下方法:

@FunctionalInterface
public interface ToolCallResultConverter {

	/**
	 * Given an Object returned by a tool, convert it to a String compatible with the
	 * given class type.
	 */
	String convert(@Nullable Object result, @Nullable Type returnType);

}

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

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

方法工具调用结果转换

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

class CustomerTools {

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

}

如果使用编程式方法,你可以通过设置 MethodToolCallback.Builder 的 resultConverter() 属性,提供要用于该工具的自定义 ToolCallResultConverter。

函数工具调用结果转换

使用编程式方法从函数构建工具时,你可以通过设置 FunctionToolCallback.Builder 的 resultConverter() 属性,提供要用于该工具的自定义 ToolCallResultConverter。

工具上下文

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

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 中提供的所有数据都不会发送到人工智能模型。

同样,你可以在直接调用 ChatModel 时定义工具上下文数据。

ChatModel chatModel = ...
ToolCallback[] customerTools = ToolCallbacks.from(new CustomerTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolCallbacks(customerTools)
    .toolContext(Map.of("tenantId", "acme"))
    .build();
Prompt prompt = new Prompt("Tell me more about the customer with ID 42", chatOptions);
chatModel.call(prompt);

如果 toolContext 选项在默认选项和运行时选项中都设置了,则生成的 ToolContext 将是两者的合并,其中运行时选项优先于默认选项。

直接返回

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

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

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

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

如果同时请求多个工具调用,则所有工具的 returnDirect 属性都必须设置为 true,才能将结果直接返回给调用者。否则,结果将发送回模型。

将工具调用结果直接返回给调用者

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

当模型决定调用工具时,它会发送一个包含工具名称和遵循定义模式的输入参数的响应。

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

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

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

方法直接返回

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

class CustomerTools {

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

}

如果使用编程式方法,你可以通过 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.
	 */
	ListresolveToolDefinitions(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 自定义工具执行行为。

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

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

框架控制的工具执行

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

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

当我们希望模型可以使用某个工具时,我们将其定义包含在聊天请求(Prompt)中,并调用 ChatModel API 将请求发送到人工智能模型。

当模型决定调用工具时,它会发送一个包含工具名称和遵循定义模式的输入参数的响应(ChatResponse)。

ChatModel 将工具调用请求发送到 ToolCallingManager API。

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

工具调用的结果返回给 ToolCallingManager。

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

ChatModel 将工具执行结果发送回人工智能模型(ToolResponseMessage)。

人工智能模型使用工具调用结果作为附加上下文生成最终响应,并通过 ChatClient 将其发送回调用者(ChatResponse)。

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

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

public class DefaultToolExecutionEligibilityPredicate implements ToolExecutionEligibilityPredicate {

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

}

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

使用 ToolCallAdvisor 的通知器控制工具执行

作为框架控制工具执行的替代方案,你可以使用 ToolCallAdvisor 将工具调用实现为通知器链的一部分。这种方法有几个优点:

可观测性:链中的其他通知器可以拦截和观察每个工具调用迭代

与聊天记忆集成:与聊天记忆通知器无缝协作,用于对话历史管理

可扩展性:可以扩展通知器来自定义工具调用行为

ToolCallAdvisor 实现工具调用循环并禁用模型的内部工具执行。当模型请求工具调用时,通知器执行该工具并将结果发送回模型,直到不再需要工具调用为止。

var toolCallAdvisor = ToolCallAdvisor.builder()
    .toolCallingManager(toolCallingManager)
    .advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
    .build();

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(toolCallAdvisor)
    .build();

String response = chatClient.prompt("What day is tomorrow?")
    .tools(new DateTimeTools())
    .call()
    .content();

配置选项

ToolCallAdvisor.Builder 支持以下配置选项:

toolCallingManager:用于执行工具调用的 ToolCallingManager 实例。如果未提供,将使用默认实例。

advisorOrder:通知器在链中应用的顺序。必须在 BaseAdvisor.HIGHEST_PRECEDENCE 和 BaseAdvisor.LOWEST_PRECEDENCE 之间。

conversationHistoryEnabled:控制通知器在工具调用迭代期间是否内部维护对话历史。默认值为 true。

对话历史管理

默认情况下(conversationHistoryEnabled=true),ToolCallAdvisor 在工具调用迭代期间内部维护完整的对话历史。每个后续的大语言模型调用都包括所有先前的消息。

使用 .disableMemory() 方法禁用内部对话历史管理。禁用时,仅将最后一个工具响应消息传递给下一次迭代。这在与已管理对话历史的聊天记忆通知器集成时很有用:

var toolCallAdvisor = ToolCallAdvisor.builder()
    .toolCallingManager(toolCallingManager)
    .disableMemory()  // Let ChatMemory handle history
    .advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
    .build();

var chatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory)
    .advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 200)  // Before ToolCallAdvisor
    .build();

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(chatMemoryAdvisor, toolCallAdvisor)
    .build();

直接返回

ToolCallAdvisor 支持“直接返回”功能,允许工具绕过大语言模型并将结果直接返回给客户端。当工具执行的 returnDirect=true 时,通知器跳出工具调用循环并直接返回工具结果。

用户控制的工具执行

在某些情况下,你宁愿自己控制工具执行生命周期。你可以通过将 ToolCallingChatOptions 的 internalToolExecutionEnabled 属性设置为 false 来实现。

当你使用此选项调用 ChatModel 时,工具执行将委托给调用者,让你完全控制工具执行生命周期。你有责任检查 ChatResponse 中的工具调用并使用 ToolCallingManager 执行它们。

以下示例演示了用户控制工具执行方法的最小实现:

ChatModel chatModel = ...
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();

ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolCallbacks(new CustomerTools())
    .internalToolExecutionEnabled(false)
    .build();
Prompt prompt = new Prompt("Tell me more about the customer with ID 42", chatOptions);

ChatResponse chatResponse = chatModel.call(prompt);

while (chatResponse.hasToolCalls()) {
    ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, chatResponse);

    prompt = new Prompt(toolExecutionResult.conversationHistory(), chatOptions);

    chatResponse = chatModel.call(prompt);
}

System.out.println(chatResponse.getResult().getOutput().getText());

选择用户控制的工具执行方法时,我们建议使用 ToolCallingManager 管理工具调用操作。这样,你可以受益于 Spring AI 为工具执行提供的内置支持。但是,没有什么可以阻止你实现自己的工具执行逻辑。

下一个示例显示了用户控制工具执行方法与 ChatMemory API 使用相结合的最小实现:

ToolCallingManager toolCallingManager = DefaultToolCallingManager.builder().build();
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
String conversationId = UUID.randomUUID().toString();

ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolCallbacks(ToolCallbacks.from(new MathTools()))
    .internalToolExecutionEnabled(false)
    .build();
Prompt prompt = new Prompt(
        List.of(new SystemMessage("You are a helpful assistant."), new UserMessage("What is 6 * 8?")),
        chatOptions);
chatMemory.add(conversationId, prompt.getInstructions());

Prompt promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);
ChatResponse chatResponse = chatModel.call(promptWithMemory);
chatMemory.add(conversationId, chatResponse.getResult().getOutput());

while (chatResponse.hasToolCalls()) {
    ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(promptWithMemory,
            chatResponse);
    chatMemory.add(conversationId, toolExecutionResult.conversationHistory()
        .get(toolExecutionResult.conversationHistory().size() - 1));
    promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);
    chatResponse = chatModel.call(promptWithMemory);
    chatMemory.add(conversationId, chatResponse.getResult().getOutput());
}

UserMessage newUserMessage = new UserMessage("What did I ask you earlier?");
chatMemory.add(conversationId, newUserMessage);

ChatResponse newResponse = chatModel.call(new Prompt(chatMemory.get(conversationId)));

异常处理

当工具调用失败时,异常作为 ToolExecutionException 传播,可以捕获该异常来处理错误。ToolExecutionExceptionProcessor 可用于处理 ToolExecutionException,有两种结果:生成要发送回人工智能模型的错误消息,或抛出要由调用者处理的异常。

@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,则 DefaultToolExecutionExceptionProcessor 是 ToolExecutionExceptionProcessor 接口的自动配置实现。默认情况下,RuntimeException 的错误消息发送回模型,而受检异常和错误(例如 IOException、OutOfMemoryError)始终抛出。DefaultToolExecutionExceptionProcessor 构造函数允许你将 alwaysThrow 属性设置为 true 或 false。如果为 true,则将抛出异常,而不是将错误消息发送回模型。

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

spring.ai.tools.throw-exception-on-error:如果为 true,工具调用错误将作为异常抛出,由调用者处理。如果为 false,错误将转换为消息并发送回人工智能模型,允许其处理并响应错误。默认值 false

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

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

ToolExecutionExceptionProcessor 由默认的 ToolCallingManager(DefaultToolCallingManager)内部用于处理工具执行期间的异常。

工具解析

将工具传递给模型的主要方法是在调用 ChatClient 或 ChatModel 时提供 ToolCallback,使用方法作为工具和函数作为工具中描述的策略之一。

但是,Spring AI 还支持使用 ToolCallbackResolver 接口在运行时动态解析工具。

public interface ToolCallbackResolver {

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

}

使用此方法时:

在客户端,你向 ChatClient 或 ChatModel 提供工具名称,而不是 ToolCallback。

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

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

SpringBeanToolCallbackResolver 从 Function、Supplier、Consumer 或 BiFunction 类型的 Spring bean 解析工具。

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

如果你依赖 Spring Boot 自动配置,你可以通过提供自定义 ToolCallbackResolver bean 来自定义解析逻辑。

@Bean
ToolCallbackResolver toolCallbackResolver(ListtoolCallbacks) {
    StaticToolCallbackResolver staticToolCallbackResolver = new StaticToolCallbackResolver(toolCallbacks);
    return new DelegatingToolCallbackResolver(List.of(staticToolCallbackResolver));
}

ToolCallbackResolver 由 ToolCallingManager 内部用于在运行时动态解析工具,支持框架控制的工具执行和用户控制的工具执行。

工具参数增强

Spring AI 提供了一个实用程序,用于使用附加参数动态增强工具输入模式。这允许从模型捕获额外信息——例如推理或元数据——而无需修改底层工具实现。

常见用例包括:

内部思考/推理:在执行工具之前捕获模型的分步推理

记忆增强:提取见解以存储在长期记忆中

分析和跟踪:收集元数据、用户意图或使用模式

多智能体协调:传递智能体标识符或协调信号

快速开始

将增强参数定义为 Java 记录:

public record AgentThinking(
    @ToolParam(description = "Your reasoning for calling this tool", required = true)
    String innerThought,

    @ToolParam(description = "Confidence level (low, medium, high)", required = false)
    String confidence
) {}

使用 AugmentedToolCallbackProvider 包装你的工具:

AugmentedToolCallbackProviderprovider = AugmentedToolCallbackProvider
    .builder()
    .toolObject(new MyTools())  // Your @Tool annotated class
    .argumentType(AgentThinking.class)
    .argumentConsumer(event -> {
        AgentThinking thinking = event.arguments();
        log.info("Tool: {} | Reasoning: {}", event.toolDefinition().name(), thinking.innerThought());
    })
    .removeExtraArgumentsAfterProcessing(true)
    .build();

与 ChatClient 一起使用:

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultToolCallbacks(provider)
    .build();

大语言模型会看到带有附加字段的增强模式。你的消费者接收 AgentThinking 记录,而原始工具仅接收其预期的参数。

核心组件

AugmentedToolCallbackProvider- 包装工具对象或提供者,使用指定的记录类型增强所有工具

AugmentedToolCallback- 包装单个 ToolCallback 实例

AugmentedArgumentEvent- 包含 toolDefinition()、rawInput() 和消费者的 arguments()

ToolInputSchemaAugmenter - 用于模式操作的底层实用程序

配置

removeExtraArgumentsAfterProcessing 选项控制增强参数是否传递给原始工具:

true(默认)- 在调用工具之前删除增强参数

false - 在输入中保留增强参数(如果工具可以忽略额外字段)

可观测性

工具调用包括可观测性支持,带有 spring.ai.tool 观测指标,用于测量完成时间和传播跟踪信息。

(可选)Spring AI 可以将工具调用参数和结果导出为跨度属性,出于敏感性原因默认禁用。

日志记录

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

相关推荐