Skip to content
Spring AI 文档版本 2.1.0-SNAPSHOT 源快照 ee2ed6a4 更新于 2026-10-16

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。关于在工具上声明该标志,参见直接返回。

另见

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。