文章目录
工具调用(也称为函数调用)是AI应用中的常见模式,允许模型通过一组API或工具进行交互,从而增强其能力。工具主要用于以下场景:
-
信息检索
这类工具可从外部源(如数据库、网络服务、文件系统或网络搜索引擎)检索信息,目的是扩展模型的知识库,使其能够回答原本无法解答的问题。它们可用于检索增强生成(RAG)场景。例如:
- 获取某地当前天气
- 检索最新新闻文章
- 查询数据库中的特定记录
-
执行操作
这类工具可在软件系统中执行操作,如发送邮件、创建数据库记录、提交表单或触发工作流。目标是自动化需要人工干预的任务。例如:
- 聊天机器人预订机票
- 自动填写网页表单
- 基于测试用例生成Java类(TDD场景)
-
安全注意事项
虽然工具调用被视为模型能力,但实际执行逻辑由客户端应用程序控制。模型仅能请求工具调用并提供参数,应用程序负责执行并返回结果。模型无法直接访问工具API,这是关键的安全设计。
快速入门
让我们通过一个示例了解如何在Spring AI中使用工具调用。我们将实现两个简单工具:一个用于信息检索,另一个用于执行操作。信息检索工具用于获取用户时区的当前日期和时间,操作工具用于设置指定时间的闹钟。
信息检索
AI模型无法访问实时信息。任何涉及当前日期、天气预报等实时信息的问题,模型都无法直接回答。但我们可以提供能够检索此类信息的工具,并让模型在需要实时数据时调用这些工具。
首先在DateTimeTools类中实现一个获取用户时区当前日期时间的工具。该工具不需要参数,通过Spring框架的LocaleContextHolder获取用户时区。工具方法使用@Tool注解,并添加详细描述帮助模型理解调用时机。
class DateTimeTools {
@Tool(description = "获取用户时区的当前日期和时间")
String getCurrentDateTime() {
return LocalDateTime.now()
.atZone(LocaleContextHolder.getTimeZone().toZoneId())
.toString();
}
}
接下来将工具提供给模型,本例使用ChatClient与模型交互,通过tools()方法传入DateTimeTools实例。当模型需要实时时间信息时,会自动请求调用该工具。ChatClient会执行工具调用并将结果返回给模型,模型结合结果生成最终响应。
ChatModel chatModel = ...
String response = ChatClient.create(chatModel)
.prompt("明天是几号?")
.tools(new DateTimeTools())
.call()
.content();
System.out.println(response);
输出示例:
明天是2025-04-03。
如果直接提问但不提供工具,模型将返回:
我无法访问实时信息,请提供当前日期以便计算明天的日期。
这证明模型在缺乏工具时无法自主获取实时数据
执行操作
AI模型可以生成实现目标的计划(如制定丹麦旅行计划),但无法直接执行。此时需要工具来落实模型生成的计划。
在现有DateTimeTools类中新增设置闹钟的工具。该工具接收ISO-8601格式的时间参数,向控制台输出闹钟设置信息。同样使用@Tool注解并添加详细描述
class DateTimeTools {
@Tool(description = "获取用户时区的当前日期和时间")
String getCurrentDateTime() {
return LocalDateTime.now()
.atZone(LocaleContextHolder.getTimeZone().toZoneId())
.toString();
}
@Tool(description = "为指定ISO-8601时间设置用户闹钟")
void setAlarm(String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("闹钟已设置为 " + alarmTime);
}
}
通过ChatClient同时提供两个工具。当请求"10分钟后设置闹钟"时,模型会先调用getCurrentDateTime获取当前时间,计算目标时间后调用setAlarm工具。ChatClient自动处理工具调用请求并返回结果,模型最终生成响应
ChatModel chatModel = ...
String response = ChatClient.create(chatModel)
.prompt("能设置10分钟后的闹钟吗?")
.tools(new DateTimeTools())
.call()
.content();
System.out.println(response);
应用日志将显示正确设置的闹钟时间,验证工具调用流程
概述
Spring AI通过一组灵活的抽象机制支持工具调用,允许以统一方式定义、解析和执行工具。本节概述Spring AI中工具调用的核心概念与组件。
工具调用的主要操作流程
- 定义工具 :当需要向模型提供工具时,需在聊天请求中包含其定义。每个工具定义包含名称、描述及输入参数的模式(schema)。
- 模型发起调用 :当模型决定调用工具时,会返回包含工具名称和符合预定义模式的输入参数的响应。
- 应用执行工具 :应用程序负责根据工具名称识别并执行对应工具,传入提供的输入参数。
- 处理结果 :工具调用的结果由应用程序处理。
- 返回结果至模型 :应用程序将工具调用结果返回给模型。
- 生成最终响应 :模型结合工具调用结果作为上下文生成最终响应
核心组件
- 工具(Tools) :工具调用的构建基础,通过ToolCallback接口建模。Spring AI提供对方法和函数的内置支持以定义ToolCallback,同时也允许自定义实现以扩展更多场景。
- ChatModel集成 :ChatModel实现通过ToolCallingManager接口透明地将工具调用请求分发给对应的ToolCallback实现,并将执行结果返回模型,最终生成响应。
- 工具注册与解析 :
- ChatClient和ChatModel接受ToolCallback对象列表,使工具对模型可用,并由ToolCallingManager执行。
- 除直接传递ToolCallback外,还可通过ToolCallbackResolver接口动态解析工具名称列表
方法作为工具
Spring AI通过以下两种方式支持从方法定义工具(即ToolCallback):
- 声明式定义 :使用@Tool注解
- 编程式定义 :使用底层MethodToolCallback实现
声明式定义:@Tool注解
通过**@Tool**注解可将方法标记为工具。例如:
class DateTimeTools {
@Tool(description = "获取用户时区的当前日期和时间")
String getCurrentDateTime() {
return LocalDateTime.now()
.atZone(LocaleContextHolder.getTimeZone().toZoneId())
.toString();
}
}
@Tool注解支持以下关键属性:
- name :工具名称。若未指定,默认使用方法名。名称需在单个类中唯一,且在同一聊天请求中所有工具名称不可重复。
- description :工具描述,用于帮助模型理解调用时机和用途。若未指定,默认使用方法名,但强烈建议提供详细描述以避免模型误用或忽略工具。
- returnDirect :是否直接将结果返回客户端而非传递给模型(详见"直接返回"章节)。
- resultConverter :指定ToolCallResultConverter实现,用于将工具调用结果转换为字符串返回模型(详见"结果转换"章节)。
方法可以是静态或实例方法,支持任意访问修饰符(public/protected/包级私有/私有)。包含方法的类可以是顶级类或嵌套类,只需确保实例化时可访问。
若工具类为Spring Bean(如标注**@Component**),Spring AI会自动支持AOT编译;否则需通过**@RegisterReflection**注解配置GraalVM编译器。
方法参数支持多种类型(基本类型、POJO、枚举、列表、数组、映射等),返回类型可为void或可序列化类型。部分类型(如复杂泛型)可能不受支持(详见"方法工具限制"章节)
参数注解:@ToolParam
Spring AI会自动为**@Tool方法生成输入参数的JSON Schema,模型据此理解调用格式。通过@ToolParam**注解可补充参数信息:
class DateTimeTools {
@Tool(description = "设置指定时间的用户闹钟")
void setAlarm(@ToolParam(description = "ISO-8601格式的时间") String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("闹钟已设置为 " + alarmTime);
}
}
@ToolParam支持以下属性:
- description :参数描述(如格式要求、允许值等),帮助模型正确使用参数。
- required :是否为必填参数。默认所有参数为必填,但标注@Nullable的参数会被视为可选(除非显式标记为required=true)
此外,可结合Swagger的**@Schema或Jackson的@JsonProperty**注解定义JSON Schema(详见"JSON Schema"章节)。
向ChatClient添加工具
使用声明式方法时,可通过ChatClient的**tools()**方法传递工具实例。这些工具仅对当前聊天请求有效:
ChatClient.create(chatModel)
.prompt("明天是几号?")
.tools(new DateTimeTools()) // 添加工具实例
.call()
.content();
内部实现中,ChatClient会将**@Tool方法转换为ToolCallback数组。若需手动控制,可使用ToolCallbacks**工具类:
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
向ChatClient添加默认工具
通过ChatClient.Builder的defaultTools()方法可注册默认工具,这些工具会被所有由此构建器创建的ChatClient实例共享。若同时提供默认工具和运行时工具,运行时工具会覆盖默认工具:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new DateTimeTools()) // 默认工具
.build();
注意 :默认工具可能被意外暴露给不需要的请求,需谨慎使用
向ChatModel添加工具
通过ToolCallingChatOptions的toolCallbacks()方法可向ChatModel添加工具,仅对当前请求有效:
ToolCallback\[\] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build();
Prompt prompt = new Prompt("明天是几号?", chatOptions);
chatModel.call(prompt);
向ChatModel添加默认工具
在创建ChatModel时,可通过**defaultOptions()**方法指定默认工具。所有由此模型处理的请求均会共享这些工具:
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(new OllamaApi())
.defaultOptions(
ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build()
)
.build();
注意 :默认工具可能被误用于不相关的请求,需确保其适用性\n
编程式定义:MethodToolCallback
通过MethodToolCallback可编程式地将方法转换为工具。
class DateTimeTools {
String getCurrentDateTime() {
return LocalDateTime.now()
.atZone(LocaleContextHolder.getTimeZone().toZoneId())
.toString();
}
}
MethodToolCallback.Builder用于构建工具实例并定义关键信息:
- toolDefinition :通过ToolDefinition定义工具名称、描述及输入模式(必填)。
- toolMetadata :通过ToolMetadata定义额外配置(如是否直接返回结果、结果转换器)。
- toolMethod :工具方法的Method实例(必填)。
- toolObject :包含工具方法的对象实例(若方法为静态方法可省略)。
- toolCallResultConverter :结果转换器,默认使用DefaultToolCallResultConverter
工具定义(ToolDefinition)
通过ToolDefinition.Builder定义工具元数据:
- name :工具名称。若未指定,默认使用方法名。名称需在单个类中唯一,且在同一聊天请求中不可重复。
- description :工具描述,用于帮助模型理解调用场景。若未指定,默认使用方法名,但建议提供详细描述以避免误用。
- inputSchema :输入参数的JSON模式。若未指定,会根据方法参数自动生成。可通过@ToolParam注解补充参数信息(如是否必填)。
工具元数据(ToolMetadata)
通过ToolMetadata.Builder定义额外配置:
- returnDirect :是否直接将结果返回客户端(详见"直接返回"章节)。
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(method)
.description("获取用户时区的当前日期和时间")
.build())
.toolMethod(method)
.toolObject(new DateTimeTools())
.build();
若方法为静态方法,可省略toolObject:
class DateTimeTools {
static String getCurrentDateTime() { /\* ... \*/ }
}
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(method)
.description("获取用户时区的当前日期和时间")
.build())
.toolMethod(method)
.build();
参数注解:@ToolParam
Spring AI会自动为方法参数生成JSON模式。通过**@ToolParam**可补充参数信息:
class DateTimeTools {
void setAlarm(@ToolParam(description = "ISO-8601格式的时间") String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("闹钟已设置为 " + alarmTime);
}
}
@ToolParam支持以下属性:
- description :参数描述(如格式要求)。
- required :是否必填。默认为true,但标注@Nullable的参数会被视为可选。
向ChatClient添加工具
通过tools()方法传递MethodToolCallback实例,仅对当前请求有效:
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("明天是几号?")
.tools(toolCallback)
.call()
.content();
向ChatClient添加默认工具
通过defaultTools()方法注册默认工具,会被所有由此构建器创建的ChatClient实例共享:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(toolCallback)
.build();
向ChatModel添加工具
通过ToolCallingChatOptions的**toolCallbacks()**方法传递工具,仅对当前请求有效:
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build();
Prompt prompt = new Prompt("明天是几号?", chatOptions);
chatModel.call(prompt);
向ChatModel添加默认工具
在创建ChatModel时通过**defaultOptions()**指定默认工具:
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(new OllamaApi())
.defaultOptions(
ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build()
)
.build();
方法工具使用限制
以下类型不能作为方法工具的参数或者返回值
- Optional类型不被支持,需使用其他方式处理可选参数
- 异步类型(如CompletableFuture/Future)不被支持,工具需同步执行
- 响应式类型(如Mono/Flux/Flow)不被支持,需转换为阻塞操作
- 函数式类型(如Function/Supplier)不被支持,需改用函数式工具规范
注意:函数式类型可通过函数式工具规范支持(如FunctionToolCallback),详见"函数作为工具"章节
函数作为工具
Spring AI通过以下两种方式支持将函数定义为工具:
- 编程式定义 :使用底层FunctionToolCallback实现
- 动态注册 :通过@Bean在运行时解析
编程式定义:FunctionToolCallback
通过FunctionToolCallback可将函数式类型(Function、Supplier、Consumer、BiFunction)转换为工具
public class WeatherService implements Function<WeatherRequest, WeatherResponse> {
@Override
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用于构建工具实例并定义关键信息:
- name :工具名称,需唯一且不可重复(必填)。
- toolFunction :函数式对象(如Function、Supplier等,必填)。
- description :工具描述,帮助模型理解调用场景(建议详细说明)。
- inputType :输入类型(必填)。
- inputSchema :输入参数的JSON模式(若未指定,会根据inputType自动生成)。
- toolMetadata :附加配置(如returnDirect)。
- toolCallResultConverter :结果转换器,默认使用DefaultToolCallResultConverter。
示例:
ToolCallback toolCallback = FunctionToolCallback.builder("currentWeather", new WeatherService())
.description("获取指定地点的天气信息")
.inputType(WeatherRequest.class)
.build();
函数输入/输出要求
- 输入和输出类型需为Void或POJO,且必须可序列化。
- 函数及输入/输出类型需为public。
向ChatClient添加工具
通过tools()方法传递FunctionToolCallback实例,仅对当前请求有效:
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("哥本哈根的天气如何?")
.tools(toolCallback)
.call()
.content();
向ChatClient添加默认工具
通过defaultTools()方法注册默认工具,会被所有由此构建器创建的ChatClient实例共享:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(toolCallback)
.build();
向ChatModel添加工具
通过ToolCallingChatOptions的**toolCallbacks()**方法传递工具,仅对当前请求有效:
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build();
Prompt prompt = new Prompt("哥本哈根的天气如何?", chatOptions);
chatModel.call(prompt);
向ChatModel添加默认工具
在创建ChatModel时通过**defaultOptions()**指定默认工具:
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(new OllamaApi())
.defaultOptions(
ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build()
)
.build();
动态定义:@Bean注解
除了编程式定义工具外,Spring AI支持通过Spring Bean动态解析工具。通过ToolCallbackResolver接口(由SpringBeanToolCallbackResolver实现),可将任意Function、Supplier、Consumer或BiFunction类型的Bean注册为工具。工具名称默认为Bean名称,可通过Spring的@Description注解提供工具描述,帮助模型理解调用场景。
示例:定义天气查询工具
@Configuration(proxyBeanMethods = false)
class WeatherTools {
private final WeatherService weatherService = new WeatherService();
@Bean
@Description("获取指定地点的天气信息") // 强烈建议提供详细描述
public Function<WeatherRequest, WeatherResponse> currentWeather() {
return weatherService::apply;
}
}
public record WeatherRequest(
@ToolParam(description = "城市或国家名称") String location, // 使用@ToolParam补充参数信息
Unit unit
) {}
public record WeatherResponse(double temp, Unit unit) {}
动态工具特性
- 类型限制 :不支持Optional、异步类型(如CompletableFuture)及响应式类型(如Mono)(详见"函数工具限制"章节)。
- JSON模式 :输入参数的JSON模式会根据@ToolParam注解自动生成。默认所有参数为必填。
- 类型安全风险 :工具解析在运行时进行,无法保证编译时类型安全。建议通过常量显式指定工具名称以避免硬编码:
@Configuration(proxyBeanMethods = false)
class WeatherTools {
public static final String CURRENT_WEATHER_TOOL = "currentWeather"; // 定义工具名称常量
@Bean(CURRENT_WEATHER_TOOL)
@Description("获取指定地点的天气信息")
public Function<WeatherRequest, WeatherResponse> currentWeather() { /* ... */ }
}
向ChatClient添加工具
通过工具名称(即Bean名称)动态注册工具,仅对当前请求有效:
ChatClient.create(chatModel)
.prompt("哥本哈根的天气如何?")
.tools("currentWeather") // 传递工具名称
.call()
.content();
向ChatClient添加默认工具
通过defaultTools()方法注册默认工具,会被所有由此构建器创建的ChatClient实例共享:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools("currentWeather") // 默认工具名称
.build();
向ChatModel添加工具
通过ToolCallingChatOptions的toolNames()方法传递工具名称,仅对当前请求有效:
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolNames("currentWeather") // 指定工具名称
.build();
Prompt prompt = new Prompt("哥本哈根的天气如何?", chatOptions);
chatModel.call(prompt);
向ChatModel添加默认工具
在创建ChatModel时通过defaultOptions()指定默认工具名称:
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(new OllamaApi())
.defaultOptions(ToolCallingChatOptions.builder()
.toolNames("currentWeather") // 默认工具名称
.build())
.build();
函数工具限制
以下类型目前不支持 作为工具使用的函数的输入或输出类型:
- 原始类型 (如int、double)
- Optional类型
- 集合类型 (如List、Map、Array、Set)
- 异步类型 (如CompletableFuture、Future)
- 响应式类型 (如Flow、Mono、Flux)
原始类型和集合类型可通过方法式工具规范方式 支持(详见"方法作为工具"章节)
工具规范
在Spring AI中,工具通过ToolCallback接口构建。前文已介绍如何通过Spring AI内置支持从方法和函数定义工具(详见《方法作为工具》和《函数作为工具》章节)。本节将深入探讨工具规范的细节及其扩展方法。
工具回调(Tool Callback)
ToolCallback接口定义了AI模型可调用的工具,包含工具的定义 与执行逻辑 。若需从零开始自定义工具(如基于MCP客户端或ChatClient构建模块化代理应用),需实现此接口。
接口核心方法:
public interface ToolCallback {
// 定义工具元数据,供模型决定调用时机与方式
ToolDefinition getToolDefinition();
// 提供工具附加配置(如返回结果处理方式)
ToolMetadata getToolMetadata();
// 执行工具调用并返回结果(基础方法)
String call(String toolInput);
// 执行工具调用并携带上下文(支持扩展参数)
String call(String toolInput, ToolContext toolContext);
}
Spring AI提供内置实现:
- MethodToolCallback:基于方法的工具实现
- FunctionToolCallback:基于函数的工具实现
工具定义(Tool Definition)
ToolDefinition接口定义工具的元数据,供模型理解工具能力:
public interface ToolDefinition {
String name(); // 工具名称,同一模型调用中需唯一
String description(); // 工具描述,帮助模型理解用途
String inputSchema(); // 输入参数的JSON模式(详见《JSON模式》章节)
}
示例:手动构建天气查询工具定义
ToolDefinition toolDefinition = ToolDefinition.builder()
.name("currentWeather")
.description("获取指定地点的天气信息")
.inputSchema("""
{
"type": "object",
"properties": {
"location": {"type": "string"},
"unit": {"type": "string", "enum": ["C", "F"]}
},
"required": ["location", "unit"]
}
""")
.build();
方法工具定义(Method Tool Definition)
从方法生成工具时,ToolDefinition会自动创建。若需自定义,可通过ToolDefinition.Builder:
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
// 自动从方法生成(含@Tool注解信息)
ToolDefinition autoDef = ToolDefinition.from(method);
// 手动覆盖部分属性
ToolDefinition customDef = ToolDefinition.builder(method)
.name("currentDateTime")
.description("获取用户时区的当前日期和时间")
.inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
.build();
机制说明 :
- 默认使用方法名作为工具名称与描述
- 若方法标注@Tool注解,则优先使用注解中的name和description
函数工具定义(Function Tool Definition)
从函数生成工具时,ToolDefinition由FunctionToolCallback.Builder自动构建:
Function<WeatherRequest, WeatherResponse> weatherFn = ...;
ToolCallback toolCallback = FunctionToolCallback.builder("currentWeather", weatherFn)
.description("获取指定地点的天气信息")
.inputType(WeatherRequest.class)
.build();
关键点 :
- 输入模式根据inputType自动生成
- 支持通过@ToolParam注解补充参数描述
JSON Schema
向AI模型提供工具时,模型需要了解调用工具所需的输入参数模式(Schema)。该模式用于指导模型如何构造调用请求。Spring AI通过JsonSchemaGenerator类内置支持为工具生成输入参数的JSON Schema,并将其作为ToolDefinition的一部分。
描述(Description)
除了工具本身的描述外,还可为输入参数添加描述,帮助模型理解参数格式、允许值等。Spring AI支持以下注解定义参数描述(优先级从高到低):
- @ToolParam(description = “…”)(Spring AI原生注解)
- @JsonClassDescription(Jackson注解)
- @JsonPropertyDescription(Jackson注解)
- @Schema(description = “…”)(Swagger注解)
示例:通过**@ToolParam**定义时间格式描述
class DateTimeTools {
@Tool(description = "设置指定时间的用户闹钟")
void setAlarm(@ToolParam(description = "ISO-8601格式的时间") String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("闹钟已设置为 " + alarmTime);
}
}
此描述会嵌入生成的JSON Schema中,指导模型正确传递参数。
必填/可选(Required/Optional)
默认所有输入参数均为必填,模型调用工具时必须提供值。可通过以下注解将参数标记为可选(优先级从高到低):
- @ToolParam(required = false)(Spring AI原生注解)
- @JsonProperty(required = false)(Jackson注解)
- @Schema(required = false)(Swagger注解)
- @Nullable(Spring框架注解)
示例:将email参数设为可选
class CustomerTools {
@Tool(description = "更新客户信息")
void updateCustomerInfo(Long id, String name, @ToolParam(required = false) String email) {
System.out.println("客户ID " + id + " 的信息已更新");
}
}
关键作用 :明确必填参数可减少模型"幻觉"(hallucination)。若未标记可选参数,模型可能在无实际值时虚构数据,导致错误。
生成逻辑与嵌套支持
- JsonSchemaGenerator会递归处理嵌套类型(如POJO、枚举),支持复杂结构的Schema生成。
- 对于方法和函数式工具,均支持通过注解自定义Schema
结果转换
工具调用的结果通过ToolCallResultConverter序列化为字符串后返回给AI模型。ToolCallResultConverter接口负责将工具返回的对象转换为字符串格式
@FunctionalInterface
public interface ToolCallResultConverter {
String convert(@Nullable Object result, @Nullable Type returnType);
}
结果必须为可序列化类型。默认使用Jackson进行JSON序列化(DefaultToolCallResultConverter),但可通过自定义实现覆盖
方法工具的结果转换
声明式定义 :通过**@Tool注解的resultConverter**属性指定自定义转换器:
class CustomerTools {
@Tool(
description = "查询客户信息",
resultConverter = CustomToolCallResultConverter.class // 自定义转换器
)
Customer getCustomerInfo(Long id) {
return customerRepository.findById(id);
}
}
编程式定义 :通过MethodToolCallback.Builder的resultConverter()方法设置:
MethodToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(...)
.resultConverter(new CustomToolCallResultConverter()) // 设置转换器
.build();
函数工具的结果转换
通过FunctionToolCallback.Builder的**resultConverter()**方法指定自定义转换器:
FunctionToolCallback toolCallback = FunctionToolCallback.builder("toolName", function)
.resultConverter(new CustomToolCallResultConverter()) // 自定义转换逻辑
.build();
默认行为 :
- 默认使用DefaultToolCallResultConverter将结果转换为JSON(依赖Jackson)。
- 若结果为void或null,返回空字符串。
自定义场景 :
- 需要非JSON格式(如XML、纯文本)。
- 需要对敏感数据进行过滤或格式化
工具上下文(Tool Context)
Spring AI通过ToolContext API支持向工具传递额外的上下文信息。此功能允许提供用户定义的附加数据,这些数据可在工具执行时与AI模型传递的参数结合使用。
传递上下文信息到工具
在工具方法中通过参数注入ToolContext,即可访问上下文数据:
class CustomerTools {
@Tool(description = "查询客户信息")
Customer getCustomerInfo(Long id, ToolContext toolContext) {
// 使用上下文中的tenantId参数 [[10]]
return customerRepository.findById(id, toolContext.get("tenantId"));
}
}
调用ChatClient时通过toolContext()方法设置上下文数据:
String response = ChatClient.create(chatModel)
.prompt("请告诉我ID为42的客户信息")
.tools(new CustomerTools())
.toolContext(Map.of("tenantId", "acme")) // 设置上下文参数
.call()
.content();
关键特性
- 数据隔离 :ToolContext中的数据不会发送给AI模型 ,仅在工具执行时可用。
- 多层级合并 :若默认配置和运行时均设置了toolContext,最终上下文会合并两者,且运行时配置优先级更高 。
- 直接调用ChatModel :通过ToolCallingChatOptions设置上下文:
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(customerTools)
.toolContext(Map.of("tenantId", "acme")) // 直接设置上下文
.build();
Prompt prompt = new Prompt("请告诉我ID为42的客户信息", chatOptions);
chatModel.call(prompt);
直接返回(Return Direct)
默认情况下,工具调用的结果会返回给模型,模型利用该结果继续对话流程。但在某些场景中,需直接将结果返回给调用者 ,而非发送回模型:
- 使用RAG工具时,避免模型对结果进行不必要的后处理。
- 某些工具需直接终止代理(Agent)的推理循环。
配置方式
通过ToolCallback的returnDirect属性控制返回行为:
- 默认行为 :结果发送回模型(returnDirect = false)。
- 直接返回 :设置returnDirect = true,结果直接返回调用者。
示例:声明式配置
@Tool(description = "查询客户信息", returnDirect = true)
Customer getCustomerInfo(Long id) { /* ... */ }
示例:编程式配置
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(...)
.toolMetadata(ToolMetadata.builder().returnDirect(true).build()) // 启用直接返回
.build();
多工具调用的限制
若一次请求调用多个工具,所有工具 的returnDirect需设为true,否则结果仍会发送回模型。
直接返回的流程
- 定义工具时启用returnDirect:在聊天请求中包含工具定义,并设置returnDirect = true。
- 模型发起调用 :模型生成包含工具名称和参数的调用请求。
- 应用执行工具 :根据工具名称和参数执行调用。
- 直接返回结果 :应用将结果直接返回给调用者,不经过模型处理 。
关键作用 :
- 优化性能:减少模型不必要的后处理步骤。
- 精确控制:直接返回敏感或结构化数据(如数据库查询结果)
方法直接返回(Method Return Direct)
当通过声明式方法构建工具时,可通过**@Tool注解的returnDirect**属性标记工具结果直接返回调用者(而非发送回模型):
class CustomerTools {
@Tool(description = "查询客户信息", 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();
关键机制:
- 结果绕过模型:设置returnDirect = true后,工具结果直接返回客户端,不再传递给模型进行后续处理。
- 多工具一致性 :若一次调用多个工具,所有工具需启用returnDirect,否则结果仍会发送回模型
工具执行
工具执行是指通过输入参数调用工具并返回结果的过程。该过程由ToolCallingManager接口管理,其负责工具执行的生命周期
public interface ToolCallingManager {
// 从聊天选项中解析工具定义
List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);
// 执行模型请求的工具调用
ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);
}
若使用Spring AI的Spring Boot Starter,DefaultToolCallingManager会自动配置为默认实现。可通过自定义Bean覆盖默认行为:
@Bean
ToolCallingManager toolCallingManager() {
return ToolCallingManager.builder().build(); // 自定义工具调用管理器
}
框架控制的工具执行(默认模式)
Spring AI默认自动拦截模型发起的工具调用请求,执行工具并返回结果。具体流程如下:
- 工具定义注入 :在聊天请求(Prompt)中包含工具定义,并调用ChatModel API发送请求至AI模型。
- 模型发起调用 :模型生成包含工具名称和参数的响应(ChatResponse)。
- 调用管理器处理 :ChatModel将工具调用请求传递给ToolCallingManager。
- 工具执行 :ToolCallingManager根据参数调用对应工具,获取结果。
- 结果返回模型 :工具结果经ToolCallingManager返回ChatModel,再通过ToolResponseMessage发送给AI模型。
- 生成最终响应 :模型结合工具结果生成最终响应(ChatResponse),通过ChatClient返回调用者。
限制 :框架内部的工具调用消息(如中间状态)不直接暴露给用户 。若需访问这些消息,需改用用户控制的工具执行模式 。
关键机制 :
- 透明拦截 :ChatModel自动处理工具调用,无需手动干预。
- 生命周期管理 :ToolCallingManager协调工具解析、执行与结果传递
用户控制的工具执行
在某些场景中,您可能需要手动控制工具执行的生命周期 。通过将ToolCallingChatOptions的internalToolExecutionEnabled属性设为false,调用ChatModel时工具执行将委托给调用者,从而完全掌控流程。
示例:用户控制工具执行的核心实现
ChatModel chatModel = ...;
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(new CustomerTools()) // 注册工具
.internalToolExecutionEnabled(false) // 禁用框架自动执行
.build();
Prompt prompt = new Prompt("请告诉我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());
异常处理
工具调用失败时,异常会以ToolExecutionException形式抛出。可通过ToolExecutionExceptionProcessor处理异常:
-
返回错误信息 :将错误发送回模型继续处理。
-
抛出异常 :由调用者直接处理。
默认异常处理器 :
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
return new DefaultToolExecutionExceptionProcessor(true); // true=抛出异常,false=返回错误信息
}
自定义工具异常处理 : 在自定义ToolCallback的call()方法中,需显式抛出ToolExecutionException:
public String call(String toolInput) {
try {
// 工具执行逻辑
} catch (Exception e) {
throw new ToolExecutionException("工具执行失败", e); // 包装为ToolExecutionException
}
}
关键机制 :
- 手动循环处理 :需持续检查ChatResponse中的工具调用请求,直至无剩余调用。
- 对话历史维护 :每次工具调用后更新Prompt的对话历史,确保上下文连贯。
- 异常传播控制 :通过DefaultToolExecutionExceptionProcessor配置是否中断流程
工具解析(Tool Resolution)
在Spring AI中,传递工具给模型的主要方式是通过ChatClient或ChatModel直接提供ToolCallback(如《方法作为工具》和《函数作为工具》中所述)。此外,Spring AI还支持通过ToolCallbackResolver接口在运行时动态解析工具 。
动态工具解析机制
核心接口 :
public interface ToolCallbackResolver {
@Nullable
ToolCallback resolve(String toolName); // 根据工具名称解析对应的ToolCallback
}
使用流程 :
- 客户端 :向ChatClient或ChatModel传递工具名称(而非ToolCallback实例)。
- 服务端 :通过ToolCallbackResolver实现将工具名称映射为具体的ToolCallback
默认解析器链
Spring AI默认使用DelegatingToolCallbackResolver,其内部委托以下解析器依次处理:
- SpringBeanToolCallbackResolver :
-
从Spring上下文中解析Function、Supplier、Consumer或BiFunction
类型的Bean作为工具。
-
支持通过@Bean注解动态注册工具(详见《动态定义:@Bean》章节)。
-
- StaticToolCallbackResolver :
- 从静态列表中解析预定义的ToolCallback实例。
- 若使用Spring Boot自动配置,会自动收集应用上下文中所有ToolCallback类型的Bean
自定义解析逻辑
可通过定义ToolCallbackResolver Bean覆盖默认行为:
@Bean
ToolCallbackResolver toolCallbackResolver(List<ToolCallback> toolCallbacks) {
// 静态解析器:基于显式注册的ToolCallback列表
StaticToolCallbackResolver staticResolver = new StaticToolCallbackResolver(toolCallbacks);
// 组合解析器:支持多解析器链式处理
return new DelegatingToolCallbackResolver(List.of(staticResolver)); // 可添加其他解析器
}