使用Spring AI定义模型可调用的工具

By ref-nobody 创建时间 2025年5月1日 | 本文最后更新于 2025年5月9日 #ai, #function calling, #spring, #翻译

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

工具主要用于以下方面:

  • 信息检索。这一类别的工具可用于从外部来源检索信息,例如数据库、网络服务、文件系统或网络搜索引擎。其目标是增强模型的知识,使其能够回答原始模型无法回答的问题。因此,它们可用于检索增强生成(Retrieval Augmented Generation,RAG)场景。例如,工具可用于检索某个地点的当前天气、检索最新的新闻文章或查询数据库中的特定记录。
  • 采取行动。这一类别的工具可用于在软件系统中采取行动,例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。其目标是自动化那些需要人工干预或明确编程的任务。例如,工具可用于为与聊天机器人互动的客户预订航班、在网页上填写表单或在代码生成场景中根据自动化测试(测试驱动开发,TDD)实现 Java 类。

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

Spring AI 提供了方便的 API,用于定义工具、解析模型的工具调用请求以及执行工具调用。以下各节将概述 Spring AI 中的工具调用功能。

查看各模型支持的不同的能力图谱:Chat Models Comparison

概述

Spring AI 通过一组灵活的抽象支持工具调用,这些抽象使您能够以一致的方式定义、解析和执行工具。

  1. 当我们希望让模型能够使用某个工具时,我们会将其定义包含在聊天请求中。每个工具定义包括一个名称描述以及输入参数的模式(schema)。
  2. 当模型决定调用一个工具时,它会发送一个包含工具名称和按照已定义模式构建的输入参数的响应。
  3. 应用程序负责使用工具名称来识别并使用提供的输入参数执行该工具。
  4. 工具调用的结果由应用程序处理。
  5. 应用程序将工具调用的结果发送回模型。
  6. 模型使用工具调用的结果作为附加上下文,生成最终的响应。

可以看到当中间增加了工具调用之后,调用大模型的次数从一次变为了两次,如果中间需要多个工具的场景,则会多次调用模型,相应的费用也会增加。

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

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

ChatClientChatModel 都接受一个 ToolCallback 对象列表,以便将工具提供给模型,并最终由 ToolCallingManager 执行这些工具。

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

使用方法作为大模型调用的工具

Spring AI 提供了两种内置方式,用于从方法中指定工具(即 ToolCallback):

  1. 声明式:使用 @Tool 注解。
  2. 编程式:使用低级的 MethodToolCallback 实现。

声明式注解@Tools来定义

示例:

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

}

该方法可以是静态的,也可以是实例方法,并且可以具有任何访问权限(publicprotected、包私有或 private)。包含该方法的类可以是顶级类,也可以是嵌套类,并且它也可以具有任何访问权限(只要在你计划实例化它的地方可以访问即可)。

你可以为该方法定义任意数量的参数(包括零个参数),并且参数类型可以是大多数常见类型(基本数据类型、POJOs、枚举、列表、数组、映射等)。同样,该方法可以返回大多数类型,包括 void。如果方法返回一个值,则返回类型必须是可序列化的类型,因为结果将被序列化并发送回模型。

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

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

函数式类型可以通过基于函数的工具定义方法来支持。

Spring AI 会自动为带有 @Tool 注解的方法的输入参数生成 JSON 模式(schema)。该模式用于让模型了解如何调用工具以及如何准备工具请求。可以使用 @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);
    }

}

如果一个参数被标注为 @Nullable,它将被视为可选的,除非明确使用 @ToolParam 注解标记为必需。
除了 @ToolParam 注解之外,你还可以使用来自 Swagger 的 @Schema 注解或来自 Jackson 的 @JsonProperty 注解。

将工具添加到ChatClient

当我们使用ChatClient来调用模型的时候,可以将创建的工具的实例对象传给tools方法,这种方式下这个工具只会在我们发送给大模型的指定请求中才会生效。

    @GetMapping("/ai/dateTool")
    public String getCurrentDateTime() {
        return ChatClient.create(chatModel)
                .prompt("current time")
                .tools(new DateTimeTools())
                .call()
                .content();
    }

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

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

ChatClient添加默认的工具

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

默认工具会被所有从同一个 ChatClient.Builder 构建的 ChatClient 实例所执行的所有模型请求共享。它们适用于在不同模型请求中频繁使用的工具,但如果使用不当,可能会带来风险,导致这些工具在不应该使用时被调用。

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

将工具添加到ChatModel

当使用声明式定义方法时,你可以将工具类实例传递给ChatModelToolCallingChatOptionstoolCallbacks() 方法。这些工具仅会在它们被添加到的特定ChatModel中可用。

ChatModel中的使用方式与在ChatClient中的使用类似,不再赘述。

编程式调用MethodToolCallback

在编程式调用中,我们只需要像平时编写类和方法一样来定义作为工具的类。

class DateTimeTools {

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

}

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

  • toolDefinitionToolDefinition 实例定义了工具的名称、描述和输入模式。你可以使用 ToolDefinition.Builder 类来构建它。必需
  • toolMetadataToolMetadata 实例来定义额外的设置信息 ,例如结果是否应直接返回给客户端,以及要使用的转换器。你可以使用 ToolMetadata.Builder 类来构建它。
  • toolMethodMethod 实例来表示工具使用的方法。必需
  • toolObject:工具方法所在的类的对象实例。如果方法是静态的,可以省略此参数。
  • toolCallResultConverterToolCallResultConverter 实例用于将工具调用的结果转换为字符串对象并发送回 AI 模型的。如果未提供,将使用默认转换器(DefaultToolCallResultConverter)。
ToolDefinition.Builder

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

  • inputSchema:工具输入参数的 JSON 模式。如果未提供,将根据方法参数自动生成模式。你可以使用 @ToolParam 注解来提供有关输入参数的额外信息,例如参数的描述、参数是必需的还是可选的。默认情况下,所有输入参数都被视为必需的。
  • name:工具的名称。如果未提供,将使用方法名称。AI 模型在调用工具时会使用此名称来识别工具。因此,同一个类中不允许有两个同名的工具。该名称必须在模型可用于特定聊天请求的所有工具中是唯一的。
  • description:工具的描述,模型可以使用它来了解何时以及如何调用工具。如果未提供,将使用方法名称作为工具描述。然而,强烈建议提供详细的描述,因为这对于模型理解工具的用途以及如何使用它至关重要。未能提供良好的描述可能导致模型在应该使用工具时没有使用它,或者错误地使用它。
ToolMetadata.Builder

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

  • returnDirect:工具结果是否应直接返回给客户端,还是传递回模型。更多详情请参阅 直接返回
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();

如果方法是静态方法,则可以省略toolObject。

使用函数作为大模型调用的工具(
Functions as Tools)

Spring AI 提供了从function中指定工具的内置支持,既可以用原始的 FunctionToolCallback 编程方式来加载,也可以作为 @Bean 在运行时动态解析。

使用编程式FunctionToolCallback

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

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实例。

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

有了ToolCallBack实例,就可以按照上面说过的方式添加给ChatClient或者ChatModel来使用了。

使用@Bean配置

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

@Configuration(proxyBeanMethods = false)
class WeatherTools {

    WeatherService weatherService = new WeatherService();

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

}

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

@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() {
		...
	}

}

使用这种方式将工具声明为bean,在使用的时候,可以通过工具的名称来引用它。

在ChatClient中使用工具

当使用动态定义方法时,你可以将工具名称(即函数 Bean 的名称)传递给 ChatClienttools() 方法。该工具将仅在它被添加到的特定的模型请求中可用。

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

其他的在ChatModel中使用和设置默认工具的使用方式类似,不再赘述。

工具指南

在 Spring AI 中,工具是通过 ToolCallback 接口建模的。在前面的部分中,我们已经介绍了如何使用 Spring AI 提供的内置支持通过方法和函数的方式定义工具。下面将深入探讨工具定义的细节,以及如何对其进行定制和扩展以支持更多场景。

Tool Callback

ToolCallback 接口提供了一种定义可以被 AI 模型调用的工具的方式,包括工具的定义逻辑和执行逻辑。当你想要从头开始定义一个工具时,这是需要实现的主要接口。例如,你可以使用模型上下文协议(Model Context Protocol)的 MCP ClientChatClient(用于构建模块化的代理应用程序)来定义一个 ToolCallback

在spring ai中已经内置定义了方法工具MethodToolCallback和函数工具FunctionToolCallback两种ToolCallback的实现。

JSON模式

当向 AI 模型提供一个工具时,模型需要了解调用该工具的输入类型的模式(schema)。这个模式用于指导模型如何调用工具以及如何准备工具请求。Spring AI 通过 JsonSchemaGenerator 类提供了内置支持,用于生成工具输入类型的 JSON 模式。该模式作为 ToolDefinition 的一部分。

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

下面将介绍在为工具的输入参数生成 JSON 模式时可以自定义的两个主要选项:参数的描述是否为必填项

参数的描述

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

  • @ToolParam(description = “…​”) 来自 Spring AI
  • @JsonClassDescription(description = “…​”) 来自 Jackson
  • @JsonPropertyDescription(description = “…​”) 来自 Jackson
  • @Schema(description = “…​”) 来自 Swagger

这种方法既适用于方法,也适用于函数,并且你可以递归地将其用于嵌套类型。

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

class DateTimeTools {

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

}

是否为必填项

默认情况下,每个输入参数都被视为必需的,这会强制 AI 模型在调用工具时为其提供一个值。然而,你可以通过使用以下注解之一(按优先级顺序)将输入参数设为可选:

  • @ToolParam(required = false) 来自 Spring AI
  • @JsonProperty(required = false) 来自 Jackson
  • @Schema(required = false) 来自 Swagger
  • @Nullable 来自 Spring 框架

这种方法既适用于方法,也适用于函数,并且你可以递归地将其用于嵌套类型。

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

}

正确定义输入参数的必填状态对于减少幻觉(hallucinations)的风险并确保模型在调用工具时提供正确的输入至关重要。在前面的例子中,email 参数是可选的,这意味着模型可以在不提供该参数值的情况下调用工具。如果该参数是必需的,模型在调用工具时必须为其提供一个值。如果不存在该值,模型可能会自行生成一个值,从而导致幻觉现象。

结果转换

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

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

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

方法工具调用的结果集转换

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

class CustomerTools {

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

}

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

函数调用的结果集转换

当使用编程式方法以函数的方式定义工具时,你可以通过设置 FunctionToolCallback.BuilderresultConverter() 属性,为工具提供一个自定义的 ToolCallResultConverter

工具上下文

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

class CustomerTools {

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

}

toolContext会通过我们在使用ChatClient/ChatModel进行调用的时候传递的参数来计算。

ChatModel chatModel = ...

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

System.out.println(response);

需要注意的是ToolContext中的数据不会传递给AI模型。

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

工具调用结果直接返回

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

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

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

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

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

1. 当我们希望让模型能够使用某个工具时,我们会将其定义包含在模型请求中。如果我们希望工具执行的结果直接返回给调用者,我们将 returnDirect 属性设置为 true
2. 当模型决定调用一个工具时,模型的响应中会包含工具名称和按照已定义模式构建的输入参数。
3. 应用程序负责使用工具名称来识别并使用提供的输入参数执行该工具。
4. 工具调用的结果由应用程序处理。
5. 应用程序将工具调用的结果直接发送给调用者,而不是将其发送回模型。

工具执行的生命周期

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

如果你使用了任何一个 Spring AI 的 Spring Boot Starter,DefaultToolCallingManagerToolCallingManager 接口的自动配置实现。你可以通过提供自己的 ToolCallingManager Bean 来自定义工具执行的行为。

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

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

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

1. 当我们希望让模型能够使用某个工具时,我们会将其定义包含在模型请求(Prompt)中,并调用 ChatModel API,将请求发送给 AI 模型。
2. 当模型决定调用一个工具时,它会发送一个响应(ChatResponse),其中包含工具名称和按照已定义模式构建的输入参数。
3. ChatModel 将工具调用请求发送给 ToolCallingManager API。
4. ToolCallingManager 负责识别要调用的工具,并使用提供的输入参数执行该工具。
5. 工具调用的结果返回给 ToolCallingManager
6. ToolCallingManager 将工具执行的结果返回给 ChatModel
7. ChatModel 将工具执行的结果发送回 AI 模型(ToolResponseMessage)。
8. AI 模型使用工具调用的结果作为附加上下文,生成最终响应,并通过 ChatClient 将其发送回调用者(ChatResponse)。

目前,与模型之间关于工具执行的内部消息交换并未向用户暴露。如果你需要访问这些消息,需要自定义来控制工具执行周期。

确定某个工具调用是否符合执行条件的逻辑由 ToolExecutionEligibilityPredicate 接口处理。默认情况下,工具执行的资格通过检查 ToolCallingChatOptionsinternalToolExecutionEnabled 属性是否设置为 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 实现。

自定义控制工具执行流程

在某些情况下,你可能更倾向于自行控制工具执行的生命周期。你可以通过将 ToolCallingChatOptionsinternalToolExecutionEnabled 属性设置为 false 来实现这一点。

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

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

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

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

ChatResponse chatResponse = chatModel.call(prompt);

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

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

    chatResponse = chatModel.call(prompt);
}

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

当选择自定义控制工具执行周期时,建议使用 ToolCallingManager 来管理工具调用操作。这样,你可以利用 Spring AI 提供的内置工具执行支持。然而,这并不妨碍你实现自己的工具执行逻辑。

动态解析工具调用

将工具传递给模型的主要方法是在调用 ChatClientChatModel 时提供 ToolCallback,然而,Spring AI 还支持通过 ToolCallbackResolver 接口在运行时动态解析工具。当使用这种方法时:

  • 在客户端,你向 ChatClientChatModel 提供工具名称,而不是 ToolCallback 实例。
  • 在服务器端,一个 ToolCallbackResolver 实现负责将工具名称解析为对应的 ToolCallback 实例。

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

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

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

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

ToolCallbackResolverToolCallingManager 在内部用于在运行时动态解析工具。

问题记录

在使用ollama在本地部署chat和embedding模型进行测试的时候,本地使用的模型如下:
gemma3:4b,qwen3:4b,nomic-embed-text:latest,其中gemma3不支持tools调用,如果在该模型上使用tools会出现异常:400 - {"error":"registry.ollama.ai/library/gemma3:4b does not support tools"}

Leave a Reply

Your email address will not be published. Required fields are marked *

目录