SpringAI - ToolCalling(三)
SpringAI-工具的特性
工具的生命周期
生命周期
SpringAI默认会自动执行AI模型对工具调用的请求,并将工具执行结果返回给AI模型。
整个过程由
ChatModel
(SpringAI与AI模型交互的核心实现)及ToolCallingManager
(工具调用的核心管理器)共同完成。(ChatModel
后续会深入学习和记录)下图是官方的工具执行的生命周期流程图
- 当向AI模型提供工具时,SpringAI会将通过
ToolDefinition
定义的工具信息包含到Prompt
中作为消息请求,由ChatModel
与AI模型进行交互。 - 当AI模型认为需要调用工具时,会响应包含需要调用工具的定义信息及输入参数,SpringAI封装成
ChatResponse
响应在ChatModel
。 ChatModel
将工具调用的请求(定义信息及输入参数)给到ToolCallingManager
。ToolCallingManager
通过定义信息识别需要调用的工具,将输入参数发送给工具进行调用执行。- 工具调用结果返回到
ToolCallingManager
。 ToolCallingManager
将结果再返回给ChatModel
。ChatModel
将结果封装成ToolResponseMessage
把数据附加给AI模型。- AI模型通过附加数据完成最终处理并响应给
ChatModel
,并通过ChatClient
将其返回调用方。
- 当向AI模型提供工具时,SpringAI会将通过
整个过程由SpringAI来控制并完成,SpringAI也提供了将控制交由开发者自行完成,这个后续会记录到。
ToolCallingManager
在生命周期中可以看到工具执行是由
ToolCallingManager
来完成,SpringAI通过ToolCallingManager
管理整个工具调用执行过程的。下面是
ToolCallingManager
接口的源码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26/**
* Service responsible for managing the tool calling process for a chat model.
*
* @author Thomas Vitale
* @since 1.0.0
*/
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);
/**
* Create a default {@link ToolCallingManager} builder.
*/
static DefaultToolCallingManager.Builder builder() {
return DefaultToolCallingManager.builder();
}
}resolveToolDefinitions
方法:解析所有工具的定义,用于ChatModel
创建与AI交互请求。executeToolCalls
方法:顾名思义,用于执行工具调用。SpringAI默认使用
DefaultToolCallingManager
为自动配置实现。下面是自动配置的源码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ToolCallingManager toolCallingManager(ToolCallbackResolver toolCallbackResolver,
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ToolCallingObservationConvention> observationConvention) {
var toolCallingManager = ToolCallingManager.builder()
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
.toolCallbackResolver(toolCallbackResolver)
.toolExecutionExceptionProcessor(toolExecutionExceptionProcessor)
.build();
observationConvention.ifAvailable(toolCallingManager::setObservationConvention);
return toolCallingManager;
}
控制工具执行
SpringAI是允许开发者自行控制工具执行。通过
ToolCallingChatOptions
设置属性internalToolExecutionEnabled
为false
。记录一个控制工具执行的案例。
模拟一个自定义
ToolCallingManager
来执行工具,实际还是使用DefaultToolCallingManager
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ExampleToolCallingManager implements ToolCallingManager {
// 模拟一个自定义的 ToolCallingManager,实际使用的 DefaultToolCallingManager
private final ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();
public List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions) {
return toolCallingManager.resolveToolDefinitions(chatOptions);
}
public ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse) {
log.info("\nExample tool calling manager!");
return toolCallingManager.executeToolCalls(prompt, chatResponse);
}
}将
ExampleToolCallingManager
注入Bean
容器。默认的ToolCallingManager
是使用了@ConditionalOnMissingBean
注解的,当自定义的注入后默认的就不会再有了。1
2
3
4
5
6
7
public class ToolCallingManagerConfiguration {
public ExampleToolCallingManager exampleToolCallingManager(ToolCallingManager toolCallingManager) {
return new ExampleToolCallingManager();
}
}在生命周期中由
ChatModel
和ToolCallingManager
共同完成。所以直接使用ChatModel
与AI交互1
2
3private final DeepSeekChatModel chatModel;
private final ExampleToolCallingManager exampleToolCallingManager;构建 ToolCallingChatOptions ,关闭自动工具执行并添加工具(
ChatOptions
可以配置一些选项传给ChatModel
)1
2
3
4
5
6
7// 传入给 ChatModel 的一些选项
ToolCallingChatOptions options = ToolCallingChatOptions.builder()
// 关闭自动控制工具执行
.internalToolExecutionEnabled(false)
// 可以通过 ToolCallingChatOptions 添加工具
.toolCallbacks(ToolCallbacks.from(new MockWeatherTools()))
.build();构建
Prompt
调用AI模型1
2
3
4
5
6
7
8
9
10
11
12
13Prompt prompt = Prompt.builder()
// 提示词
.messages(SystemMessage.builder()
.text("气温超过30度需要向用户发送高温预警提示")
.build())
.messages(UserMessage.builder()
.text("今天上海热不热?")
.build())
.chatOptions(options)
.build();
// 调用AI模型
ChatResponse chatResponse = chatModel.call(prompt);通过
ChatResponse
判断是否需要需要执行工具,执行工具并得倒结果。最后打印AI输出的最终响应内容。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 是否存在工具调用的请求
while (chatResponse.hasToolCalls()) {
// 使用模拟的 ToolCallingManager 去执行工具,并得倒工具执行的结果
ToolExecutionResult toolExecutionResult = exampleToolCallingManager.executeToolCalls(prompt, chatResponse);
Prompt toolCallPrompt = Prompt.builder()
// 工具执行的结果封装为消息,并包含此次与AI模型交互的整个上下文消息
.messages(toolExecutionResult.conversationHistory())
.chatOptions(options)
.build();
// 将工具调用的结果附加给AI模型
chatResponse = chatModel.call(toolCallPrompt);
}
log.info("\n[ControllerToolExecution] response content -> \n{}", chatResponse.getResult().getOutput().getText());日志打印的输出。通过自定义的
ExampleToolCallingManager
执行了工具调用。AI并通过附加数据给出来最终响应。1
2
3
4
5
6
72025-07-14T14:43:39.431+08:00 INFO 9607 --- [spring-ai-example] [ main] c.s.a.e.t.t.ExampleToolCallingManager :
Example tool calling manager!
2025-07-14T14:43:39.435+08:00 INFO 9607 --- [spring-ai-example] [ main] c.s.a.e.tools.one.MockWeatherTools :
获取天气,城市 -> 上海
2025-07-14T14:43:44.521+08:00 INFO 9607 --- [spring-ai-example] [ main] s.a.e.t.t.ControllerToolExecutionExample :
[ControllerToolExecution] response content ->
今天上海的气温是31.5°C,体感温度约为35°C,天气较为炎热。建议注意防暑降温!
异常处理
异常通常要么是捕获要么外抛,捕获较为简单,直接在工具中捕获处理就可以了。
工具往外抛异常要分两种,一种是外抛的SpringAI提供的
ToolExecutionException
,另一种就是其他异常了,如RuntimeException
。如果抛出的是其他异常,比如以
RuntimeException
为例。- 这些异常会陪抛至调用处,比如会抛给
ToolCallingManager
。 - 如果
ToolCallingManager
也没有处理就会继续外抛,一直抛至最外层调用处。
- 这些异常会陪抛至调用处,比如会抛给
如果抛出
ToolExecutionException
,那么SpringAI会将异常传播至ToolExecutionExceptionProcessor
来处理。下图是异常传播的源码
SpringAI默认自动配置
DefaultToolExecutionExceptionProcessor
作为处理对象。下面是自动配置的源码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor(ToolCallingProperties properties) {
ArrayList<Class<? extends RuntimeException>> rethrownExceptions = new ArrayList<>();
// ClientAuthorizationException is used by Spring Security in oauth2 flows,
// for example with ServletOAuth2AuthorizedClientExchangeFilterFunction and
// OAuth2ClientHttpRequestInterceptor.
Class<? extends RuntimeException> oauth2Exception = getClassOrNull(
"org.springframework.security.oauth2.client.ClientAuthorizationException");
if (oauth2Exception != null) {
rethrownExceptions.add(oauth2Exception);
}
return DefaultToolExecutionExceptionProcessor.builder()
.alwaysThrow(properties.isThrowExceptionOnError())
.rethrowExceptions(rethrownExceptions)
.build();
}在
DefaultToolExecutionExceptionProcessor
中也有两种处理方式- 默认是将错误消息当作工具结果附加给AI模型由其处理。
- 通过配置
spring.ai.tools.throw-exception-on-error
为true
将异常外抛,则中断工具调用
也可以自定义
ToolExecutionExceptionProcessor
来处理异常。比较简单,实现
ToolExecutionExceptionProcessor
接口即可。下面模拟一个处理:1
2
3
4
5
6
7
8
9
10
public class ExampleToolExecutionExceptionProcessor implements ToolExecutionExceptionProcessor {
public String process(ToolExecutionException e) {
log.info("\nExample tool execution exception -> {}, ", e.getMessage());
return ConvertorUtils.toJsonString(Map.of(
"result", "fail",
"error", e.getMessage()));
}
}添加自定义的
ToolExecutionExceptionProcessor
给ToolCallingManager
。如果
ToolCallingManager
使用的是SringAI自动配置的DefaultToolCallingManager
。则直接将自定义的
ToolExecutionExceptionProcessor
注入到Bean
即可,会取代掉自动配置的DefaultToolExecutionExceptionProcessor
如果是自定义的
ToolCallingManager
,需要自行添加自定义的ToolExecutionExceptionProcessor
来使用,否则会不起作用。比如上面自定义的
ExampleToolCallingManager
,使用自定义的ExampleToolExecutionExceptionProcessor
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ExampleToolCallingManager implements ToolCallingManager {
// 模拟一个自定义的 ToolCallingManager,实际使用的 DefaultToolCallingManager
private final ToolCallingManager toolCallingManager = ToolCallingManager
.builder()
// 添加 ExampleToolExecutionExceptionProcessor 处理 ToolExecutionException
.toolExecutionExceptionProcessor(new ExampleToolExecutionExceptionProcessor())
.build();
public List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions) {
return toolCallingManager.resolveToolDefinitions(chatOptions);
}
public ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse) {
log.info("\nExample tool calling manager!");
return toolCallingManager.executeToolCalls(prompt, chatResponse);
}
}
工具规范
工具使用过程
- 在SpringAI中使用工具,可以将步骤大致总结出以下几点
- 通过
ToolDefinition
来定义的基本信息。 - 将工具的对象实例转换成
ToolCallback
- 定义输入参数的
Json Schema
- 定义工具执行完后的结果转换器
- 执行过程中SpringAI提供了
ToolContext
作为工具执行上下文 - 最后就是是否启用了直接返回
returnDirect
。
- 通过
ToolDefinition
在定义工具时,不论是一下那种方式,都是通过
ToolDefinition
来定义的基本信息。不论是使用
@Tool
注解还是
ToolDefinition.Builder
手动构建。或者是函数工具时直接通过
FunctionToolCallback.Builder
进行构建。下图是build()
时构建ToolDefinition
通过接口定义的源码可以看到默认是
DefaultToolDefinition
,SpringAI中也没有其他实现类了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public 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();
/**
* Create a default {@link ToolDefinition} builder.
*/
static DefaultToolDefinition.Builder builder() {
return DefaultToolDefinition.builder();
}
}如果某个模型的工具调用所需要的定义信息更多,可以通过实现该接口来满足SpringAI的工具定义规范。
ToolCallback
不论使用函数工具还是方法工具,都需要将工具的对象实例转换成
ToolCallback
- 函数工具对象实例不论是手动构建还是注入到
Bean
容器,最后都是转换成FunctionToolCallback
。 - 方法工具也是一样,都是转换成
MethodToolCallback
- 函数工具对象实例不论是手动构建还是注入到
ToolCallback
的两个实现类中存放了工具的定义信息、调用实例、方法等元数据。接口本身提供了.call()
方法作为工具调用的规范。下图可以看到在
ToolCallingManager
中是通过ToolCallback
的.call()
来完成工具调用的
入参 JSON Schema
- 工具的输入参数使用的
Json Schema
来作为规范。 - SpringAI提供了
JsonSchemaGenerator
类来支持生成工具输入参数的Json Schema
。 - SpringAI还支持以下集中注解来对输入参数进行约束、描述等
- SpringAI提供的
@ToolParam
- Jackson的
@JsonPropertyDescription
- 复杂对象还可以使用
@JsonClassDescription
- Swagger的
@Schema
- SpringAI提供的
结果转换器
工具调用结果可以通过
ToolCallResultConverter
进行特定格式的序列化后再附加给AI模型。SpringAI默认使用的
DefaultToolCallResultConverter
,通过 Jackson 序列化为JSON字符串附加给AI模型。如果有需要可以实现
ToolCallResultConverter
自定义序列化过程来满足结果返回的规范。
ToolContext
SpringAI提供了
ToolContext
作为向工具传递额外的上下文信息。可以通过其将额外的数据传递至工具内。使用方式
定义工具方法的时候,参数中加一个
ToolContext
。如下:1
2
3
4
5
6
public Object getAltitude( { String location, ToolContext context)
log.info("\n从上下文中获取海拔,位置 -> {}", location);
Object altitude = context.getContext().get("altitude");
return Map.of("location", location, "altitude", altitude, "unit" , "千米");
}在添加工具的时候也添加一下
ToolContext
。如果定义工具中有ToolContext
,而未添加上下文则会报错。1
2
3
4
5
6
7
8
9
10public void toolsContextExample() {
String content = toolsClient.prompt()
.user("上海海拔高不高嘛?")
.tools(new MockWeatherTools())
// 通过上下文设置返回的高度
.toolContext(Map.of("altitude", 18.8))
.call()
.content();
log.info("\n[toolsExample] content -> \n{}", content);
}执行结果
1
2
3
4
5
6
72025-07-14T20:02:45.815+08:00 INFO 16535 --- [spring-ai-example] [ main] c.s.a.e.t.t.ExampleToolCallingManager :
Example tool calling manager!
2025-07-14T20:02:45.823+08:00 INFO 16535 --- [spring-ai-example] [ main] c.s.a.e.tools.one.MockWeatherTools :
从上下文中获取海拔,位置 -> 上海
2025-07-14T20:02:50.598+08:00 INFO 16535 --- [spring-ai-example] [ main] c.s.a.e.tools.one.MethodToolsExample :
[toolsExample] content ->
上海的海拔大约是18.8千米,相对来说是比较高的。
下图是官方给出的
ToolContext
在工具生命周期中的流程图。在第三步调用工具的时候将ToolContext
传入给工具。源码中也很容易发现是在
ToolCallingManager
中调用工具时传入ToolContext
直接返回
SpringAI默认情况下,工具调用结果将作为附加数据补充给模型,随后模型可利用该结果继续对话。
当将
returnDirect
属性设置为true
时,可以使工具调用结果直接返回给调用方,而非AI模型。下面是官方给出的直接返回时工具的生命周期流程图
- 前面4个步骤,基本是保持一直的。只有第一步在添加工具时将
returnDirect
属性设置为true
。 - 在工具调用结果返回时,直接返回调用方,而非传回模型。
- 前面4个步骤,基本是保持一直的。只有第一步在添加工具时将
下面是源码中判断
returnDirect
是直接返回ChatResponse
还是继续调用AI。
总结
- 工具的生命周期主要由
ChatModel
和ToolCallingManager
来完成。ChatModel
负责与AI交互并通知ToolCallingManager
ToolCallingManager
负责执行工具以及返回结果
- SpringAI默认自动配置
DefaultToolCallingManager
,需要自定义则实现ToolCallingManager
接口即可。 - 在工具的生命周期中可以通过SpringAI默认会自动控制执行工具调用。也允许由开发者自行控制。
- 设置属性
internalToolExecutionEnabled
为false
关闭自动控制。 - 自行使用
ChatModel
与AI通信,返回的ChatResponse
中如果AI决定需要调用工具。 - 开发者通过
ToolCallingManager
(可以使用SpringAI默认的,也可以自行定义)来执行工具。
- 设置属性
- 工具如果抛出
ToolExecutionException
,SpringAI会将异常传播至ToolExecutionExceptionProcessor
来处理。 - 按照SpringAI提供的以下几个规范,可自行扩展工具相关开发。
- 工具定义接口:
ToolDefinition
- 工具执行接口:
ToolCallback
- 输入参数规范使用的
Json Schema
- 结果转换器接口:
ToolCallResultConverter
- 工具执行上下文:
ToolContext
- 直接返回:
returnDirect
- 工具定义接口:
最后
- 工具调用算是AI应用开发的重要核心把,目前仅有的了解,好像AI Agent的实现主要核心之一也是工具调用。还有MCP的核心好像也是工具。
- 在SpringAI的工具调用中不仅仅提供了各种快速开发工具与AI进行交互的能力,还允许开发者自行实现控制工具执行的过程,同时还提供了相关规范由开发者遵循来扩展工具的开发。
- 模拟工具的使用单独写了个案例提交在Github上,包:
com.spring.ai.example.tools.four
,但是不单独记录了。 - 本篇记录的案例也提交在Github上了,包:
com.spring.ai.example.tools.three