Spring AI Tool Calling源码浅析析
定义阶段:你定义了一个 Java Function<Request, Response>。当你将一个函数注册为 Bean 时(通常使用 @Bean 并配合 @Description 注解),Spring AI 会将其包装成 FunctionCallback 或 ToolCallback。@Bean@Description("根据城市查询天气") // 描述会被发给 AI解析阶段:Spring AI
Spring AI Tool Calling源码浅析析
Spring AI Tool Calling源码浅析析工具相关的组件工具执行流程关注点0:Spring AI 工具的自动配置关注点1: Spring AI如何把定义的工具告知模型关注点2:Spring AI如何知道模型决定调用工具关注点3:Spring AI如何执行工具的调用参考文档
工具相关的组件
-
ToolDefinition(工具的定义):包含工具名称,描述、输入的Schema
-
ToolMetadata(工具元数据):目前只有控制是否将工具返回的结果直接返回(returnDirect),不在走模型响应
-
ToolCallback(工具回调):工具的的回调处理,即我们的业务逻辑
-
MethodToolCallback:方法调用,通过反射实现
-
FunctionToolCallback:Function.apply方式实现
-
-
ToolCallbackProvider(工具回调提供者):Spring AI 会自动扫描 Spring 上下文中定义的 ToolCallback 类型的 Bean,并将它们组合成 Provider
-
StaticToolCallbackProvider:直接对外提供已构建好的ToolCallback
-
MethodToolCallbackProvider:扫描带有Tool注解的方法封装为MethodToolCallback,并对外提供
-
-
ToolCallbackResolver(工具回调):用于需要动态地、按需地查找工具回调
-
StaticToolCallbackResolver:通过ToolCallbackProvider获取的都会添加到StaticToolCallbackResolver
-
SpringBeanToolCallbackResolver:通过name从Spring的上下文动态的获取ToolCallback
-
DelegatingToolCallbackResolver:组合设计模式,组合StaticToolCallbackResolver和SpringBeanToolCallbackResolver对外提供统一的入口
-
-
ToolCallingManager(工具回调管理器):默认实现是DefaultToolCallingManager,核心功能如下
-
解析工具定义(resolveToolDefinitions):从ToolCallingChatOptions中解析出工具定义,确保模型能正确识别和使用工具
-
执行工具调用(executeToolCalls):根据模型响应,执行相应的工具调用,并返回工具的执行结果
-
构建工具上下文(buildToolContext):为工具调用提供上下文信息,历史的Message记录
-
管理工具回调:通过 ToolCallbackResolver 解析工具回调,支持动态工具调用
-
-
ToolCallResultConverter(工具结果转换器):用于把工具返回的结果转换为字符串
-
ToolContext(工具上下文):被构建于工具回调管理器
-
通过getContext方法获取整个上下文,通过getToolCallHistory方法获取Message的历史记录
-
工具执行流程

-
当我们想让模型使用某个工具时,会在聊天请求中包含其工具定义(
提示)并调用聊天模型API 向 AI 模型发送请求。 -
当模型决定调用工具时,会发送响应(
聊天回应工具名称和输入参数均依据定义的模式建模。 -
这
聊天模型将工具调用请求发送给ToolCallingManager应用程序接口。 -
这
ToolCallingManager负责识别调用的工具,并以提供的输入参数执行。 -
工具调用的结果返回给
ToolCallingManager. -
这
ToolCallingManager返回工具执行结果聊天模型. -
这
聊天模型将工具执行结果返回给 AI 模型(工具响应信息). -
AI模型利用工具调用结果作为额外上下文生成最终响应,并将其发送给呼叫者(
聊天回应)通过ChatClient.
下面我们通过OpenAiChatModel来结合源码分析一下
关注点0:Spring AI 工具的自动配置
-
ToolCallingAutoConfiguration
-
SpringBeanToolCallbackResolver:通过name从Spring的上下文动态的获取ToolCallback
-
ToolCallbackProvider:通过工具回调提供者获取ToolCallback
-
@Bean
@ConditionalOnMissingBean
ToolCallbackResolver toolCallbackResolver(
GenericApplicationContext applicationContext, // @formatter:off
List<ToolCallback> toolCallbacks,
// Deprecated in favor of the tcbProviders. Kept for backward compatibility.
ObjectProvider<List<ToolCallbackProvider>> tcbProviderList,
ObjectProvider<ToolCallbackProvider> tcbProviders) { // @formatter:on
List<ToolCallback> allFunctionAndToolCallbacks = new ArrayList<>(toolCallbacks);
// Merge ToolCallbackProviders from both ObjectProviders.
List<ToolCallbackProvider> totalToolCallbackProviders = new ArrayList<>(
tcbProviderList.stream().flatMap(List::stream).toList());
totalToolCallbackProviders.addAll(tcbProviders.stream().toList());
// De-duplicate ToolCallbackProviders
totalToolCallbackProviders = totalToolCallbackProviders.stream().distinct().toList();
totalToolCallbackProviders.stream()
.filter(pr -> !isMcpToolCallbackProvider(ResolvableType.forInstance(pr)))
.map(pr -> List.of(pr.getToolCallbacks()))
.forEach(allFunctionAndToolCallbacks::addAll);
var staticToolCallbackResolver = new StaticToolCallbackResolver(allFunctionAndToolCallbacks);
var springBeanToolCallbackResolver = SpringBeanToolCallbackResolver.builder()
.applicationContext(applicationContext)
.build();
return new DelegatingToolCallbackResolver(List.of(staticToolCallbackResolver, springBeanToolCallbackResolver));
}
关注点1: Spring AI如何把定义的工具告知模型
-
分为三步:
-
定义阶段:你定义了一个 Java Function<Request, Response>。
-
当你将一个函数注册为 Bean 时(通常使用 @Bean 并配合 @Description 注解),SpringBeanToolCallbackResolver 会动态的发现并包装为 FunctionCallback 。
-
Tool注解的方法会经过MethodToolCallbackProvider处理为MethodToolCallback
@Operation(summary = "流式聊天接口")
@GetMapping(path = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamChat(HttpServletResponse response,
@RequestParam("userInput") String userInput,
@RequestParam("sessionId") Long sessionId) {
Flux<String> answerFlux = chatClient.prompt().user(userInput)
.advisors(a -> a.param(CONVERSATION_ID, sessionId))
.toolNames("currentWeather")
.tools(new BaiduTranslateTools(baiduTranslateProperties))
.stream()
.content();
return answerFlux;
}
@Bean
@Description("根据城市查询天气") // 描述会被发给 AI
public Function<WeatherRequest, WeatherResponse> currentWeather() { ... }
-
解析阶段:Spring AI 使用反射分析 Request 类,生成 JSON Schema。
{
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名称" },
"unit": { "type": "string", "enum": ["C", "F"] }
},
"required": ["city"]
}
-
传输阶段:Spring AI 构造 HTTP 请求,将 Schema 放入 tools 数组发送给模型。
Spring AI 将上述 Schema 包装进 LLM(如 OpenAI)的标准 API 请求格式中。这个请求体(Payload)大致如下:
{
"model": "gpt-4",
"messages": [
{ "role": "user", "content": "北京天气怎么样?" }
],
// 关键在这里!Spring AI 自动填充了这个 tools 字段
"tools": [
{
"type": "function",
"function": {
"name": "currentWeather", // 对应 Bean 的名字
"description": "根据城市查询天气", // 对应 @Description
"parameters": {
// 这里就是上面生成的 JSON Schema
"type": "object",
"properties": { ... }
}
}
}
]
}
-
相关的源码:org.springframework.ai.openai.OpenAiChatModel#call
@Override
public ChatResponse call(Prompt prompt) {
Prompt requestPrompt = buildRequestPrompt(prompt);
return this.internalCall(requestPrompt, null);
}
public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) {
//构成请求:见关注点1
ChatCompletionRequest request = createRequest(prompt, false);
// 模型决策需要进行工具调用:见关注点2
if (ToolCallingChatOptions.isInternalToolExecutionEnabled(prompt.getOptions()) && response != null
&& response.hasToolCalls()) {
//执行工具调用:见关注点3
var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response);
if (toolExecutionResult.returnDirect()) {
//直接返回工具的调用
return ChatResponse.builder()
.from(response)
.generations(ToolExecutionResult.buildGenerations(toolExecutionResult))
.build();
}
else {
//发送工具调用的结果再给到模型
return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()),
response);
}
}
return response;
}
ChatCompletionRequest createRequest(Prompt prompt, boolean stream) {
request = ModelOptionsUtils.merge(requestOptions, request, ChatCompletionRequest.class);
// 将工具定义添加到请求的 tools 参数中。
List<ToolDefinition> toolDefinitions = this.toolCallingManager.resolveToolDefinitions(requestOptions);
if (!CollectionUtils.isEmpty(toolDefinitions)) {
request = ModelOptionsUtils.merge(
OpenAiChatOptions.builder().tools(this.getFunctionTools(toolDefinitions)).build(), request,
ChatCompletionRequest.class);
}
return request;
}
关注点2:Spring AI如何知道模型决定调用工具
-
ToolCallback 的 call() 方法是在 LLM(大模型)分析了你的 Prompt,决定需要调用工具,并返回了工具调用请求(Tool Call Request)之后 执行的。
-
模型决策:LLM 接收到 Prompt,发现需要调用工具(例如 currentWeather),于是返回一个特殊的响应,由于此时 finishReason 是 tool_calls(或 function_call),而不是普通的文本。
关注点3:Spring AI如何执行工具的调用
-
框架拦截:Spring AI 的 ChatModel(如 OpenAiChatModel)检测到这个响应包含工具调用指令
-
相关的源码:
@Override
public ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse) {
Optional<Generation> toolCallGeneration = chatResponse.getResults()
.stream()
.filter(g -> !CollectionUtils.isEmpty(g.getOutput().getToolCalls()))
.findFirst();
AssistantMessage assistantMessage = toolCallGeneration.get().getOutput();
ToolContext toolContext = buildToolContext(prompt, assistantMessage);
//执行工具调用
InternalToolExecutionResult internalToolExecutionResult = executeToolCall(prompt, assistantMessage,
toolContext);
List<Message> conversationHistory = buildConversationHistoryAfterToolExecution(prompt.getInstructions(),
assistantMessage, internalToolExecutionResult.toolResponseMessage());
return ToolExecutionResult.builder()
.conversationHistory(conversationHistory)
.returnDirect(internalToolExecutionResult.returnDirect())
.build();
}
private InternalToolExecutionResult executeToolCall(Prompt prompt, AssistantMessage assistantMessage,
ToolContext toolContext) {
String toolResult;
try {
//如果是FunctionToolCallback执行apply MethodToolCallback执行对应的方法
toolResult = toolCallback.call(toolInputArguments, toolContext);
}
catch (ToolExecutionException ex) {
toolResult = toolExecutionExceptionProcessor.process(ex);
}
toolResponses.add(new ToolResponseMessage.ToolResponse(toolCall.id(), toolName, toolResult));
}
return new InternalToolExecutionResult(new ToolResponseMessage(toolResponses, Map.of()), returnDirect);
}
参考文档
更多推荐



所有评论(0)