【SpringAIAlibaba新手村系列】(13)Tool Calling 函数工具调用技术

简介: 本文详细解析 Spring AI 的 Tool Calling 技术,阐明其如何弥补大模型“会说不会做”的局限。通过 @Tool 注解,开发者可轻松将 Java 方法暴露为 AI 工具。文中深入讲解了 ToolCallbacks.from() 注册工具的原理,以及工具方法在当前 Spring Boot 进程内通过反射动态执行的底层逻辑,强调了模型决策与框架执行的协同过程,为理解 AI 赋能实际操作奠定基础。

第十三章 Tool Calling 函数工具调用技术

版本标注

  • Spring AI: 1.1.2
  • Spring AI Alibaba: 1.1.2.0

章节定位

  • Tool Calling 仍然是最核心的能力之一。
  • 在 Spring AI Alibaba 1.1.2.x 中,它通常不是孤立存在,而是嵌入到 ReactAgent、多智能体编排、RAG Workflow、MCP Client 和 Graph 节点中协同工作。

s01 > s02 > s03 > s04 > s05 > s06 > s07 > s08 > s09 > s10 > s11 > s12 > [ s13 ] s14 > s15 > s16 > s17 > s18

"大模型会说, 工具调用让它开始会做" -- Tool Calling 是 AI 从对话走向执行的关键一步。


一、为什么需要 Tool Calling?

1.1 AI 的能力边界

我自己第一次真正意识到 Tool Calling 的价值,不是在看概念定义的时候,而是在写接口时突然发现:模型虽然会说,但它其实不会“做”。

AI 大模型虽然很聪明,但它有一个根本限制:

它只能"生成文字",无法执行真实世界的操作

比如你问 AI:

  • "今天天气怎么样?" → AI 只能猜测或回答"我不知道实时天气"
  • "帮我查下北京到上海的火战票" → AI 无法真的帮你查询
  • "现在几点了?" → AI 也无法获取当前时间

1.2 Tool Calling 的诞生

Tool Calling 真正解决的,不是“让模型回答得更花哨”,而是让它在需要外部信息时,不再只靠猜。

Tool Calling(函数工具调用) 让 AI 能够:

"在回答过程中,发现需要某些信息时,自动调用外部工具获取"

传统方式:
用户:现在几点 → AI:我不知道(靠猜)

Tool Calling 方式:
用户:现在几点 
→ AI:发现需要获取时间 → 自动调用 getCurrentTime() 函数 
→ 获取到"2025年1月1日 10:30:00" → 把时间告诉用户

1.3 可以调用的工具类型

工具类型 示例 说明
查询类 天气查询、股价查询 获取外部信息
操作类 发送邮件、订机票 执行实际操作
计算类 数学计算、代码执行 处理复杂运算
数据库类 查询订单、用户信息 访问业务数据

二、Tool Calling 核心原理

2.1 工作流程

┌─────────────────────────────────────────────────────────┐
│              Tool Calling 工作流程                      │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. 注册工具                                            │
│     把函数(方法)注册到 AI 能识别的格式                    │
│                                                         │
│  2. 用户提问                                            │
│     "北京明天天气怎么样?"                                │
│                                                         │
│  3. AI 分析                                             │
│     → 需要调用天气查询工具                               │
│     → 自动提取参数:城市="北京",时间="明天"              │
│                                                         │
│  4. 执行工具                                            │
│     调用 getWeather(city="北京", date="明天")            │
│     返回天气数据                                         │
│                                                         │
│  5. 返回答案                                            │
│     结合工具返回的结果,给出完整回答                      │
│                                                         │
└─────────────────────────────────────────────────────────┘

2.2 @Tool 注解

在 Spring AI 里,最直观的入门方式就是直接用 @Tool 暴露一个普通 Java 方法。这样写的好处是很明显的:先把工具定义清楚,再让模型学会什么时候用它。

Spring AI 提供了 @Tool 注解来声明一个可调用的方法:

public class DateTimeTools
{
   
    @Tool(description = "获取当前时间")
    public String getCurrentTime()
    {
   
        return LocalDateTime.now().toString();
    }

    @Tool(description = "根据城市名称获取天气")
    public String getWeather(String city)
    {
   
        // 调用外部天气API
        return city + "明天晴转多云,20-28度";
    }
}

三、项目代码详解

3.1 定义工具类

package com.atguigu.study.utils;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * 日期时间工具类
 * 我这里先拿“日期时间”做示例,是因为这个工具足够简单,结果也稳定,最适合观察模型到底有没有真的触发工具调用。
 */
@Component
public class DateTimeTools
{
   
    /**
     * 获取当前时间
     *
     * 这里真正关键的不是方法体,而是 @Tool(description = ...)
     * 这段描述本质上就是给模型看的“工具说明书”。
     */
    @Tool(description = "获取当前日期和时间")
    public String getCurrentDateTime()
    {
   
        LocalDateTime now = LocalDateTime.now();
        return now.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss"));
    }

    /**
     * 获取当前日期
     */
    @Tool(description = "获取当前日期")
    public String getCurrentDate()
    {
   
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
    }
}

3.2 控制器代码

package com.atguigu.study.controller;

import com.atguigu.study.utils.DateTimeTools;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

/**
 * 工具调用控制器
 * 展示让 AI 调用外部函数的能力
 */
@RestController
public class ToolCallingController
{
   
    // 注入 ChatModel(底层API,更灵活)
    @Resource
    private ChatModel chatModel;

    // 注入 ChatClient(高级API,更简洁)
    @Resource
    private ChatClient chatClient;

    /**
     * 方式一:使用底层 ChatModel API(更适合理解原理)
     *
     * 接口:http://localhost:8013/toolcall/chat?msg=你是谁现在几点
     */
    @GetMapping("/toolcall/chat")
    public String chat(@RequestParam(name = "msg", defaultValue = "你是谁现在几点") String msg)
    {
   
        // 1. 把普通 Java 工具对象转换成 Spring AI 能识别的工具列表
        ToolCallback[] tools = ToolCallbacks.from(new DateTimeTools());

        // 2. 在这次模型调用里显式注册工具
        ChatOptions options = ToolCallingChatOptions.builder()
                .toolCallbacks(tools)
                .build();

        // 3. 把“用户问题 + 工具能力”一起交给模型
        Prompt prompt = new Prompt(msg, options);

        // 4. 如果模型判断需要工具,就会先触发工具调用,再基于结果生成最终回答
        return chatModel.call(prompt)
                .getResult()
                .getOutput()
                .getText();
    }

    /**
     * 方式二:使用高级 ChatClient API(更适合日常开发)
     *
     * 接口:http://localhost:8013/toolcall/chat2?msg=你是谁现在几点
     */
    @GetMapping("/toolcall/chat2")
    public Flux<String> chat2(@RequestParam(name = "msg", defaultValue = "你是谁现在几点") String msg)
    {
   
        return chatClient.prompt(msg)
                // .tools() 方法直接注册工具
                .tools(new DateTimeTools())
                .stream()
                .content();
    }
}

3.3 ToolCallbacks.from(new DateTimeTools()) 到底在做什么?

这一句代码看起来很短,但它其实是 Tool Calling 里非常关键的一步:

ToolCallback[] tools = ToolCallbacks.from(new DateTimeTools());

很多初学者第一次看到时会以为:

是不是这里已经把 DateTimeTools 里的方法执行了一遍?

其实不是。它做的事情更准确地说是:

把一个普通的 Java 工具对象,转换成 Spring AI 能识别和调用的一组 ToolCallback

也就是说,这一步不是“执行工具”,而是“注册工具”。

3.1. new DateTimeTools() 只是一个普通对象

你自己写的这个类,本质上只是一个普通 Java 类:

public class DateTimeTools {
   

    @Tool(description = "获取当前日期和时间")
    public String getCurrentDateTime() {
    ... }

    @Tool(description = "获取当前日期")
    public String getCurrentDate() {
    ... }
}

如果没有 Spring AI 再做一层处理,大模型其实并不知道:

  • 这个类里有哪些方法
  • 哪些方法可以作为工具暴露出去
  • 每个方法是干什么的
  • 调用时需要什么参数

3.2. ToolCallbacks.from(...) 的作用是“翻译”

ToolCallbacks.from(new DateTimeTools()) 会去扫描这个对象里的方法,重点找:

  • 哪些方法带有 @Tool
  • 每个方法的名称
  • 每个方法的描述信息
  • 方法参数结构
  • 方法返回值类型

然后把这些信息包装成:

ToolCallback[]

你可以把 ToolCallback 理解成:

一个工具的说明书 + 调用代理。

它里面既包含“这个工具是干什么的”的元信息,也包含“模型决定调用它时,最终应该执行哪个 Java 方法”的调用逻辑。

3.3. 为什么返回的是数组?

因为一个工具类里可能不止一个工具方法。

比如 DateTimeTools 里就有:

  • getCurrentDateTime()
  • getCurrentDate()

所以 ToolCallbacks.from(...) 最终返回的不是单个对象,而是一组 ToolCallback

也就是说:

ToolCallback[] tools = ToolCallbacks.from(new DateTimeTools());

相当于把 DateTimeTools 这个类里可暴露的所有工具方法,统一整理成了一个“可供模型调用的工具列表”。

3.4. 这一步为什么不是“立刻执行工具”?

因为工具真正执行的时机,不是在 from(...) 这里,而是在后面模型推理时。

完整流程是这样的:

  1. 你先用 ToolCallbacks.from(...) 把工具注册好
  2. 再把这些工具交给 ChatModelChatClient
  3. 模型分析用户问题时,判断自己是否需要调用工具
  4. 如果需要,就由 Spring AI 通过 ToolCallback 去真正执行对应方法
  5. 方法执行结果再返回给模型,模型据此组织最终回答

所以:

  • from(...) 是注册阶段
  • 工具方法真正运行,是模型决策之后的执行阶段

3.5. 一句话总结

ToolCallbacks.from(new DateTimeTools()) 的本质是:

通过反射扫描 DateTimeTools 对象中的 @Tool 方法,把它们包装成 Spring AI 可识别的 ToolCallback[],从而让大模型后续能够按工具名和参数自动调用这些 Java 方法。

3.4 工具真正执行时,代码到底是怎么跑起来的?

这个问题我自己一开始也非常困惑。平常写 Java 代码时,我们已经习惯了这种执行方式:

String now = dateTimeTools.getCurrentDateTime();

也就是“代码写到哪里,就执行到哪里”。但 Tool Calling 看起来不是这样,因为你明明没有手写这一句调用,工具方法最后却真的执行了。

这里最关键的结论是:

Spring AI 不会额外启动一个新进程去执行工具,它还是在当前 Spring Boot 应用进程里完成方法调用。

只不过这次不是你在源码里写死了调用哪个方法,而是模型先返回一个“我要调用哪个工具”的结构化请求,框架再根据这个请求去定位并执行对应的 Java 方法。

整个执行链路大致可以理解成这样:

  1. 你先通过 ToolCallbacks.from(...) 注册工具
  2. 模型收到用户问题后,先判断自己是否需要某个工具
  3. 如果需要,它会返回一个工具调用请求(例如工具名 + 参数)
  4. Spring AI 拿到这个请求后,在当前应用里找到对应工具方法
  5. 通过反射执行这个 Java 方法
  6. 把工具返回结果再交还给模型
  7. 模型基于这个结果生成最终答案

如果用更接近程序执行的方式表达,大概等价于:

String toolName = "getCurrentDateTime";
Method method = registry.find(toolName);
Object result = method.invoke(targetObject);

所以 Tool Calling 不是“随机执行代码”,也不是“AI 自己直接运行 Java”。它更像是一种数据驱动的动态方法调用

  • 普通调用:你在源码里提前写死调用谁
  • Tool Calling:模型先产出调用指令,框架再运行时决定调用谁

这也是为什么 Tool Calling 看上去像“模型会用工具了”,但从程序底层看,本质上还是当前 Java 应用在执行普通方法,只不过中间多了一层“模型决策 + 框架分发”的过程。


四、自定义工具的最佳实践

4.1 @Tool 注解的关键要素

// ✅ 好的描述示例
@Tool(description = "获取指定城市的当前天气状况")
public String getWeather(String city) {
    ... }

// ❌ 模糊的描述(AI 不知道怎么用这个工具)
@Tool(description = "天气查询")
public String getWeather(String city) {
    ... }

4.2 工具方法命名

// ✅ 清晰的方法名(一看就知道干什么)
public String getWeatherByCity(String city)
public String queryUserOrderHistory(String userId)

// ⚠️ 模糊的方法名(不推荐)
public String doSomething(String param)
public String query(String id)

4.3 参数设计

// ✅ 必要的参数,清晰易懂
public String getWeather(
    @Param(name = "city", description = "城市名称,如北京、上海") String city,
    @Param(name = "date", description = "查询日期,格式yyyy-MM-dd") String date
)

// ✅ 不需要参数的简单工具
@Tool(description = "获取当前登录用户信息")
public UserInfo getCurrentUser() {
    ... }

五、本章小结

5.1 核心概念

概念 说明
@Tool Spring AI 注解,声明可调用的方法
ToolCallback 工具调用的封装类
ToolCallingChatOptions 配置工具调用的选项类
.tools() ChatClient 注册工具的方法

5.2 使用流程

1. 编写工具类(用@Component让Spring管理)
2. 在方法上添加@Tool注解
3. 编写清晰的方法描述
4. 在控制器中注册工具
5. AI自动识别并调用工具

5.3 注意事项

  • 描述要清晰:AI 通过描述理解工具用途
  • 参数要完整:帮助 AI 正确提取参数
  • 返回值要规范:清晰的返回值便于 AI 理解结果

本章重点

  1. 理解 Tool Calling 的核心原理
  2. 掌握 @Tool 注解的使用方法
  3. 学会在 ChatClient 中注册工具

下章剧透(s14):

学会了自定义 Tool 后,下一章我们将学习 MCP(Model Context Protocol)——标准化外部工具调用的协议!


💡 TIP:@Tool vs MCP 工具的区别

本章我们学习的 Tool Calling 使用的是 @Tool 注解方式,这是 Spring AI 原生的工具定义方式。而下一章要学的 MCP 是另一种标准化协议。它们区别如下:

特性 @Tool 注解 MCP 协议
定义方式 代码 + 注解 独立配置文件
适用范围 同项目内的方法 任意外部服务
连接方式 进程内直接调用 HTTP/STDIO
复用性 同项目复用 跨项目复用
复杂度 简单 需要额外配置

怎么选?

  • 如果是项目内部的工具(如业务逻辑),用 @Tool 更简单直接
  • 如果要调用外部独立服务(如天气API、数据库),用 MCP 更标准化

本章学的 @Tool 是基础,下一章学的 MCP 是进阶。两者并不冲突,实际项目经常结合使用!


📝 编辑者:Flittly
📅 更新时间:2026年4月
🔗 相关资源Spring AI Tool Calling

目录
相关文章
|
11天前
|
人工智能 运维 Java
【SpringAIAlibaba新手村系列】(12)RAG 检索增强生成技术
本文深入探讨 RAG 技术在 AiOps 场景中的应用,以基于 ops.txt 构建运维知识库为例,讲解了如何通过将文本切分、向量化并存入向量数据库,实现 AI 故障查询。内容涵盖 EmbeddingModel、VectorStore 的基本概念,以及利用 Redis 的 SETNX 机制防止知识库重复导入的工程实践。核心在于让 AI 结合外部知识库,更准确地回答运维问题。
165 0
|
人工智能 JavaScript Java
【SpringAIAlibaba新手村系列】(1)初识 Spring AI Alibaba 框架
本文介绍了SpringAIAlibaba框架的基本概念和使用方法。作为Spring官方AI框架的阿里云实现版本,它简化了Java开发者调用AI模型的过程。文章详细讲解了核心概念如ChatModel、ChatClient,以及阿里云百炼平台的功能。通过HelloWorld项目示例,展示了如何配置APIKey、编写控制层代码,实现普通调用和流式输出两种AI交互方式。重点阐述了SpringAI与SpringAIAlibaba的关系,以及自动配置机制的工作原理,帮助开发者快速上手这一框架。
1452 4
|
19天前
|
人工智能 前端开发 Java
【SpringAIAlibaba新手村系列】(4)流式输出与响应式编程
本文围绕 Spring AI 中的流式输出与响应式编程展开,重点解释了传统一次性响应与流式返回的差异,以及 Flux 在异步数据流中的核心作用。文章结合 ChatModel.stream() 与 ChatClient 的多种代码示例,说明如何实现 AI 内容的边生成边返回,并帮助读者理解流式调用在用户体验、性能和长文本场景中的实际价值。
405 4
【SpringAIAlibaba新手村系列】(4)流式输出与响应式编程
|
10天前
|
人工智能 Java 定位技术
【SpringAIAlibaba新手村系列】(14)MCP 本地服务与工具集成
本章从 MCP Server 视角出发,说明如何将本地天气查询能力整理并暴露为标准化工具服务。内容涵盖 @Tool、ToolCallbackProvider、MethodToolCallbackProvider 的作用,以及 Streamable-HTTP 协议下服务端的能力注册与对外提供逻辑。
179 13
|
9天前
|
人工智能 Java 定位技术
【SpringAIAlibaba新手村系列】(16)调用百度 MCP 服务
本章展示如何在客户端接入第三方百度 MCP 服务。通过 spring-ai-starter-mcp-client、application.yml 与 mcp-server.json5 完成 stdio 方式连接,自动发现并注册远端工具到 ChatClient,实现天气、IP 归属地、路线规划等能力调用。
204 9
|
18天前
|
人工智能 Java API
【SpringAIAlibaba新手村系列】(5)Prompt 提示词基础与多种消息类型
本章详解Spring AI 1.1.2中Prompt核心机制:以System/User/Assistant/Tool四类消息构建结构化提示,强调“角色决定语义”;涵盖多模型配置、链式API与底层Message组装两种实践方式,并给出系统消息设计最佳实践。
289 7
|
15天前
|
NoSQL Java 数据库
【SpringAIAlibaba新手村系列】(11)Embedding 向量化与向量数据库
本文围绕 Embedding 与向量数据库展开,讲解了文本向量化、相似度检索和 VectorStore 的基本用法,并结合 SimpleVectorStore 示例说明了 Spring 中自动装配与手动注册 Bean 的区别,为后续学习 RAG 打下基础。
239 4
【SpringAIAlibaba新手村系列】(11)Embedding 向量化与向量数据库
|
存储 人工智能 Java
【SpringAIAlibaba新手村系列】(3)ChatModel 与 ChatClient 的深度对比
本章深度解析 Spring AI 中 `ChatModel`(底层接口)与 `ChatClient`(高级封装)的本质区别:前者如“手动挡”,精准控制但需写大量样板代码;后者似“智能点餐机”,链式调用、支持系统提示、模板、工具调用等,开发高效。初学者推荐优先使用 `ChatClient`。
294 0
【SpringAIAlibaba新手村系列】(3)ChatModel 与 ChatClient 的深度对比
|
17天前
|
人工智能 JSON Java
【SpringAIAlibaba新手村系列】(7)结构化输出与对象映射
本文详解 Spring AI 结构化输出功能,通过 Java Record 与 .entity() 方法,实现 AI 的 JSON 响应自动映射为 Java 对象,解决纯文本难以集成的问题。文中还对比了 Lambda 写法并提供 Prompt 设计最佳实践。
159 3
|
11天前
|
人工智能 JSON Java
Spring AI Alibaba + MCP:调用MCP市场公开服务实操
本文详细讲解Spring Ai Alibaba调用MCP市场公开服务的全流程,以高德地图MCP服务为例,包含API-Key申请、客户端配置、代码实操,助力开发者快速掌握Spring Ai Alibaba与MCP服务对接技巧。
265 6
Spring AI Alibaba + MCP:调用MCP市场公开服务实操

热门文章

最新文章

下一篇
开通oss服务