Spring Blog

Tool Calling in Spring AI 2.0: A Composable, Agentic Architecture

8.5内容质量
Tool Calling in Spring AI 2.0: A Composable, Agentic Architecture

TL;DR · AI 摘要

Spring AI 2.0 重构了工具调用机制,通过 advisor 链实现可组合的代理架构,提升 AI 系统的灵活性和可扩展性。

核心要点

  • Spring AI 2.0 将工具调用提升为 advisor 链中的核心组件,支持递归调用和组合行为。
  • 使用 `@Tool` 注解可以轻松定义工具方法,并通过 `.tools()` 显式传递给 `ChatClient`。
  • 工具调用循环由 `ToolCallingAdvisor` 实现,直到模型生成无工具调用的响应时停止。

结构提纲

按章节快速跳转。

  1. 工具调用是构建代理 AI 系统的核心机制,Spring AI 2.0 重构了这一机制。

  2. 1.x 版本中,每个聊天模型实现都包含私有的工具执行循环,无法组合或扩展。

  3. 2.0 版本将工具循环提升为 advisor 链中的可组合组件,支持递归调用和多种循环机制。

  4. 通过 `@Tool` 注解可以定义工具方法,并自动生成 JSON 参数模式。

  5. `ToolCallingAdvisor` 是一个递归顾问,用于实现工具调用循环,直到模型生成无工具调用的响应。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • Spring AI 2.0 工具调用架构
    • 核心改进
      • advisor 链机制
      • 工具调用循环
    • 工具定义
      • @Tool 注解
      • JSON 参数模式
    • 工具调用循环实现
      • ToolCallingAdvisor

金句 / Highlights

值得收藏与分享的关键句。

  • Spring AI 2.0 将工具循环提升为 advisor 链中的可组合组件,支持递归调用和多种循环机制。

    第 2 段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 通过 `@Tool` 注解可以轻松定义工具方法,并通过 `.tools()` 显式传递给 `ChatClient`。

    第 3 段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • `ToolCallingAdvisor` 是一个递归顾问,用于实现工具调用循环,直到模型生成无工具调用的响应时停止。

    第 4 段

    ⬇︎ 下载 PNG𝕏 分享到 X
#Spring AI#AI#工具调用#架构设计
打开原文

标题:Spring AI 2.0 中的工具调用:可组合的智能代理架构

URL 来源:https://spring.io/blog/2026/06/15/spring-ai-composable-tool-calling

Markdown 内容: 工具调用 —— 即 AI 模型能够调用应用定义的函数并根据结果采取行动 —— 是智能代理系统的基本构建块。图片 1 能够发现信息、采取行动并循环直到达成目标的模型即为代理。

Spring AI 2.0 从底层重新设计了工具调用。在 1.x 版本中,每个聊天模型的实现都包含其私有的工具执行循环 —— 虽然功能齐全,但隐藏较深。没有方法可以接入它、观察中间步骤或与其他行为组合。你可以调用工具,但你无法在工具调用的基础上构建。

2.0 版本将工具循环提升为 advisor 链 中的一等、可组合组件。ChatClient 会将每个请求通过一个有序的 advisor 链运行,并支持循环,允许 advisor 重新进入下游链。相同的机制驱动了工具调用循环、结构化输出重试循环和评估循环。

  • * *

[定义工具](https://spring.io/blog/2026/06/15/spring-ai-composable-tool-calling#defining-tools)

定义工具最简单的方式是使用 @Tool 注解在任意方法上:

code
class WeatherTools {

    @Tool(description = "获取给定城市的当前天气")
    public String getWeather(String city) {
        return weatherService.fetch(city);
    }

    @Tool(description = "在给定日期预订两个城市之间的航班")
    public BookingConfirmation bookFlight(
            String origin,
            String destination,
            @ToolParam(description = "格式为 YYYY-MM-DD 的日期") String date) {
        return flightService.book(origin, destination, date);
    }
}

Spring AI 会自动生成输入参数的 JSON 模式。@ToolParam 为每个参数添加描述和可选/必填提示。标注了 @Nullable 的参数默认被视为可选。

工具通过 .tools() 显式传递给 ChatClient

code
String response = ChatClient.create(chatModel)
    .prompt("阿姆斯特丹的天气如何?如果天气晴朗,请预订从伦敦出发的航班。")
    .tools(new WeatherTools())
    .call()
    .content();

参考文档详细介绍了 所有工具定义选项,包括编程式 MethodToolCallbackFunctionToolCallback API。

  • * *

[工具调用循环:`ToolCallingAdvisor`](https://spring.io/blog/2026/06/15/spring-ai-composable-tool-calling#the-tool-calling-loop-toolcallingadvisor)

ToolCallingAdvisor 是一个递归的 advisor —— 一种会反复重新进入下游链,直到满足停止条件的 advisor。在此情况下,停止条件是模型生成一个不包含工具调用的响应。DefaultChatClient 会自动将其添加到 advisor 链中 —— 任何时候只能存在一个 ToolAdvisor —— 从那时起,它将拥有完整的工具执行生命周期:

图片 2
图片 2

工具通过 @Tool@McpTool1java.util.FunctionToolCallback 定义 —— 顾问提取它们的名称、描述和输入模式,并将生成的 工具定义 与用户的问题和系统提示一起注入到初始上下文中。

在每次迭代中,累积的 对话历史(用户消息、AI 工具调用请求以及前几轮的工具响应)会与当前上下文合并并发送给 LLM。LLM 生成一个完成结果,顾问会检查该结果:

  • 如果包含工具调用ToolCallingManager 会找到并执行引用的工具,将工具响应追加到对话历史中,然后循环回去。
  • 如果不包含工具调用:最终答案将返回给用户。

阻塞(.call())和流式(.stream())模式都得到了全面支持。

顾问相对于 ToolCallingAdvisor 的位置(默认顺序 HIGHEST_PRECEDENCE + 300)决定了它是否只看到最终结果(外部)或每一轮迭代(内部)——下一节将通过内存展示这一点。

  • * *

内存与工具循环

你将 MessageChatMemoryAdvisor 放置在 ToolCallingAdvisor 相对于的位置决定了内存存储捕获的对话上下文数量。

在循环外部(默认 —— 顺序 HIGHEST_PRECEDENCE + 200):内存顾问在循环开始前加载一次历史记录,并仅持久化最终的用户和助手消息。工具请求和响应消息不会写入存储。这对于每个 ChatMemoryRepository 实现都是安全的,并且与 Spring AI 1.x 的行为一致,其中工具循环在聊天模型内部运行,内存无法观察到工具消息。

在循环内部(顺序大于 ToolCallingAdvisor.DEFAULT_ORDER):内存顾问在每次迭代中都会被调用,并持久化完整的工具请求/响应记录。这为 LLM 提供了更丰富的上下文 —— 它可以推理出已经尝试过的内容、调用了哪些工具以及它们返回了什么。

为了避免重复写入,当内存顾问位于循环内部时,必须禁用 ToolCallingAdvisor 的内部对话历史。对于自动注册的 `ToolCallingAdvisor`,这是自动完成的 —— DefaultChatClient 会检测到任何放置在循环内部的 MemoryAdvisor,并自动禁用内部历史,无需额外配置。如果你手动构建 ToolCallingAdvisor,请自行在构建器上调用 .disableInternalConversationHistory()

Image 3
Image 3

并非所有的 ChatMemoryRepository 都能够持久化工具消息。仓库需要知道如何序列化 ToolResponseMessage 以及工具调用请求,同时还要处理普通用户和助手的对话轮次 —— 而大多数当前的实现只建模了后者。从 2.0 版本开始,支持完整消息集的内置仓库包括 InMemoryChatMemoryRepositoryRedisChatMemoryRepositoryNeo4jChatMemoryRepository —— 这些仓库在循环中使用都是安全的。

对于需要完整工具消息支持的 JDBC 持久化 —— 加上事件源历史记录、轮次感知的压缩以及多代理分支隔离 —— 请使用新的社区项目 Spring-AI-Session。该项目专为此场景设计,并计划在 Spring AI 2.1 版本中包含。

  • * *

扩展到数百个工具:`ToolSearchToolCallingAdvisor`

Image 4
Image 4

标准的 ToolCallingAdvisor 在每次请求时会将所有注册的工具定义发送给模型。对于工具库较小的情况,这没有问题。但在有 30 个以上工具的情况下 —— 或者在多服务器 MCP 设置中,单个会话可能聚合数百个工具定义时 —— 会导致上下文膨胀、准确性下降和不必要的令牌成本。

ToolSearchToolCallingAdvisorToolCallingAdvisor 的一种即插即用的替代方案,它实现了 _渐进式工具披露_ 模式:而不是一开始就发送所有工具定义,它会按需逐步将工具暴露给模型。在会话开始时,它会索引完整的工具集;在每次迭代中,它只注入一个内置的 toolSearchTool,模型可以使用它通过自然语言查询来检索相关工具。只有发现的工具才会包含在后续请求中。

通过以下属性启用它:

code
spring.ai.chat.client.tool-search-advisor.enabled=true
spring.ai.chat.client.tool-search-advisor.tool-index-type=vector  # regex (默认), lucene, 或 vector

由于工具索引是按会话范围限定的,调用方必须在每次请求中提供一个 会话 ID —— 顾问使用它来隔离不同对话和租户之间的索引。默认情况下,会话 ID 从顾问上下文中的 ChatMemory.CONVERSATION_ID 读取:

code
chatClient.prompt()
    .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "user-42-session"))
    .user("Help me plan my trip to Amsterdam")
    .call()
    .content();

如果已经使用了不同的名称传递会话标识符,可以通过 spring.ai.chat.client.tool-search-advisor.session-id-key-name 配置键名。

有三种 ToolIndex 策略可用:regex(轻量级,无需额外依赖,默认)、lucene(关键字搜索,包含在 starter 中)和 vector(基于嵌入的语义搜索,需要 VectorStore bean)。有关完整的配置表面,请参阅 Tool Search Tool 参考文档

我们的2025年12月文章深入探讨了这一模式,包括基准测试结果,显示在OpenAI、Anthropic和Gemini模型上实现了34–64%的token减少,以及多步骤发现流程的工作示例。该顾问从社区毕业,成为Spring AI核心的一部分,作为2.0版本的一部分。

  • * *

[工具参数增强](https://spring.io/blog/2026/06/15/spring-ai-composable-tool-calling#tool-argument-augmentation)

Spring AI允许你动态地向工具的输入模式中添加额外的参数,而无需修改工具的实现。模型会看到增强后的模式并填写额外的字段;你的代码通过消费者接收它们;原始工具只接收到自己的参数,保持不变。

主要的使用场景是内部思考:强制模型在执行工具之前阐述其推理过程,这可以提高可追溯性,并可以存储在长期记忆中或用于评估。

图片5
图片5

使用AugmentedToolCallbackProvider包装你的工具:

code
public record AgentThinking(
    @ToolParam(description = "调用此工具的理由")
    String innerThought) {}

AugmentedToolCallbackProvider<AgentThinking> toolProvider = 
    AugmentedToolCallbackProvider.<AgentThinking>builder()
        .toolObject(new WeatherTools())     // 包装原始工具
        .argumentType(AgentThinking.class)  // 增强模式类型
        .argumentConsumer(event -> log.info( // 可选的增强内容消费者
            "工具: {} | 推理: {}", event.toolDefinition().name(), event.arguments().innerThought()))
        .build();

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools(toolProvider)
    .build();

有关完整的API,请参阅工具参数增强参考

  • * *

[MCP工具](https://spring.io/blog/2026/06/15/spring-ai-composable-tool-calling#mcp-tools)

MCP(Model Context Protocol)工具从两个方向与Spring AI的工具调用架构集成:你的应用程序可以消费远程MCP服务器暴露的工具,也可以将它自己的Spring管理的工具暴露给MCP客户端。

[使用MCP服务器工具](https://spring.io/blog/2026/06/15/spring-ai-composable-tool-calling#consuming-mcp-server-tools)

添加MCP客户端启动器并在application.properties中配置连接到一个或多个MCP服务器:

code
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
code
spring.ai.mcp.client.stdio.connections.my-server.command=npx
spring.ai.mcp.client.stdio.connections.my-server.args=-y,@modelcontextprotocol/server-everything

自动配置会连接到所有配置的MCP服务器,发现它们的工具,并将它们作为单个SyncMcpToolCallbackProvider bean(或AsyncMcpToolCallbackProvider用于异步客户端类型)暴露。MCP提供者故意不会自动注册到ChatClient中——它们实现了ToolCallbackProvider,但急于列出工具会强制在启动时向每个连接的MCP服务器发起网络往返。相反,你注入提供者并显式地连接它:

code
@Autowired SyncMcpToolCallbackProvider mcpTools;
code
// 作为默认工具用于每个请求
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools(mcpTools)
    .build();

// 或者每次调用时指定
chatClient.prompt()
    .user("在网页上搜索最新的 Spring AI 发布说明")
    .tools(mcpTools)
    .call()
    .content();

工具回调的自动配置默认是启用的,可以通过设置 spring.ai.mcp.client.toolcallback.enabled=false 来关闭。当连接到多个可能暴露相同名称工具的 MCP 服务器时,会自动应用 DefaultMcpToolNamePrefixGenerator 以避免冲突。有关配置属性、传输选项和工具过滤的完整列表,请参阅 MCP 客户端参考文档

将 Spring 工具作为 MCP 服务器公开

反过来,将你的 Spring Bean 作为 MCP 工具公开,只需将 @Tool 替换为 @McpTool 并添加 MCP 服务器启动器:

code
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
code
@Component
public class WeatherTools {

    @McpTool(description = "获取给定城市的当前天气")
    public String getWeather(
            @McpToolParam(description = "城市名称") String city) {
        return weatherService.fetch(city);
    }
}

MCP 服务器的自动配置会扫描带有 @McpTool 注解的 Bean,为它们的参数生成 JSON 模式,并将其注册到 MCP 服务器中 —— 不需要额外的连接。

有关传输配置、安全性和可观测性选项的详细信息,请参阅 MCP 服务器参考文档

组合本地和 MCP 工具

一旦注册,本地 @Tool 方法和远程 MCP 工具共享相同的 ToolCallback 接口 —— 模型和 ToolCallingAdvisor 不会区分它们。.tools(...).defaultTools(...) 是异构的,可以在一次调用中接受两种类型,因此你可以自由混合使用:

code
chatClient.prompt()
    .tools(new LocalTools(), mcpTools)
    .call()
    .content();

在混合设置中需要注意以下几点:

  • 名称冲突仅在 MCP 端处理。DefaultMcpToolNamePrefixGenerator 会在 MCP 服务器之间对重复项进行前缀处理,但它并不知道本地的 @Tool 方法。如果本地工具和远程 MCP 工具名称相同,你需要自己重命名其中一个,或者使用 McpToolFilter 来排除远程工具。
  • 限制暴露的内容。 MCP 工具来自外部源,你无法完全控制它们的表面。一个 McpToolFilter Bean 允许你根据服务器身份、工具名称或描述来选择哪些工具进入命名空间 —— 这对于限制嘈杂或不可信的 MCP 服务器的影响范围非常有用。
  • * *

扩展循环:构建你自己的 `ToolAdvisor`

ToolAdvisor 是一个标记接口:任何自定义的工具调用顾问都必须实现它,这样 DefaultChatClient 才能识别它、强制单顾问约束,并将其注册为默认 ToolCallingAdvisor 的替代者。

ToolSearchToolCallingAdvisor 并不是某种特殊的框架魔法 —— 它是 ToolCallingAdvisor(实现了 ToolAdvisor)的一个子类,它重写了若干受保护的钩子方法,以在明确定义的点上拦截循环:

| 钩子 | 触发时机 | | --- | --- | | doInitializeLoop / doInitializeLoopStream | 在第一次迭代之前,只触发一次 | | doBeforeCall / doBeforeStream | 在每次迭代之前 | | doAfterCall / doAfterStream | 在每次迭代之后 | | doFinalizeLoop / doFinalizeLoopStream | 在循环结束后,只触发一次 |

ToolSearchToolCallingAdvisor 使用 doInitializeLoop 来索引工具集并增强系统消息,使用 doBeforeCall 来注入到目前为止发现的工具。任何自定义的 ToolCallingAdvisor 子类都遵循相同的模式。

自动配置集成

自定义的 ToolCallingAdvisor 实现可以插入到自动配置系统中,而无需任何手动的 ChatClient 连接。扩展点是 ToolCallingAdvisor.Builder<?> Bean。

ChatClientAutoConfiguration 声明了一个默认的 ToolCallingAdvisor.Builder<?> Bean,由 @ConditionalOnMissingBean 保护。要替换它,可以在一个在 ChatClientAutoConfiguration 之前运行的自动配置中注册你自己的 ToolCallingAdvisor.Builder<?> Bean —— 类型为基类 ToolCallingAdvisor.Builder<?>

code
@AutoConfiguration(beforeName = "org.springframework.ai.model.chat.client.autoconfigure.ChatClientAutoConfiguration")
@ConditionalOnProperty(prefix = "my.advisor", name = "enabled", havingValue = "true")
public class MyToolAdvisorAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    ToolCallingAdvisor.Builder<?> toolCallingAdvisorBuilder(ToolCallingManager toolCallingManager) {
        return MyCustomToolCallingAdvisor.builder()
            .toolCallingManager(toolCallingManager);
    }
}

然后,ChatClient.Builder 使用你自定义的构建器,透明地自动注册你的顾问。

ToolSearchToolCallingAdvisor 正是使用了这种机制 —— 它的自动配置注册了一个类型为 ToolCallingAdvisor.Builder<?>ToolSearchToolCallingAdvisor.Builder,这正是 DefaultChatClient 为了替换默认顾问而自动注册它所需要的全部内容。

  • * *

用户控制的工具执行

自动注册的循环涵盖了大多数情况,但有些场景确实需要你来掌控每一次迭代:在外部批准步骤上限制工具执行、将中间进度转发到 SSE 或 WebSocket 端点、在回合之间应用条件逻辑,或根据侧信道信号停止循环。

退出自动注册是一个按调用切换的选项 —— 在请求上设置 AdvisorParams.toolCallingAdvisorAutoRegister(false),你将负责在 ChatResponse 中检测工具调用,并通过 ToolCallingManager 执行它们。

code
ChatClient chatClient = ...
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();
java
ToolCallback[] tools = ToolCallbacks.from(new WeatherTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder().toolCallbacks(tools).build();

String question = "What is the weather in Amsterdam and Paris?";

// ToolCallingAdvisor 已被禁用 —— 不会自动运行工具循环
ChatClientResponse response = chatClient.prompt()
    .user(question)
    .options(chatOptions)
    .advisors(AdvisorParams.toolCallingAdvisorAutoRegister(false))
    .call()
    .chatClientResponse();

Prompt prompt = new Prompt(List.of(new UserMessage(question)), chatOptions);

// 手动控制循环 —— 每次迭代都可以被观察和中断
while (response.chatResponse() != null && response.chatResponse().hasToolCalls()) {
    ToolExecutionResult result = toolCallingManager.executeToolCalls(prompt, response.chatResponse());
    prompt = new Prompt(result.conversationHistory(), chatOptions);
    response = chatClient.prompt()
        .messages(result.conversationHistory())
        .options(chatOptions)
        .advisors(AdvisorParams.toolCallingAdvisorAutoRegister(false))
        .call()
        .chatClientResponse();
}

相同的模式也适用于流式 API,其中每次迭代的 Flux 块可以转发给订阅者,同时通过 ChatClientMessageAggregator 进行聚合。有关流式版本和完整执行模型的详细信息,请参阅 用户控制的工具执行参考

  • * *

从 Spring AI 1.x 升级

##### FunctionToolCallback 豆替换 Function 豆和 .functions()

SpringBeanToolCallbackResolvertoolNames() API —— 通过名称从裸 Function/Supplier/Consumer 豆解析工具 —— 已被移除。现在工具必须注册为显式的 ToolCallback 豆。对于函数风格的工具,请使用 FunctionToolCallback.builder()

code
// 之前(1.x) —— 通过名称解析裸 Function 豆
@Bean
@Description("Get the weather in location")
Function<WeatherRequest, WeatherResponse> currentWeather() {
    return weatherService::getWeather;
}
chatClient.prompt().toolNames("currentWeather"); // 不再存在

// 之后(2.0) —— 显式的 ToolCallback 豆
@Bean
ToolCallback currentWeather() {
    return FunctionToolCallback.builder("currentWeather", weatherService::getWeather)
        .description("Get the weather in location")
        .inputType(WeatherRequest.class)
        .build();
}

// 注入豆并将其传递给 ChatClient —— 名称解析已被移除
@Autowired ToolCallback currentWeather;

chatClient.prompt()
    .user("What's the weather in Copenhagen?")
    .tools(currentWeather)
    .call()
    .content();

##### internalToolExecutionEnabled 已被移除

internalToolExecutionEnabled 选项和对应的配置属性已被移除。每个模型的内部工具执行不再存在 —— ToolCallingAdvisor 是唯一的执行路径。请从代码中删除所有对 .internalToolExecutionEnabled(...) 的调用。

对于用户控制的执行(之前通过设置 internalToolExecutionEnabled(false) 实现),请改用 AdvisorParams.toolCallingAdvisorAutoRegister(false)

##### [](https://spring.io/blog/2026/06/15/spring-ai-composable-tool-calling#toolcalladvisor-renamed-to-toolcallingadvisor)ToolCallAdvisor 重命名为 ToolCallingAdvisor

如果你直接引用了 ToolCallAdvisor,请更新为 ToolCallingAdvisor

##### [](https://spring.io/blog/2026/06/15/spring-ai-composable-tool-calling#streamtoolcallresponses-removed-from-advisor-builders)streamToolCallResponses 从 advisor 构建器中移除

ToolCallingAdvisor.BuilderToolSearchToolCallingAdvisor.Builder 上的 .streamToolCallResponses(...) 选项已被移除。该选项实际上存在缺陷:当启用时,它会将模型的 _工具请求_ 消息传递到下游,但由 advisor 本身生成的 _工具响应_ 消息仍留在循环中。任何位于工具调用 advisor 之外的 advisor 只能观察到每次工具交互的一半 —— 一个请求但没有对应的响应 —— 这比什么也看不到更糟糕。

为了避免提供一个不完整功能,我们选择将其移除。若要观察每个工具请求和响应,请将你的 advisor 置于 工具循环内部 —— 给它一个大于 ToolCallingAdvisor.DEFAULT_ORDER 的顺序,这样它将在每次迭代中被调用,并能够访问完整的请求/响应历史。如果这还不够 —— 例如,你还需拦截模型响应和工具执行之间 —— 请退回到 用户控制的工具执行,并自行控制循环。

##### [](https://spring.io/blog/2026/06/15/spring-ai-composable-tool-calling#options-are-now-immutable)选项现在是不可变的

ChatOptions#copy()[*]Options#fromOptions() 已被移除。使用 .mutate() 来创建现有选项实例的修改副本。

完整的变更列表请参见 升级说明FunctionCallback 到 ToolCallback 的迁移指南 详细介绍了从函数到工具的重命名。

  • * *

总结

Spring AI 2.0 的工具调用架构设计旨在与你一同成长:从一个简单的 @Tool 注解开始,随着应用的成熟逐步添加内存和可观测性,当跨服务时插入 MCP 工具,当工具集扩展时将默认循环替换为 ToolSearchToolCallingAdvisor,当你的领域需要时扩展循环本身。

所有这些都通过一个机制组合在一起:advisor 的顺序。advisor 相对于工具循环的位置 —— 在外部(安全的默认设置,除非你选择加入否则用于内存)或在内部(内存可以捕获完整的工具记录) —— 同样控制着可观测性、重试和你添加的任何自定义 advisor。

  • * *

参考资料

  • * *
  1. @McpTool 位于 远程 MCP 服务器 端 — 它标记了一个方法,该方法由服务器作为 MCP 工具暴露出来。在 Spring AI 的 客户端 端,SyncMcpToolCallbackProvider(或其异步版本)连接到 MCP 服务器,发现其工具,并创建 ToolCallback 代理,ToolCallingAdvisor 会像调用其他工具一样调用这些代理。有关详细信息,请参阅 MCP 工具 部分。