【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

目录
相关文章
|
6天前
|
人工智能 JSON 监控
Claude Code 源码泄露:一份价值亿元的 AI 工程公开课
我以为顶级 AI 产品的护城河是模型。读完这 51.2 万行泄露的源码,我发现自己错了。
4333 17
|
17天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
14942 138
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
5天前
|
人工智能 数据可视化 安全
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
本文详解如何用阿里云Lighthouse一键部署OpenClaw,结合飞书CLI等工具,让AI真正“动手”——自动群发、生成科研日报、整理知识库。核心理念:未来软件应为AI而生,CLI即AI的“手脚”,实现高效、安全、可控的智能自动化。
3104 8
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
|
7天前
|
人工智能 自然语言处理 数据挖掘
零基础30分钟搞定 Claude Code,这一步90%的人直接跳过了
本文直击Claude Code使用痛点,提供零基础30分钟上手指南:强调必须配置“工作上下文”(about-me.md+anti-ai-style.md)、采用Cowork/Code模式、建立标准文件结构、用提问式提示词驱动AI理解→规划→执行。附可复制模板与真实项目启动法,助你将Claude从聊天工具升级为高效执行系统。
|
6天前
|
人工智能 定位技术
Claude Code源码泄露:8大隐藏功能曝光
2026年3月,Anthropic因配置失误致Claude Code超51万行源码泄露,意外促成“被动开源”。代码中藏有8大未发布功能,揭示其向“超级智能体”演进的完整蓝图,引发AI编程领域震动。(239字)
2456 9

热门文章

最新文章