#5、Spring AI Tool Calling 深度解读(从概念>原理>定义工具>使用工具>核心接口剖析)

1. 工具调用是什么

1.1 一句话定义

Tool Calling(工具调用) 是大语言模型(LLM)借用外部工具来完成它自己做不到的事情------LLM 在生成回复的过程中,自主决定调用你注册的工具/函数,获取外部数据或执行操作,然后将结果纳入上下文继续生成。 比如用户提问"帮我查询北京最新的天气",AI 本身并没有这些知识,它就可以调用"查询天气工具"来完成任务。

1.2 为什么需要它

局限 解决方案
训练数据截止于某个时间点 工具可以查询实时数据(天气、股价、数据库)
无法访问内部系统 工具可以查订单、查库存、发邮件
无法执行精确计算 工具可以调用数学库、业务规则引擎
没有"行动"能力 工具可以创建工单、下单、发送通知

1.3 与 Function Calling 的关系

业界统称 Function Calling (OpenAI 术语),Spring AI 将其抽象为 Tool Calling。两者本质相同,Spring AI 的命名更通用------因为它不只支持 OpenAI,还支持 Claude(工具调用)、Gemini(函数声明)等各种模型。


2. 核心原理:LLM ↔ 你的代码

2.1 完整调用序列

scss 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                     Application                                 │
│  ┌─────────────┐     ┌──────────────┐     ┌──────────────────┐  │
│  │ ChatClient   │────▶│   Adapter    │────▶│   LLM Provider   │  │
│  │ (M6 引入)    │     │ (Model Spec) │     │  (OpenAI/etc.)   │  │
│  └──────┬───────┘     └──────────────┘     └────────┬─────────┘  │
│         │                                            │           │
│         │ ① 注册工具描述                          ←─┤           │
│         │                                            │           │
│         │             ② 用户提问 "北京的天气怎么样?" │           │
│         │─────────────────────────────────────────────▶          │
│         │                                            │           │
│         │             ③ 返回: tool_call (getWeather) │           │
│         │◀─────────────────────────────────────────────          │
│         │                                            │           │
│         │ ④ 解析 ToolExecutionRequest                │           │
│         │      │                                      │           │
│         │      ▼                                     │           │
│         │ ⑤ 执行 getWeather(城市="北京")             │           │
│         │      │                                      │           │
│         │      ▼                                     │           │
│         │ ⑥ 返回结果: {温度: 28, 天气: 晴}          │           │
│         │                                            │           │
│         │ ⑦ 将工具结果 + 原始对话发回 LLM           │           │
│         │─────────────────────────────────────────────▶          │
│         │                                            │           │
│         │ ⑧ 最终回复: "北京今天 28°C,天气晴朗"     │           │
│         │◀─────────────────────────────────────────────          │
└─────────────────────────────────────────────────────────────────┘

2.2 分步详解

步骤 发生了什么 M6 中的关键对象
应用注册工具。Spring AI 将 @Tool 方法或 ToolCallback 解析为 JSON Schema(函数名、描述、参数结构) ToolCallback, ToolCallbackProvider
用户提问,连同工具 Schema 发给 LLM。模型"看到"可用工具 ChatClient, Prompt
LLM 判断需要调用工具,返回一个工具调用请求(工具名称 + JSON 参数) ToolExecutionRequest
Spring AI 解析响应,匹配 ToolCallback 准备执行 ChatClient, ToolCallback
执行真正的业务逻辑 你的 @Tool 方法
工具结果封装 ToolExecutionResult
结果注入对话上下文,再次发 LLM ChatClient
LLM 生成最终回复

2.3 大白话理解

  1. 工具定义:程序告诉 AI 大模型"你可以使用这些工具",并描述每个工具的功能和所需参数
  2. 工具选择:AI 大模型在对话中判断需要使用某个工具,并准备好相应的参数
  3. 返回意图:AI 大模型返回"我想用 XX 工具,参数是 XXX"的信息
  4. 工具执行:我们的程序接收请求,执行相应的工具操作
  5. 结果返回:程序将工具执行的结果发回给 AI 大模型
  6. 继续对话:AI 大模型根据工具返回的结果,生成最终回答给用户

2.4 关键洞察

工具调用是一个循环,不是一次往返。 LLM 可能在一次交互中多次调用工具------甚至一个工具的输出作为另一个工具的输入(工具链)。Spring AI 从 M6 开始通过 ChatClient 内置的自动循环替你管理这个过程。
虽然看起来是 AI 在调用工具,但实际上整个过程是由我们的应用程序控制的 。AI 只负责决定什么时候需要用工具,以及需要传递什么参数,真正执行工具的是我们的程序。安全性是这种设计的核心考量------AI 模型永远无法直接接触你的 API 或系统资源,所有操作都必须通过你的程序来执行。


3.Spring AI 工具开发

在 Spring AI 中,定义工具主要有两种模式:基于 Methods 方法或者 Functions 函数式编程。

二者的详细对比:

特性 Methods 方式 Functions 方式
定义方式 使用 @Tool和 @ToolParam注解标记类方法 使用函数式接口并通过 Spring Bean 定义
语法复杂度 简单,直观 较复杂,需要定义请求/响应对象
支持的参数类型 大多数 Java 类型,包括基本类型、POJO、集合等 不支持基本类型、Optional、集合类型
支持的返回类型 几乎所有可序列化类型,包括 void 不支持基本类型、Optional、集合类型等
使用场景 适合大多数新项目开发 适合与现有函数式API集成
注册方式 支持按需注册和全局注册 通常在配置类中预先定义
类型转换 自动处理 需要更多手动配置
文档支持 通过注解提供描述 通过Bean描述和JSON属性注解

举个例子来对比这两种定义模式:

1)Methods 模式:通过 @Tool 注解定义工具,通过 tools 方法绑定工具

java 复制代码
class WeatherTools {
    @Tool(description = "Get current weather for a location")
    public String getWeather(@ToolParam(description = "The city name") String city) {
        return "Current weather in " + city + ": Sunny, 25°C";
    }
}

// 使用方式
ChatClient.create(chatModel)
    .prompt("What's the weather in Beijing?")
    .tools(new WeatherTools())
    .call();

2)Functions 模式:通过 @Bean 注解定义工具,通过 functions 方法绑定工具

java 复制代码
@Configuration
public class ToolConfig {
    @Bean
    @Description("Get current weather for a location")
    public Function<WeatherRequest, WeatherResponse> weatherFunction() {
        return request -> new WeatherResponse("Weather in " + request.getCity() + ": Sunny, 25°C");
    }
}

// 使用方式
ChatClient.create(chatModel)
    .prompt("What's the weather in Beijing?")
    .functions("weatherFunction")
    .call();

3.1工具定义

Spring AI 提供了两种定义工具的方法 ------ 注解式编程式

1)注解式:只需使用 @Tool 注解标记普通 Java 方法,就可以定义工具了,简单直观。

每个工具最好都添加详细清晰的描述,帮助 AI 理解何时应该调用这个工具。对于工具方法的参数,可以使用 @ToolParam 注解提供额外的描述信息和是否必填。

示例代码:

java 复制代码
class WeatherTools {
    @Tool(description = "获取指定城市的当前天气情况")
    String getWeather(@ToolParam(description = "城市名称") String city) {
        // 获取天气的实现逻辑
        return "北京今天晴朗,气温25°C";
    }
}

2)编程式:如果想在运行时动态创建工具,可以选择编程式来定义工具,更灵活。

先定义工具类:

java 复制代码
class WeatherTools {
    String getWeather(String city) {
        // 获取天气的实现逻辑
        return "北京今天晴朗,气温25°C";
    }
}

然后将工具类转换为 ToolCallback 工具定义类,之后就可以把这个类绑定给 ChatClient,从而让 AI 使用工具了。

java 复制代码
Method method = ReflectionUtils.findMethod(WeatherTools.class, "getWeather", String.class);
ToolCallback toolCallback = MethodToolCallback.builder()
    .toolDefinition(ToolDefinition.builder(method)
            .description("获取指定城市的当前天气情况")
            .build())
    .toolMethod(method)
    .toolObject(new WeatherTools())
    .build();

其实你会发现,编程式就是把注解式的那些参数,改成通过调用方法来设置了而已。

@Tool 注解核心属性:

属性 类型 默认值 说明
name String 方法名(蛇形) 工具名称,LLM 通过此名称调用
description String 工具描述,LLM 据此判断何时调用
returnDirect boolean false 若为 true,工具结果直接作为最终回复返回给用户,不再经过 LLM 加工

@ToolParam 注解核心属性

属性 类型 默认值 说明
required boolean false 是否必填(方法参数默认必填)
description String "" 参数描述
deprecated boolean false 标记为已弃用

3.2使用工具

定义好工具后,Spring AI 提供了多种灵活的方式将工具提供给 ChatClient,让 AI 能够在需要时调用这些工具。

1)按需使用:这是最简单的方式,直接在构建 ChatClient 请求时通过 tools() 方法附加工具。这种方式适合只在特定对话中使用某些工具的场景。

java 复制代码
String response = ChatClient.create(chatModel)
    .prompt("北京今天天气怎么样?")
    .tools(new WeatherTools())  // 在这次对话中提供天气工具
    .call()
    .content();

2)全局使用:如果某些工具需要在所有对话中都可用,可以在构建 ChatClient 时注册默认工具。这样,这些工具将对从同一个 ChatClient 发起的所有对话可用。

java 复制代码
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools(new WeatherTools(), new TimeTools())  // 注册默认工具
    .build();

3)更底层的使用方式:除了给 ChatClient 绑定工具外,也可以给更底层的 ChatModel 绑定工具(毕竟工具调用是 AI 大模型支持的能力),适合需要更精细控制的场景。

java 复制代码
// 先得到工具对象
ToolCallback[] weatherTools = ToolCallbacks.from(new WeatherTools());
// 绑定工具到对话
ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolCallbacks(weatherTools)
    .build();
// 构造 Prompt 时指定对话选项
Prompt prompt = new Prompt("北京今天天气怎么样?", chatOptions);
chatModel.call(prompt);

4)动态解析:一般情况下,使用前面 3 种方式即可。对于更复杂的应用,Spring AI 还支持通过 ToolCallbackResolver 在运行时动态解析工具。这种方式特别适合工具需要根据上下文动态确定的场景,比如从数据库中根据工具名搜索要调用的工具。在本节的工具进阶知识中会讲到,先了解到有这种方式即可。


总结一下,在使用工具时,Spring AI 会自动处理工具调用的全过程:从 AI 模型决定调用工具 => 到执行工具方法 => 再到将结果返回给模型 => 最后模型基于工具结果生成最终回答。这整个过程对开发者来说是透明的,我们只需专注于 实现工具 的业务逻辑即可。

3.3工具生态

首先,工具的本质就是一种插件。能不自己写的插件,就尽量不要自己写。我们可以直接在网上找一些优秀的工具实现,比如 Spring AI Alibaba 官方文档 中提到了社区插件。

虽然文档里只提到了屈指可数的插件数,但我们可以顺藤摸瓜,在 GitHub 社区找到官方提供的更多 工具源码,包含大量有用的工具!比如翻译工具、网页搜索工具、爬虫工具、地图工具等。

4. 核心接口深度剖析

4.1 ToolCallback------工具定义的核心契约

java 复制代码
// Spring AI 1.0.x
public interface ToolCallback {

    /** 工具名称,LLM 通过此名称引用工具 */
    String getName();

    /** 工具描述,LLM 理解工具用途的关键文本(越清晰越好) */
    String getDescription();

    /** 工具的输入 JSON Schema(OpenAPI 格式) */
    JsonSchema getInputSchema();

    /** 执行工具,参数为 JSON 字符串,返回 JSON 字符串 */
    String call(String functionInput);

    // --- 1.0+ 新增 ---
    
    /** 获取工具的执行结果转换器(可选) */
    default ToolResponseConverter getResponseConverter() { return null; }

    /** 工具元数据(名称、描述、Schema 的封装) */
    default ToolMetadata getMetadata() {
        return new ToolMetadata(getName(), getDescription(), getInputSchema());
    }
}

4.2 ToolExecutionRequest------LLM 的调用指令

java 复制代码
public class ToolExecutionRequest {

    /** LLM 想要调用的工具名称(必须匹配 ToolCallback.getName()) */
    private final String name;

    /** 工具参数,JSON 格式的字符串 */
    private final String arguments;

    /** 工具调用 ID(用于多轮对话追踪,由模型提供) */
    private final String id;

    // --- 1.0+ 增强 ---
    
    /** 内部调用追踪 ID */
    private final String toolCallId;
    
    /** 父工具调用 ID(支持嵌套调用链追踪) */
    private final String parentToolCallId;
}

4.3 ToolExecutionResult------工具执行结果

java 复制代码
public class ToolExecutionResult {

    /** 原始请求 */
    private final ToolExecutionRequest request;

    /** 执行输出(JSON 字符串) */
    private final String output;

    /** 执行状态 */
    private final ToolExecutionStatus status;

    /** 执行耗时(毫秒) */
    private final long duration;

    /** 异常信息(执行失败时) */
    private final Throwable error;

    /** ------ 1.0+ 新增 ------ */
    
    /** 是否直接返回(对应 @Tool(returnDirect=true)) */
    private final boolean isDirect;
    
    /** 元数据(可附加审计信息) */
    private final Map<String, Object> metadata;
}

public enum ToolExecutionStatus {
    SUCCESS,
    FAILURE,
    RETRY_PENDING
}

4.4 ToolCallbackProvider------工具注册中心

java 复制代码
@FunctionalInterface
public interface ToolCallbackProvider {

    /** 返回当前可用的所有工具回调 */
    Collection<ToolCallback> getToolCallbacks();

    /** ------ 1.0+ 新增 ------ */
    
    /** 根据工具名称获取特定回调 */
    default ToolCallback getToolCallback(String name) {
        return getToolCallbacks().stream()
            .filter(tc -> tc.getName().equals(name))
            .findFirst()
            .orElse(null);
    }

    /** 动态更新可用工具(运行时增减) */
    default void refresh() { }
}

4.5 ToolContext------工具执行上下文(1.0+)

java 复制代码
public class ToolContext {
    /** 用户认证信息 */
    private final Authentication authentication;
    /** 会话 ID */
    private final String sessionId;
    /** 请求 ID */
    private final String requestId;
    /** 自定义属性 */
    private final Map<String, Object> attributes;
}

4.6 ChatClient 中的工具相关 API

java 复制代码
// ===== 构建阶段(ChatClient.Builder) =====

// 注册默认工具(对所有对话生效)
ChatClient.Builder.defaultTools(Object... toolObjects);

// 注册带上下文的默认工具
ChatClient.Builder.defaultTools(ToolCallback... toolCallbacks);

// 设置工具执行回调(审计钩子)
ChatClient.Builder.defaultToolExecutionCallback(ToolExecutionCallback callback);


// ===== 请求阶段(ChatClient.RequestSpec) =====

// 为本次请求指定工具(覆盖默认)
RequestSpec.tools(Object... toolObjects);

// 为本次请求指定工具(ToolCallback 方式)
RequestSpec.tools(ToolCallback... toolCallbacks);

// 设置本次请求的工具执行回调
RequestSpec.toolExecutionCallback(ToolExecutionCallback callback);

// 设置工具调用时的行为参数
RequestSpec.toolParameters(ToolParameters parameters);

4.7 ToolExecutionCallback------审计钩子

java 复制代码
public interface ToolExecutionCallback {

    /** 工具执行前触发 */
    default void onToolStart(ToolExecutionRequest request, ToolContext context) {
    }

    /** 工具执行成功时触发 */
    default void onToolSuccess(ToolExecutionRequest request, 
                                ToolExecutionResult result, 
                                ToolContext context) {
    }

    /** 工具执行失败时触发 */
    default void onToolError(ToolExecutionRequest request, 
                              Throwable error, 
                              ToolContext context) {
    }

    /** 工具执行完成(无论成功/失败,最终都会调用) */
    default void onToolCompletion(ToolExecutionRequest request, 
                                   ToolExecutionResult result, 
                                   ToolContext context) {
    }
}

4.8 架构关系图

sql 复制代码
┌──────────────────────────────────────────────────────────────────────┐
│                      ToolCallbackProvider                            │
│                         (注册中心)                                  │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐   │
│  │  @Tool Bean      │  │ ToolCallback Impl │  │ MethodToolCallback│   │
│  │  (声明式)        │  │  (编程式)         │  │  (手动构建)      │   │
│  └────────┬─────────┘  └────────┬─────────┘  └────────┬─────────┘   │
│           │                      │                      │            │
│           └──────────┬───────────┴───────────┬──────────┘            │
│                      │                       │                       │
│                      ▼                       ▼                       │
│              ┌──────────────────────────────────────────┐            │
│              │           ToolCallback                    │            │
│              │  getName() / getDescription() / call()    │            │
│              └──────────────────┬───────────────────────┘            │
│                                 │                                     │
└─────────────────────────────────┼─────────────────────────────────────┘
                                  │
                                  ▼
┌──────────────────────────────────────────────────────────────────────┐
│                         ChatClient                                   │
│                                                                      │
│  ① 构建 Prompt(含 Tool Schema)                                     │
│  ② 向 LLM 发送请求                                                   │
│  ③ 解析响应 → ToolExecutionRequest[]                                 │
│  ④ 匹配 ToolCallback → call()                                        │
│  ⑤ 触发 ToolExecutionCallback(审计)                                  │
│  ⑥ 将结果注入 Conversation                                           │
│  ⑦ 循环直到 LLM 返回纯文本或达到限制                                   │
└──────────────────────────────────────────────────────────────────────┘

相关推荐
AI分享猿3 小时前
重度长上下文开发怎么选?先看缓存是否吃额度
缓存·ai编程
亦暖筑序3 小时前
AgentScope-Java 入门:用 Middleware 审计 Agent 调用
java·ai编程·agentscope
子昕4 小时前
Opus 5只要Fable一半价格,我准备把Claude续上了
ai编程
aqi004 小时前
15天学会AI应用开发(十六)LangChain实现对话记忆功能
人工智能·python·大模型·ai编程·ai应用
赫媒派5 小时前
Agent 能被搜到?ARD:MCP/A2A 资源统一发现
ai编程
Pokerhead5 小时前
如何评价 DeepSeek-V4 的价格?
人工智能·大模型·ai编程·deepseek
风景的人生6 小时前
流式输出与springboot中的响应式编程
java·spring boot·ai编程