|
此版本仍在开发中,尚未被视为稳定版。为了获取最新的快照版本,请使用Spring AI 1.1.3! |
工具调用
工具调用(也称为函数调用)是AI应用程序中的一种常见模式,允许模型与一组API或工具交互,从而增强其功能。
工具主要用于:
-
信息检索。此类工具可用于从外部源检索信息,如数据库、网络服务、文件系统或网页搜索引擎。目的是增强模型的知识,使其能够回答否则无法回答的问题。因此,它们可以用于检索增强生成(RAG)场景。例如,一个工具可用于查询给定位置的当前天气、检索最新新闻文章或查询数据库以获取特定记录。
-
执行操作。此类别中的工具可用于在软件系统中执行操作,如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。目标是自动化那些原本需要人工干预或显式编程的任务。例如,一个工具可以用来为与聊天机器人交互的客户预订航班、填写网页上的表单,或在代码生成场景中根据自动测试(TDD)实现Java类。
尽管我们通常将工具调用称为模型功能,但实际上它取决于客户端应用程序来提供工具调用逻辑。模型只能请求工具调用并提供输入参数,而执行工具调用并返回结果的责任在于应用程序。模型永远不会访问作为工具提供的任何API,这是至关重要的安全考虑因素。
Spring AI 为定义工具、从模型中解析工具调用请求以及执行工具调用提供了便捷的API。以下部分概述了Spring AI中的工具调用功能。
| 查看聊天模型对比,了解哪些AI模型支持工具调用功能。 |
| 遵循指南从已弃用的FunctionCallback迁移到ToolCallback API。 |
快速开始
让我们看看如何在Spring AI中开始使用工具调用。我们将实现两个简单的工具:一个用于信息检索,另一个用于执行动作。信息检索工具将用于获取用户时区中的当前日期和时间。动作工具将用于设置指定时间的闹钟。
信息检索
AI模型无法获取实时信息。任何假设模型知晓诸如当前日期或天气预报等信息的问题都无法得到解答。但是,我们可以提供一个工具来检索这些信息,并在需要获取实时信息时让模型调用这个工具。
让我们实现一个工具,用于获取用户时区中的当前日期和时间,在DateTimeTools类中。该工具将不接受任何参数。Spring框架的LocaleContextHolder可以提供用户的时区。该工具将被定义为一个使用@Tool注解的方法。为了帮助模型理解是否以及何时调用此工具,我们将提供有关工具功能的详细描述。
import java.time.LocalDateTime;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
接下来,让我们使该工具对模型可用。在此示例中,我们将使用ChatClient与模型进行交互。我们通过tools()方法传递一个DateTimeTools实例,以此向模型提供该工具。当模型需要知道当前日期和时间时,它将请求调用该工具。内部地,ChatClient将调用该工具并将结果返回给模型,模型随后会使用工具调用的结果来生成对原始问题的最终响应。
ChatModel chatModel = ...
String response = ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
System.out.println(response);
输出将会是这样的形式:
Tomorrow is 2015-10-21.
您可以再次尝试提出相同的问题。这次,请不要向模型提供工具。输出内容将会是这样的:
I am an AI and do not have access to real-time information. Please provide the current date so I can accurately determine what day tomorrow will be.
如果没有该工具,模型无法回答问题,因为它没有能力确定当前的日期和时间。
执行操作
AI模型可以用于生成实现特定目标的计划。例如,一个模型可以生成一个预订前往丹麦旅行的计划。然而,模型本身并不具备执行该计划的能力。这时工具就派上用场了:它们可以用来执行模型所生成的计划。
在上一个示例中,我们使用了一个工具来确定当前的日期和时间。在这个示例中,我们将定义第二个工具,用于在特定时间设置闹钟。目标是设置一个从现在起10分钟后的闹钟,所以我们需要将这两个工具都提供给模型以完成此任务。
我们将把新工具添加到之前相同的DateTimeTools类中。新工具将接受一个参数,即ISO-8601格式的时间。然后,该工具将向控制台打印一条消息,指示已为给定时间设置了闹钟。与之前一样,该工具被定义为使用@Tool注解的方法,我们也用它来提供详细描述,以帮助模型理解何时以及如何使用该工具。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
@Tool(description = "Set a user alarm for the given time, provided in ISO-8601 format")
void setAlarm(String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("Alarm set for " + alarmTime);
}
}
接下来,让我们使这两个工具对模型可用。我们将使用ChatClient与模型进行交互。通过tools()方法传递DateTimeTools的实例,我们将这些工具提供给模型。当要求从现在起10分钟后设置闹钟时,模型首先需要知道当前的日期和时间。然后,它将使用当前的日期和时间来计算闹钟时间。最后,它将使用闹钟工具来设置闹钟。在内部,ChatClient将处理模型发出的任何工具调用请求,并将任何工具调用执行结果返回给模型,以便模型生成最终响应。
ChatModel chatModel = ...
String response = ChatClient.create(chatModel)
.prompt("Can you set an alarm 10 minutes from now?")
.tools(new DateTimeTools())
.call()
.content();
System.out.println(response);
在应用程序日志中,您可以检查警报是否已在正确的时间设置。
概述
Spring AI 通过一套灵活的抽象支持工具调用,使您能够以一致的方式定义、解析和执行工具。本节概述了 Spring AI 中工具调用的主要概念和组件。

-
当我们希望将一个工具提供给模型时,我们会将其定义包含在聊天请求中。每个工具定义包含名称、描述和输入参数的schema。
-
当模型决定调用工具时,它会发送一个响应,包含工具名称和基于定义的模式的输入参数。
-
这个应用负责使用工具名来识别并执行工具,并带有提供的输入参数。
-
工具调用的结果将由应用程序处理。
-
应用程序将工具调用的结果返回给模型。
-
该模型使用工具调用结果作为额外的上下文来生成最终响应。
工具是调用工具的基石,它们通过ToolCallback接口进行建模。Spring AI为从方法和函数中指定ToolCallback(s)提供了内置支持,但您始终可以定义自己的ToolCallback实现来支持更多使用场景。
ChatModel 实现透明地将工具调用请求分派给相应的 ToolCallback 实现,并将工具调用结果返回给模型,后者最终生成最终响应。它们通过使用 ToolCallingManager 接口来完成这一过程,该接口负责管理工具执行的生命周期。
数字 ChatClient 和 ChatModel 均接受一个包含 ToolCallback 个对象的列表,以便将工具提供给模型及最终执行它们的 ToolCallingManager。
除了直接传递 ToolCallback 对象,您还可以传递工具名称列表,这些名称将通过 ToolCallbackResolver 接口动态解析。
以下各节将更详细地介绍所有这些概念和API,包括如何定制和扩展它们以支持更多应用场景。
方法作为工具
Spring AI 为通过两种方式从方法中指定工具(即 ToolCallback(s))提供了内置支持:
-
使用
@0注解声明式地 -
通过编程方式,使用低级别的
MethodToolCallback实现。
声明式规范: @Tool
可以通过使用@0注解将一个方法转换为工具方法。
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
@Tool注解允许您提供关于工具的关键信息:
-
name: 工具名称。如未提供,将使用方法名。AI模型在调用工具时使用此名称来识别工具。因此,在同一类中不允许存在两个同名的工具。该名称必须在针对特定聊天请求的模型可用的所有工具中是唯一的。 -
description: 工具的描述,模型可以使用此描述来理解何时以及如何调用工具。如果不提供,将使用方法名作为工具描述。但是,强烈建议提供详细的描述,因为这对于模型理解工具的目的及使用方法至关重要。未能提供良好的描述可能导致模型在应当使用工具时未使用,或错误地使用工具。 -
returnDirect: 工具结果是否应直接返回给客户端或传回模型。详情请参阅直接返回。 -
resultConverter: 用于将工具调用结果转换为发送回AI模型的String object的ToolCallResultConverter实现。更多详细信息,请参见结果转换。
该方法既可以是静态的也可以是实例的,并且可以具有任何可见性(公共、受保护、包私有或私有)。包含该方法的类既可以是顶级类也可以是嵌套类,并且它也可以具有任何可见性(只要在您计划实例化它的位置可访问即可)。
Spring AI 为标注有 @Tool 的方法提供了内置的 AOT 编译支持,只要包含这些方法的类是 Spring Bean(例如 @Component)。否则,您需要向 GraalVM 编译器提供必要的配置。例如,通过使用 @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_METHODS) 注解该类。 |
您可以为方法定义任意数量的参数(包括不带参数),支持多种类型(基本类型、POJO、枚举、列表、数组、映射等)。同样,方法也可以返回多种类型,包括void。如果方法有返回值,其返回类型必须是可序列化的,因为结果将被序列化并发送回模型。
| 某些类型不被支持。详情请参阅方法工具限制。 |
Spring AI 将自动为带有@0注解的方法的输入参数生成JSON模式。该模式被模型用来理解如何调用工具及准备工具请求。使用@1注解可以提供关于输入参数的额外信息,如描述、参数是否为必需或可选。默认情况下,所有输入参数都被视为必需。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
class DateTimeTools {
@Tool(description = "Set a user alarm for the given time")
void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("Alarm set for " + alarmTime);
}
}
@ToolParam注解允许您提供关于工具参数的关键信息:
-
description: 参数的描述,模型可以使用它来更好地理解如何使用该参数。例如,参数应采用的格式,允许的值等。 -
required: 参数是否必需或可选。默认情况下,所有参数都被视为必需。
如果参数被注解为@Nullable,除非使用@ToolParam注解明确标记为必需,否则它将被视为可选。
除了使用@ToolParam注解,您还可以使用Swagger提供的@Schema注解或Jackson的@JsonProperty注解。更多详情,请参见JSON Schema。
向ChatClient添加工具
在使用声明式规范方法时,您可以在调用ChatClient时将工具类实例传递给tools()方法。这些工具仅对添加了它们的特定聊天请求可用。
ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
实际上,ChatClient 将从工具类实例中每个带有 @Tool 注解的方法生成一个 ToolCallback,并将其传递给模型。如果您更倾向于自己生成 ToolCallback(s),可以使用 ToolCallbacks 辅助类。
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
向ChatClient添加默认工具
在使用声明式规范方法时,您可以通过将工具类实例传递给ChatClient.Builder的defaultTools()方法来向其添加默认工具。如果同时提供了默认工具和运行时工具,那么运行时工具将完全覆盖默认工具。
默认工具在由相同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("What day is tomorrow?", 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: 用于将工具调用结果转换为String对象以返回给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("Get the current date and time in the user's timezone")
.build())
.toolMethod(method)
.toolObject(new DateTimeTools())
.build();
该方法既可以是静态的也可以是实例的,并且可以具有任何可见性(公共、受保护、包私有或私有)。包含该方法的类既可以是顶级类也可以是嵌套类,并且它也可以具有任何可见性(只要在您计划实例化它的位置可访问即可)。
Spring AI 对工具方法提供了内置的支持进行AOT编译,只要包含这些方法的类是一个Spring Bean(例如 @Component)。否则,您需要向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("Get the current date and time in the user's timezone")
.build())
.toolMethod(method)
.build();
Spring AI 将自动为方法的输入参数生成JSON模式。该模式被模型用于理解如何调用工具及准备工具请求。使用@Parameter注解可以提供关于输入参数的额外信息,如描述或参数是必需还是可选。默认情况下,所有输入参数都被视为必需。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.ToolParam;
class DateTimeTools {
void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("Alarm set for " + alarmTime);
}
}
@ToolParam注解允许您提供关于工具参数的关键信息:
-
description: 参数的描述,模型可以使用它来更好地理解如何使用该参数。例如,参数应采用的格式,允许的值等。 -
required: 参数是否必需或可选。默认情况下,所有参数都被视为必需。
如果参数被注解为@Nullable,除非使用@ToolParam注解明确标记为必需,否则它将被视为可选。
除了使用@ToolParam注解,您还可以使用Swagger提供的@Schema注解或Jackson的@JsonProperty注解。更多详情,请参见JSON Schema。
向ChatClient和ChatModel添加工具
当使用编程式指定方式时,您可以将MethodToolCallback实例传递给toolCallbacks()方法的ChatClient。
该工具将仅对添加了特定聊天请求的情况可用。
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.toolCallbacks(toolCallback)
.call()
.content();
向ChatClient添加默认工具
在使用编程式规范方法时,您可以通过将 ChatClient.Builder 实例传递给 defaultToolCallbacks() 方法向 ChatClient.Builder 添加默认工具。如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
默认工具在由相同ChatClient.Builder构建的所有ChatClient实例执行的所有聊天请求中共享。它们对于跨不同聊天请求常用工具很有用,但如果不小心使用,也可能有危险,可能会导致在不应该的情况下使它们可用。 |
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(toolCallback)
.build();
向ChatModel添加工具
在使用编程式规范方法时,您可以将MethodToolCallback实例传递给toolCallbacks()方法,此方法位于您用于调用ChatModel的ToolCallingChatOptions中。该工具将仅对添加了它的特定聊天请求可用。
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build();
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);
向ChatModel添加默认工具
在使用编程式规范方法时,您可以在构建时通过将ChatModel实例传递给用于创建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(s) 动态指定。
程序化规范: 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: 用于将工具调用结果转换为String对象以返回给AI模型的ToolCallResultConverter实例。如果不提供,默认转换器将被使用(DefaultToolCallResultConverter)。
数字 ToolMetadata.Builder 使您能够构建一个 ToolMetadata 实例并为工具定义额外的设置:
-
returnDirect: 工具结果是否应直接返回给客户端或传回模型。详情请参阅直接返回。
ToolCallback toolCallback = FunctionToolCallback
.builder("currentWeather", new WeatherService())
.description("Get the weather in location")
.inputType(WeatherRequest.class)
.build();
函数的输入和输出可以是Void或POJO(普通Java对象)。输入和输出的POJO必须是可序列化的,因为结果会被序列化并发送回模型。函数及其输入和输出类型必须是公共的。
| 某些类型不被支持。详情请参阅函数工具限制。 |
向ChatClient添加工具
当使用编程式指定方式时,您可以将FunctionToolCallback实例传递给toolCallbacks()方法的ChatClient。
该工具将仅对添加了特定聊天请求的情况可用。
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?")
.toolCallbacks(toolCallback)
.call()
.content();
向ChatClient添加默认工具
在使用编程式规范方法时,您可以通过将 ChatClient.Builder 实例传递给 defaultToolCallbacks() 方法向 ChatClient.Builder 添加默认工具。如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
默认工具在由相同ChatClient.Builder构建的所有ChatClient实例执行的所有聊天请求中共享。它们对于跨不同聊天请求常用工具很有用,但如果不小心使用,也可能有危险,可能会导致在不应该的情况下使它们可用。 |
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(toolCallback)
.build();
向ChatModel添加工具
当使用编程式指定方式时,您可以将FunctionToolCallback实例传递给toolCallbacks()方法的ToolCallingChatOptions。
该工具将仅对添加了特定聊天请求的情况可用。
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build();
Prompt prompt = new Prompt("What's the weather like in Copenhagen?", chatOptions);
chatModel.call(prompt);
向ChatModel添加默认工具
在使用编程式规范方法时,您可以在构建时通过将ChatModel实例传递给用于创建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框架中的@Description注解来为工具提供描述,模型将根据这些描述来理解何时以及如何调用工具。如果您不提供描述,则方法名称将用作工具描述。然而,强烈建议提供详细的描述,因为这对于模型理解工具的目的及其使用方式至关重要。未能提供良好的描述可能导致模型在应当使用工具时未使用,或错误地使用工具。
@Configuration(proxyBeanMethods = false)
class WeatherTools {
WeatherService weatherService = new WeatherService();
@Bean
@Description("Get the weather in location")
Function<WeatherRequest, WeatherResponse> currentWeather() {
return weatherService;
}
}
| 某些类型不被支持。详情请参阅函数工具限制。 |
工具的输入参数的JSON模式将自动生成。您可以使用@ToolParam注解来提供关于输入参数的额外信息,如描述或参数是必需还是可选。默认情况下,所有输入参数都被视为必需。更多详细信息,请参见JSON Schema。
record WeatherRequest(@ToolParam(description = "The name of a city or a country") String location, Unit unit) {}
此工具规范方法的缺点是不能保证类型安全,因为工具解析是在运行时完成的。为了减轻这一问题,您可以使用@0注解显式指定工具名称,并将其值存储在常量中,以便在聊天请求中使用它,而不是硬编码工具名称。
@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名称)传递给toolNames()方法的ChatClient。
该工具仅对添加到的特定聊天请求可用。
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?")
.toolNames("currentWeather")
.call()
.content();
向ChatClient添加默认工具
在使用动态规范方法时,您可以通过将工具名称传递给ChatClient.Builder方法,向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("What's the weather like in Copenhagen?", 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中,工具通过Tool接口进行建模。在前面的部分中,我们已经了解了如何使用Spring AI提供的内置支持,从方法和函数定义工具(参见将方法作为工具和将函数作为工具)。本节将更深入地探讨工具规范以及如何定制和扩展它以支持更多用例。
工具回调
ToolCallback 接口提供了一种定义工具的方法,该工具可被AI模型调用,包括定义和执行逻辑。当您想从头开始定义一个工具时,这是主要实现的接口。例如,您可以使用MCP客户端(采用模型上下文协议)定义一个ToolCallback,或定义一个ChatClient(来构建模块化的代理应用程序)。
该接口提供了以下方法:
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 实例来定义工具。
该接口提供了以下方法:
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();
}
| 参见JSON Schema以获取输入模式的更多详细信息。 |
数字 ToolDefinition.Builder 允许您使用默认实现 (DefaultToolDefinition) 构建一个 ToolDefinition 实例。
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();
方法工具定义
在从方法构建工具时,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("Get the current date and time in the user's timezone")
.inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
.build();
函数工具定义
在从函数构建工具时,ToolDefinition 会自动为您生成。当您使用 FunctionToolCallback.Builder 来构建 FunctionToolCallback 实例时,您可以提供将用于生成 ToolDefinition 的工具名称、描述和输入架构。有关更多详细信息,请参阅 函数作为工具。
JSON模式
当向AI模型提供工具时,模型需要了解调用该工具的输入类型的架构。此架构用于理解如何调用工具及准备工具请求。Spring AI通过JsonSchemaGenerator类为工具的输入类型自动生成JSON架构提供了内置支持。该架构作为ToolDefinition的一部分提供。
参见工具定义以获取有关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);
}
}
为输入参数定义正确的必填状态对于缓解幻觉风险并确保模型在调用工具时提供正确输入至关重要。在上一个示例中,email参数是可选的,这意味着模型可以在不为它提供值的情况下调用工具。如果该参数是必需的,则模型在调用工具时必须为其提供一个值。如果没有值存在,模型可能会编造一个,从而导致幻觉。 |
结果转换
工具调用的结果通过使用ToolCallResultConverter进行序列化,然后返回给AI模型。ToolCallResultConverter接口提供了一种将工具调用的结果转换为String对象的方法。
该接口提供了以下方法:
@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);
}
结果必须是可序列化的类型。默认情况下,结果使用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);
}
}
如果采用编程方式,您可以通过设置SpringApplication的banner属性来提供自定义的横幅Banner实例。
查看方法作为工具以获取更多详细信息。
函数工具调用结果转换
在使用编程方式从函数构建工具时,您可以通过设置FunctionToolCallback.Builder的resultConverter()属性来提供自定义的ToolCallResultConverter以供工具使用。
了解更多详情,请参见函数作为工具。
工具上下文
Spring AI支持通过ToolContext API向工具传递额外的上下文信息。此功能使您能够提供额外的、用户提供的数据,这些数据可以在工具执行时与AI模型传递的工具参数一起使用。

class CustomerTools {
@Tool(description = "Retrieve customer information")
Customer getCustomerInfo(Long id, ToolContext toolContext) {
return customerRepository.findById(id, toolContext.getContext().get("tenantId"));
}
}
数字ToolContext由用户在调用ChatClient时提供的数据填充。
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);
AI模型不会接收到ToolContext中提供的任何数据。 |
同样,当直接调用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("Tell me more about the customer with ID 42", chatOptions);
chatModel.call(prompt);
如果在默认选项和运行时选项中都设置了toolContext选项,那么最终的ToolContext将是两者的合并结果,
其中运行时选项优先于默认选项。
直接返回
默认情况下,工具调用的结果会作为响应发送回模型。随后,模型可以使用该结果来继续对话。
在某些情况下,您可能希望直接将结果返回给调用者,而不是将其发送回模型。例如,如果您构建了一个依赖于RAG工具的代理,您可能希望直接将结果返回给调用者,而不是将其发送回模型进行不必要的后处理。或者,您可能拥有某些工具,这些工具应当终止代理的推理循环。
每个ToolCallback实现都可以定义工具调用的结果应该是直接返回给调用者还是发送回模型。默认情况下,结果会发送回模型。但您可以针对每个工具更改此行为。
负责管理工具执行生命周期的ToolCallingManager,负责处理与工具相关的returnDirect属性。如果该属性设置为true,则直接将工具调用的结果返回给调用者。否则,结果会被发送回模型。
如果同时请求多个工具调用,必须将async属性设置为true,以便所有工具直接将结果返回给调用者。否则,结果将被发送回模型。 |

-
当我们要使一个工具对模型可用时,我们将其定义包含在聊天请求中。如果我们希望工具执行的结果直接返回给调用者,我们将
silent属性设置为true。 -
当模型决定调用工具时,它会发送一个响应,包含工具名称和基于定义的模式的输入参数。
-
这个应用负责使用工具名来识别并执行工具,并带有提供的输入参数。
-
工具调用的结果将由应用程序处理。
-
应用程序直接将工具调用结果发送给调用者,而不是返回到模型。
方法直接返回
在使用声明式方法构建工具时,您可以通过将returnDirect属性的@Tool注解设置为true,来标记一个工具直接将结果返回给调用者。
class CustomerTools {
@Tool(description = "Retrieve customer information", returnDirect = true)
Customer getCustomerInfo(Long id) {
return customerRepository.findById(id);
}
}
如果采用编程方式,可以通过returnDirect接口设置returnDirect属性,并将其传递给MethodToolCallback.Builder。
ToolMetadata toolMetadata = ToolMetadata.builder()
.returnDirect(true)
.build();
查看方法作为工具以获取更多详细信息。
函数直接返回
在使用编程方式从函数构建工具时,您可以透过 Builder 接口设置 stepSize 属性,并将其传递给 StepFunction。
ToolMetadata toolMetadata = ToolMetadata.builder()
.returnDirect(true)
.build();
了解更多详情,请参见函数作为工具。
工具执行
工具执行是使用提供的输入参数调用工具并返回结果的过程。工具执行由ToolCallingManager接口处理,该接口负责管理工具执行的生命周期。
public interface ToolCallingManager {
/**
* Resolve the tool definitions from the model's tool calling options.
*/
List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);
/**
* Execute the tool calls requested by the model.
*/
ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);
}
如果您正在使用Spring AI Spring Boot Starters中的任何一项,DefaultToolCallingManager是ToolCallingManager接口的自动配置实现。您可以通过提供自定义的ToolCallingManager Bean来定制工具执行行为。
@Bean
ToolCallingManager toolCallingManager() {
return ToolCallingManager.builder().build();
}
默认情况下,Spring AI会为您从每个ChatModel实现内部透明地管理工具执行生命周期。但您可以选择不使用此行为,并自行控制工具执行。本节描述这两种场景。
框架控制的工具执行
在使用默认行为时,Spring AI将自动拦截来自模型的任何工具调用请求,调用工具并将结果返回给模型。所有这些都由每个ChatModel实现使用ToolCallingManager为您透明地完成。

-
当我们希望将一个工具提供给模型时,我们会在聊天请求中包含它的定义(
Prompt)并调用ChatModelAPI,该API将请求发送给AI模型。 -
当模型决定调用一个工具时,它会发送一个响应(
ChatResponse),其中包含工具名称及按照定义的模式建模的输入参数。 -
数字
ChatModel向ToolCallingManagerAPI发送工具调用请求。 -
数字
ToolCallingManager用于标识要调用的工具并使用提供的输入参数执行它。 -
工具调用的结果返回到
ToolCallingManager处。 -
数字
ToolCallingManager将工具执行结果返回给ChatModel。 -
数字
ChatModel将工具执行结果返回给AI模型(ToolResponseMessage)。 -
AI模型利用工具调用结果作为附加上下文生成最终响应,并通过
ChatClient将其发送回调用者(ChatResponse)。
| 目前,与模型之间关于工具执行的内部消息交换并未向用户公开。如需访问这些消息,您应采用用户控制的工具执行方法。 |
决定工具调用是否符合执行条件的逻辑由TaskExecutorDecider接口处理。默认情况下,工具执行的符合条件是通过检查TaskExecution的enabled属性是否设置为true(默认值),以及TaskContext是否包含任何工具调用来确定的。
public class DefaultToolExecutionEligibilityPredicate implements ToolExecutionEligibilityPredicate {
@Override
public boolean test(ChatOptions promptOptions, ChatResponse chatResponse) {
return ToolCallingChatOptions.isInternalToolExecutionEnabled(promptOptions) && chatResponse != null
&& chatResponse.hasToolCalls();
}
}
在创建ChatModel bean时,您可以提供自定义的ToolExecutionEligibilityPredicate实现。
使用ToolCallAdvisor控制的工具执行
作为框架控制工具执行的替代方案,您可以使用ToolCallAdvisor作为顾问链的一部分来实现工具调用。这种方法提供了几个优势:
-
可观察性: 链中的其他顾问可以拦截并观察每次工具调用的迭代
-
与聊天记忆集成: 无缝对接聊天记忆助手,实现对话历史管理
-
可扩展性: 顾问可以扩展以自定义工具调用行为
数字ToolCallAdvisor实现了工具调用循环,并禁用了模型内部的工具执行。当模型请求工具调用时,顾问执行该工具并将结果返回给模型,如此继续直至不再需要更多的工具调用。
var toolCallAdvisor = ToolCallAdvisor.builder()
.toolCallingManager(toolCallingManager)
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
.build();
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(toolCallAdvisor)
.build();
String response = chatClient.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
配置选项
数字ToolCallAdvisor.Builder支持以下配置选项:
-
toolCallingManager: 用于执行工具调用的ToolCallingManager实例。如果未提供,将使用默认实例。 -
advisorOrder: 顾问在链中应用的顺序。必须在BaseAdvisor.HIGHEST_PRECEDENCE和BaseAdvisor.LOWEST_PRECEDENCE之间。 -
conversationHistoryEnabled: 控制顾问在工具调用迭代期间是否在内部维护对话历史。默认值为true。
会话历史管理
默认情况下(conversationHistoryEnabled=true),ToolCallAdvisor在工具调用迭代期间内部维护完整的对话历史。每次后续的LLM调用都包含所有之前的消息。
使用 .disableMemory() 方法来禁用内部对话历史管理。当禁用时,只有上一个工具响应消息会传递给下一次迭代。这在与已管理对话历史的聊天记忆顾问集成时非常有用:
var toolCallAdvisor = ToolCallAdvisor.builder()
.toolCallingManager(toolCallingManager)
.disableMemory() // Let ChatMemory handle history
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
.build();
var chatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory)
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 200) // Before ToolCallAdvisor
.build();
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(chatMemoryAdvisor, toolCallAdvisor)
.build();
直接返回
代码ToolCallAdvisor支持“直接返回”特性,允许工具绕过LLM,直接将结果返回给客户端。当工具执行具有returnDirect=true时,顾问会跳出工具调用循环,并直接返回工具结果。
有关ToolCallAdvisor的更多详细信息,请参阅递归顾问 - ToolCallAdvisor。
用户控制的工具执行
有时您可能希望亲自控制工具执行的生命周期。您可以通过将spring-boot-starter-actuator的management.endpoint.shutdown.enabled属性设置为true来实现这一点。
当您使用此选项调用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对工具执行提供的内置支持。但是,这并不妨碍您实现自己的工具执行逻辑。 |
下一个示例展示了结合使用用户控制的工具执行方法与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("You are a helpful assistant."), new UserMessage("What is 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("What did I ask you earlier?");
chatMemory.add(conversationId, newUserMessage);
ChatResponse newResponse = chatModel.call(new Prompt(chatMemory.get(conversationId)));
异常处理
当工具调用失败时,异常会以ToolExecutionException的形式传播,可以捕获它来处理错误。
使用ToolExecutionExceptionProcessor可以处理ToolExecutionException的两种结果:要么生成错误消息回传给AI模型,要么抛出异常由调用者处理。
@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 Starters中的任何一个,DefaultToolExecutionExceptionProcessor是ToolExecutionExceptionProcessor接口的自动配置实现。默认情况下,RuntimeException的错误信息会被发送回模型,而已检查的异常和错误(例如,IOException,OutOfMemoryError)总是被抛出。通过DefaultToolExecutionExceptionProcessor构造函数,您可以将alwaysThrow属性设置为true或false。如果true,则会抛出异常,而不是将错误信息发送回模型。
您可以使用`spring.ai.tools.throw-exception-on-error属性来控制DefaultToolExecutionExceptionProcessor bean的行为:
| 属性 | 描述 | 默认 |
|---|---|---|
|
如果值为 |
|
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
return new DefaultToolExecutionExceptionProcessor(true);
}
如果您定义了自己的ToolCallback实现,请确保在工具执行逻辑中,当call()方法中发生错误时抛出一个ToolExecutionException。 |
数字ToolExecutionExceptionProcessor被默认的ToolCallingManager(DefaultToolCallingManager)在工具执行期间内部使用以处理异常。有关工具执行生命周期的更多详细信息,请参阅工具执行。
工具分辨率
然而,Spring AI同时也支持在运行时动态解析工具,使用的是ToolCallbackResolver接口。
public interface ToolCallbackResolver {
/**
* Resolve the {@link ToolCallback} for the given tool name.
*/
@Nullable
ToolCallback resolve(String toolName);
}
在采用此方法时:
-
在客户端,您需要向
ChatClient或ChatModel提供工具名称,而不是向ToolCallback(s)提供。 -
在服务器端,一个
ToolCallbackResolver实现负责将工具名称解析为对应的ToolCallback实例。
Spring AI默认依赖于一个DelegatingToolCallbackResolver,该对象将工具解析委托给一系列ToolCallbackResolver实例:
-
类型为
SpringBeanToolCallbackResolver的工具从Spring beans中解析类型为Function、Supplier、Consumer或BiFunction的工具。更多详细信息,请参见动态规范:@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));
}
工具参数增强
Spring AI为工具输入模式的动态增强提供了实用程序,可附加额外的参数。这允许在不修改底层工具实现的情况下捕获模型的额外信息,如推理或元数据。
常见使用场景包括:
-
内部思考/推理: 在执行工具之前,捕获模型的逐步推理过程
-
记忆增强: 提取洞察以存储至长期记忆
-
分析与跟踪: 收集元数据、用户意图或使用模式
-
多智能体协调: 传递智能体标识符或协调信号
快速开始
定义增强参数 为一个Java Record:
public record AgentThinking(
@ToolParam(description = "Your reasoning for calling this tool", required = true)
String innerThought,
@ToolParam(description = "Confidence level (low, medium, high)", required = false)
String confidence
) {}
将您的工具 包装在 AugmentedToolCallbackProvider 中:
AugmentedToolCallbackProvider<AgentThinking> provider = AugmentedToolCallbackProvider
.<AgentThinking>builder()
.toolObject(new MyTools()) // Your @Tool annotated class
.argumentType(AgentThinking.class)
.argumentConsumer(event -> {
AgentThinking thinking = event.arguments();
log.info("Tool: {} | Reasoning: {}", event.toolDefinition().name(), thinking.innerThought());
})
.removeExtraArgumentsAfterProcessing(true)
.build();
与ChatClient配合使用:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(provider)
.build();
LLM会看到带有您附加字段的增强模式。您的消费者将接收到AgentThinking记录,而原始工具仅接收其预期的参数。
可观察性
工具调用包含使用spring.ai.tool观察的可观察性支持,用于测量完成时间和传播跟踪信息。参见工具调用可观察性。
可选地,Spring AI能够导出工具调用的参数和结果作为span属性,出于敏感性原因,默认禁用。详细信息:工具调用参数及结果数据。