工具调用(Tool Calling)
工具调用(亦称函数调用)是 AI 应用中的一种常见模式,它使模型能够与一组 API 或工具交互,从而扩展其功能。
工具主要用于:
信息检索。此类工具可用于从外部来源(如数据库、网络服务、文件系统或网络搜索引擎
)检索信息。其目的是扩充模型的知识,使其能够回答原本无法解答的问题。因此,它们可应用于检索增强生成(RAG)场景。例如,可以使用一个工具来检索给定地点的当前天气、获取最新新闻文章或查询数据库中的特定记录。
采取行动。此类工具可用于在软件系统中执行操作,例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流程。其目标是自动化那些原本需要人工干预或显式编程的任务。举例来说,可利用这类工具为与聊天机器人互动的客户预订航班、填写网页表单,或在代码生成场景中基于自动化测试(TDD)实现 Java 类。
尽管我们通常将工具调用视为模型的一项能力,但实际上,提供工具调用逻辑的是客户端应用程序。模型只能请求工具调用并提供输入参数,而应用程序则负责根据这些输入参数执行工具调用并返回结果。模型永远无法访问作为工具提供的任何 API,这是一个至关重要的安全考虑。
Spring AI 提供了便捷的 API,用于定义工具、解析模型发出的工具调用请求并执行这些调用。以下部分概述了 Spring AI 中的工具调用功能。
快速入门
让我们看看如何在 Spring AI 中开始使用工具调用。我们将实现两个简单的工具:一个用于信息检索,另一个用于执行操作。信息检索工具将用于获取用户所在时区的当前日期和时间。操作工具则用于设置指定时间的闹钟。
信息检索(Information Retrieval)
AI 模型无法获取实时信息。任何假设了解当前日期或天气预报等信息的提问,模型都无法直接回答。然而,我们可以提供一个工具来检索这些信息,并在需要实时信息时让模型调用该工具。
让我们在‘DateTimeTools
‘类中实现一个工具,用于获取用户所在时区的当前日期和时间。该工具不需要任何参数。Spring 框架中的‘LocaleContextHolder
‘可以提供用户的时区。该工具将定义为一个带有‘@Tool‘注解的方法。为了帮助模型理解是否以及何时调用此工具,我们将提供该工具功能的详细描述。
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.
如果没有该工具,模型就不知道如何回答问题,因为它没有能力确定当前的日期和时间。
采取行动(Taking Actions)
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 中工具调用的主要概念和组件。
- 当我们想要让模型可以使用某个工具时,我们会将其定义包含在聊天请求中。每个工具定义包含名称、描述和输入参数的模式。
- 当模型决定调用某个工具时,它会发送一个响应,其中包含工具名称和根据定义的模式建模的输入参数。
- 应用程序负责使用工具名称来识别并使用提供的输入参数来执行该工具。
- 工具调用的结果由应用程序处理。
- 应用程序将工具调用结果发送回模型。
- 该模型使用工具调用结果作为附加上下文来生成最终响应。
工具是工具调用的基石,它们通过 ToolCallback 接口进行建模。Spring AI 内置了从方法和函数指定 ToolCallback 的支持,但您始终可以定义自己的 ToolCallback 实现,以支持更多用例。
ChatModel 实现透明地将工具调用请求分派给相应的 ToolCallback 实现,并将工具调用结果发送回模型,最终生成最终响应。这一过程通过 ToolCallingManager 接口完成,该接口负责管理工具执行的生命周期。
ChatClient 和 ChatModel 均接受一系列 ToolCallback 对象,以便将工具提供给模型及最终执行它们的 ToolCallingManager 使用。
除了直接传递 ToolCallback 对象外,您还可以传递一个工具名称列表,这些名称将通过 ToolCallbackResolver 接口动态解析。
接下来的章节将深入探讨所有这些概念和 API,包括如何定制和扩展它们以支持更多用例。
方法作为工具(Methods as Tools)
Spring AI 提供了两种内置方式来从方法中指定工具(即 ToolCallback (s)):
- 声明式地使用@Tool注解
- 以编程方式使用低级MethodToolCallback实现。
声明性规范:@Tool
你可以通过使用 @Tool
注解将方法转换为工具。
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 模型的 ToolCallResultConverter 实现。
该方法既可以是静态的也可以是实例的,并且可以具有任何可见性(公共、受保护、包私有或私有)。包含该方法的类可以是顶级类或嵌套类,同样可以具有任何可见性(只要在您计划实例化的地方可访问)。
Spring AI 为 @Tool 注解的方法提供了内置的 AOT 编译支持,只要包含这些方法的类是 Spring bean(例如 @Component)。否则,您需要向 GraalVM 编译器提供必要的配置。例如,通过使用 @RegisterReflection (memberCategories = MemberCategory.INVOKE_DECLARED_METHODS) 注解类。
您可以为该方法定义任意数量的参数(包括无参数),这些参数可以是大多数类型(基本类型、POJO、枚举、列表、数组、映射等)。同样,该方法可以返回大多数类型,包括 void。如果方法返回值,则返回类型必须是可序列化的类型,因为结果将被序列化并发送回模型。
Spring AI 将自动为带有 @Tool 注解的方法生成输入参数的 JSON 模式。该模式用于让模型理解如何调用工具并准备工具请求。可以使用 @ToolParam 注解来提供关于输入参数的额外信息,例如描述或参数是否为必填项或可选项。默认情况下,所有输入参数都被视为必填项。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
class DateTimeTools {
@Tool(description = "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:参数是否为必填项或可选项。默认情况下,所有参数均被视为必填项。
除了 @ToolParam
注解外,您还可以使用 Swagger 中的 @Schema
注解或 Jackson 中的 @JsonProperty
。
向 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 添加默认工具
采用声明式规范方法时,您可以通过将工具类实例传递给‘defaultTools ()‘方法,向‘ChatClient.Builder‘添加默认工具。若同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
默认工具在所有通过同一 ChatClient.Builder 构建的 ChatClient 实例执行的聊天请求间共享。这些工具对于在不同聊天请求中常用的功能非常有用,但若使用不当也可能带来风险,可能导致本不应可用的工具被错误地暴露出来。
ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new DateTimeTools())
.build();
向 ChatModel 添加工具
在使用声明式规范方法时,您可以将工具类实例传递给调用 ChatModel 时所使用的 ToolCallingChatOptions 的 toolCallbacks () 方法。此类工具仅在添加到特定聊天请求时可用。
ChatModel chatModel = ...
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build();
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);
向 ChatModel 添加默认工具
采用声明式规范方法时,您可以在构建时通过将工具类实例传递给用于创建 ChatModel 的 ToolCallingChatOptions 实例的 toolCallbacks () 方法,来添加默认工具。如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
默认工具由该 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:oolMetadata 实例,用于定义额外设置,例如是否应将结果直接返回给客户端,以及要使用的结果转换器。您可以使用 ToolMetadata.Builder 类来构建它。
- toolMetadata:- toolDefinition:oolMetadata 实例,用于定义额外设置,例如是否应将结果直接返回给客户端,以及要使用的结果转换器。您可以使用 ToolMetadata.Builder 类来构建它。
- toolMethod:代表工具方法的 Method 实例。必需。
- toolObject:含工具方法的对象实例。如果方法是静态的,可以省略此参数。
- toolCallResultConverter:用于将工具调用结果转换为字符串对象以便发送回 AI 模型的 ToolCallResultConverter 实例。若未提供,则将使用默认转换器(DefaultToolCallResultConverter)。
ToolDefinition.Builder 允许您构建一个 ToolDefinition 实例,并定义工具名称、描述和输入架构:
- name:工具的名称。如未提供,将使用方法名称。AI 模型通过此名称在调用时识别工具。因此,在同一类中不允许存在两个名称相同的工具。对于特定聊天请求,该名称在所有可用工具中必须是唯一的。
- description:该工具的描述,模型可借此理解何时以及如何调用此工具。若未提供描述,则默认使用方法名称作为工具描述。然而,强烈建议提供详细描述,因为这对于模型理解工具的用途及使用方法至关重要。未能提供良好的描述可能导致模型在该使用时未调用工具,或错误地使用工具。
- inputSchema:工具的输入参数的 JSON 模式。如果未提供,将根据方法参数自动生成模式。您可以使用 @ToolParam 注解来提供关于输入参数的额外信息,例如描述或参数是否为必需或可选。默认情况下,所有输入参数都被视为必需。更多详情请参见 JSON 模式
ToolMetadata.Builder 允许您构建一个 ToolMetadata 实例,并为工具定义额外的设置:
- returnDirect:工具结果应直接返回给客户端还是传回模型。
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinitions.builder(method)
.description("Get the current date and time in the user's timezone")
.build())
.toolMethod(method)
.toolObject(new DateTimeTools())
.build();
您可以为该方法定义任意数量的参数(包括无参数),这些参数可以是大多数类型(基本类型、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 模式。该模式被模型用来理解如何调用工具并准备工具请求。@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。
向 ChatClient 与 ChatModel 添加工具
采用程序化规范方法时,您可以将 MethodToolCallback 实例传递给 ChatClient 的 toolCallbacks () 方法。该工具仅对添加它的特定聊天请求可用。
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.toolCallbacks(toolCallback)
.call()
.content();
向 ChatClient 添加默认工具
采用编程规范方法时,您可以通过将 MethodToolCallback 实例传递给 defaultToolCallbacks () 方法,向 ChatClient.Builder 添加默认工具。若同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具。
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(toolCallback)
.build();
向 ChatModel 添加工具
采用编程规范方法时,您可以将 MethodToolCallback 实例传递给调用 ChatModel 时使用的 ToolCallingChatOptions 的 toolCallbacks () 方法。该工具仅在添加至的特定聊天请求中可用。
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build():
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);
向 ChatModel 添加默认工具
采用程序化规范方法时,您可以在构建 ChatModel 时通过将 MethodToolCallback 实例传递给用于创建 ChatModel 的 ToolCallingChatOptions 实例的 toolCallbacks () 方法,来添加默认工具。若同时提供了默认工具和运行时工具,则运行时工具将完全覆盖默认工具。
ToolCallback toolCallback = ...
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build())
.build();
方法工具限制
以下类型目前不支持作为工具方法参数或返回类型:
- Primitive types
- Optional
- Collection types (e.g. List, Map, Array, Set)
- Asynchronous types (e.g. CompletableFuture, Future)
- Reactive types (e.g. Flow, Mono, Flux).
功能类型通过基于函数的工具规范方法得到支持。
工具规范(Tool Specification)
在 Spring AI 中,工具通过 ToolCallback 接口进行建模。在前面的章节中,我们已经了解了如何利用 Spring AI 内置支持从方法和函数定义工具(参见 方法作为工具 和 函数作为工具 )。
工具回调(Tool Callback)
ToolCallback 接口提供了一种定义工具的方式,该工具可由 AI 模型调用,包括定义与执行逻辑。当您希望从头开始定义一个工具时,这是需要实现的主要接口。例如,您可以从 MCP 客户端(使用模型上下文协议)或 ChatClient(用于构建模块化代理应用)中定义一个 ToolCallback。
该接口提供以下方法:
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);
}
工具定义(Tool Definition)
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();
}
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();
方法工具定义(Method Tool Definition)
在根据方法构建工具时,ToolDefinition
会自动为您生成。若您更倾向于自行生成 ToolDefinition
,则可以使用这个便捷的构建器。
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinition.from(method);
由方法生成的 ToolDefinition 包括将方法名作为工具名称,方法名作为工具描述,以及方法输入参数的 JSON 模式。如果该方法带有 @Tool 注解,则工具名称和描述将取自该注解(如果已设置)。
如果您更愿意明确提供部分或全部属性,可以使用‘ToolDefinition.Builder‘来构建自定义的‘ToolDefinition‘实例。
函数工具定义(Function Tool Definition)
在从函数构建工具时,ToolDefinition 会自动为您生成。当您使用 FunctionToolCallback.Builder 构建 FunctionToolCallback 实例时,您可以提供工具名称、描述和输入模式,这些将用于生成 ToolDefinition。
JSON Schema
在为 AI 模型提供工具时,模型需了解调用工具所需的输入类型架构。此架构用于理解如何调用工具及准备工具请求。Spring AI 通过 JsonSchemaGenerator 类内置支持生成工具输入类型的 JSON Schema。该架构作为 ToolDefinition 的一部分提供。
JsonSchemaGenerator 类在幕后被用来为方法或函数的输入参数生成 JSON 模式,采用了 方法即工具 和 函数即工具 中描述的任何策略。JSON 模式生成逻辑支持一系列注解,您可以在方法和函数的输入参数上使用这些注解来自定义生成的模式。
本节介绍了在生成工具输入参数的 JSON 模式时,可自定义的两大主要选项:描述和必填状态。
Description
除了为工具本身提供描述外,您还可以为工具的输入参数提供描述。该描述可用于提供有关输入参数的关键信息,例如参数应采用何种格式、允许哪些值等。这有助于模型理解输入模式及其使用方法。Spring AI 提供了内置支持,可通过以下注解之一生成输入参数的描述:
- @ToolParam(description = “…”) from Spring AI
- @JsonClassDescription(description = “…”) from Jackson
- @JsonPropertyDescription(description = “…”) from Jackson
- @Schema(description = “…”) from Swagger.
这种方法适用于方法和函数,并且可以递归地应用于嵌套类型。
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);
}
}
必填 / 可选(Required/Optional)
默认情况下,每个输入参数都被视为必填项,这迫使 AI 模型在调用工具时为其提供值。然而,您可以通过使用以下任一注解(按此优先顺序)来使输入参数变为可选:
- @ToolParam(required = false) from Spring AI
- @JsonProperty(required = false) from Jackson
- @Schema(required = false) from Swagger
- @Nullable from Spring Framework.
这种方法适用于方法和函数,并且可以递归地应用于嵌套类型。
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);
}
}
结果转换(Result Conversion)
工具调用的结果通过 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。
方法工具调用结果转换(Method Tool Call Result Conversion)
在使用声明式方法构建工具时,您可以通过设置 @Tool
注解的 resultConverter ()
属性,为工具提供一个自定义的 ToolCallResultConverter
来使用。
class CustomerTools {
@Tool(description = "Retrieve customer information", resultConverter = CustomToolCallResultConverter.class)
Customer getCustomerInfo(Long id) {
return customerRepository.findById(id);
}
}
如果采用编程方式,您可以通过设置‘MethodToolCallback.Builder‘的‘resultConverter ()‘属性,为工具提供一个自定义的‘ToolCallResultConverter‘来使用。
函数工具调用结果转换(Function Tool Call Result Conversion)
在通过编程方法从函数构建工具时,您可以通过设置 FunctionToolCallback.Builder 的 resultConverter () 属性,为该工具提供自定义的 ToolCallResultConverter 来使用。
工具上下文(Tool Context)
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);
同样地,您可以在直接调用 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 将是两者的合并结果,其中运行时选项优先于默认选项。
直接返回(Return Direct)
默认情况下,工具调用的结果会作为响应返回给模型。随后,模型可以利用该结果继续对话。
在某些情况下,您可能更愿意直接将结果返回给调用者,而不是将其发送回模型。例如,如果您构建了一个依赖于 RAG 工具的代理,您可能希望直接将结果返回给调用者,而不是将其发送回模型进行不必要的后处理。或者,您可能拥有某些工具,这些工具应当终止代理的推理循环。
每个 ToolCallback 实现都可以定义工具调用的结果是直接返回给调用者,还是发送回模型。默认情况下,结果会被发送回模型。但你可以针对每个工具更改这一行为。
工具调用管理器负责管理工具执行的生命周期,并处理与工具相关的 returnDirect 属性。如果该属性设置为 true,则工具调用的结果将直接返回给调用者;否则,结果将返回给模型。
如果同时请求多个工具调用,必须将 returnDirect 属性设置为 true,以便所有工具直接将结果返回给调用者。否则,结果将被发送回模型。
- 当我们希望模型能够使用某个工具时,会在聊天请求中包含其定义。若需将工具执行结果直接返回给调用者,则将 returnDirect 属性设置为 true。
- 当模型决定调用工具时,它会发送一个响应,其中包含工具名称以及根据定义的模式建模的输入参数。
- 应用程序负责使用工具名称来识别并执行该工具,同时应用提供的输入参数。
- 工具调用的结果由应用程序处理。
- 应用程序直接将工具调用结果发送给调用者,而不是将其返回给模型。
方法直接返回(Method Return Direct)
使用声明式方法构建工具时,您可以通过将 @Tool
注解的 returnDirect 属性设置为 true
,来标记一个工具直接将结果返回给调用者。
class CustomerTools {
@Tool(description = "Retrieve customer information", 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();
工具执行(Tool Execution)
工具执行过程涉及使用提供的输入参数调用工具并返回结果。这一过程由 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 Starter,‘DefaultToolCallingManager‘ 是 ‘ToolCallingManager‘ 接口的自动配置实现。您可以通过提供自己的 ‘ToolCallingManager‘ bean 来自定义工具执行行为。
@Bean
ToolCallingManager toolCallingManager() {
return ToolCallingManager.builder().build();
}
默认情况下,Spring AI 在每个 ChatModel 实现中为您透明地管理工具执行的生命周期。但您也可以选择退出此行为,自行控制工具的执行。本节将描述这两种场景。
框架控制的工具执行(Framework-Controlled Tool Execution)
在使用默认行为时,Spring AI 会自动拦截来自模型的任何工具调用请求,调用工具并将结果返回给模型。所有这些操作都由每个 ChatModel 实现通过 ToolCallingManager 透明地为您完成。
- 当我们希望让模型能够使用某个工具时,我们会在聊天请求(Prompt)中包含其定义,并调用 ChatModel API,该 API 将请求发送给 AI 模型。
- 当模型决定调用工具时,它会发送一个响应(ChatResponse),其中包含工具名称和根据定义模式建模的输入参数。
- ChatModel 将工具调用请求发送至 ToolCallingManager API。
- 工具调用管理器负责识别要调用的工具,并使用提供的输入参数执行该工具。
- 工具调用的结果被返回给 ToolCallingManager。
- ToolCallingManager 将工具执行结果返回给 ChatModel。
- ChatModel 将工具执行结果返回给 AI 模型(ToolResponseMessage)。
- AI 模型利用工具调用结果作为附加上下文生成最终响应,并通过 ChatClient 将其返回给调用者(ChatResponse)。
决定工具调用是否符合执行条件的逻辑由 ToolExecutionEligibilityPredicate 接口处理。默认情况下,工具执行的资格是通过检查 ToolCallingChatOptions 的 internalToolExecutionEnabled 属性是否设置为 true(默认值),以及 ChatResponse 是否包含任何工具调用来确定的。
public class DefaultToolExecutionEligibilityPredicate implements ToolExecutionEligibilityPredicate {
@Override
public boolean test(ChatOptions promptOptions, ChatResponse chatResponse) {
return ToolCallingChatOptions.isInternalToolExecutionEnabled(promptOptions) && chatResponse != null
&& chatResponse.hasToolCalls();
}
}
您可以在创建 ChatModel bean 时提供自定义的 ToolExecutionEligibilityPredicate 实现。
用户控制的工具执行(User-Controlled Tool Execution)
在某些情况下,您可能更希望自行控制工具执行的生命周期。您可以通过将 ToolCallingChatOptions 的 internalToolExecutionEnabled 属性设置为 false 来实现这一点。
当您使用此选项调用 ChatModel 时,工具执行将委托给调用者,使您能够完全控制工具执行的生命周期。您需负责检查 ChatResponse 中的工具调用,并使用 ToolCallingManager 来执行它们。
以下示例展示了用户控制工具执行方法的最小化实现:
ChatModel chatModel = ...
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(new CustomerTools())
.internalToolExecutionEnabled(false)
.build();
Prompt prompt = new Prompt("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());
接下来的示例展示了用户控制工具执行方法与 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)));
异常处理(Exception Handling)
当工具调用失败时,异常会以 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‘接口的自动配置实现。默认情况下,错误信息会被发送回模型。通过‘DefaultToolExecutionExceptionProcessor‘的构造函数,您可以将‘alwaysThrow‘属性设置为‘true‘或‘false‘。若设为‘true‘,则会抛出异常而非将错误信息返回给模型。
您可以使用 ‘spring.ai.tools.throw-exception-on-error‘ 属性来控制 ‘DefaultToolExecutionExceptionProcessor‘ bean 的行为:
属性 | 描述 | 默认 |
---|---|---|
spring.ai.tools.throw-exception-on-error |
若为 true ,工具调用错误将作为异常抛出,由调用者处理。若为假,错误则被转换为消息并返回给 AI 模型,使其能够处理并响应错误。 | false |
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
return new DefaultToolExecutionExceptionProcessor(true);
}
ToolExecutionExceptionProcessor 由默认的 ToolCallingManager(DefaultToolCallingManager)内部使用,用于处理工具执行期间的异常。
工具解析(Tool Resolution)
向模型传递工具的主要方法是在调用 ChatClient 或 ChatModel 时提供 ToolCallback (s), 使用 方法作为工具 和 函数作为工具 中描述的策略之一。
然而,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 解析来自类型为 Function、Supplier、Consumer 或 BiFunction 的 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));
}
ToolCallbackResolver 由 ToolCallingManager 内部使用,用于在运行时动态解析工具,支持 框架控制的工具执行 和 用户控制的工具执行 。
可观测性(Observability)
工具调用包含可观测性支持,通过‘spring.ai.tool‘观测点来测量完成时间并传播追踪信息。
Spring AI 可选择性地将工具调用参数和结果导出为跨度属性,出于敏感性考虑,默认情况下此功能处于禁用状态。详情请参阅: 工具调用参数及结果数据 。
日志记录(Logging)
工具调用功能的所有主要操作均记录在 DEBUG 级别。您可以通过将 org.springframework.ai
包的日志级别设置为 DEBUG
来启用日志记录。
最后编辑:Jeebiz 更新时间:2025-08-31 23:07