ChatModel 工具调用
ChatModel 是面向聊天服务提供商的低层请求/响应接口。它接收工具定义,将其发送给模型,并返回模型的响应。该响应中的工具调用不会自动执行——执行工具调用是调用方的责任。
对于大多数应用而言,ChatClient 是推荐的入口:它通过 ToolCallingAdvisor 处理工具调用循环,支持组合记忆(memory)与可观测性(observability)顾问,并提供自动配置扩展点。此页面面向那些有意选择较低层路径的用户。
何时直接调用 ChatModel
ChatModel 是合适的选择,当:
-
你不希望使用建议器链(advisor chain)(即无内存建议器(memory advisor)、无可观测性建议器(observability advisor)、无
ToolCallingAdvisor)。 -
你正在将工具调用(tool calling)集成到自定义的编排器(orchestrator)(例如领域特定的工作流引擎、非 Spring 的智能体框架)中,且该编排器拥有自己的循环控制。
-
你正在基于 Spring AI 构建基础设施——例如,实现一个自定义的
ChatClient,或者开发一个暴露自身高级 API 的库。
其他所有场景——聊天应用、RAG、代理工作流、工具密集型助手——请使用 ChatClient。
无内部工具执行
ChatModel 不会执行工具调用。ChatModel.call(prompt) 和 ChatModel.stream(prompt) 返回模型的原始响应,包括任何工具调用请求,但不会执行这些调用。执行所请求的工具并重新调用模型是调用者的责任——请参阅 手动驱动循环。
如果您来自 Spring AI 1.x,其中每个 ChatModel 都运行自己的内部工具执行循环,请参阅 从 1.x 到 2.0 升级工具调用 了解变更内容及迁移方法。
向 ChatModel 传递工具
工具通过 ToolCallingChatOptions.toolCallbacks(…) 进行传递。该选项接受 List<ToolCallback> 或 ToolCallback[]。使用 ToolCallbacks.from(…) 可将带有 @Tool 注解的对象转换为回调。
按请求处理工具
ChatModel chatModel = ...
ToolCallback[] tools = ToolCallbacks.from(new DateTimeTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(tools)
.build();
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
ChatResponse response = chatModel.call(prompt);
这将工具定义发送给模型。如果模型决定调用工具,响应中将包含调用请求;您需要自行执行该请求(参见 手动驱动循环)。
默认工具(在模型上配置)
部分 ChatModel 构建器支持默认选项。在默认选项中设置的工具将对每个请求生效,除非被覆盖:
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.options(ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build())
.build();
默认工具会在通过该 ChatModel 实例发起的每次请求时都发送给模型。这对于需要始终可用的工具来说很方便,但也可能存在风险——风险分级工具和高危操作工具通常应按需添加到单次请求中,而不是作为默认工具。
覆盖语义
当工具既在 ChatModel 的默认选项中设置,又在每次请求的选项中设置时,每次请求的工具列表将完全替代默认值:
ChatModel chatModel = OllamaChatModel.builder()
.options(ToolCallingChatOptions.builder()
.toolCallbacks(defaultTools) // 5 default tools
.build())
.build();
ChatOptions runtimeOptions = ToolCallingChatOptions.builder()
.toolCallbacks(otherTool) // 1 tool
.build();
ChatResponse response = chatModel.call(new Prompt("...", runtimeOptions));
// The model sees ONLY otherTool — defaultTools were replaced, not appended.
若要在单个请求中使用默认值外加一个额外工具,请在运行时选项中明确包含默认值:
List<ToolCallback> combined = new ArrayList<>(Arrays.asList(defaultTools));
combined.add(otherTool);
ChatOptions runtimeOptions = ToolCallingChatOptions.builder()
.toolCallbacks(combined)
.build();
此覆盖行为仅适用于 ChatModel API。当使用 ChatClient 时,每次调用的 .tools(…) 会追加到 .defaultTools(…) 中——这两层配置可以自然组合。参见 向 ChatClient 传递工具。
手动驱动循环
当模型返回包含工具调用的响应时,你需要执行这些工具,并将结果再次传递给模型进行调用。这是 ToolCallingAdvisor 为 ChatClient 自动运行的循环。而对于 ChatModel,则需要你自行编写该逻辑。
阻塞
ChatModel chatModel = ...
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();
ToolCallback[] tools = ToolCallbacks.from(new WeatherTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(tools)
.build();
Prompt prompt = new Prompt("What is the weather in Amsterdam and Paris?", chatOptions);
ChatResponse response = chatModel.call(prompt);
while (response.hasToolCalls()) {
ToolExecutionResult result = toolCallingManager.executeToolCalls(prompt, response);
if (result.returnDirect()) {
// Tool's returnDirect=true — break out without sending back to the model
return result.conversationHistory();
}
prompt = new Prompt(result.conversationHistory(), chatOptions);
response = chatModel.call(prompt);
}
String finalAnswer = response.getResult().getOutput().getText();
主要组件:
-
ToolCallingManager— 执行工具调用。Spring Boot 会自动配置默认的DefaultToolCallingManager。在未使用自动配置时,可以通过ToolCallingManager.builder().build()手动构建。 -
response.hasToolCalls()— 如果模型至少请求了一个工具,则返回true。 -
toolCallingManager.executeToolCalls(prompt, response)— 查找并执行每个请求的工具,并返回包含更新后对话历史的ToolExecutionResult。 -
result.returnDirect()— 如果所有被调用的工具都设置了returnDirect = true,则返回true。请参见 Return Direct。 -
result.conversationHistory()— 包含原始消息、助手的工具调用请求以及工具响应。请将其用作下一个 prompt 的消息。
Streaming
流式变体在检查工具调用之前,会通过 ChatClientMessageAggregator 对每次迭代的分块进行聚合。您可以在聚合的同时,将原始分块流转发给下游订阅者(例如 SSE 端点):
ChatModel chatModel = ...
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();
ToolCallback[] tools = ToolCallbacks.from(new WeatherTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(tools)
.build();
Prompt prompt = new Prompt("What is the weather in Amsterdam and Paris?", chatOptions);
while (true) {
AtomicReference<ChatResponse> aggregated = new AtomicReference<>();
new MessageAggregator().aggregate(
chatModel.stream(prompt).doOnNext(chunk -> forwardToSse(chunk)),
aggregated::set
).blockLast();
ChatResponse response = aggregated.get();
if (!response.hasToolCalls()) {
break;
}
ToolExecutionResult result = toolCallingManager.executeToolCalls(prompt, response);
if (result.returnDirect()) {
break;
}
prompt = new Prompt(result.conversationHistory(), chatOptions);
}
有关使用 ChatClient 实现完全由 advisor 组合驱动的流式处理,请参阅 ToolCallingAdvisor: 用户控制的流式处理。
工具上下文
工具上下文——传递给工具方法的非模型数据——在 ChatModel 和 ChatClient 中的行为相同。通过 ToolCallingChatOptions 进行设置:
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(ToolCallbacks.from(new CustomerTools()))
.toolContext(Map.of("tenantId", "acme"))
.build();
Prompt prompt = new Prompt("Tell me about customer 42", chatOptions);
chatModel.call(prompt);
当默认和运行时 toolContext 均被设置时,最终生成的上下文是两者的合并结果(与会被替换的 toolCallbacks 不同)——对于匹配的键,运行时条目的优先级高于默认值。
有关工具端 API,请参阅Tool Context。
直接返回
returnDirect 标志会被 ToolCallingManager.executeToolCalls(…) 遵循。执行后,请检查 ToolExecutionResult.returnDirect():
ToolExecutionResult result = toolCallingManager.executeToolCalls(prompt, response);
if (result.returnDirect()) {
// Skip the next model call — return the tool result to the caller
return result.conversationHistory();
}
如果模型在一次迭代中请求了多个工具调用,则 returnDirect() 为 true 仅当所有被调用的工具都具有 returnDirect = true。有关在工具上声明该标志的说明,请参见 Return Direct。
参见
-
工具调用(Tool Calling) — 推荐的基于
ChatClient的路径 -
ToolCallingAdvisor — 为
ChatClient执行循环的 Advisor -
使用 ChatClient 的用户控制流式处理 — 在 Advisor 链仍然存在的类似模式