工具调用

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

工具主要用于:

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

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

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

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

快速入门

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

信息检索

AI 模型无法访问实时信息。任何假设模型能够感知当前日期或天气预报等信息的问题都无法由模型回答。不过,我们可以提供一个工具来检索这些信息,并让模型在需要访问实时信息时调用此工具。

让我们实现一个工具,用于在一个类中获取用户时区的当前日期和时间DateTimeTools。该工具不接受任何参数。SpringLocaleContextHolder框架的 可以提供用户的时区。该工具将被定义为一个带有 注解的方法@Tool。为了帮助模型理解是否以及何时调用此工具,我们将提供该工具功能的详细描述。

接下来,让我们将工具提供给模型。在本例中,我们将使用ChatClient与模型交互。我们将通过 方法传递 的实例,将该工具提供给模型DateTimeTools。tools()当模型需要获取当前日期和时间时,它会请求调用该工具。在内部,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与模型交互。我们将通过DateTimeTools方法传递一个 实例来将这两个工具提供tools()给模型。当我们要求设置 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从方法和函数中指定(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:工具结果应直接返回给客户端还是传递回模型。有关更多详细信息,请参阅直接返回。
  • resultConverter:ToolCallResultConverter用于将工具调用的结果转换为可String object发送回 AI 模型的实现。更多详情,请参阅结果转换。

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

@Tool只要包含方法的类是 Spring bean(例如), Spring AI 就内置了对带 -annotated 方法的 AOT 编译的支持@Component。否则,您需要向 GraalVM 编译器提供必要的配置。例如,通过在类上添加 -annotated 进行注释@RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_METHODS)。

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

Spring AI 会自动为带注解的方法的输入参数生成 JSON 模式@Tool。模型会使用该模式来了解如何调用工具并准备工具请求。@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,您还可以使用@SchemaSwagger@JsonPropertyJackson 的注解。更多详情,请参阅JSON Schema 。

添加工具 ChatClient

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

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

底层ChatClient会生成一个工具类实例中ToolCallback带有 from each@Tool注解的方法,并将其传递给模型。如果您希望ToolCallback自己生成,可以使用ToolCallbacks实用程序类。

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

添加默认工具 ChatClient

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

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

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

添加工具ChatModel

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

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构造时通过将工具类实例传递给用于创建 的实例toolCallbacks()的方法来添加默认工具。如果同时提供了默认工具和运行时工具,则运行时工具将完全覆盖默认工具。ToolCallingChatOptionsChatModel

默认工具在该实例执行的所有聊天请求之间共享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用于将工具调用的结果转换为String对象并发送回 AI 模型的实例。如果未提供,则将使用默认转换器 ( 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。如果该方法返回值,则返回类型必须是可序列化类型,因为结果将被序列化并发送回模型。

作者:Jeebiz  创建时间:2025-08-03 19:51
最后编辑:Jeebiz  更新时间:2025-08-08 00:47