工具调用(也称为函数调用)是人工智能应用程序中的一种常见模式,允许模型与一组API或工具交互,以增强其功能。
工具主要用于:
信息检索。此类工具可用于从外部源(如数据库、web服务、文件系统或web搜索引擎)检索信息。目标是增强模型的知识,使其能够回答否则无法回答的问题。因此,它们可用于检索增强生成(RAG)场景。例如,一个工具可用于检索给定位置的当前天气,检索最新的新闻文章,或查询数据库中的特定记录。
采取行动。此类工具可用于在软件系统中执行操作,例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。目标是自动化那些需要人工干预或显式编程的任务。例如,一个工具可用于为与聊天机器人交互的客户预订航班,填写网页上的表单,或在代码生成场景中基于自动测试(TDD)实现Java类。
尽管我们通常将工具调用称为模型功能,但实际上是由客户端应用程序提供工具调用逻辑。模型只能请求工具调用并提供输入参数,而应用程序负责根据输入参数执行工具调用并返回结果。该模型永远无法访问作为工具提供的任何API,这是一个关键的安全考虑因素。
Spring AI提供了方便的API来定义工具、解析来自模型的工具调用请求以及执行工具调用。以下部分概述了Spring AI中的工具调用功能。
查看聊天模型比较,了解哪些AI模型支持工具调用调用。
按照指南从不推荐使用的FunctionCallback迁移到ToolCallback API。
让我们看看如何在Spring AI中开始使用工具调用。我们将实现两个简单的工具:一个用于信息检索,一个用于采取行动。信息检索工具将用于获取用户所在时区的当前日期和时间。操作工具将用于在指定时间内设置警报。
人工智能模型无法访问实时信息。任何假设知道当前日期或天气预报等信息的问题都无法由模型回答。但是,我们可以提供一个可以检索此信息的工具,并在需要访问实时信息时让模型调用此工具。
让我们在DateTimeTools类中实现一个工具,以获取用户所在时区的当前日期和时间。这个工具不会有任何争论。Spring Framework中的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.
如果没有这个工具,模型就不知道如何回答这个问题,因为它没有能力确定当前的日期和时间。
人工智能模型可用于生成实现某些目标的计划。例如,模型可以生成预订丹麦之旅的计划。但是,模型无法执行该计划。这就是工具发挥作用的地方:它们可以用来执行模型生成的计划。
在前面的示例中,我们使用了一个工具来确定当前日期和时间。在这个例子中,我们将定义第二个工具,用于在特定时间设置警报。目标是从现在开始设置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对象外,您还可以传递一个工具名称列表,该列表将使用ToolCallback Resolver接口动态解析。
以下部分将详细介绍所有这些概念和API,包括如何定制和扩展它们以支持更多用例。
Spring AI通过两种方式为从方法中指定工具(即ToolCallback)提供内置支持:
声明性地,使用@Tool注释
通过编程方式,使用低级MethodToolCallback实现。
您可以通过使用@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:ToolCallResultConverter实现,用于将工具调用的结果转换为String对象,以发送回AI模型。有关更多详细信息,请参阅结果转换。
该方法可以是静态的或实例的,并且可以具有任何可见性(公共、受保护、包私有或私有)。包含该方法的类可以是顶级类或嵌套类,它也可以具有任何可见性(只要在您计划实例化它的地方可以访问它)。
Spring AI为@Tool注释方法的AOT编译提供了内置支持,只要包含这些方法的类是Spring bean(例如@Component)。否则,您需要为GraalVM编译器提供必要的配置。例如,通过用@RegisterReflection注释类(MemberCategory=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:参数是必需的还是可选的。默认情况下,所有参数都被认为是必需的。
如果一个参数被注释为@Nullable,则它将被视为可选的,除非使用@ToolParam注释明确标记为必需的。
除了@ToolParam注释外,您还可以使用Swagger的@Schema注释或Jackson的@JsonProperty。有关更多详细信息,请参阅JSON模式。
当使用声明性规范方法时,您可以在调用ChatClient时将工具类实例传递给tools()方法。此类工具仅适用于添加到其中的特定聊天请求。
ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
在幕后,ChatClient将从工具类实例中的每个@Tool注释方法生成一个ToolCallback,并将其传递给模型。如果您更喜欢自己生成ToolCallback,可以使用ToolCallback实用程序类。
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
使用声明性规范方法时,您可以向ChatClient添加默认工具。生成器通过将工具类实例传递给defaultTools()方法。如果同时提供默认工具和运行时工具,则运行时工具将完全覆盖默认工具。
默认工具在由同一ChatClient构建的所有ChatClient实例执行的所有聊天请求中共享。建设者。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用,它们也可能是危险的,有可能在不应该的时候提供它们。
ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new DateTimeTools())
.build();
使用声明性规范方法时,您可以将工具类实例传递给用于调用ChatModel的ToolCallingChatOptions的toolcallback()方法。此类工具仅适用于添加到其中的特定聊天请求。
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实例的toolcallback()方法。如果同时提供默认工具和运行时工具,则运行时工具将完全覆盖默认工具。
默认工具在该ChatModel实例执行的所有聊天请求中共享。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用,它们也可能是危险的,有可能在不应该的时候提供它们。
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build())
.build();
通过以编程方式构建MethodToolCallback,可以将方法转换为工具。
class DateTimeTools {
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
方法工具回调。Builder允许您构建MethodToolCallback实例并提供有关该工具的关键信息:
toolDefinition:定义工具名称、描述和输入模式的toolDefinition实例。您可以使用工具定义构建它。生成器类。必修的。
toolMetadata:定义其他设置的toolMetadata实例,例如是否应将结果直接返回给客户端,以及要使用的结果转换器。您可以使用ToolMetadata构建它。生成器类。
toolMethod:表示工具方法的Method实例。必修的。
toolObject:包含工具方法的对象实例。如果该方法是静态的,则可以省略此参数。
toolCallResultConverter:用于将工具调用的结果转换为String对象以发送回AI模型的toolCallResultConverter实例。如果没有提供,将使用默认转换器(DefaultToolCallResultConverter)。
工具定义。Builder允许您构建ToolDefinition实例并定义工具名称、描述和输入模式:
name:工具的名称。如果没有提供,将使用方法名称。AI模型在调用工具时使用此名称来标识工具。因此,不允许在同一类中有两个同名工具。对于特定的聊天请求,该名称在模型可用的所有工具中必须是唯一的。
description:工具的描述,模型可以使用它来了解何时以及如何调用工具。如果没有提供,方法名称将用作工具描述。然而,强烈建议提供详细的描述,因为这对于模型理解工具的目的以及如何使用它至关重要。如果不能提供一个好的描述,可能会导致模型在应该使用的时候不使用工具,或者使用不当。
inputSchema:工具输入参数的JSON模式。如果没有提供,将根据方法参数自动生成模式。您可以使用@ToolParam注释提供有关输入参数的其他信息,例如描述或参数是必需的还是可选的。默认情况下,所有输入参数都被认为是必需的。有关更多详细信息,请参阅JSON模式。
工具元数据。Builder允许您构建ToolMetadata实例并为该工具定义其他设置:
returnDirect:工具结果是应该直接返回给客户端还是传递回模型。有关更多详细信息,请参阅直接退货。
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(method)
.description("Get the current date and time in the user's timezone")
.build())
.toolMethod(method)
.toolObject(new DateTimeTools())
.build();
该方法可以是静态的或实例的,并且可以具有任何可见性(公共、受保护、包私有或私有)。包含该方法的类可以是顶级类或嵌套类,它也可以具有任何可见性(只要在您计划实例化它的地方可以访问它)。
Spring AI为工具方法的AOT编译提供了内置支持,只要包含这些方法的类是Spring bean(例如@Component)。否则,您需要为GraalVM编译器提供必要的配置。例如,通过用@RegisterReflection注释类(MemberCategory=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(ToolDefinition.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注释允许您提供有关工具参数的关键信息:
description:参数的描述,模型可以使用它来更好地理解如何使用它。例如,参数应该采用什么格式,允许使用什么值等等。
required:参数是必需的还是可选的。默认情况下,所有参数都被认为是必需的。
如果一个参数被注释为@Nullable,则它将被视为可选的,除非使用@ToolParam注释明确标记为必需的。
除了@ToolParam注释外,您还可以使用Swagger的@Schema注释或Jackson的@JsonProperty。有关更多详细信息,请参阅JSON模式。
使用编程规范方法时,可以将MethodToolCallback实例传递给ChatClient的tools()方法。该工具仅适用于其添加到的特定聊天请求。
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.tools(toolCallback)
.call()
.content();
使用程序化规范方法时,您可以向ChatClient添加默认工具。通过将MethodToolCallback实例传递给defaultTools()方法来构建生成器。如果同时提供默认工具和运行时工具,则运行时工具将完全覆盖默认工具。
默认工具在由同一ChatClient构建的所有ChatClient实例执行的所有聊天请求中共享。建设者。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用,它们也可能是危险的,有可能在不应该的时候提供它们。
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(toolCallback)
.build();
使用程序化规范方法时,可以将MethodToolCallback实例传递给用于调用ChatModel的ToolCallingChatOptions的toolcallback()方法。该工具仅适用于其添加到的特定聊天请求。
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build():
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);
使用程序化规范方法时,您可以在构造时将默认工具添加到ChatModel中,方法是将MethodToolCallback实例传递给用于创建ChatModel的ToolCallingChatOptions实例的toolcallback()方法。如果同时提供默认工具和运行时工具,则运行时工具将完全覆盖默认工具。
默认工具在该ChatModel实例执行的所有聊天请求中共享。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用,它们也可能是危险的,有可能在不应该的时候提供它们。
ToolCallback toolCallback = ...
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build())
.build();
以下类型目前不支持作为用作工具的方法的参数或返回类型:
随意的
异步类型(例如CompletableFuture、Future)
反应型(如流量、单声道、通量)
功能类型(例如功能、供应商、消费者)。
使用基于函数的工具规范方法支持函数类型。有关更多详细信息,请参阅函数即工具。
Spring AI提供了从函数中指定工具的内置支持,可以使用低级FunctionToolCallback实现进行编程,也可以在运行时动态解析@Bean。
通过以编程方式构建FunctionToolCallback,可以将函数类型(Function、Supplier、Consumer或BiFunction)转换为工具。
public class WeatherService implements Function {
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) {}
函数工具回调。Builder允许您构建FunctionToolCallback实例并提供有关该工具的关键信息:
name:工具的名称。AI模型在调用工具时使用此名称来标识工具。因此,不允许在同一上下文中有两个同名工具。对于特定的聊天请求,该名称在模型可用的所有工具中必须是唯一的。必修的。
toolFunction:表示工具方法的函数对象(Function、Supplier、Consumer或BiFunction)。必修的。
description:工具的描述,模型可以使用它来了解何时以及如何调用工具。如果没有提供,方法名称将用作工具描述。然而,强烈建议提供详细的描述,因为这对于模型理解工具的目的以及如何使用它至关重要。如果不能提供一个好的描述,可能会导致模型在应该使用的时候不使用工具,或者使用不当。
inputType:函数输入的类型。必修的。
inputSchema:工具输入参数的JSON模式。如果没有提供,将根据inputType自动生成模式。您可以使用@ToolParam注释提供有关输入参数的其他信息,例如描述或参数是必需的还是可选的。默认情况下,所有输入参数都被认为是必需的。有关更多详细信息,请参阅JSON模式。
toolMetadata:定义其他设置的toolMetadata实例,例如是否应将结果直接返回给客户端,以及要使用的结果转换器。您可以使用ToolMetadata构建它。生成器类。
toolCallResultConverter:用于将工具调用的结果转换为String对象以发送回AI模型的toolCallResultConverter实例。如果没有提供,将使用默认转换器(DefaultToolCallResultConverter)。
工具元数据。Builder允许您构建ToolMetadata实例并为该工具定义其他设置:
returnDirect:工具结果是应该直接返回给客户端还是传递回模型。有关更多详细信息,请参阅直接退货。
ToolCallback toolCallback = FunctionToolCallback
.builder("currentWeather", new WeatherService())
.description("Get the weather in location")
.inputType(WeatherRequest.class)
.build();
函数输入和输出可以是Void或POJO。输入和输出POJO必须是可序列化的,因为结果将被序列化并发送回模型。函数以及输入和输出类型必须是公共的。
某些类型不受支持。有关更多详细信息,请参阅功能工具限制。
使用编程规范方法时,可以将FunctionToolCallback实例传递给ChatClient的tools()方法。该工具仅适用于其添加到的特定聊天请求。
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?")
.tools(toolCallback)
.call()
.content();
使用程序化规范方法时,您可以向ChatClient添加默认工具。通过将FunctionToolCallback实例传递给defaultTools()方法来构建生成器。如果同时提供默认工具和运行时工具,则运行时工具将完全覆盖默认工具。
默认工具在由同一ChatClient构建的所有ChatClient实例执行的所有聊天请求中共享。建设者。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用,它们也可能是危险的,有可能在不应该的时候提供它们。
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(toolCallback)
.build();
使用编程规范方法时,可以将FunctionToolCallback实例传递给ToolCallingChatOptions的toolcallback()方法。该工具仅适用于其添加到的特定聊天请求。
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中,方法是将FunctionToolCallback实例传递给用于创建ChatModel的ToolCallingChatOptions实例的toolcallback()方法。如果同时提供默认工具和运行时工具,则运行时工具将完全覆盖默认工具。
默认工具在该ChatModel实例执行的所有聊天请求中共享。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用,它们也可能是危险的,有可能在不应该的时候提供它们。
ToolCallback toolCallback = ...
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build())
.build();
您可以将工具定义为Spring bean,并让Spring AI在运行时使用Toolcallback Resolver接口(通过SpringBeanToolcallback Resolver实现)动态解析它们,而不是以编程方式指定工具。此选项使您可以将任何Function、Supplier、Consumer或BiFunction bean用作工具。bean名称将用作工具名称,Spring Framework的@Description注释可用于提供工具的描述,模型可以使用它来了解何时以及如何调用工具。如果不提供描述,则方法名称将用作工具描述。然而,强烈建议提供详细的描述,因为这对于模型理解工具的目的以及如何使用它至关重要。如果不能提供一个好的描述,可能会导致模型在应该使用的时候不使用工具,或者使用不当。
@Configuration(proxyBeanMethods = false)
class WeatherTools {
WeatherService weatherService = new WeatherService();
@Bean
@Description("Get the weather in location")
Function currentWeather() {
return weatherService;
}
}
某些类型不受支持。有关更多详细信息,请参阅功能工具限制。
工具输入参数的JSON模式将自动生成。您可以使用@ToolParam注释提供有关输入参数的其他信息,例如描述或参数是必需的还是可选的。默认情况下,所有输入参数都被认为是必需的。有关更多详细信息,请参阅JSON模式。
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 currentWeather() {
...
}
}
使用动态规范方法时,您可以将工具名称(即函数bean名称)传递给ChatClient的tools()方法。该工具仅适用于其添加到的特定聊天请求。
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?")
.tools("currentWeather")
.call()
.content();
使用动态规范方法时,您可以向ChatClient添加默认工具。通过将工具名称传递给defaultTools()方法来构建。如果同时提供默认工具和运行时工具,则运行时工具将完全覆盖默认工具。
默认工具在由同一ChatClient构建的所有ChatClient实例执行的所有聊天请求中共享。建设者。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用,它们也可能是危险的,有可能在不应该的时候提供它们。
ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools("currentWeather")
.build();
使用动态规范方法时,可以将工具名称传递给用于调用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实例的toolName()方法。如果同时提供默认工具和运行时工具,则运行时工具将完全覆盖默认工具。
默认工具在该ChatModel实例执行的所有聊天请求中共享。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用,它们也可能是危险的,有可能在不应该的时候提供它们。
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolNames("currentWeather")
.build())
.build();
以下类型目前不支持作为用作工具的函数的输入或输出类型:
原语类型
随意的
集合类型(例如列表、映射、数组、集合)
异步类型(例如CompletableFuture、Future)
反应型(如流量、单声道、通量)。
使用基于方法的工具规范方法支持基本类型和集合。有关更多详细信息,请参阅方法即工具。
在Spring AI中,工具是通过ToolCallback接口建模的。在前面的部分中,我们已经了解了如何使用Spring AI提供的内置支持从方法和函数中定义工具(请参阅方法即工具和函数即工具)。本节将深入探讨工具规范,以及如何对其进行定制和扩展以支持更多用例。
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);
}
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模式。
工具定义。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 method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinition.from(method);
从方法生成的ToolDefinition包括作为工具名称的方法名称、作为工具描述的方法名称以及方法输入参数的JSON模式。如果该方法使用@Tool进行注释,则工具名称和描述将从注释中获取(如果已设置)。
有关更多详细信息,请参阅方法即工具。
如果您更愿意显式提供部分或全部属性,可以使用ToolDefinition。生成器生成自定义ToolDefinition实例。
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinition.builder(method)
.name("currentDateTime")
.description("Get the current date and time in the user's timezone")
.inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
.build();
从函数构建工具时,会自动为您生成工具定义。当您使用FunctionToolCallback时。构建器要构建FunctionToolCallback实例,您可以提供将用于生成ToolDefinition的工具名称、描述和输入模式。有关更多详细信息,请参阅函数即工具。
当为AI模型提供工具时,模型需要知道调用工具的输入类型的模式。模式用于理解如何调用工具和准备工具请求。Spring AI提供内置支持,通过JsonSchemaGenerator类为工具生成输入类型的JSON模式。该模式作为ToolDefinition的一部分提供。
有关工具定义以及如何向其传递输入模式的更多详细信息,请参阅工具定义。
JsonSchemaGenerator类在幕后用于使用方法即工具和函数即工具中描述的任何策略,为方法或函数的输入参数生成JSON模式。JSON模式生成逻辑支持一系列注释,您可以在方法和函数的输入参数上使用这些注释来定制结果模式。
本节介绍在为工具的输入参数生成JSON模式时可以自定义的两个主要选项:描述和所需状态。
除了为工具本身提供描述外,您还可以为工具的输入参数提供描述。描述可用于提供有关输入参数的关键信息,例如参数应采用什么格式、允许使用什么值等。这有助于模型理解输入模式以及如何使用它。Spring AI提供内置支持,使用以下注释之一生成输入参数的描述:
Spring AI 的@ToolParam(description = "…")
@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模型在调用工具时为其提供一个值。但是,您可以按照以下优先级顺序使用以下注释之一将输入参数设置为可选:
@Spring AI的ToolParam(必需=false)
@Jackson的JsonProperty(required=false)
@Swagger的架构(必需=false)
@Spring框架中的null。
这种方法适用于方法和函数,您可以递归地将其用于嵌套类型。
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);
}
}
如果使用编程方法,您可以通过设置MethodToolCallback的resultConverter()属性来提供自定义ToolCallResultConverter供工具使用。建设者。
有关更多详细信息,请参阅方法即工具。
当使用编程方法从函数构建工具时,您可以通过设置FunctionToolCallback的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.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);
ToolContext中提供的数据都不会发送到AI模型。
同样,您可以在直接调用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,则工具调用的结果将直接返回给调用者。否则,结果将被发送回模型。
如果同时请求多个工具调用,则必须将returnDirect属性设置为true,以便所有工具将结果直接返回给调用者。否则,结果将被发送回模型。
当使用声明性方法从方法构建工具时,您可以通过将@tool注释的returnDirect属性设置为true来标记工具,以将结果直接返回给调用者。
class CustomerTools {
@Tool(description = "Retrieve customer information", returnDirect = true)
Customer getCustomerInfo(Long id) {
return customerRepository.findById(id);
}
}
如果使用编程方法,您可以通过ToolMetadata接口设置returnDirect属性,并将其传递给MethodToolCallback。建设者。
ToolMetadata toolMetadata = ToolMetadata.builder()
.returnDirect(true)
.build();
有关更多详细信息,请参阅方法即工具。
当使用编程方法从函数构建工具时,您可以通过ToolMetadata接口设置returnDirect属性,并将其传递给FunctionToolCallback。建设者。
ToolMetadata toolMetadata = ToolMetadata.builder()
.returnDirect(true)
.build();
有关更多详细信息,请参阅函数即工具。
工具执行是使用提供的输入参数调用工具并返回结果的过程。工具执行由ToolCallingManager接口处理,该接口负责管理工具执行生命周期。
public interface ToolCallingManager {
/**
* Resolve the tool definitions from the model's tool calling options.
*/
List 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实现中为您透明地管理工具执行生命周期。但是,您可以选择退出此行为并自己控制工具的执行。本节介绍这两种情况。
使用默认行为时,Spring AI将自动拦截来自模型的任何工具调用请求,调用工具并将结果返回给模型。所有这些都是由每个ChatModel实现使用ToolCallingManager透明地完成的。
@Bean
ToolCallingManager toolCallingManager() {
return ToolCallingManager.builder().build();
}
目前,与模型交换的关于工具执行的内部消息没有向用户公开。如果你需要访问这些消息,你应该使用用户控制的工具执行方法。
确定工具调用是否符合执行条件的逻辑由ToolExecutionEligibilityPredicate接口处理。默认情况下,通过检查ToolCallingChatOptions的internal ToolExecutionEnabled属性是否设置为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的自定义实现。
在某些情况下,您宁愿自己控制工具执行生命周期。您可以通过将ToolCallingChatOptions的internal ToolExecutionEnabled属性设置为false来实现。
当您使用此选项调用ChatModel时,工具执行将委托给调用者,使您可以完全控制工具执行生命周期。您有责任检查ChatResponse中的工具调用,并使用ToolCallingManager执行它们。
以下示例演示了用户控制的工具执行方法的最小实现:
ChatModel chatModel = ...
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(new CustomerTools())
.internalToolExecutionEnabled(false)
.build();
Prompt prompt = new Prompt("Tell me more about the customer with ID 42", chatOptions);
ChatResponse chatResponse = chatModel.call(prompt);
while (chatResponse.hasToolCalls()) {
ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, chatResponse);
prompt = new Prompt(toolExecutionResult.conversationHistory(), chatOptions);
chatResponse = chatModel.call(prompt);
}
System.out.println(chatResponse.getResult().getOutput().getText());
在选择用户控制的工具执行方法时,我们建议使用ToolCallingManager来管理工具调用操作。这样,您就可以从Spring AI为工具执行提供的内置支持中受益。但是,没有什么能阻止您实现自己的工具执行逻辑。
下面的示例显示了用户控制工具执行方法的最小实现,并结合了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 Starter,DefaultToolExecutionExceptionProcessor是ToolExecutionExceptionProcessing接口的自动配置实现。默认情况下,错误消息会被发送回模型。DefaultToolExecutionExceptionProcessor构造函数允许您将alwaysTask属性设置为true或false。如果为真,将抛出异常,而不是向模型发送错误消息。
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
return new DefaultToolExecutionExceptionProcessor(true);
}
如果您定义了自己的ToolCallback实现,请确保在call()方法中的工具执行逻辑发生错误时抛出ToolExecutionException。
ToolExecutionExceptionProcessor由默认ToolCallingManager内部使用,用于处理工具执行过程中的异常。有关工具执行生命周期的更多详细信息,请参阅工具执行。
将工具传递给模型的主要方法是在调用ChatClient或ChatModel时提供ToolCallback,使用方法即工具和函数即工具中描述的策略之一。
然而,Spring AI还支持在运行时使用Toolcallback Resolver接口动态解析工具。
public interface ToolCallbackResolver {
/**
* Resolve the {@link ToolCallback} for the given tool name.
*/
@Nullable
ToolCallback resolve(String toolName);
}
使用此方法时:
在客户端,您向ChatClient或ChatModel提供工具名称,而不是ToolCallback。
在服务器端,ToolCallback Resolver实现负责将工具名称解析为相应的ToolCallback实例。
默认情况下,Spring AI依赖于DelegatingToolcallback Resolver,它将工具解析委托给Toolcallback Resolver实例列表:
SpringBeanToolcallback解析器解析Function、Supplier、Consumer或BiFunction类型的Spring bean中的工具。有关更多详细信息,请参阅动态规范:@Bean。
StaticToolcallback解析器从静态的ToolCallback实例列表中解析工具。使用Spring Boot Autoconfiguration时,此解析器会自动配置应用程序上下文中定义的所有ToolCallback类型的bean。
如果您依赖于Spring Boot Autoconfiguration,则可以通过提供自定义Toolcallback Resolver bean来自定义解析逻辑。
@Bean
ToolCallbackResolver toolCallbackResolver(List toolCallbacks) {
StaticToolCallbackResolver staticToolCallbackResolver = new StaticToolCallbackResolver(toolCallbacks);
return new DelegatingToolCallbackResolver(List.of(staticToolCallbackResolver));
}
ToolCallingManager在内部使用Toolcallback解析器在运行时动态解析工具,支持框架控制的工具执行和用户控制的工具运行。
工具调用包括对spring.ai.Tool观测的可观察性支持,这些观测可以测量完成时间并传播跟踪信息。请参见工具调用可观察性。
Spring AI可以选择将工具调用参数和结果导出为span属性,默认情况下由于敏感性原因而禁用。详细信息:工具调用参数和结果数据。
工具调用功能的所有主要操作都记录在DEBUG级别。您可以通过将org.springframework.ai包的日志级别设置为DEBUG来启用日志记录。