主题
ChatModel 工具调用
ChatModel 是到某个聊天服务商的底层请求/响应接口。它接受工具定义、把它们发给模型,并返回模型的响应。该响应中的工具调用不会被自动执行——那是调用方的责任。
对于多数应用,ChatClient 是推荐的入口:它通过 ToolCallingAdvisor 处理工具调用循环、与记忆和可观测性顾问组合,并支持自动配置扩展点。本页是为刻意想要底层路径的用户而写。
何时直接用 ChatModel
在以下情形 ChatModel 是正确选择:
- 你不想要顾问链(无记忆顾问、无可观测性顾问、无
ToolCallingAdvisor)。 - 你在把工具调用集成进一个自定义编排器(一个领域专有的工作流引擎、一个非 Spring 的智能体框架),它拥有自己的循环。
- 你在 Spring AI 之上构建基础设施——例如一个自定义的
ChatClient实现,或一个暴露自身更高层 API 的库。
对一切其他场景——聊天应用、RAG、智能体工作流、重度使用工具的助手——请使用 ChatClient。
无内部工具执行
ChatModel 不执行工具调用。ChatModel.call(prompt) 和 ChatModel.stream(prompt) 返回模型的原始响应,包括任何工具调用请求,但不执行它们。执行被请求的工具并重新调用模型是调用方的责任——参见手动驱动循环。
NOTE
如果你来自 Spring AI 1.x(那里每个 ChatModel 都运行自己的内部工具执行循环),关于发生了什么变化及如何迁移,参见将工具调用从 1.x 升级到 2.0。
向 ChatModel 传递工具
工具通过 ToolCallingChatOptions.toolCallbacks(...) 传递。该选项接受一个 List<ToolCallback> 或一个 ToolCallback[]。用 ToolCallbacks.from(...) 把 @Tool 注解对象转换为回调。
逐请求工具
java
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 构建器接受默认选项。默认选项里设置的工具会适用于每个请求,除非被覆盖:
java
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.options(ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build())
.build();DANGER
默认工具会在这个 ChatModel 实例发出的每个请求上被发给模型。这对那些应始终可用的工具很方便,但也可能危险——风险级和破坏性工具通常应按请求添加,而非作为默认。
覆盖语义
当工具同时设置在 ChatModel 的默认选项和逐请求选项里时,逐请求的工具列表会完全替换默认值:
java
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.要在单个请求上使用默认值再加一个额外工具,请在运行时选项里显式包含默认值:
java
List<ToolCallback> combined = new ArrayList<>(Arrays.asList(defaultTools));
combined.add(otherTool);
ChatOptions runtimeOptions = ToolCallingChatOptions.builder()
.toolCallbacks(combined)
.build();NOTE
这一覆盖行为是 ChatModel API 特有的。使用 ChatClient 时,逐次调用的 .tools(...) 会追加到 .defaultTools(...)——两层自然组合。参见向 ChatClient 传递工具。
手动驱动循环
当模型返回一个带工具调用的响应时,你执行这些工具并带着结果再次调用模型。这正是 ToolCallingAdvisor 为 ChatClient 自动运行的那个循环。用 ChatModel 时,你自己写它。
阻塞
java
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—— 执行工具调用。默认的DefaultToolCallingManager由 Spring Boot 自动配置。当不使用自动配置时,用ToolCallingManager.builder().build()手动构建一个。response.hasToolCalls()—— 若模型请求了至少一个工具则为true。toolCallingManager.executeToolCalls(prompt, response)—— 查找每个被请求的工具,执行它,并返回一个含更新后对话历史的ToolExecutionResult。result.returnDirect()—— 若所有被调用的工具都有returnDirect = true则为true。参见直接返回。result.conversationHistory()—— 原始消息加上助手的工具调用请求和工具响应。把它作为下一个提示词的消息。
流式
流式变体在检查工具调用之前,通过 ChatClientMessageAggregator 聚合每次迭代的各块。你可以在聚合的同时把原始块流转发给下游订阅者(例如一个 SSE 端点):
java
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 驱动、带完整顾问组合的流式,参见 ToolCallingAdvisor:用户控制的流式。
工具上下文
工具上下文——传给工具方法的非模型数据——在 ChatModel 上与在 ChatClient 上工作方式相同。通过 ToolCallingChatOptions 设置它:
java
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 参见工具上下文。
直接返回
returnDirect 标志由 ToolCallingManager.executeToolCalls(...) 采纳。执行后,检查 ToolExecutionResult.returnDirect():
java
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。关于在工具上声明该标志,参见直接返回。
另见
- 工具调用 —— 推荐的、基于
ChatClient的路径 ToolCallingAdvisor—— 为ChatClient运行该循环的顾问- 用 ChatClient 做用户控制的流式 —— 顾问链仍在位时的类似模式
- 将工具调用从 1.x 升级到 2.0