SpringAI工具调用原理解析
目录
一、工具调用整体流程
二、工具调用检测机制
1. 结构检测:AssistantMessage 与 ToolCall
2. 元数据检测:验证 finishReason
3. 响应解析的核心:buildGeneration 方法
三、工具执行机制
四、无缝集成:ChatClient 的角色
在智能应用开发中,工具调用(Tool Calling)是大模型落地的重要能力。它让模型不仅能对话,还能主动调用外部函数或服务,从而具备“执行”能力。Spring AI 对此提供了完整的框架支持,本文将结合源码解析,深入剖析 Spring AI 是如何检测、解析并执行模型的工具调用的。
一、工具调用整体流程
- 1. 当我们想要让模型可以使用某个工具时,我们会将其定义包含在聊天请求(Prompt)中,并调用ChatModel将请求发送到 AI 模型的 API。
- 当模型决定调用某个工具时,它会发送一个响应(ChatResponse),其中包含工具名称和根据定义的模式建模的输入参数。
- 将ChatModel工具调用请求发送到ToolCallingManagerAPI。
- 负责ToolCallingManager识别要调用的工具并使用提供的输入参数执行它。
- 工具调用的结果返回给ToolCallingManager。
- 将ToolCallingManager工具执行结果返回给ChatModel。
- 将ChatModel工具执行结果发送回AI模型(ToolResponseMessage)。
二、工具调用检测机制
1. 结构检测:AssistantMessage
与 ToolCall
当 AI 模型返回响应时,Spring AI 会将其封装在 AssistantMessage
对象中。这个类是检测工具调用的第一道关卡。
AssistantMessage.java:69-71
public boolean hasToolCalls() {return!CollectionUtils.isEmpty(this.toolCalls);
}
通过检查 toolCalls
列表是否为空,框架可以快速判断响应中是否包含了结构化的工具调用请求。每一个工具调用请求都被解析为一个 ToolCall
记录(Record):
AssistantMessage.java:103-105
public record ToolCall(String id, String type, String name, String arguments) {
}
这个记录清晰地定义了工具调用的所有要素:一个唯一的 id
、类型(通常是 "function")、工具的 name
以及 JSON 格式的 arguments
。
2. 元数据检测:验证 finishReason
仅仅有 ToolCall
结构还不够。大语言模型在返回响应时,会附带一个 finishReason
元数据,用来说明生成停止的原因(例如,正常结束、达到长度限制,或是因为需要调用工具)。Spring AI 利用这个元数据作为第二重验证。
核心的检测逻辑位于 AbstractToolCallSupport
类中:
AbstractToolCallSupport.java:265-272
protected boolean isToolCall(Generation generation, Set<String> toolCallFinishReasons) {var finishReason = (generation.getMetadata().getFinishReason()!= null)? generation.getMetadata().getFinishReason() : "";return generation.getOutput().hasToolCalls() && toolCallFinishReasons.stream().map(s -> s.toLowerCase()).toList().contains(finishReason.toLowerCase());
}
这段代码清晰地展示了双重验证:
generation.getOutput().hasToolCalls()
: 进行结构检测,确认AssistantMessage
中存在ToolCall
对象。toolCallFinishReasons.stream()...contains(finishReason.toLowerCase())
: 进行元数据检测,确认模型的finishReason
是 "tool_calls" 或 "function_call" 等预期的值。
只有当这两个条件同时满足时,Spring AI 才会确认这是一个有效的工具调用意图。
3. 响应解析的核心:buildGeneration
方法
那么,AssistantMessage
中的 toolCalls
列表和 finishReason
是从何而来的呢?答案在于各个模型实现中的响应解析逻辑。以 OpenAI 为例,其核心转换逻辑如下:
private Generation buildGeneration(Choice choice, Map<String, Object> metadata, ChatCompletionRequest request) {List<AssistantMessage.ToolCall> toolCalls = choice.message().toolCalls() == null? List.of(): choice.message().toolCalls().stream().map(toolCall -> new AssistantMessage.ToolCall(toolCall.id(), "function",toolCall.function().name(), toolCall.function().arguments())).toList();String finishReason = (choice.finishReason()!= null? choice.finishReason().name() : "");var generationMetadataBuilder = ChatGenerationMetadata.builder().finishReason(finishReason);//... 其他逻辑...var assistantMessage = new AssistantMessage(textContent, metadata, toolCalls, media);return new Generation(assistantMessage, generationMetadataBuilder.build());
}
这段代码是连接模型原始输出和 Spring AI 内部模型的桥梁。它明确地从模型的原始响应(Choice
对象)中提取 toolCalls
和 finishReason
,并将它们构建成 Spring AI 的标准 Generation
和 AssistantMessage
对象。
toolCalls
和 finishReason
是实现 Agent 功能不可或缺的两个关键要素。任何主流的大模型如果想要融入 Spring AI 生态,就必须支持这两个特性,因为它们是模型从一个纯粹的文本生成器转变为一个能够执行动作的智能代理的基础。
三、工具执行机制
一旦检测到工具调用意图,执行的接力棒就交给了 DefaultToolCallingManager
。
DefaultToolCallingManager.java:195-233
for (AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) {String toolName = toolCall.name();String toolInputArguments = toolCall.arguments();// 查找匹配的工具回调FunctionCallback toolCallback = toolCallbacks.stream().filter(tool -> toolName.equals(tool.getName())).findFirst().orElseGet(() -> toolCallbackResolver.resolve(toolName));// 执行工具并获取结果String toolResult = toolCallback.call(toolInputArguments, toolContext);toolResponses.add(new ToolResponseMessage.ToolResponse(toolCall.id(), toolName, toolResult));
}
这个流程清晰地展示了从解析到执行的每一步:
- 遍历工具调用:从
AssistantMessage
中获取所有ToolCall
请求并逐一处理。 - 解析参数:提取工具的名称和 JSON 格式的参数。
- 解析与匹配:根据
toolName
在已注册的FunctionCallback
列表中查找并解析出对应的工具实例。 - 执行调用:调用
toolCallback.call()
方法,将参数传入,执行实际的 Java 函数。 - 封装结果:将执行结果封装成
ToolResponseMessage
,准备在下一轮对话中返回给模型。
四、无缝集成:ChatClient
的角色
这一切复杂的内部机制,对于开发者而言,都被 DefaultChatClient
优雅地封装了起来。通过其内置的 advisor
链,整个工具调用的检测、解析、执行和结果反馈流程被自动化处理。开发者只需简单地配置 ToolCallbackProvider
,并在调用时使用 .tools()
或 .functions()
方法注册工具即可。
精心设计的责任分离
通过对源码的深入分析,我们可以看到 Spring AI 在工具调用功能上体现出的卓越设计思想:
- 双重检测机制:结合结构化数据(
toolCalls
)和元数据(finishReason
)进行双重验证,保证了意图识别的准确性和鲁棒性。 - 责任分离:模型层(
ChatModel
)专注于将服务商的原始响应解析为统一的ToolCall
对象,而ChatClient
和ToolCallingManager
则负责工具的解析、执行和生命周期管理。