Spring AI之工具调用

文章目录


工具调用(也称为函数调用)是AI应用中的常见模式,允许模型通过一组API或工具进行交互,从而增强其能力。工具主要用于以下场景:

  • 信息检索

    这类工具可从外部源(如数据库、网络服务、文件系统或网络搜索引擎)检索信息,目的是扩展模型的知识库,使其能够回答原本无法解答的问题。它们可用于检索增强生成(RAG)场景。例如:

    • 获取某地当前天气
    • 检索最新新闻文章
    • 查询数据库中的特定记录
  • 执行操作

    这类工具可在软件系统中执行操作,如发送邮件、创建数据库记录、提交表单或触发工作流。目标是自动化需要人工干预的任务。例如:

    • 聊天机器人预订机票
    • 自动填写网页表单
    • 基于测试用例生成Java类(TDD场景)
  • 安全注意事项
    虽然工具调用被视为模型能力,但实际执行逻辑由客户端应用程序控制。模型仅能请求工具调用并提供参数,应用程序负责执行并返回结果。模型无法直接访问工具API,这是关键的安全设计。

快速入门

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

信息检索

AI模型无法访问实时信息。任何涉及当前日期、天气预报等实时信息的问题,模型都无法直接回答。但我们可以提供能够检索此类信息的工具,并让模型在需要实时数据时调用这些工具。

首先在DateTimeTools类中实现一个获取用户时区当前日期时间的工具。该工具不需要参数,通过Spring框架的LocaleContextHolder获取用户时区。工具方法使用@Tool注解,并添加详细描述帮助模型理解调用时机。

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

输出示例:

明天是2025-04-03。

如果直接提问但不提供工具,模型将返回:

我无法访问实时信息,请提供当前日期以便计算明天的日期。

这证明模型在缺乏工具时无法自主获取实时数据

执行操作

AI模型可以生成实现目标的计划(如制定丹麦旅行计划),但无法直接执行。此时需要工具来落实模型生成的计划。

在现有DateTimeTools类中新增设置闹钟的工具。该工具接收ISO-8601格式的时间参数,向控制台输出闹钟设置信息。同样使用@Tool注解并添加详细描述

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同时提供两个工具。当请求"10分钟后设置闹钟"时,模型会先调用getCurrentDateTime获取当前时间,计算目标时间后调用setAlarm工具。ChatClient自动处理工具调用请求并返回结果,模型最终生成响应

ChatModel chatModel = ...

String response = ChatClient.create(chatModel)
        .prompt("能设置10分钟后的闹钟吗?")
        .tools(new DateTimeTools())
        .call()
        .content();

System.out.println(response);

应用日志将显示正确设置的闹钟时间,验证工具调用流程

概述

Spring AI通过一组灵活的抽象机制支持工具调用,允许以统一方式定义、解析和执行工具。本节概述Spring AI中工具调用的核心概念与组件。

工具调用的主要操作流程
在这里插入图片描述

  1. 定义工具 :当需要向模型提供工具时,需在聊天请求中包含其定义。每个工具定义包含名称、描述及输入参数的模式(schema)。
  2. 模型发起调用 :当模型决定调用工具时,会返回包含工具名称和符合预定义模式的输入参数的响应。
  3. 应用执行工具 :应用程序负责根据工具名称识别并执行对应工具,传入提供的输入参数。
  4. 处理结果 :工具调用的结果由应用程序处理。
  5. 返回结果至模型 :应用程序将工具调用结果返回给模型。
  6. 生成最终响应 :模型结合工具调用结果作为上下文生成最终响应

核心组件

  • 工具(Tools) :工具调用的构建基础,通过ToolCallback接口建模。Spring AI提供对方法和函数的内置支持以定义ToolCallback,同时也允许自定义实现以扩展更多场景。
  • ChatModel集成 :ChatModel实现通过ToolCallingManager接口透明地将工具调用请求分发给对应的ToolCallback实现,并将执行结果返回模型,最终生成响应。
  • 工具注册与解析
    • ChatClientChatModel接受ToolCallback对象列表,使工具对模型可用,并由ToolCallingManager执行。
    • 除直接传递ToolCallback外,还可通过ToolCallbackResolver接口动态解析工具名称列表

方法作为工具

Spring AI通过以下两种方式支持从方法定义工具(即ToolCallback):

  1. 声明式定义 :使用@Tool注解
  2. 编程式定义 :使用底层MethodToolCallback实现

声明式定义:@Tool注解

通过**@Tool**注解可将方法标记为工具。例如:

class DateTimeTools {

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

@Tool注解支持以下关键属性:

  • name :工具名称。若未指定,默认使用方法名。名称需在单个类中唯一,且在同一聊天请求中所有工具名称不可重复。
  • description :工具描述,用于帮助模型理解调用时机和用途。若未指定,默认使用方法名,但强烈建议提供详细描述以避免模型误用或忽略工具。
  • returnDirect :是否直接将结果返回客户端而非传递给模型(详见"直接返回"章节)。
  • resultConverter :指定ToolCallResultConverter实现,用于将工具调用结果转换为字符串返回模型(详见"结果转换"章节)。

方法可以是静态或实例方法,支持任意访问修饰符(public/protected/包级私有/私有)。包含方法的类可以是顶级类或嵌套类,只需确保实例化时可访问。

若工具类为Spring Bean(如标注**@Component**),Spring AI会自动支持AOT编译;否则需通过**@RegisterReflection**注解配置GraalVM编译器。

方法参数支持多种类型(基本类型、POJO、枚举、列表、数组、映射等),返回类型可为void或可序列化类型。部分类型(如复杂泛型)可能不受支持(详见"方法工具限制"章节)

参数注解:@ToolParam

Spring AI会自动为**@Tool方法生成输入参数的JSON Schema,模型据此理解调用格式。通过@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的参数会被视为可选(除非显式标记为required=true)

此外,可结合Swagger的**@Schema或Jackson的@JsonProperty**注解定义JSON Schema(详见"JSON Schema"章节)。

向ChatClient添加工具

使用声明式方法时,可通过ChatClient的**tools()**方法传递工具实例。这些工具仅对当前聊天请求有效:

ChatClient.create(chatModel)
    .prompt("明天是几号?")
    .tools(new DateTimeTools())  // 添加工具实例
    .call()
    .content();

内部实现中,ChatClient会将**@Tool方法转换为ToolCallback数组。若需手动控制,可使用ToolCallbacks**工具类:

ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
向ChatClient添加默认工具

通过ChatClient.BuilderdefaultTools()方法可注册默认工具,这些工具会被所有由此构建器创建的ChatClient实例共享。若同时提供默认工具和运行时工具,运行时工具会覆盖默认工具:

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools(new DateTimeTools()) // 默认工具
    .build();

注意 :默认工具可能被意外暴露给不需要的请求,需谨慎使用

向ChatModel添加工具

通过ToolCallingChatOptionstoolCallbacks()方法可向ChatModel添加工具,仅对当前请求有效:

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

ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolCallbacks(dateTimeTools)
    .build();

Prompt prompt = new Prompt("明天是几号?", chatOptions);
chatModel.call(prompt);
向ChatModel添加默认工具

在创建ChatModel时,可通过**defaultOptions()**方法指定默认工具。所有由此模型处理的请求均会共享这些工具:

ChatModel chatModel = OllamaChatModel.builder()
    .ollamaApi(new OllamaApi())
    .defaultOptions(
        ToolCallingChatOptions.builder()
        .toolCallbacks(dateTimeTools)
        .build()
    )
    .build();

注意 :默认工具可能被误用于不相关的请求,需确保其适用性\n

编程式定义:MethodToolCallback

通过MethodToolCallback可编程式地将方法转换为工具。

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

MethodToolCallback.Builder用于构建工具实例并定义关键信息:

  • toolDefinition :通过ToolDefinition定义工具名称、描述及输入模式(必填)。
  • toolMetadata :通过ToolMetadata定义额外配置(如是否直接返回结果、结果转换器)。
  • toolMethod :工具方法的Method实例(必填)。
  • toolObject :包含工具方法的对象实例(若方法为静态方法可省略)。
  • toolCallResultConverter :结果转换器,默认使用DefaultToolCallResultConverter
工具定义(ToolDefinition)

通过ToolDefinition.Builder定义工具元数据:

  • name :工具名称。若未指定,默认使用方法名。名称需在单个类中唯一,且在同一聊天请求中不可重复。
  • description :工具描述,用于帮助模型理解调用场景。若未指定,默认使用方法名,但建议提供详细描述以避免误用。
  • inputSchema :输入参数的JSON模式。若未指定,会根据方法参数自动生成。可通过@ToolParam注解补充参数信息(如是否必填)。
工具元数据(ToolMetadata)

通过ToolMetadata.Builder定义额外配置:

  • returnDirect :是否直接将结果返回客户端(详见"直接返回"章节)。
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");

ToolCallback toolCallback = MethodToolCallback.builder()
    .toolDefinition(ToolDefinition.builder(method)
    .description("获取用户时区的当前日期和时间")
    .build())
    .toolMethod(method)
    .toolObject(new DateTimeTools())
    .build();

若方法为静态方法,可省略toolObject

class DateTimeTools {

static String getCurrentDateTime() { /\* ... \*/ }

}

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

ToolCallback toolCallback = MethodToolCallback.builder()
    .toolDefinition(ToolDefinition.builder(method)
    .description("获取用户时区的当前日期和时间")
    .build())
    .toolMethod(method)
    .build();
参数注解:@ToolParam

Spring AI会自动为方法参数生成JSON模式。通过**@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 :是否必填。默认为true,但标注@Nullable的参数会被视为可选。
向ChatClient添加工具

通过tools()方法传递MethodToolCallback实例,仅对当前请求有效:

ToolCallback toolCallback = ...

ChatClient.create(chatModel)
    .prompt("明天是几号?")
    .tools(toolCallback)
    .call()
    .content();
向ChatClient添加默认工具

通过defaultTools()方法注册默认工具,会被所有由此构建器创建的ChatClient实例共享:

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools(toolCallback)
    .build();
向ChatModel添加工具

通过ToolCallingChatOptions的**toolCallbacks()**方法传递工具,仅对当前请求有效:

ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build();

Prompt prompt = new Prompt("明天是几号?", chatOptions);
chatModel.call(prompt);
向ChatModel添加默认工具

在创建ChatModel时通过**defaultOptions()**指定默认工具:

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

方法工具使用限制

以下类型不能作为方法工具的参数或者返回值

  1. Optional类型不被支持,需使用其他方式处理可选参数
  2. 异步类型(如CompletableFuture/Future)不被支持,工具需同步执行
  3. 响应式类型(如Mono/Flux/Flow)不被支持,需转换为阻塞操作
  4. 函数式类型(如Function/Supplier)不被支持,需改用函数式工具规范

注意:函数式类型可通过函数式工具规范支持(如FunctionToolCallback),详见"函数作为工具"章节

函数作为工具

Spring AI通过以下两种方式支持将函数定义为工具:

  1. 编程式定义 :使用底层FunctionToolCallback实现
  2. 动态注册 :通过@Bean在运行时解析

编程式定义:FunctionToolCallback

通过FunctionToolCallback可将函数式类型(Function、Supplier、Consumer、BiFunction)转换为工具

public class WeatherService implements Function<WeatherRequest, WeatherResponse> {
    @Override
    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用于构建工具实例并定义关键信息:

  • name :工具名称,需唯一且不可重复(必填)。
  • toolFunction :函数式对象(如Function、Supplier等,必填)。
  • description :工具描述,帮助模型理解调用场景(建议详细说明)。
  • inputType :输入类型(必填)。
  • inputSchema :输入参数的JSON模式(若未指定,会根据inputType自动生成)。
  • toolMetadata :附加配置(如returnDirect)。
  • toolCallResultConverter :结果转换器,默认使用DefaultToolCallResultConverter。

示例:

ToolCallback toolCallback = FunctionToolCallback.builder("currentWeather", new WeatherService())
    .description("获取指定地点的天气信息")
    .inputType(WeatherRequest.class)
    .build();
函数输入/输出要求
  • 输入和输出类型需为Void或POJO,且必须可序列化。
  • 函数及输入/输出类型需为public。
向ChatClient添加工具

通过tools()方法传递FunctionToolCallback实例,仅对当前请求有效:

ToolCallback toolCallback = ...
ChatClient.create(chatModel)
    .prompt("哥本哈根的天气如何?")
    .tools(toolCallback)
    .call()
    .content();
向ChatClient添加默认工具

通过defaultTools()方法注册默认工具,会被所有由此构建器创建的ChatClient实例共享:

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools(toolCallback)
    .build();
向ChatModel添加工具

通过ToolCallingChatOptions的**toolCallbacks()**方法传递工具,仅对当前请求有效:

ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolCallbacks(toolCallback)
    .build();

Prompt prompt = new Prompt("哥本哈根的天气如何?", chatOptions);
chatModel.call(prompt);
向ChatModel添加默认工具

在创建ChatModel时通过**defaultOptions()**指定默认工具:

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

动态定义:@Bean注解

除了编程式定义工具外,Spring AI支持通过Spring Bean动态解析工具。通过ToolCallbackResolver接口(由SpringBeanToolCallbackResolver实现),可将任意Function、Supplier、Consumer或BiFunction类型的Bean注册为工具。工具名称默认为Bean名称,可通过Spring的@Description注解提供工具描述,帮助模型理解调用场景。

示例:定义天气查询工具

@Configuration(proxyBeanMethods = false)
class WeatherTools {

    private final WeatherService weatherService = new WeatherService();

    @Bean
    @Description("获取指定地点的天气信息")  // 强烈建议提供详细描述
    public Function<WeatherRequest, WeatherResponse> currentWeather() {
        return weatherService::apply;
    }
}

public record WeatherRequest(
    @ToolParam(description = "城市或国家名称") String location,  // 使用@ToolParam补充参数信息
    Unit unit
) {}

public record WeatherResponse(double temp, Unit unit) {}
动态工具特性
  • 类型限制 :不支持Optional、异步类型(如CompletableFuture)及响应式类型(如Mono)(详见"函数工具限制"章节)。
  • JSON模式 :输入参数的JSON模式会根据@ToolParam注解自动生成。默认所有参数为必填。
  • 类型安全风险 :工具解析在运行时进行,无法保证编译时类型安全。建议通过常量显式指定工具名称以避免硬编码:
@Configuration(proxyBeanMethods = false)
class WeatherTools {
    public static final String CURRENT_WEATHER_TOOL = "currentWeather";  // 定义工具名称常量

    @Bean(CURRENT_WEATHER_TOOL)
    @Description("获取指定地点的天气信息")
    public Function<WeatherRequest, WeatherResponse> currentWeather() { /* ... */ }
}
向ChatClient添加工具

通过工具名称(即Bean名称)动态注册工具,仅对当前请求有效:

ChatClient.create(chatModel)
    .prompt("哥本哈根的天气如何?")
    .tools("currentWeather")  // 传递工具名称
    .call()
    .content();
向ChatClient添加默认工具

通过defaultTools()方法注册默认工具,会被所有由此构建器创建的ChatClient实例共享:

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools("currentWeather")  // 默认工具名称
    .build();
向ChatModel添加工具

通过ToolCallingChatOptions的toolNames()方法传递工具名称,仅对当前请求有效:

ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolNames("currentWeather")  // 指定工具名称
    .build();
Prompt prompt = new Prompt("哥本哈根的天气如何?", chatOptions);
chatModel.call(prompt);
向ChatModel添加默认工具

在创建ChatModel时通过defaultOptions()指定默认工具名称:

ChatModel chatModel = OllamaChatModel.builder()
    .ollamaApi(new OllamaApi())
    .defaultOptions(ToolCallingChatOptions.builder()
            .toolNames("currentWeather")  // 默认工具名称
            .build())
    .build();

函数工具限制

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

  • 原始类型 (如int、double)
  • Optional类型
  • 集合类型 (如List、Map、Array、Set)
  • 异步类型 (如CompletableFuture、Future)
  • 响应式类型 (如Flow、Mono、Flux)

原始类型和集合类型可通过方法式工具规范方式 支持(详见"方法作为工具"章节)

工具规范

在Spring AI中,工具通过ToolCallback接口构建。前文已介绍如何通过Spring AI内置支持从方法和函数定义工具(详见《方法作为工具》和《函数作为工具》章节)。本节将深入探讨工具规范的细节及其扩展方法。

工具回调(Tool Callback)

ToolCallback接口定义了AI模型可调用的工具,包含工具的定义执行逻辑 。若需从零开始自定义工具(如基于MCP客户端或ChatClient构建模块化代理应用),需实现此接口。

接口核心方法:

public interface ToolCallback {
    // 定义工具元数据,供模型决定调用时机与方式
    ToolDefinition getToolDefinition();  
    // 提供工具附加配置(如返回结果处理方式)
    ToolMetadata getToolMetadata();  
    // 执行工具调用并返回结果(基础方法)
    String call(String toolInput);  
    // 执行工具调用并携带上下文(支持扩展参数)
    String call(String toolInput, ToolContext toolContext);  
}

Spring AI提供内置实现:

  • MethodToolCallback:基于方法的工具实现
  • FunctionToolCallback:基于函数的工具实现

工具定义(Tool Definition)

ToolDefinition接口定义工具的元数据,供模型理解工具能力:

public interface ToolDefinition {
    String name();        // 工具名称,同一模型调用中需唯一
    String description(); // 工具描述,帮助模型理解用途
    String inputSchema(); // 输入参数的JSON模式(详见《JSON模式》章节)
}

示例:手动构建天气查询工具定义

ToolDefinition toolDefinition = ToolDefinition.builder()
    .name("currentWeather")
    .description("获取指定地点的天气信息")
    .inputSchema("""
        {
            "type": "object",
            "properties": {
                "location": {"type": "string"},
                "unit": {"type": "string", "enum": ["C", "F"]}
            },
            "required": ["location", "unit"]
        }
    """)
    .build();
方法工具定义(Method Tool Definition)

从方法生成工具时,ToolDefinition会自动创建。若需自定义,可通过ToolDefinition.Builder

Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
// 自动从方法生成(含@Tool注解信息)
ToolDefinition autoDef = ToolDefinition.from(method);  
// 手动覆盖部分属性
ToolDefinition customDef = ToolDefinition.builder(method)
    .name("currentDateTime")
    .description("获取用户时区的当前日期和时间")
    .inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
    .build();

机制说明

  • 默认使用方法名作为工具名称与描述
  • 若方法标注@Tool注解,则优先使用注解中的name和description
函数工具定义(Function Tool Definition)

从函数生成工具时,ToolDefinitionFunctionToolCallback.Builder自动构建:

Function<WeatherRequest, WeatherResponse> weatherFn = ...;
ToolCallback toolCallback = FunctionToolCallback.builder("currentWeather", weatherFn)
    .description("获取指定地点的天气信息")
    .inputType(WeatherRequest.class)
    .build();

关键点

  • 输入模式根据inputType自动生成
  • 支持通过@ToolParam注解补充参数描述

JSON Schema

向AI模型提供工具时,模型需要了解调用工具所需的输入参数模式(Schema)。该模式用于指导模型如何构造调用请求。Spring AI通过JsonSchemaGenerator类内置支持为工具生成输入参数的JSON Schema,并将其作为ToolDefinition的一部分。

描述(Description)

除了工具本身的描述外,还可为输入参数添加描述,帮助模型理解参数格式、允许值等。Spring AI支持以下注解定义参数描述(优先级从高到低):

  • @ToolParam(description = “…”)(Spring AI原生注解)
  • @JsonClassDescription(Jackson注解)
  • @JsonPropertyDescription(Jackson注解)
  • @Schema(description = “…”)(Swagger注解)

示例:通过**@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);
    }
}

此描述会嵌入生成的JSON Schema中,指导模型正确传递参数。

必填/可选(Required/Optional)

默认所有输入参数均为必填,模型调用工具时必须提供值。可通过以下注解将参数标记为可选(优先级从高到低):

  • @ToolParam(required = false)(Spring AI原生注解)
  • @JsonProperty(required = false)(Jackson注解)
  • @Schema(required = false)(Swagger注解)
  • @Nullable(Spring框架注解)

示例:将email参数设为可选

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

关键作用 :明确必填参数可减少模型"幻觉"(hallucination)。若未标记可选参数,模型可能在无实际值时虚构数据,导致错误。

生成逻辑与嵌套支持
  • JsonSchemaGenerator会递归处理嵌套类型(如POJO、枚举),支持复杂结构的Schema生成。
  • 对于方法和函数式工具,均支持通过注解自定义Schema

结果转换

工具调用的结果通过ToolCallResultConverter序列化为字符串后返回给AI模型。ToolCallResultConverter接口负责将工具返回的对象转换为字符串格式

@FunctionalInterface
public interface ToolCallResultConverter {
    String convert(@Nullable Object result, @Nullable Type returnType);
}

结果必须为可序列化类型。默认使用Jackson进行JSON序列化(DefaultToolCallResultConverter),但可通过自定义实现覆盖

方法工具的结果转换

声明式定义 :通过**@Tool注解的resultConverter**属性指定自定义转换器:

class CustomerTools {
    @Tool(
        description = "查询客户信息", 
        resultConverter = CustomToolCallResultConverter.class  // 自定义转换器
    )
    Customer getCustomerInfo(Long id) {
        return customerRepository.findById(id);
    }
}

编程式定义 :通过MethodToolCallback.Builder的resultConverter()方法设置:

MethodToolCallback toolCallback = MethodToolCallback.builder()
    .toolDefinition(...)
    .resultConverter(new CustomToolCallResultConverter())  // 设置转换器
    .build();
函数工具的结果转换

通过FunctionToolCallback.Builder的**resultConverter()**方法指定自定义转换器:

FunctionToolCallback toolCallback = FunctionToolCallback.builder("toolName", function)
    .resultConverter(new CustomToolCallResultConverter())  // 自定义转换逻辑
    .build();

默认行为

  • 默认使用DefaultToolCallResultConverter将结果转换为JSON(依赖Jackson)。
  • 若结果为void或null,返回空字符串。

自定义场景

  • 需要非JSON格式(如XML、纯文本)。
  • 需要对敏感数据进行过滤或格式化

工具上下文(Tool Context)

Spring AI通过ToolContext API支持向工具传递额外的上下文信息。此功能允许提供用户定义的附加数据,这些数据可在工具执行时与AI模型传递的参数结合使用。
在这里插入图片描述

传递上下文信息到工具

在工具方法中通过参数注入ToolContext,即可访问上下文数据:

class CustomerTools {
    @Tool(description = "查询客户信息")
    Customer getCustomerInfo(Long id, ToolContext toolContext) {
        // 使用上下文中的tenantId参数 [[10]]
        return customerRepository.findById(id, toolContext.get("tenantId")); 
    }
}

调用ChatClient时通过toolContext()方法设置上下文数据:

String response = ChatClient.create(chatModel)
    .prompt("请告诉我ID为42的客户信息")
    .tools(new CustomerTools())
    .toolContext(Map.of("tenantId", "acme"))  // 设置上下文参数
    .call()
    .content();
关键特性
  1. 数据隔离ToolContext中的数据不会发送给AI模型 ,仅在工具执行时可用。
  2. 多层级合并 :若默认配置和运行时均设置了toolContext,最终上下文会合并两者,且运行时配置优先级更高
  3. 直接调用ChatModel :通过ToolCallingChatOptions设置上下文:
ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolCallbacks(customerTools)
    .toolContext(Map.of("tenantId", "acme"))  // 直接设置上下文
    .build();
Prompt prompt = new Prompt("请告诉我ID为42的客户信息", chatOptions);
chatModel.call(prompt);

直接返回(Return Direct)

默认情况下,工具调用的结果会返回给模型,模型利用该结果继续对话流程。但在某些场景中,需直接将结果返回给调用者 ,而非发送回模型:

  • 使用RAG工具时,避免模型对结果进行不必要的后处理。
  • 某些工具需直接终止代理(Agent)的推理循环。
    在这里插入图片描述
配置方式

通过ToolCallbackreturnDirect属性控制返回行为:

  • 默认行为 :结果发送回模型(returnDirect = false)。
  • 直接返回 :设置returnDirect = true,结果直接返回调用者。

示例:声明式配置

@Tool(description = "查询客户信息", returnDirect = true)
Customer getCustomerInfo(Long id) { /* ... */ }

示例:编程式配置

ToolCallback toolCallback = MethodToolCallback.builder()
    .toolDefinition(...)
    .toolMetadata(ToolMetadata.builder().returnDirect(true).build())  // 启用直接返回
    .build();
多工具调用的限制

若一次请求调用多个工具,所有工具returnDirect需设为true,否则结果仍会发送回模型。

直接返回的流程
  1. 定义工具时启用returnDirect:在聊天请求中包含工具定义,并设置returnDirect = true。
  2. 模型发起调用 :模型生成包含工具名称和参数的调用请求。
  3. 应用执行工具 :根据工具名称和参数执行调用。
  4. 直接返回结果 :应用将结果直接返回给调用者,不经过模型处理

关键作用

  • 优化性能:减少模型不必要的后处理步骤。
  • 精确控制:直接返回敏感或结构化数据(如数据库查询结果)
方法直接返回(Method Return Direct)

当通过声明式方法构建工具时,可通过**@Tool注解的returnDirect**属性标记工具结果直接返回调用者(而非发送回模型):

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();
函数直接返回(Function Return Direct)

当通过编程式方法从函数构建工具时,同样可通过ToolMetadata接口设置returnDirect,并传递给FunctionToolCallback.Builder

ToolMetadata toolMetadata = ToolMetadata.builder()
    .returnDirect(true)  // 启用直接返回
    .build();

关键机制

  • 结果绕过模型:设置returnDirect = true后,工具结果直接返回客户端,不再传递给模型进行后续处理。
  • 多工具一致性 :若一次调用多个工具,所有工具需启用returnDirect,否则结果仍会发送回模型

工具执行

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

public interface ToolCallingManager {
    // 从聊天选项中解析工具定义
    List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);
    // 执行模型请求的工具调用
    ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);
}

若使用Spring AI的Spring Boot Starter,DefaultToolCallingManager会自动配置为默认实现。可通过自定义Bean覆盖默认行为:

@Bean
ToolCallingManager toolCallingManager() {
    return ToolCallingManager.builder().build();  // 自定义工具调用管理器
}

框架控制的工具执行(默认模式)

Spring AI默认自动拦截模型发起的工具调用请求,执行工具并返回结果。具体流程如下:
在这里插入图片描述

  1. 工具定义注入 :在聊天请求(Prompt)中包含工具定义,并调用ChatModel API发送请求至AI模型。
  2. 模型发起调用 :模型生成包含工具名称和参数的响应(ChatResponse)。
  3. 调用管理器处理 :ChatModel将工具调用请求传递给ToolCallingManager。
  4. 工具执行 :ToolCallingManager根据参数调用对应工具,获取结果。
  5. 结果返回模型 :工具结果经ToolCallingManager返回ChatModel,再通过ToolResponseMessage发送给AI模型。
  6. 生成最终响应 :模型结合工具结果生成最终响应(ChatResponse),通过ChatClient返回调用者。

限制 :框架内部的工具调用消息(如中间状态)不直接暴露给用户 。若需访问这些消息,需改用用户控制的工具执行模式

关键机制

  • 透明拦截 :ChatModel自动处理工具调用,无需手动干预。
  • 生命周期管理 :ToolCallingManager协调工具解析、执行与结果传递

用户控制的工具执行

在某些场景中,您可能需要手动控制工具执行的生命周期 。通过将ToolCallingChatOptions的internalToolExecutionEnabled属性设为false,调用ChatModel时工具执行将委托给调用者,从而完全掌控流程。

示例:用户控制工具执行的核心实现

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

异常处理

工具调用失败时,异常会以ToolExecutionException形式抛出。可通过ToolExecutionExceptionProcessor处理异常:

  • 返回错误信息 :将错误发送回模型继续处理。

  • 抛出异常 :由调用者直接处理。

默认异常处理器 :

@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
    return new DefaultToolExecutionExceptionProcessor(true); // true=抛出异常,false=返回错误信息
}

自定义工具异常处理 : 在自定义ToolCallback的call()方法中,需显式抛出ToolExecutionException:

public String call(String toolInput) {
    try {
        // 工具执行逻辑
    } catch (Exception e) {
        throw new ToolExecutionException("工具执行失败", e); // 包装为ToolExecutionException
    }
}

关键机制

  • 手动循环处理 :需持续检查ChatResponse中的工具调用请求,直至无剩余调用。
  • 对话历史维护 :每次工具调用后更新Prompt的对话历史,确保上下文连贯。
  • 异常传播控制 :通过DefaultToolExecutionExceptionProcessor配置是否中断流程

工具解析(Tool Resolution)

在Spring AI中,传递工具给模型的主要方式是通过ChatClientChatModel直接提供ToolCallback(如《方法作为工具》和《函数作为工具》中所述)。此外,Spring AI还支持通过ToolCallbackResolver接口在运行时动态解析工具

动态工具解析机制

核心接口

public interface ToolCallbackResolver {
    @Nullable
    ToolCallback resolve(String toolName);  // 根据工具名称解析对应的ToolCallback
}

使用流程

  1. 客户端 :向ChatClient或ChatModel传递工具名称(而非ToolCallback实例)。
  2. 服务端 :通过ToolCallbackResolver实现将工具名称映射为具体的ToolCallback

默认解析器链

Spring AI默认使用DelegatingToolCallbackResolver,其内部委托以下解析器依次处理:

  1. SpringBeanToolCallbackResolver
    • 从Spring上下文中解析Function、Supplier、Consumer或BiFunction

      类型的Bean作为工具。

    • 支持通过@Bean注解动态注册工具(详见《动态定义:@Bean》章节)。

  2. StaticToolCallbackResolver
    • 从静态列表中解析预定义的ToolCallback实例。
    • 若使用Spring Boot自动配置,会自动收集应用上下文中所有ToolCallback类型的Bean

自定义解析逻辑

可通过定义ToolCallbackResolver Bean覆盖默认行为:

@Bean
ToolCallbackResolver toolCallbackResolver(List<ToolCallback> toolCallbacks) {
    // 静态解析器:基于显式注册的ToolCallback列表
    StaticToolCallbackResolver staticResolver = new StaticToolCallbackResolver(toolCallbacks);
    // 组合解析器:支持多解析器链式处理
    return new DelegatingToolCallbackResolver(List.of(staticResolver));  // 可添加其他解析器
}
### Spring框架中的AI集成与项目 Spring生态系统提供了强大的基础设施支持,使得开发者能够更便捷地构建复杂的应用程序。对于希望将人工智能(AI)功能融入到基于Java的企业级应用中的开发人员来说,Spring提供了一系列工具和服务来简化这一过程[^1]。 #### 使用Spring Data JPA加速机器学习模型的数据准备 为了训练有效的机器学习算法,高质量的数据至关重要。通过利用`Spring Data JPA`组件,可以轻松实现数据库访问层的设计模式,从而高效获取用于训练的数据集。这不仅提高了数据检索效率,还促进了代码重用性和可维护性[^2]。 ```java public interface UserRepository extends JpaRepository<User, Long> { List<User> findByAgeGreaterThan(int age); } ``` #### 结合TensorFlow Serving部署预测服务 借助于Spring Boot的强大特性——微服务体系架构的支持能力,以及其内置的RESTful API生成功能,很容易搭建起一个服务于前端请求并返回推理结果的服务端点。特别是当与Google TensorFlow Serving相结合时,这种组合允许快速迭代模型版本的同时保持对外接口的一致性[^3]。 ```yaml server: port: 8080 tensorflow-serving: host: localhost model_name: my_model version: latest ``` ```python import requests def predict(data): url = f"http://{tf_serving_host}:8501/v1/models/{model_name}/versions/{version}:predict" response = requests.post(url, json=data) return response.json() ``` #### 利用Spring Cloud Stream处理实时流数据分析 随着物联网(IoT)设备数量的增长,在线分析变得越来越重要。Spring Cloud Stream为消息驱动型应用程序提供了一套声明式的编程模型,配合Apache Kafka或其他消息中间件一起工作,可用于接收来自传感器或者其他源头产生的连续事件流,并对其进行即时计算和响应[^4]。 ```properties spring.cloud.stream.bindings.input.destination=iot-events spring.cloud.stream.kafka.binder.brokers=localhost:9092 ``` ```java @StreamListener("input") public void processEvent(String event){ // Process incoming events here... } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NtK11KGXVkk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值