工具调用
工具调用(也称为函数调用)是人工智能应用中常见的一种模式,允许模型与一组API或工具交互,增强其能力。
工具主要用于:
-
信息检索。这类工具可用于从外部来源检索信息,如数据库、网络服务、文件系统或网页搜索引擎。目标是增强模型的知识,使其能够回答那些原本无法回答的问题。因此,它们可以在检索增强生成(RAG)场景中使用。例如,工具可以用来检索某一地点的当前天气、获取最新新闻文章,或查询数据库中的特定记录。
-
采取行动。该类工具可用于在软件系统中执行作,如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流程。目标是自动化那些原本需要人工干预或显式编程的任务。例如,工具可用于为与聊天机器人互动的客户预订航班、填写网页表单,或在代码生成场景中基于自动化测试(TDD)实现Java类。
尽管我们通常将工具调用称为模型能力,但实际上工具调用逻辑由客户端应用程序提供。模型只能请求工具调用并提供输入参数,而应用程序负责从输入参数执行工具调用并返回结果。模型永远无法访问任何作为工具提供的API,这对安全至关重要。
Spring AI 提供便捷的 API,用于定义工具、解析模型的工具调用请求并执行工具调用。以下章节概述了 Spring AI 中工具调用能力。
| 查看聊天模型比较,了解哪些AI模型支持工具调用调用。 |
| 请按照指南从已弃用的 FunctionCallback 迁移到 ToolCallback API。 |
快速入门
让我们看看如何在 Spring AI 中开始使用工具调用。我们将实施两个简单的工具:一个用于信息检索,一个用于采取行动。信息检索工具将用于获取用户当前时区的日期和时间。作工具将用于设定指定时间的闹钟。
信息检索
AI模型无法获取实时信息。任何假设已知信息(如当前日期或天气预报)的问题,模型无法回答。不过,我们可以提供一个工具来检索这些信息,并在需要实时信息时让模型调用该工具。
我们来实现一个工具,可以在DateTimeTools类。这个工具不会接受任何反驳。这LocaleContextHolderSpring Framework 可以提供用户的时区信息。该工具将被定义为一个注释为@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();
}
}
接下来,让我们让模型使用这个工具。在这个例子中,我们将使用聊天客户端与模型互动。我们将通过传递一个实例 给模型提供该工具DateTimeTools通过tools()方法。当模型需要知道当前的日期和时间时,它会请求调用该工具。在内部,聊天客户端调用工具并将结果返回给模型,模型随后利用工具调用结果生成对原始问题的最终回答。
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.
没有工具,模型无法回答问题,因为它无法确定当前的日期和时间。
采取行动
人工智能模型可以用来生成实现特定目标的计划。例如,模型可以生成预订丹麦旅行的计划。然而,模型无法执行该计划。这就是工具发挥作用的地方:它们可以用来执行模型生成的计划。
在之前的例子中,我们使用了一个工具来确定当前的日期和时间。在这个例子中,我们将定义第二个工具,用于在特定时间设置闹钟。目标是设置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);
}
}
接下来,让我们让模型同时使用这两种工具。我们会用聊天客户端与模型互动。我们将通过传递 的实例为模型提供工具DateTimeTools通过tools()方法。当我们要求设置10分钟后的闹钟时,模特首先需要知道当前的日期和时间。然后,它会利用当前的日期和时间来计算闹钟时间。最后,它会使用报警工具来设置报警。在内部,聊天客户端处理模型中的任何工具调用请求,并将执行结果返回模型,以便模型生成最终响应。
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 中工具调用的主要概念和组件。
-
当我们想让某个工具对模型开放时,会在聊天请求中包含其定义。每个工具定义包含名称、描述和输入参数的模式。
-
当模型决定调用某个工具时,它会发送一个响应,包含工具名称和根据定义模式建模的输入参数。
-
应用程序负责使用工具名称识别并执行该工具,并使用提供的输入参数。
-
工具调用的结果由应用程序处理。
-
应用程序将工具调用结果返回给模型。
-
模型通过工具调用结果作为额外上下文生成最终响应。
工具是工具调用的构建模块,它们由工具回调接口。Spring AI 内置支持工具回调(s)来自方法和函数,但你总可以自己定义工具回调支持更多用例的实现。
聊天模型实现透明地将工具调用请求分发到对应的工具回调实现 和 会将工具调用结果返回模型,模型最终生成最终响应。他们通过ToolCallingManager界面,负责管理工具执行生命周期。
双聊天客户端和聊天模型接受一个列表工具回调使模型和ToolCallingManager最终会处决他们。
除了通过工具回调直接传输对象,你也可以传递工具名称列表,这些名称将通过ToolCallbackResolver接口。
接下来的章节将详细介绍所有这些概念和API,包括如何定制和扩展它们以支持更多用例。
方法作为工具
Spring AI 内置支持指定工具(例如:工具回调(s)))通过两种方式从中提取:
-
声明式地使用以下
@Tool注解 -
在程序化上,使用低层次
方法工具回调实现。
声明式规范:@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注释功能允许您提供关于该工具的关键信息:
该方法可以是静态的或实例的,并且可以具有任意可见性(公开、保护、包私有或私有)。包含该方法的类可以是顶层类或嵌套类,并且它可以有任何可见性(只要在你计划实例化的地方能访问)。
Spring AI 内置支持 AOT 编译@Tool只要包含这些方法的类是Spring Bean(例如,@Component).否则,你需要为GraalVM编译器提供必要的配置。例如,通过对类进行注释@RegisterReflection(成员类别 = MemberCategory.INVOKE_DECLARED_METHODS). |
你可以为该方法定义任意数量的参数(包括无参数),大多数类型(原语、POJO、枚举、列表、数组、映射等)。同样,该方法可以返回大多数类型,包括无效.如果方法返回一个值,返回类型必须是可序列化的类型,因为结果会被序列化并返回模型。
| 有些类型不支持。详情请参见方法工具限制。 |
Spring AI 会生成输入参数的 JSON 模式@Tool-自动注释方法。模型利用该模式来理解如何调用工具并准备工具请求。这@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注释允许你提供关于工具参数的关键信息:
-
描述:参数的描述,模型可以利用它更好地理解如何使用它。例如,参数应该采用什么格式,允许的值等。 -
必填: 参数是必需还是可选。默认情况下,所有参数都被视为必填。
如果参数被注释为@Nullable除非用@ToolParam注解。
除了@ToolParam注释,你也可以使用@Schema来自Swagger的注释,或者@JsonProperty来自Jackson。详情请参见 JSON Schema。
添加工具聊天客户端
使用声明式规范方法时,你可以将工具类实例传递给tools()调用聊天客户端.这些工具只会针对被添加的具体聊天请求使用。
ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
在引擎盖下,聊天客户端将生成一个工具回调从每个人@Tool在工具类实例中进行注释方法,并将其传递给模型。如果你更愿意生成工具回调你自己也可以用工具回调实用类。
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
添加默认工具聊天客户端
使用声明式规范方法时,你可以在ChatClient.Builder通过将工具类实例传递给defaultTools()方法。
如果同时提供默认和运行时工具,运行时工具将完全覆盖默认工具。
默认工具会在所有由聊天客户端由同一实例构建ChatClient.Builder.它们对于在不同聊天请求中常用的工具非常有用,但如果使用不当,也可能存在危险,可能导致不该被使用时暴露出来。 |
ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new DateTimeTools())
.build();
添加工具聊天模型
使用声明式规范方法时,你可以将工具类实例传递给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);
添加默认工具聊天模型
使用声明式规范方法时,你可以添加默认工具于聊天模型在构建时,通过将工具类实例传递给toolCallbacks()方法工具呼叫聊天选项用于创建聊天模型.
如果同时提供默认和运行时工具,运行时工具将完全覆盖默认工具。
默认工具会在所有由该机构执行的聊天请求中共享聊天模型实例。它们对于在不同聊天请求中常用的工具非常有用,但如果使用不当,也可能存在危险,可能导致不该被使用时暴露出来。 |
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build())
.build();
程序化规范:方法工具回调
你可以通过构建一个方法变成工具方法工具回调编程。
class DateTimeTools {
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
这方法工具回调。构建器允许你构建方法工具回调实例并提供关于该工具的关键信息:
-
工具定义:这工具定义定义工具名称、描述和输入模式的实例。你可以用工具定义。构建器课程。必修。 -
toolMetadata:这工具元数据该实例定义了额外设置,比如结果是否应直接返回给客户端,以及使用结果转换器。你可以用工具元数据.构建器类。 -
工具方法:这方法代表工具方法的实例。必填。 -
toolObject:包含工具方法的对象实例。如果方法是静态的,可以省略这个参数。 -
工具呼叫结果转换器:这工具调用结果转换器用于将工具调用结果转换为字符串对象,发送回 AI 模型。如果没有提供,将使用默认转换器 (默认工具呼叫结果转换器).
这工具定义。构建器允许你构建工具定义实例并定义工具名称、描述和输入模式:
-
名称:工具的名字。如果未提供,将使用方法名称。AI模型在调用工具时使用这个名称来识别它。因此,同一类中不能有两个同名工具。对于特定聊天请求,该名称必须在模特所有可用工具中是唯一的。 -
描述:工具的描述,模型可以用来理解何时以及如何调用该工具。如果未提供,方法名称将作为工具描述使用。不过,强烈建议提供详细描述,因为这对模型理解工具的目的和使用至关重要。未能提供良好描述可能导致模型在应有时间使用工具或使用错误。 -
输入模式工具输入参数的JSON模式。如果未提供,模式将根据方法参数自动生成。你可以使用@ToolParam注释用于提供关于输入参数的额外信息,如描述或该参数是必需还是可选。默认情况下,所有输入参数都被视为必需。详情请参见 JSON Schema。
这工具元数据.构建器允许你构建工具元数据实例并定义工具的额外设置:
-
返回直达:工具结果应直接返回给客户还是返回模型。详情请参见“直退”。
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(成员类别 = MemberCategory.INVOKE_DECLARED_METHODS). |
你可以为该方法定义任意数量的参数(包括无参数),大多数类型(原语、POJO、枚举、列表、数组、映射等)。同样,该方法可以返回大多数类型,包括无效.如果方法返回一个值,返回类型必须是可序列化的类型,因为结果会被序列化并返回模型。
| 有些类型不支持。详情请参见方法工具限制。 |
如果方法是静态的,可以省略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注释可用于提供关于输入参数的额外信息,如描述或参数是必需还是可选。默认情况下,所有输入参数都被视为必需。
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注释允许你提供关于工具参数的关键信息:
-
描述:参数的描述,模型可以利用它更好地理解如何使用它。例如,参数应该采用什么格式,允许的值等。 -
必填: 参数是必需还是可选。默认情况下,所有参数都被视为必填。
如果参数被注释为@Nullable除非用@ToolParam注解。
除了@ToolParam注释,你也可以使用@Schema来自Swagger的注释,或者@JsonProperty来自Jackson。详情请参见 JSON Schema。
添加工具聊天客户端和聊天模型
使用程序化规范方法时,你可以传递方法工具回调实例到toolCallbacks()方法聊天客户端.
该工具只会针对被添加的具体聊天请求使用。
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.toolCallbacks(toolCallback)
.call()
.content();
添加默认工具聊天客户端
使用程序化规范方法时,你可以在ChatClient.Builder通过方法工具回调实例到defaultToolCallbacks()方法。
如果同时提供默认和运行时工具,运行时工具将完全覆盖默认工具。
默认工具会在所有由聊天客户端由同一实例构建ChatClient.Builder.它们对于在不同聊天请求中常用的工具非常有用,但如果使用不当,也可能存在危险,可能导致不该被使用时暴露出来。 |
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(toolCallback)
.build();
添加工具聊天模型
使用程序化规范方法时,你可以传递方法工具回调实例到toolCallbacks()方法工具呼叫聊天选项你以前会叫一个聊天模型.该工具只会针对被添加的具体聊天请求使用。
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build():
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);
添加默认工具聊天模型
使用程序化规范方法时,你可以在聊天模型在施工时,通过方法工具回调实例到toolCallbacks()方法工具呼叫聊天选项用于创建聊天模型.
如果同时提供默认和运行时工具,运行时工具将完全覆盖默认工具。
默认工具会在所有由该机构执行的聊天请求中共享聊天模型实例。它们对于在不同聊天请求中常用的工具非常有用,但如果使用不当,也可能存在危险,可能导致不该被使用时暴露出来。 |
ToolCallback toolCallback = ...
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build())
.build();
方法工具的局限性
以下类型目前不支持作为工具的方法的参数或返回类型:
-
自选 -
异步类型(例如:
完成未来,前途) -
反应类型(例如:
流,单,通量) -
函数类型(例如:
功能,提供商,消费者).
功能类型通过基于功能的工具规范方法得到支持。详情请参见“功能作为工具”。
作为工具的功能
Spring AI 内置支持函数指定工具,无论是通过低层程序功能工具回调实现或动态实现为@Bean(s) 在运行时解决。
程序化规范:功能工具回调
你可以转成功能型 (功能,提供商,消费者或双功能)通过构建一个工具功能工具回调编程。
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) {}
这功能工具回调。构建器允许你构建功能工具回调实例并提供关于该工具的关键信息:
-
名称:工具的名字。AI模型在调用工具时使用这个名称来识别它。因此,在同一语境下使用两个同名工具是不允许的。对于特定聊天请求,该名称必须在模特所有可用工具中是唯一的。必填。 -
工具功能:表示工具方法的功能对象(功能,提供商,消费者或双功能).必填。 -
描述:工具的描述,模型可以用来理解何时以及如何调用该工具。如果未提供,方法名称将作为工具描述使用。不过,强烈建议提供详细描述,因为这对模型理解工具的目的和使用至关重要。未能提供良好描述可能导致模型在应有时间使用工具或使用错误。 -
输入类型:函数输入的类型。必填。 -
输入模式工具输入参数的JSON模式。如果未提供,该模式将根据输入类型.你可以使用@ToolParam注释用于提供关于输入参数的额外信息,如描述或该参数是必需还是可选。默认情况下,所有输入参数都被视为必需。详情请参见 JSON Schema。 -
toolMetadata:这工具元数据该实例定义了额外设置,比如结果是否应直接返回给客户端,以及使用结果转换器。你可以用工具元数据.构建器类。 -
工具呼叫结果转换器:这工具调用结果转换器用于将工具调用结果转换为字符串对象,发送回 AI 模型。如果没有提供,将使用默认转换器 (默认工具呼叫结果转换器).
这工具元数据.构建器允许你构建工具元数据实例并定义工具的额外设置:
-
返回直达:工具结果应直接返回给客户还是返回模型。详情请参见“直退”。
ToolCallback toolCallback = FunctionToolCallback
.builder("currentWeather", new WeatherService())
.description("Get the weather in location")
.inputType(WeatherRequest.class)
.build();
输入和输出函数可以是无效或者POJO。输入和输出POJO必须可序列化,因为结果会被序列化并返回模型。函数以及输入和输出类型都必须是公开的。
| 有些类型不支持。详情请参见功能工具限制。 |
添加工具聊天客户端
使用程序化规范方法时,你可以传递功能工具回调实例到toolCallbacks()方法聊天客户端.该工具只会针对被添加的具体聊天请求使用。
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?")
.toolCallbacks(toolCallback)
.call()
.content();
添加默认工具聊天客户端
使用程序化规范方法时,你可以在ChatClient.Builder通过功能工具回调实例到defaultToolCallbacks()方法。
如果同时提供默认和运行时工具,运行时工具将完全覆盖默认工具。
默认工具会在所有由聊天客户端由同一实例构建ChatClient.Builder.它们对于在不同聊天请求中常用的工具非常有用,但如果使用不当,也可能存在危险,可能导致不该被使用时暴露出来。 |
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(toolCallback)
.build();
添加工具聊天模型
使用程序化规范方法时,你可以传递功能工具回调实例到toolCallbacks()方法工具呼叫聊天选项.该工具只会针对被添加的具体聊天请求使用。
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);
添加默认工具聊天模型
使用程序化规范方法时,你可以在聊天模型在施工时,通过功能工具回调实例到toolCallbacks()方法工具呼叫聊天选项用于创建聊天模型.
如果同时提供默认和运行时工具,运行时工具将完全覆盖默认工具。
默认工具会在所有由该机构执行的聊天请求中共享聊天模型实例。它们对于在不同聊天请求中常用的工具非常有用,但如果使用不当,也可能存在危险,可能导致不该被使用时暴露出来。 |
ToolCallback toolCallback = ...
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build())
.build();
动态规范:@Bean
你可以不用编程指定工具,而是将工具定义为 Spring beans,让 Spring AI 在运行时动态解析它们,使用ToolCallbackResolver接口(通过SpringBeanToolCallbackResolver实现)。这个选项让你可以使用任何一个功能,提供商,消费者或双功能豆子作为工具。豆子名称将用作工具名称,并且@DescriptionSpring Framework 的注释可用于提供工具描述,模型用以理解何时以及如何调用该工具。如果你没有提供描述,方法名称将被用作工具描述。不过,强烈建议提供详细描述,因为这对模型理解工具的目的和使用至关重要。未能提供良好描述可能导致模型在应有时间使用工具或使用错误。
@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) {}
这种工具规范方法的缺点是无法保证类型安全,因为工具解析是在运行时完成的。为了缓解这个问题,你可以用@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() {
...
}
}
添加工具聊天客户端
使用动态规范方法时,你可以将工具名称(即函数豆名)传递给toolNames()方法聊天客户端.
该工具只会针对被添加的具体聊天请求使用。
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?")
.toolNames("currentWeather")
.call()
.content();
添加默认工具聊天客户端
使用动态规范方法时,你可以在ChatClient.Builder通过将工具名称传递给defaultToolNames()方法。
如果同时提供默认和运行时工具,运行时工具将完全覆盖默认工具。
默认工具会在所有由聊天客户端由同一实例构建ChatClient.Builder.它们对于在不同聊天请求中常用的工具非常有用,但如果使用不当,也可能存在危险,可能导致不该被使用时暴露出来。 |
ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolNames("currentWeather")
.build();
添加工具聊天模型
使用动态规范方法时,你可以将工具名称传递给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);
添加默认工具聊天模型
使用动态规范方法时,你可以添加默认工具于聊天模型在施工时,通过将工具名称传递给toolNames()方法工具呼叫聊天选项用于创建聊天模型.
如果同时提供默认和运行时工具,运行时工具将完全覆盖默认工具。
默认工具会在所有由该机构执行的聊天请求中共享聊天模型实例。它们对于在不同聊天请求中常用的工具非常有用,但如果使用不当,也可能存在危险,可能导致不该被使用时暴露出来。 |
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolNames("currentWeather")
.build())
.build();
功能工具的局限性
以下类型目前不支持作为工具使用的函数的输入或输出类型:
-
原始类型
-
自选 -
集合类型(例如:
列表,地图,数组,设置) -
异步类型(例如:
完成未来,前途) -
反应类型(例如:
流,单,通量).
原始类型和集合通过基于方法的工具规范方法得到支持。更多细节请参见“方法即工具”。
工具规格
在 Spring AI 中,工具通过工具回调接口。 在之前的部分中,我们看到了如何使用 Spring AI 内置支持的方法和函数来定义工具(参见“作为工具的方法”和“作为工具”功能)。本节将深入探讨工具规范,以及如何定制和扩展以支持更多用例。
工具回调
这工具回调界面提供了一种定义工具的方法,AI模型可以调用,包括定义和执行逻辑。当你想从零定义工具时,它是实现的主要接口。例如,你可以定义一个工具回调来自MCP客户端(使用模型上下文协议)或聊天客户端(构建模块化代理应用)。
该接口提供了以下方法:
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 为工具方法(方法工具回调)和工具函数(功能工具回调).
工具定义
这工具定义界面为 AI 模型提供必要的信息,以便了解工具的可用性,包括工具名称、描述和输入模式。 每工具回调实现必须提供工具定义实例来定义工具。
该接口提供了以下方法:
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 模式。 |
这工具定义。构建器让你能构建一个工具定义实例使用默认实现(DefaultToolDefinition).
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 method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinitions.from(method);
这工具定义由方法生成的代码包括方法名称作为工具名称,方法名称作为工具描述,以及方法输入参数的 JSON 模式。如果该方法被注释为@Tool,工具名称和描述将取自注释(如果已设置)。
| 详情请参见“方法即工具”。 |
如果你更愿意明确提供部分或全部属性,可以使用工具定义。构建器打造一个定制工具定义实例。
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();
函数工具定义
当从函数构建工具时,工具定义会自动为你生成。当你使用功能工具回调。构建器建造一个功能工具回调实例中,你可以提供工具名称、描述和输入模式,用于生成工具定义. 详情请参见“功能作为工具”。
JSON 模式
在向 AI 模型提供工具时,模型需要知道输入类型的模式以便调用该工具。该模式用于理解如何调用工具并准备工具请求。Spring AI 内置支持通过JsonSchemaGenerator类。 该模式作为工具定义.
有关工具定义以及如何将输入模式传递给它。 |
这JsonSchemaGenerator类在底层用于生成方法或函数输入参数的JSON模式,使用《方法作为工具》和《函数作为工具》中描述的任何策略。JSON模式生成逻辑支持一系列注释,你可以在方法和函数的输入参数上使用这些注释来定制最终模式。
本节介绍了在生成工具输入参数的JSON模式时可以自定义的两个主要选项:描述和所需状态。
描述
除了为工具本身提供描述外,你还可以为工具的输入参数提供描述。这些描述可以用来提供关于输入参数的关键信息,比如参数应采用何种格式、允许的值等。这有助于模型理解输入模式以及如何使用它。Spring AI 内置支持通过以下注释之一生成输入参数的描述:
-
@ToolParam(描述 = “... ")来自Spring AI -
@JsonClassDescription(描述 = “... ")来自Jackson -
@JsonPropertyDescription(描述 = “... ")来自Jackson -
@Schema(描述 = “... ")来自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(必要=假)来自Spring AI -
@JsonProperty(必要 = 假)来自Jackson -
@Schema(必需 = 假)来自Swagger -
@Nullable来自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);
}
}
定义输入参数所需的正确状态对于降低幻觉风险并确保模型在调用工具时提供正确输入至关重要。在前述示例中,电子邮件参数是可选的,意味着模型可以调用工具而无需提供数值。如果需要参数,模型在调用工具时必须提供该参数的值。如果没有参数值,模型很可能会编造一个值,导致幻觉。 |
结果转换
工具调用的结果通过工具调用结果转换器然后再送回AI模型。 这工具调用结果转换器接口提供了将工具调用结果转换为字符串对象。
该接口提供了以下方法:
@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 序列化为 JSON 格式 (默认工具呼叫结果转换器),但你可以通过提供自己的序列化过程来定制工具调用结果转换器实现。
春季AI依赖于工具调用结果转换器无论是方法工具还是函数工具。
方法工具调用结果转换
在用声明式方法构建工具时,你可以提供自定义工具调用结果转换器通过设置结果转换器()属性@Tool注解。
class CustomerTools {
@Tool(description = "Retrieve customer information", resultConverter = CustomToolCallResultConverter.class)
Customer getCustomerInfo(Long id) {
return customerRepository.findById(id);
}
}
如果采用程序化方法,你可以提供自定义工具调用结果转换器通过设置结果转换器()属性方法工具回调。构建器.
详情请参见“方法即工具”。
工具背景
Spring AI 支持通过工具上下文应用程序接口。 该功能允许你提供额外的用户提供数据,这些数据可以与AI模型传递的工具参数一起用于工具执行中。
class CustomerTools {
@Tool(description = "Retrieve customer information")
Customer getCustomerInfo(Long id, ToolContext toolContext) {
return customerRepository.findById(id, toolContext.getContext().get("tenantId"));
}
}
这工具上下文是用户调用时提供的数据填充的聊天客户端.
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模型。 |
同样,调用工具上下文数据时也可以定义聊天模型径直。
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);
如果工具上下文选项在默认选项和运行时选项中都被设置,结果工具上下文将是两者的合并,其中运行时选项优先于默认选项。
直归
默认情况下,工具调用的结果会作为响应返回给模型。然后,模型可以利用该结果继续对话。
有些情况下,你更愿意直接将结果返回给调用者,而不是发送回模型。例如,如果你构建了一个依赖RAG工具的代理,你可能想直接将结果返回给调用者,而不是发送给模型进行不必要的后处理。或者你有一些工具可以结束代理的推理循环。
每工具回调实现可以定义工具调用的结果是直接返回调用者还是返回模型。默认情况下,结果会返回给模型。但你可以根据不同工具更改这种行为。
这ToolCallingManager,负责管理工具执行生命周期,负责处理返回直达与工具相关的属性。如果该属性被设置为true,工具调用的结果直接返回给调用者。否则,结果返回给模型。
如果同时请求多个工具调用,则返回直达属性必须设置为true所有工具都能直接将结果返回给呼叫者。否则,结果将返回模型。 |
-
当我们想让某个工具对模型开放时,会在聊天请求中包含其定义。如果我们希望工具执行结果直接返回给调用者,我们会设置
返回直达归属为true. -
当模型决定调用某个工具时,它会发送一个响应,包含工具名称和根据定义模式建模的输入参数。
-
应用程序负责使用工具名称识别并执行该工具,并使用提供的输入参数。
-
工具调用的结果由应用程序处理。
-
应用程序直接将工具调用结果发送给调用者,而不是返回给模型。
方法返回直达
在用声明式方法构建工具时,可以通过设置返回直达属性@Tool注释true.
class CustomerTools {
@Tool(description = "Retrieve customer information", returnDirect = true)
Customer getCustomerInfo(Long id) {
return customerRepository.findById(id);
}
}
如果采用程序化方法,你可以设置返回直达属性通过工具元数据并传递给方法工具回调。构建器.
ToolMetadata toolMetadata = ToolMetadata.builder()
.returnDirect(true)
.build();
详情请参见“方法即工具”。
函数返回直接
在用程序化方法从函数构建工具时,你可以设置返回直达属性通过工具元数据并传递给功能工具回调。构建器.
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
ToolCallingManager toolCallingManager() {
return ToolCallingManager.builder().build();
}
默认情况下,Spring AI 会在每个工具内透明地管理工具执行生命周期聊天模型实现。但你可以选择退出这种行为,自己控制工具的执行。本节描述了这两种情景。
框架控制工具执行
使用默认行为时,Spring AI 会自动拦截模型的任何工具调用请求,调用工具并将结果返回给模型。所有这些都是由每个人透明地为您完成的聊天模型使用ToolCallingManager.
-
当我们想让模型使用某个工具时,会在聊天请求中包含其定义(
提示)并调用聊天模型API 向 AI 模型发送请求。 -
当模型决定调用工具时,会发送响应(
聊天回应工具名称和输入参数均依据定义的模式建模。 -
这
聊天模型将工具调用请求发送给ToolCallingManager应用程序接口。 -
这
ToolCallingManager负责识别调用的工具,并以提供的输入参数执行。 -
工具调用的结果返回给
ToolCallingManager. -
这
ToolCallingManager返回工具执行结果聊天模型. -
这
聊天模型将工具执行结果返回给 AI 模型(工具响应信息). -
AI模型利用工具调用结果作为额外上下文生成最终响应,并将其发送给呼叫者(
聊天回应)通过聊天客户端.
| 目前,与模型交换的关于工具执行的内部消息不会向用户开放。如果你需要访问这些消息,应该使用用户控制的工具执行方法。 |
判断工具调用是否符合执行条件的逻辑由ToolExecutionEligibility谓词接口。默认情况下,工具执行资格通过检查internalToolExecutionEnabled属性工具呼叫聊天选项设置为true(默认值),如果聊天回应包含任何工具调用。
public class DefaultToolExecutionEligibilityPredicate implements ToolExecutionEligibilityPredicate {
@Override
public boolean test(ChatOptions promptOptions, ChatResponse chatResponse) {
return ToolCallingChatOptions.isInternalToolExecutionEnabled(promptOptions) && chatResponse != null
&& chatResponse.hasToolCalls();
}
}
你可以提供你自定义的实现ToolExecutionEligibility谓词在创建聊天模型豆。
用户控制的工具执行
有些情况下,你更愿意自己控制工具的执行生命周期。你可以通过设置internalToolExecutionEnabled属性工具呼叫聊天选项自false.
当你调用聊天模型通过这个选项,工具执行将委托给调用者,让你完全掌控工具执行生命周期。检查工具调用是你的责任聊天回应并用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 内置的支持来执行工具。不过,没有什么能阻止你实现自己的工具执行逻辑。 |
以下示例展示了用户控制工具执行方法的最小实现,结合了聊天记忆应用程序接口:
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该错误可以被捕获以处理错误。
一个工具执行异常处理器可以用来处理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是 的自动配置实现工具执行异常处理器接口。默认情况下,错误信息为运行异常返回模型,同时检查异常和错误(例如,IOException,内存出错)总是被抛出。这DefaultToolExecutionExceptionProcessor构造器允许你设置总是投掷归属为true或false.如果true,会抛出异常,而不是向模型发送错误消息。
你可以使用'spring.ai.tools.throw-exception-on-error控制行为的属性DefaultToolExecutionExceptionProcessor豆:
| 属性 | 描述 | 默认值 |
|---|---|---|
|
如果 |
|
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
return new DefaultToolExecutionExceptionProcessor(true);
}
如果你定义了自己的工具回调实现时,确保抛出ToolExecutionException当错误作为工具执行逻辑的一部分时,call()方法。 |
这工具执行异常处理器默认情况下内部使用ToolCallingManager (DefaultToolCallingManager)以处理工具执行中的异常。有关工具执行生命周期的更多细节,请参见工具执行。
工具分辨率
然而,Spring AI 也支持在运行时动态解析工具,使用ToolCallbackResolver接口。
public interface ToolCallbackResolver {
/**
* Resolve the {@link ToolCallback} for the given tool name.
*/
@Nullable
ToolCallback resolve(String toolName);
}
使用此方法时:
-
在客户端,你为
聊天客户端或者聊天模型而不是工具回调(s)。 -
在服务器端,
ToolCallbackResolver实现负责将工具名称解析为对应的工具回调实例。
默认情况下,Spring AI 依赖于DelegatingToolCallbackResolver该工具解析委托给以下列表ToolCallbackResolver实例:
-
这
SpringBeanToolCallbackResolver解决了活字的春豆工具功能,提供商,消费者或双功能.看动态规范:@Bean更多细节请阅读。 -
这
StaticToolCallbackResolver从 静态列表中解析工具工具回调实例。使用Spring Boot Autoconfiguration时,该解析器会自动配置所有类型的豆子工具回调在应用上下文中定义。
如果你依赖 Spring Boot 自动配置,可以通过提供自定义来自定义分辨率逻辑ToolCallbackResolver豆。
@Bean
ToolCallbackResolver toolCallbackResolver(List<FunctionCallback> toolCallbacks) {
StaticToolCallbackResolver staticToolCallbackResolver = new StaticToolCallbackResolver(toolCallbacks);
return new DelegatingToolCallbackResolver(List.of(staticToolCallbackResolver));
}
可观察性
工具调用包括通过spring.ai.tool的可观测性支持,这些观测可以测量完成时间并传播追踪信息。参见工具调用可观察性。
可选地,Spring AI 可以将工具调用参数和结果导出为 span 属性,但默认为敏感性原因禁用。详情:工具调用参数和结果数据。