Spring AI ToolCalling 扩展模型能力边界

Spring AI ToolCalling

1.什么是ToolCalling

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

2.ToolCalling作用

  • 信息检索

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

  • 采取行动

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

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

3.ToolCalling 快速入门

接下来介绍如何在Spring AI中开始使用工具调用。将实现两个简单的工具:一个用于信息检索,另一个用于采取行动。信息检索工具将用于获取用户所在时区的当前日期和时间。动作工具将用于设置指定时间的警报。

3.1 信息检索

人工智能模型无法获取实时信息。当前日期或天气预报等信息模型都无法回答。然而,我们可以提供一个可以检索这些信息的工具,并让模型在需要访问实时信息时调用该工具。

定义工具方法:

java 复制代码
class DateTimeTools {

    @Tool(description = "获取当前用户所在时区的日期和时间以及星期几")
    String getCurrentDateTime() {
        LocalDate currentDate = LocalDate.now();
        DayOfWeek dayOfWeek = currentDate.getDayOfWeek();
        return "当前日期和时间是:" + LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId())
                + ",星期" + dayOfWeek;
    }

}

工具方法是一个普通的Java方法,然后使用 @Tool 注释,标识方法为工具方法。为了帮助模型理解工具并在合适的场景调用,需要提供工具功能的详细描述。

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

java 复制代码
@RestController
class MyController {

    private final ChatClient chatClient;

    public MyController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }
    @GetMapping("/ai")
    String generation(@RequestParam(required = false,defaultValue = "明天日期和星期是?")String userInput) {
        return this.chatClient.prompt()
                .advisors(new SimpleLoggerAdvisor())
               // .tools(new DateTimeTools())
                .user(userInput)
                .call()
                .content();
    }
}

响应结果类似如下:

java 复制代码
当前日期是2025年3月26日,星期三。因此,明天的日期将是2025年3月27日,星期四。

如果不向模型提供工具,注释代码tools(new DateTimeTools()),问同样的问题得到的答案如下:

java 复制代码
由于我无法实时获取当前的日期,你可以根据今天的日期自行推算明天的日期和星期。
通常,日历应用或查看设备上的日期设置可以提供准确的信息。

3.2 采取行动

人工智能模型可以用来生成完成特定目标的计划。例如,一个模型可以生成预订某个城市之旅的计划。然而,模型不具备执行计划的能力。这就是工具的用武之地:它们可以用来执行模型生成的计划。在前面的示例中,我们使用了一个工具来确定当前日期和时间。在本例中,我们将定义第二个工具,用于在特定时间设置警报。我们的目标是从现在开始设置10分钟的闹钟,所以我们需要为模型提供这两个工具来完成这个任务。

java 复制代码
public class DateTimeTools {
    @Tool(description = "获取用户所在时区的当前日期和时间以及星期")
    String getCurrentDateTime() {
        LocalDate currentDate = LocalDate.now();
        DayOfWeek dayOfWeek = currentDate.getDayOfWeek();
        return "当前日期和时间是:" + LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId())
                + ",星期" + dayOfWeek;
    }

    @Tool(description = "为给定的时间设置一个用户闹钟,时间以 ISO-8601 格式提供。")
    void setAlarm(String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("已设置闹铃 " + alarmTime);
    }
java 复制代码
    @GetMapping("/ai")
    String generation(@RequestParam(required = false,defaultValue = "设置一个10分钟后的闹铃")String userInput) {
        return this.chatClient.prompt()
                .advisors(new SimpleLoggerAdvisor())
                .tools(new DateTimeTools())
                .user(userInput)
                .call()
                .content();
    }

输出内容:

java 复制代码
已经为您成功设置了10分钟后(2025年03月26日14:07)的闹钟。当时间到达时,闹钟将会响起。

4.ToolCalling 概念和组件

Spring AI通过一组灵活的抽象来支持工具调用,方便使用者使用统一的方式定义、解析和执行工具。

1.当我们想让一个工具对模型可用时,我们将其定义包含在聊天请求中。每个工具定义由名称描述输入参数的模式组成。

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

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

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

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

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

Tools是工具调用的基础组成部分,它们由 ToolCallback 接口进行建模。Spring AI 提供了内置支持,可从方法(MethodToolCallback )和函数(FunctionToolCallback )中指定 ToolCallback,可以定义自己的 ToolCallback 实现类,以支持更多的用例。

聊天模型实现会透明地将工具调用请求分派给相应的 ToolCallback 实现类,并会将工具调用结果发送回模型,模型最终会生成最终响应。它们是通过 ToolCallingManager 接口来实现这一点的,该接口负责管理工具的执行生命周期。

ChatClientChatModel 都接受一个 ToolCallback 对象列表,以使模型和最终将执行这些工具的 ToolCallingManager 能够使用这些工具。

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

4.1 方法作为工具

方法作为工具有两种方式:

1.使用注解@Tool

2.通过编程方式,使用低级的MethodToolCallback实现。

注解@Tool

通过使用@Tool注释将方法转换为工具。

java 复制代码
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();
    }

}

注解定义:

java 复制代码
public @interface Tool {

	String name() default "";

	String description() default "";

	boolean returnDirect() default false;

	Class<? extends ToolCallResultConverter> resultConverter() default DefaultToolCallResultConverter.class;

}

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

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

returnDirect:工具结果是否应该直接返回给客户端或传递回模型。后文有详细介绍。//todo

resultConverter:ToolCallResultConverter实现用于将工具调用的结果转换为String对象以发送回AI模型。后文详细介绍。//TODO

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

可以为大多数类型(基本类型pojo枚举List数组Map等等)的方法定义任意数量的参数(包括不带参数)。类似地,该方法可以返回大多数类型,包括void。如果方法返回一个值,则返回类型必须是可序列化的类型,因为结果将被序列化并发送回模型 。

@ToolParam 工具参数

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

java 复制代码
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);
    }

}
java 复制代码
public @interface ToolParam {

	/**
	 * Whether the tool argument is required.
	 */
	boolean required() default true;

	/**
	 * The description of the tool argument.
	 */
	String description() default "";

}

description:参数的描述,模型可以使用它来更好地理解如何使用它。例如,参数应该采用什么格式,允许使用什么值,等等。

required:参数是必需的还是可选的。默认情况下,所有参数都是必需的。

如果一个参数被注释为@Nullable,它将被认为是可选的,除非使用@ToolParam注释显式标记为必需。

除了@ToolParam注释,您还可以使用Swagger@Schema注释或Jackson@JsonProperty注释。

ChatClient 使用工具调用

java 复制代码
ChatClient.create(chatModel)
    .prompt("What day is tomorrow?")
    .tools(new DateTimeTools())
    .call()
    .content();

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

java 复制代码
 ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatClient.create(chatModel)
    .prompt("What day is tomorrow?")
    .tools(dateTimeTools)
    .call()
    .content();

ChatClient 添加默认工具

java 复制代码
ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools(new DateTimeTools())
    .build();

ChatModel 添加工具

java 复制代码
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 添加默认工具

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

运行时工具将覆盖默认工具。

编程MethodToolCallback

可以通过编程方式构建一个MethodToolCallback,把一个方法变成一个工具。

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

MethodToolCallback.Builder允许你构建一个MethodToolCallback实例并提供关于该工具的关键信息:

java 复制代码
public static class Builder {

		private ToolDefinition toolDefinition;

		private ToolMetadata toolMetadata;

		private Method toolMethod;

		private Object toolObject;

		private ToolCallResultConverter toolCallResultConverter;
		}
  • toolDefinition:用于定义工具名称、描述和输入模式的工具定义实例。你可以使用 ToolDefinition.Builder 类来构建它。这是必需的。
  • toolMetadata :用于定义额外设置(例如结果是否应直接返回给客户端,以及要使用的结果转换器)的工具元数据实例。你可以使用 ToolMetadata.Builder 类来构建它。
  • toolMethod:表示工具方法的 Method 实例。这是必需的。
  • toolObject:包含工具方法的对象实例。如果方法是静态的,则可以省略此参数。
  • toolCallResultConverter:用于将工具调用的结果转换为 String 对象,以便发回给人工智能模型的 ToolCallResultConverter 实例。如果未提供,则将使用默认转换器(DefaultToolCallResultConverter)。

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

javascript 复制代码
`name`:工具名称

`description`:工具描述

`inputSchema`:工具输入参数的JSON模式。如果没有提供,模式将根据方法参数自动生成。

ToolMetadata.Builder 允许你构建一个 ToolMetadata实例,定义工具其它设置。

go 复制代码
  `returnDirect`:工具结果是否应该直接返回给客户端或传递回模型。

如果方法是静态的,可以省略toolObject()方法:

java 复制代码
class DateTimeTools {

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

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

普通方法通过MethodToolCallback构建成ToolCallback 对象。ChatModel 和**ChatClient** 即可使用工具。

方法工具的限制

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

  • Optional 类型
  • 异步类型(例如 CompletableFutureFuture
  • 响应式类型(例如 FlowMonoFlux
  • 函数式类型(例如 FunctionSupplierConsumer)。

使用基于函数的工具规范方法时支持函数式类型。

4.2 函数作为工具

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

编程规范: FunctionToolCallback

可以通过以编程方式构建 FunctionToolCallback,将函数式类型(FunctionSupplierConsumerBiFunction)转换为一个工具。

java 复制代码
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) {}
java 复制代码
ToolCallback toolCallback = FunctionToolCallback
    .builder("currentWeather", new WeatherService())
    .description("Get the weather in location")
    .inputType(WeatherRequest.class)
    .build();

FunctionToolCallback.Builder 允许你构建一个 FunctionToolCallback 实例,并提供关于工具的关键信息:

name:工具的名称。人工智能模型在调用工具时会使用此名称来识别它。因此,在同一上下文中不允许有两个名称相同的工具。对于特定的聊天请求,该名称在模型可用的所有工具中必须是唯一的。此为必填项。

toolFunction::表示工具方法的函数对象(FunctionSupplierConsumerBiFunction)。此为必填项。

description:工具的描述,模型可利用该描述来了解何时以及如何调用该工具。若未提供描述,将使用方法名称作为工具描述。不过,强烈建议提供详细描述,因为这对于模型理解工具的用途和使用方法至关重要。若描述提供不当,可能会导致模型在需要使用该工具时不使用,或者错误地使用该工具。

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

inputSchema:工具输入参数的 JSON 模式。若未提供,将根据输入类型自动生成模式。你可以使用 @ToolParam 注解来提供关于输入参数的额外信息,例如参数描述、参数是必填还是选填。默认情况下,所有输入参数都被视为必填项。

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

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

ToolMetadata.Builder 允许你构建一个 ToolMetadata 实例,并为工具定义额外的设置:

go 复制代码
  `returnDirect`:工具结果是应直接返回给客户端,还是应传回给模型。

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

函数通过FunctionToolCallback构建成ToolCallback 对象。ChatModel 和**ChatClient** 即可使用工具。

动态 @Bean

你无需通过编程方式指定工具,而是可以将工具定义为 Spring Bean,并让 Spring AI 在运行时使用 ToolCallbackResolver 接口(通过 SpringBeanToolCallbackResolver 实现)动态解析这些工具。这种方式使你能够将任何 FunctionSupplierConsumerBiFunction 类型的 bean 用作工具。bean 的名称将用作工具名称,并且可以使用 Spring 框架中的 @Description 注解来为工具提供描述,模型会利用该描述来了解何时以及如何调用该工具。如果你不提供描述,将使用方法名称作为工具描述。不过,强烈建议提供详细的描述,因为这对于模型理解工具的用途和使用方法至关重要。若未能提供良好的描述,可能会导致模型在应该使用工具时却未使用,或者错误地使用工具。

java 复制代码
@Configuration(proxyBeanMethods = false)
class WeatherTools {

    WeatherService weatherService = new WeatherService();

	@Bean
	@Description("Get the weather in location")
	Function<WeatherRequest, WeatherResponse> currentWeather() {
		return weatherService;
	}

}

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

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

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

java 复制代码
@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名称)传递给ChatClienttools()方法。该工具将仅对添加到其中的特定聊天请求可用。

java 复制代码
ChatClient.create(chatModel)
    .prompt("What's the weather like in Copenhagen?")
    //bean名称
    .tools("currentWeather")
    .call()
    .content();

ChatClient 添加默认工具

java 复制代码
ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools("currentWeather")
    .build();

ChatModel 添加工具

java 复制代码
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 添加默认工具

java 复制代码
ChatModel chatModel = OllamaChatModel.builder()
    .ollamaApi(new OllamaApi())
    .defaultOptions(ToolCallingChatOptions.builder()
            .toolNames("currentWeather")
            .build())
    .build();

函数工具的限制

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

基本数据类型

Optional 类型

集合类型(例如 ListMapArraySet

异步类型(例如 CompletableFutureFuture

响应式类型(例如 FlowMonoFlux)。

使用基于方法的工具规范方法时,基本数据类型和集合类型是受支持的。

4.3 Tool 规范

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

Tool Callback

ToolCallback 接口提供了一种定义可由人工智能模型调用的工具的方法,其中涵盖了定义和执行逻辑。当你想要从头开始定义一个工具时,这是需要实现的主要接口。例如,你可以基于 MCP 客户端(使用模型上下文协议)或聊天客户端来定义一个 ToolCallback(以构建一个模块化的智能体应用程序)。

java 复制代码
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实例来定义工具。

java 复制代码
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实例。

java 复制代码
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();

方法工具定义

方法作为工具

函数工具定义

函数作为工具

JSON Schema

当向人工智能模型提供工具时,该模型需要了解调用该工具的输入类型架构。该架构用于了解如何调用工具以及准备工具请求。Spring AI 通过 JsonSchemaGenerator 类为生成工具的输入类型的 JSON 架构提供了内置支持。

有关 ToolDefinition 的更多详细信息。Tool Definition

在底层,JsonSchemaGenerator 类用于使用 "将方法作为工具" 和 "将函数作为工具" 中描述的任何策略,为一个方法或函数的输入参数生成 JSON Schema 。JSON Schema生成逻辑支持一系列注解,你可以在方法和函数的输入参数上使用这些注解。

参数描述

除了为工具本身提供描述之外,你还可以为工具的输入参数提供描述。该描述可用于提供有关输入参数的关键信息,例如参数应采用何种格式、允许哪些值等等。这有助于模型理解输入架构以及如何使用它。Spring AI 提供了内置支持,可使用以下注解之一来生成输入参数的描述:

  • @ToolParam(description = "...") from Spring AI
  • @JsonClassDescription(description = "...") from Jackson
  • @JsonPropertyDescription(description = "...") from Jackson
  • @Schema(description = "...") from Swagger.

这种方法对方法和函数都有效,并且可以对嵌套类型递归地使用。

java 复制代码
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模型在调用工具时为它提供一个值。然而,你可以通过使用以下注释之一使输入参数成为可选的,按照优先顺序:

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

这种方法对方法和函数都有效,并且可以对嵌套类型递归地使用它。

java 复制代码
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对象的方法。

java 复制代码
@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);

}

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

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

方法工具结果转换

当使用声明性@Tools注解定义工具时,可以通过设置@Tools注解的resultConverter()属性,为工具提供一个自定义ToolCallResultConverter

java 复制代码
class CustomerTools {

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

}

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

函数工具结果转换

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

Tool Context

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

java 复制代码
class CustomerTools {

    @Tool(description = "Retrieve customer information")
    Customer getCustomerInfo(Long id, ToolContext toolContext) {
        return customerRepository.findById(id, toolContext.get("tenantId"));
    }

}
java 复制代码
ChatModel chatModel = ...

String response = ChatClient.create(chatModel)
        .prompt("Tell me more about the customer with ID 42")
        .tools(new CustomerTools())
        //设置toolContext参数值
        .toolContext(Map.of("tenantId", "acme"))
        .call()
        .content();

System.out.println(response);

ToolContext中提供的任何数据都不会发送给AI模型。

类似地,您可以在直接调用ChatModel时定义工具上下文数据。

java 复制代码
ChatModel chatModel = ...
ToolCallback[] customerTools = ToolCallbacks.from(new CustomerTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolCallbacks(customerTools)
      //设置toolContext参数值
    .toolContext(Map.of("tenantId", "acme"))
    .build();
Prompt prompt = new Prompt("Tell me more about the customer with ID 42", chatOptions);
chatModel.call(prompt);

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

Return Direct

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

有些情况下,你更希望将结果直接返回给调用者,而不是发送回模型。例如,如果你构建了一个依赖于检索增强生成(RAG)工具的智能体,你可能希望直接将结果返回给调用者,而不是将其发送回模型进行不必要的后处理。或者,你可能有某些工具应该结束智能体的推理循环。

go 复制代码
 每个`ToolCallback` 实现都可以定义工具调用的结果是应直接返回给调用者,还是发送回模型。默认情况下,结果会发送回模型。但你可以针对每个工具更改此行为。

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

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

  1. 当我们想让模型能够使用某个工具时,我们会在聊天请求中包含该工具的定义。如果我们希望工具执行的结果能直接返回给调用者,我们就将 returnDirect 属性设置为 true

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

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

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

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

方法returnDirect

java 复制代码
class CustomerTools {

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

}

如果采用编程的方式,你可以通过 ToolMetadata 接口设置 returnDirect 属性,并将其传递给 MethodToolCallback.Builder

java 复制代码
ToolMetadata toolMetadata = ToolMetadata.builder()
    .returnDirect(true)
    .build();

函数Return Direct

当以编程方式从函数构建工具时,你可以通过 ToolMetadata 接口设置 returnDirect 属性,并将其传递给 FunctionToolCallback.Builder

java 复制代码
ToolMetadata toolMetadata = ToolMetadata.builder()
    .returnDirect(true)
    .build();

4.4 Tool 执行

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

java 复制代码
public interface ToolCallingManager {

	/**
	 * 解析工具的定义
	 */
	List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);

	/**
	 * 执行工具调用。
	 */
	ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);

}

如果你正在使用任何一个 Spring AI 的 Spring Boot 启动器,DefaultToolCallingManager 就是 ToolCallingManager 接口的自动配置实现。你可以通过提供自己的 ToolCallingManager Bean 来定制工具执行行为。

java 复制代码
@Bean
ToolCallingManager toolCallingManager() {
    return ToolCallingManager.builder().build();
}

默认情况下,Spring AI 会在每个 ChatModel 实现内部自动管理工具执行生命周期。但你也可以选择不采用这种行为,而是自行控制工具的执行。接下来将描述这两种情况。

框架控制工具执行

当采用默认行为时,Spring AI 会自动拦截来自模型的任何工具调用请求,调用相应工具并将结果返回给模型。每个ChatModel实现都会借助ToolCallingManager为你无感知地完成这一切。

1.当我们想让模型能够使用某个工具时,我们会在聊天请求(提示)中包含该工具的定义,然后调用ChatModel API,将请求发送给人工智能模型。

2.当模型决定调用某个工具时,它会发送一个包含工具名称以及按照已定义模式构建的输入参数的响应(聊天响应)。

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

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

5.工具调用的结果会返回给ToolCallingManager

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

7.ChatModel将工具执行结果发送回人工智能模型(工具响应消息)。

8.人工智能模型会将工具调用结果作为额外的上下文信息来生成最终响应,并通过ChatClient将其发送回调用方(聊天响应)。

用户控制工具执行

在某些情况下,你可能更希望自己控制工具的执行生命周期。你可以通过将ToolCallingChatOptionsinternalToolExecutionEnabled属性设置为false来实现这一点。当你使用此选项调用ChatModel时,工具的执行将委托给调用方,这样你就可以完全控制工具的执行生命周期。你需要负责检查ChatResponse中的工具调用,并使用ToolCallingManager来执行这些调用。

java 复制代码
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());

异常处理

当工具调用失败时,异常会以ToolExecutionException(工具执行异常)的形式传播,可以捕获该异常来处理错误。ToolExecutionExceptionProcessor(工具执行异常处理器)可用于处理ToolExecutionException,会产生两种结果:要么生成一条错误消息并发送回人工智能模型,要么抛出一个异常由调用方来处理。

java 复制代码
@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 启动器,DefaultToolExecutionExceptionProcessor(默认工具执行异常处理器)就是ToolExecutionExceptionProcessor(工具执行异常处理器)接口的自动配置实现类。默认情况下,错误消息会被发送回模型。DefaultToolExecutionExceptionProcessor的构造函数允许你将alwaysThrow属性设置为truefalse。如果设置为true,将会抛出一个异常,而不是把错误消息发送回模型。

java 复制代码
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
    return new DefaultToolExecutionExceptionProcessor(true);
}

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

ToolExecutionExceptionProcessor由默认的ToolCallingManagerDefaultToolCallingManager)在内部使用,用于处理工具执行期间的异常。

动态解析工具

将工具传递给模型的主要方法是,在调用ChatClientChatModel时,使用 "将方法作为工具" 以及 "将函数作为工具" 中所描述的策略之一来提供ToolCallback(多个ToolCallback)。

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

java 复制代码
public interface ToolCallbackResolver {

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

}

使用这种方法时:

1.在客户端,你向ChatClientChatModel提供工具名称,而不是提供ToolCallback(多个ToolCallback)。

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

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

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

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

java 复制代码
@Bean
ToolCallbackResolver toolCallbackResolver(List<FunctionCallback> toolCallbacks) {
    StaticToolCallbackResolver staticToolCallbackResolver = 
    new StaticToolCallbackResolver(toolCallbacks);
    return new DelegatingToolCallbackResolver(List.of(staticToolCallbackResolver));
}

ToolCallbackResolverToolCallingManager在内部使用,以便在运行时动态解析工具,同时支持 "框架控制的工具执行" 和 "用户控制的工具执行"。

5.可观察性

日志记录

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

yaml 复制代码
logging:
  level:
    org:
      springframework:
        ai: debug

6.总结

Spring AI ToolCalling实际上利用了大模型的 **Function Call**(函数调用)能力。它对于大模型来说意义重大主要扩展模型能力边界。Spring AI 实现MCP ToolCalling提供基础能力。

相关推荐
Asthenia0412几秒前
面试复盘:Java String 源码分析与不可变类设计原理
后端
Asthenia04121 分钟前
面试复盘:synchronized 锁与 ReentrantLock 锁的区别及 AQS 认知完善
后端
小小鸭程序员19 分钟前
Spring Boot事务管理详解(附银行转账案例)
java·spring boot·spring·github·intellij-idea
kill bert31 分钟前
第30周Java分布式入门 docker
java·分布式·docker
joker学java37 分钟前
java基础快速入门07
后端
uhakadotcom38 分钟前
了解Pulumi:基础设施即代码的新选择
后端·面试·github
云之渺41 分钟前
java115
java
fliter1 小时前
性能比拼: TCP vs UDP(重大改进)
后端
林川的邹1 小时前
如何根据场景判断是使用ArrayList还是LinkedList?
java·后端
阿绵1 小时前
拦截器和过滤器详解
java·spring·过滤器·拦截器