8 Tool Calling

38 阅读39分钟

Tool Calling

概述

工具调用(也称为函数调用)是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 = "获取用户时区的当前日期和时间")
    String getCurrentDateTime() {
        return LocalDateTime.now()
            .atZone(LocaleContextHolder.getTimeZone().toZoneId())
            .toString();
    }

}

接下来,让我们使工具对模型可用。在此示例中,我们将使用ChatClient与模型交互。我们将通过tools()方法传递DateTimeTools实例来将工具提供给模型。当模型需要知道当前日期和时间时,它将请求调用工具。在内部,ChatClient将调用工具并将结果返回给模型,模型然后使用工具调用结果生成对原始问题的最终响应。

ChatModel chatModel = ...

String response = ChatClient.create(chatModel)
        .prompt("明天是星期几?")
        .tools(new DateTimeTools())
        .call()
        .content();

System.out.println(response);

输出将类似于:

明天是2015-10-21。

您可以再次询问相同的问题。这次,不要向模型提供工具。输出将类似于:

我是一个AI,无法访问实时信息。请提供当前日期,以便我能准确确定明天是星期几。

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

执行操作

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 = "获取用户时区的当前日期和时间")
    String getCurrentDateTime() {
        return LocalDateTime.now()
            .atZone(LocaleContextHolder.getTimeZone().toZoneId())
            .toString();
    }

    @Tool(description = "为给定时间设置用户闹钟,以ISO-8601格式提供")
    void setAlarm(String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("闹钟设置为 " + alarmTime);
    }

}

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

ChatModel chatModel = ...

String response = ChatClient.create(chatModel)
        .prompt("你能从现在开始10分钟后设置闹钟吗?")
        .tools(new DateTimeTools())
        .call()
        .content();

System.out.println(response);

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

作为方法的工具

Spring AI为从方法指定工具(即ToolCallback)提供了内置支持,可以通过两种方式实现:

  • 声明式:使用@Tool注解
  • 编程式:使用低级MethodToolCallback实现

声明式规范:@Tool

您可以通过使用@Tool注解将方法转换为工具。

class DateTimeTools {

    @Tool(description = "获取用户时区的当前日期和时间")
    String getCurrentDateTime() {
        return LocalDateTime.now()
            .atZone(LocaleContextHolder.getTimeZone().toZoneId())
            .toString();
    }

}

@Tool注解允许您提供关于工具的关键信息:

  • name:工具的名称。如果未提供,将使用方法名称。AI模型使用此名称来识别调用时的工具。因此,不允许在同一类中有两个同名工具。对于特定的聊天请求,该名称在模型可用的所有工具中必须是唯一的。
  • description:工具的描述,模型可以使用它来理解何时以及如何调用工具。如果未提供,将使用方法名称作为工具描述。但是,强烈建议提供详细的描述,因为这对于模型理解工具的用途和如何使用它至关重要。未能提供良好的描述可能导致模型在应该使用工具时不去使用或错误使用。
  • returnDirect:工具结果是否应该直接返回给客户端或传回给模型。详情请参见"直接返回"。
  • resultConverter:用于将工具调用结果转换为字符串对象以发送回AI模型的ToolCallResultConverter实现。详情请参见"结果转换"。

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

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

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

某些类型不受支持。详情请参见"方法工具限制"。

Spring AI将自动为@Tool注解方法的输入参数生成JSON模式。模型使用该模式来理解如何调用工具和准备工具请求。@ToolParam注解可用于提供关于输入参数的附加信息,例如描述或参数是必需还是可选的。默认情况下,所有输入参数都被视为必需的。

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;

class DateTimeTools {

    @Tool(description = "为给定时间设置用户闹钟")
    void setAlarm(@ToolParam(description = "ISO-8601格式的时间") String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("闹钟设置为 " + alarmTime);
    }

}

@ToolParam注解允许您提供关于工具参数的关键信息:

  • description:参数的描述,模型可以使用它来更好地理解如何使用它。例如,参数应该采用什么格式,允许什么值等。
  • required:参数是必需的还是可选的。默认情况下,所有参数都被视为必需的。

如果参数被标注为@Nullable,除非使用@ToolParam注解显式标记为必需,否则它将被视为可选的。

除了@ToolParam注解,您还可以使用Swagger的@Schema注解或Jackson的@JsonProperty注解。详情请参见"JSON模式"。

将工具添加到ChatClient

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

ChatClient.create(chatModel)
    .prompt("明天是星期几?")
    .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("明天是星期几?", 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模型的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("获取用户时区的当前日期和时间")
            .build())
    .toolMethod(method)
    .toolObject(new DateTimeTools())
    .build();

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

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

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

某些类型不受支持。详情请参见"方法工具限制"。

如果方法是静态的,可以省略toolObject()方法,因为它不是必需的。

class DateTimeTools {

    static String getCurrentDateTime() {
        return LocalDateTime.now()
            .atZone(LocaleContextHolder.getTimeZone().toZoneId())
            .toString();
    }

}
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");

ToolCallback toolCallback = MethodToolCallback.builder()
    .toolDefinition(ToolDefinitions.builder(method)
            .description("获取用户时区的当前日期和时间")
            .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 = "ISO-8601格式的时间") String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("闹钟设置为 " + alarmTime);
    }

}

@ToolParam注解允许您提供关于工具参数的关键信息:

  • description:参数的描述,模型可以使用它来更好地理解如何使用它。例如,参数应该采用什么格式,允许什么值等。
  • required:参数是必需的还是可选的。默认情况下,所有参数都被视为必需的。

如果参数被标注为@Nullable,除非使用@ToolParam注解显式标记为必需,否则它将被视为可选的。

除了@ToolParam注解,您还可以使用Swagger的@Schema注解或Jackson的@JsonProperty注解。详情请参见"JSON模式"。

将工具添加到ChatClient和ChatModel

当使用编程式规范方法时,您可以将MethodToolCallback实例传递给ChatClient的toolCallbacks()方法。该工具仅适用于添加到的特定聊天请求。

ToolCallback toolCallback = ...
ChatClient.create(chatModel)
    .prompt("明天是星期几?")
    .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("明天是星期几?", 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)

函数式类型支持使用基于函数的工具规范方法。详情请参见"函数作为工具"。

作为函数的工具

Spring AI为从函数指定工具提供了内置支持,可以通过编程方式使用低级FunctionToolCallback实现,或者在运行时作为@Bean动态解析来实现。

编程式规范: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模式"。
  • toolMetadata:定义附加设置的ToolMetadata实例,例如结果是否应直接返回给客户端以及要使用的结果转换器。您可以使用ToolMetadata.Builder类来构建它。
  • toolCallResultConverter:用于将工具调用结果转换为字符串对象以发送回AI模型的ToolCallResultConverter实例。如果未提供,将使用默认转换器(DefaultToolCallResultConverter)。

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

  • returnDirect:工具结果是否应该直接返回给客户端或传回给模型。详情请参见"直接返回"。
ToolCallback toolCallback = FunctionToolCallback
    .builder("currentWeather", new WeatherService())
    .description("获取位置的天气")
    .inputType(WeatherRequest.class)
    .build();

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

某些类型不受支持。详情请参见"函数工具限制"。

将工具添加到ChatClient

当使用编程式规范方法时,您可以将FunctionToolCallback实例传递给ChatClient的toolCallbacks()方法。该工具仅适用于添加到的特定聊天请求。

ToolCallback toolCallback = ...
ChatClient.create(chatModel)
    .prompt("哥本哈根的天气怎么样?")
    .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("哥本哈根的天气怎么样?", 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 bean,并让Spring AI在运行时使用ToolCallbackResolver接口(通过SpringBeanToolCallbackResolver实现)动态解析它们。此选项使您能够使用任何Function、Supplier、Consumer或BiFunction bean作为工具。bean名称将用作工具名称,Spring Framework的@Description注解可用于为工具提供描述,模型使用它来理解何时以及如何调用工具。如果您不提供描述,将使用方法名称作为工具描述。但是,强烈建议提供详细的描述,因为这对于模型理解工具的用途和如何使用它至关重要。未能提供良好的描述可能导致模型在应该使用工具时不去使用或错误使用。

@Configuration(proxyBeanMethods = false)
class WeatherTools {

    WeatherService weatherService = new WeatherService();

	@Bean
	@Description("获取位置的天气")
	Function<WeatherRequest, WeatherResponse> currentWeather() {
		return weatherService;
	}

}

某些类型不受支持。详情请参见"函数工具限制"。

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

record WeatherRequest(@ToolParam(description = "城市或国家的名称") String location, Unit unit) {}

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

@Configuration(proxyBeanMethods = false)
class WeatherTools {

    public static final String CURRENT_WEATHER_TOOL = "currentWeather";

	@Bean(CURRENT_WEATHER_TOOL)
	@Description("获取位置的天气")
	Function<WeatherRequest, WeatherResponse> currentWeather() {
		...
	}

}
将工具添加到ChatClient

当使用动态规范方法时,您可以将工具名称(即函数bean名称)传递给ChatClient的toolNames()方法。该工具仅适用于添加到的特定聊天请求。

ChatClient.create(chatModel)
    .prompt("哥本哈根的天气怎么样?")
    .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("哥本哈根的天气怎么样?", 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)

基本类型和集合支持使用基于方法的工具规范方法。详情请参见"作为方法的工具"。

工具规范

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

工具回调

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

该接口提供以下方法:

public interface ToolCallback {

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

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

    /**
	 * 使用给定输入执行工具,并将结果发送回AI模型。
	 */
	String call(String toolInput);

    /**
	 * 使用给定输入和上下文执行工具,并将结果发送回AI模型。
	 */
	String call(String toolInput, ToolContext toolContext);

}

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

工具定义

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

该接口提供以下方法:

public interface ToolDefinition {

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

	/**
	 * 工具描述,AI模型用它来确定工具的功能。
	 */
	String description();

	/**
	 * 用于调用工具的参数模式。
	 */
	String inputSchema();

}

有关输入模式的详细信息,请参见"JSON模式"。

ToolDefinition.Builder允许您使用默认实现(DefaultToolDefinition)构建ToolDefinition实例。

ToolDefinition toolDefinition = ToolDefinition.builder()
    .name("currentWeather")
    .description("获取位置的天气")
    .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("获取用户时区的当前日期和时间")
    .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 = "为给定时间设置用户闹钟")
    void setAlarm(@ToolParam(description = "ISO-8601格式的时间") String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("闹钟设置为 " + alarmTime);
    }

}
必需/可选

默认情况下,每个输入参数都被视为必需的,这强制AI模型在调用工具时为其提供值。但是,您可以使用以下注解之一使输入参数可选,按此优先级顺序:

  • Spring AI的@ToolParam(required = false)
  • Jackson的@JsonProperty(required = false)
  • Swagger的@Schema(required = false)
  • Spring Framework的@Nullable

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

class CustomerTools {

    @Tool(description = "更新客户信息")
    void updateCustomerInfo(Long id, String name, @ToolParam(required = false) String email) {
        System.out.println("更新了id为 " + id + " 的客户信息");
    }

}

为输入参数定义正确的必需状态对于减轻幻觉风险并确保模型在调用工具时提供正确的输入至关重要。在前面的示例中,email参数是可选的,这意味着模型可以在不提供值的情况下调用工具。如果参数是必需的,模型在调用工具时必须为其提供值。如果不存在值,模型可能会编造一个,导致幻觉。

结果转换

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

该接口提供以下方法:

@FunctionalInterface
public interface ToolCallResultConverter {

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

}

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

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

方法工具调用结果转换

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

class CustomerTools {

    @Tool(description = "检索客户信息", resultConverter = CustomToolCallResultConverter.class)
    Customer getCustomerInfo(Long id) {
        return customerRepository.findById(id);
    }

}

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

详情请参见"作为方法的工具"。

函数工具调用结果转换

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

详情请参见"作为函数的工具"。

工具上下文

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

class CustomerTools {

    @Tool(description = "检索客户信息")
    Customer getCustomerInfo(Long id, ToolContext toolContext) {
        return customerRepository.findById(id, toolContext.getContext().get("tenantId"));
    }

}

ToolContext使用用户在调用ChatClient时提供的数据填充。

ChatModel chatModel = ...

String response = ChatClient.create(chatModel)
        .prompt("告诉我更多关于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("告诉我更多关于ID为42的客户的信息", chatOptions);
chatModel.call(prompt);

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

直接返回

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

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

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

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

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

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

当模型决定调用工具时,它发送一个带有工具名称和根据定义模式建模的输入参数的响应。

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

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

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

方法直接返回

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

class CustomerTools {

    @Tool(description = "检索客户信息", 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 {

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

	/**
	 * 执行模型请求的工具调用。
	 */
	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将自动拦截来自模型的任何工具调用请求,调用工具并将结果返回给模型。所有这些都由每个使用ToolCallingManager的ChatModel实现为您透明地完成。

当我们想使工具对模型可用时,我们在聊天请求(Prompt)中包含其定义并调用将请求发送给AI模型的ChatModel API。

当模型决定调用工具时,它发送一个带有工具名称和根据定义模式建模的输入参数的响应(ChatResponse)。

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

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

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

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

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

AI模型使用工具调用结果作为附加上下文生成最终响应,并通过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的自定义实现。

用户控制的工具执行

在某些情况下,您宁愿自己控制工具执行生命周期。您可以通过将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("告诉我更多关于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("你是一个有用的助手。"), new UserMessage("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("我之前问你什么?");
chatMemory.add(conversationId, newUserMessage);

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

异常处理

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

@FunctionalInterface
public interface ToolExecutionExceptionProcessor {

	/**
	 * 将工具抛出的异常转换为可以发送回AI模型的字符串,或抛出由调用者处理的异常。
	 */
	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,错误转换为消息并发送回AI模型,允许它处理和响应错误。false
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
    return new DefaultToolExecutionExceptionProcessor(true);
}

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

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

工具解析

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

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

public interface ToolCallbackResolver {

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

}

使用此方法时:

  • 在客户端,您向ChatClient或ChatModel提供工具名称,而不是ToolCallback。
  • 在服务器端,ToolCallbackResolver实现负责将工具名称解析为相应的ToolCallback实例。

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

  • SpringBeanToolCallbackResolver从类型为Function、Supplier、Consumer或BiFunction的Spring bean解析工具。详情请参见"动态规范:@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内部用于在运行时动态解析工具,支持框架控制的工具执行和用户控制的工具执行。

可观察性

工具调用包括可观察性支持,使用spring.ai.tool observations测量完成时间并传播跟踪信息。请参见"工具调用可观察性"。

另外,Spring AI可以将工具调用参数和结果导出为span属性,出于敏感性原因默认禁用。详细信息:工具调用参数和结果数据。

日志记录

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