SpringAI Tool Calling(工具调用)

一.Tool Calling概述

随着大语言模型 (LLM) 能力的飞速发展,我们不再满足于仅仅让它们生成文本或回答问题。我们期望它们能成为真正的智能助手,能够与外部世界交互,执行具体任务,比如查询数据库、发送邮件或分析数据。为了解决这一挑战,工具调用 (Tool Calling) 应运而生。

Tool Calling (工具调用) ,是 AI 应用程序中的一种常见模式,允许大语言模型 (LLM) 根据用户请求,智能地选择、调用外部工具(如函数、API、服务)并获取结果的技术流程,从而增强其功能。

可以把 LLM 想象成为一个知识渊博、无所不知的博士,不论你问他什么理论问题,他都可以对答如流。但如果你问他 "帮我预订一下今晚公司附近那家最热门的意大利餐厅",他就会束手无策 ------ 因为他虽然能计算出公司附近最热门的意大利餐厅,但他没有连接订座系统,无法订座。

Tool Calling 就是解决这个问题的,它就像给这个博士配备了一个万能遥控器,这个遥控器有很多按钮,这些按钮可以执行一些具体任务,比如博士思考餐厅名称,按钮来执行订座。

Tool Calling 让 LLM 从一个 "无所不知的学者" 变成了一个 "无所不能的指挥家",它负责思考和规划,而具体的工作则由外部工具执行。

二.和Function Calling 什么关系?

官方介绍如下:

Tool calling (also known as function calling) is a common pattern in AI applications allowing a model to interact with a set of APIs, or tools, augmenting its capabilities.

摘自 Tool Calling :: Spring AI Reference

Function Calling 是 Tool Calling 早期更为流行的术语。它是指 LLM 请求调用一个开发者预定义的函数 (Function),这里的 "函数" 就是你代码中的一个方法。Tool Calling 是一个更通用、更广泛的概念,不仅包含了 Function Calling,还涵盖了调用其他类型的工具。

Function Calling 好比是万能遥控器上的一个具体的按钮,比如 "开机" 按钮,它非常具体,按下去就直接执行开机这个单一操作。Tool Calling 则是整个万能遥控器的概念,这个遥控器上不仅有 "开机" 按钮,还有 "调音量"、"换台"、"投屏" 等按钮。它是一个更上层、更通用的抽象,涵盖了函数、API、查询等各种工具。

简单来说:在 Spring AI 里,定义一个 Tool (工具),它通常就是一个 Function (函数),我们可以认为 Function Calling 是实现 Tool Calling 的具体技术方式。两者在日常讨论中经常混用,但 Tool Calling 这个词更全面。

三.应用场景

1. 信息检索

大模型可以借助 Tool 从外部资源(如数据库、Web 服务、文件系统或者 WEB 搜索引擎)检索信息。

如:人工智能模型无法访问实时信息,任何假设了解当前日期或天气预报等信息的问题都无法由模型回答。但是,我们可以提供一个可以检索此信息的工具,并让模型在需要访问实时信息时调用此工具。

2. 采取行动

大模型可以借助 Tool 完成一些执行操作(如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流)

如:模型可以生成预订北京旅行的计划。但是,该模型不具备执行计划的能力。这就是工具的用武之地。它们可用于执行模型生成的计划。

四.快速应用

1.添加依赖

复制代码
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webflux</artifactId>
        </dependency>
       
        <!--   tool calling     -->
        <dependency>
            <groupId>com.github.victools</groupId>
            <artifactId>jsonschema-generator</artifactId>
            <!-- 可根据实际情况使用最新稳定版本 -->
            <version>4.37.0</version>
        </dependency>
    </dependencies>

 <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud.ai</groupId>
                <artifactId>spring-ai-alibaba-bom</artifactId>
                <version>1.0.0.2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

2.配置文件

复制代码
spring:
  ai:
    dashscope:
      api-key: ${DASH_SCOPE_API_KEY} #阿里百炼平台申请的api-key

3.调用工具代码与结果演示

java 复制代码
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/chat")
public class ChatController {

    private final ChatClient client;

    public ChatController(DashScopeChatModel dashScopeChatModel) {
        this.client = ChatClient.builder(dashScopeChatModel).build();
    }

    @RequestMapping("/call")
    public String chat(String message) {
        return client.prompt()
                .user(message)
                .call()
                .content();
    }

}

4.定义工具

tool calling依赖上面已经添加

java 复制代码
import org.springframework.ai.tool.annotation.Tool;

import java.time.LocalDateTime;

public class DateTimeTools {

    @Tool(description = "A tool to get the current date and time")
    public String getCurrentDateTime() {
        return LocalDateTime.now().atZone(java.time.ZoneId.systemDefault()).toString();
    }

}

5.应用工具

java 复制代码
  @RequestMapping("/call")
    public String chat(String message) {
        return client.prompt()
                .user(message)
                .tools(new DateTimeTools()) //添加工具
                .call()
                .content();
    }

6.运行结果

五.Tool Calling 原理

1.Tool Calling 请求响应流程

a) 注册工具: 在向 AI 模型发送请求时,应用程序需要提前告诉模型有哪些工具可用,包括工具的名称 (name)、描述 (description) 和参数格式 (input schema)

描述 (Description) 是灵魂:这是模型决定是否调用工具的核心依据,描述必须清晰准确。一个模糊的描述 (如 "处理数据") 会导致模型无法正确调用。一个清晰的描述 (如 "根据城市名称查询该城市的当前天气温度") 则能极大提高调用的准确率

b) 模型决策: AI 模型根据对话内容判断是否需要调用工具,当模型决定调用工具时,它会发送一个工具调用请求,其中包含工具名称和符合预定义的输入参数。

c) 查找并执行: 应用程序接收到这个 "工具调用请求" 后,会根据工具名称找到对应的工具,并用模型提供的参数来执行它。

d) 获取结果: 工具执行后会产生一个结果 (比如,查询到了今天的温度是 25 度),应用程序会处理返回的结果。

e) 返回结果: 应用程序将这个工具执行的结果再次发送给 AI 模型。

f) 生成最终回复: AI 模型结合工具返回的结果,组织语言,生成最终的用户回复。

2.Tool Calling 部分源码解释

Spring AI 为方法转工具(即 ToolCallback)提供了两种内置方式:

  • 声明式:通过 @Tool 注解实现
  • 编程式:通过底层的 MethodToolCallback 实现

a) 声明式定义工具

Tool注解

java 复制代码
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Tool {
    String name() default "";

    String description() default "";

    boolean returnDirect() default false;

    Class<? extends ToolCallResultConverter> resultConverter() default DefaultToolCallResultConverter.class;
}
  • Name:指定工具的名称,如果不指定则默认使用方法名称。AI 模型在调用工具时使用此名称来标识工具。因此,不允许在同一类中有两个同名的工具。

  • Description:工具的描述,模型可以使用它来了解何时以及如何调用工具,如果不指定则使用方法名。建议详细清晰地描述工具的功能,这对于工具的使用至关重要,直接影响大模型的使用效果。

  • returnDirect:指定工具执行的结果是直接返回,还是要发给大模型,默认发送给大模型。如果为true 上述流程直接从4-->6不会在发给大模型处理之后返回用户了。

  • resultConverter:指定工具执行结果转换器。Spring AI 内置了一个,默认将工具调用结果转换为 String,如果有特殊业务需求可以自行实现。

input schema是什么?

JSON Schema 是一种用于描述和验证 JSON 数据结构的规范。它定义了 JSON 数据应具备的格式、类型、字段、约束条件等,就像是一份数据模板数据说明书,用来确保 JSON 数据符合预期的结构。

我们可以把 JSON Schema 想象成一个「表格填写指南」:

  • 表格 = 要接收的 JSON 数据
  • 填写指南 = JSON Schema(说明哪些项必填、是什么类型、有什么限制)

例如

java 复制代码
{
  "name": "Alice",
  "age": 28,
  "email": "alice@example.com",
  "hobbies": ["reading", "swimming"]
}

{
  "name": "Alice",
  "age": 28,
  "email": "alice@example.com",
  "hobbies": ["reading", "swimming"]
}

对应的 JSON Schema

java 复制代码
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/user.schema.json",
  "title": "User",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "用户姓名"
    },
    "age": {
      "type": "integer",
      "minimum": 0,
      "description": "用户年龄"
    },
    "email": {
      "type": "string",
      "format": "email",
      "description": "电子邮箱地址"
    },
    "hobbies": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "optional": true
    }
  },
  "required": ["name", "age", "email"]
}

工具调用流程中,JSON Schema 用于定义工具的输入参数格式。

开发人员可以通过 @ToolParam 注解为输入参数提供额外信息(如描述、是否必需等),默认情况下所有输入参数均为必需参数。

java 复制代码
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;

class DateTimeTools {

    @Tool(description = "Set a user alarm for the given time")
    void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time,
                DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("Alarm set for " + alarmTime);
    }

}

@ToolParam 注解源码定义

java 复制代码
@Target({ElementType.PARAMETER, ElementType.FIELD,
        ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ToolParam {
    /**
     * 指定参数是否为必填,默认必填。如果参数被标记为@Nullable,则该参数将被视为可选,
     * 除非使用@ToolParam注解显式标记为必需
     */
    boolean required() default true;

    /**
     * 工具参数的描述,模型通过描述可以更好的理解如何使用它,例如,参数应采用什么格式,
     * 允许使用哪些值等。
     */
    String description() default "";
}

工具分类

工具分为运行时工具(特定接口调用时加载)和默认工具(所有请求都会加载的)

可以在创建chatclient的时候加上defaultTools

java 复制代码
    private final ChatClient client;

    public ChatController(DashScopeChatModel dashScopeChatModel) {
        this.client = ChatClient.builder(dashScopeChatModel)
                .defaultTools(new DateTimeTools())
                .build();
    }

b) 编程式定义工具

定义工具

java 复制代码
public class WeatherTools {

    String getCurrentWeatherByCityName(String cityName) {
        switch (cityName) {
            case "北京":
                return "北京今天天气:晴空万里";
            case "上海":
                return "上海今天天气:电闪雷鸣";
            case "广州":
                return "广州今天天气:细雨蒙蒙";
            default:
                return "没有该城市的天气信息";
        }
    }
}

上述这个还只是一个方法 还不是工具

在代码中将该方法转化为可调用的工具

复制代码
@RequestMapping("/call2")
public String chat2(String message) {
    //获取天气工具方法
    Method method = ReflectionUtils.findMethod(WeatherTools.class,
            "getCurrentWeatherByCityName", String.class);

    //创建工具回调
    ToolCallback toolCallback = MethodToolCallback.builder()
            .toolDefinition(ToolDefinitions.builder(method)
                    .description("Get current weather by city name")
                    .build())
            .toolMethod(method)
            .toolObject(new WeatherTools())
            .build();

    return client.prompt()
            .user(message)
            .toolCallbacks(toolCallback) //添加工具
            .call()
            .content();
}.测试结果

MethodToolCallback和ToolDefinitions介绍

一下内容截取自spring ai官方 链接: https://springdoc.cn/spring-ai/api/tools.html

添加默认工具

直接在构造方法初始化client的时候添加

java 复制代码
    public ChatController(DashScopeChatModel dashScopeChatModel) {
        //获取天气工具方法
        Method method = ReflectionUtils.findMethod(WeatherTools.class,
                "getCurrentWeatherByCityName", String.class);

        //创建工具回调
        ToolCallback toolCallback = MethodToolCallback.builder()
                .toolDefinition(ToolDefinitions.builder(method)
                        .description("Get current weather by city name")
                        .build())
                .toolMethod(method)
                .toolObject(new WeatherTools())
                .build();
        
        this.client = ChatClient.builder(dashScopeChatModel)
                .defaultToolCallbacks(toolCallback) //添加默认工具
                .build();
    }

如果臃肿不美观 可以运用IOC思想提取创建ToolCallBack类的方法 交给spring容器管理 然后再构造方法注入

3.工具规范

a) ToolCallback

ToolCallback 是一个用于与 AI 模型交互的回调接口,允许开发者定义可被 AI 调用的外部工具,在 AI 应用(比如大语言模型)中集成"工具"(Tools),让 AI 模型可以在需要时调用外部功能。

java 复制代码
public interface ToolCallback {

    /**
     * 返回该工具的元信息描述
     * ToolDefinition 包含工具名称、描述、参数列表等结构化信息,供 AI 模型理解"什么情况下该调用这个工具"以及"需要传什么参数"。
     * 必须实现,是函数调用机制的核心部分
     * @return
     */
    ToolDefinition getToolDefinition();

    /**
     * 提供额外的非功能性元数据,主要用于框架层面的扩展性支持,不影响核心逻辑。
     * 使用了 default 方法,意味着子类可以不重写,默认返回空的元数据对象。
     * @return
     */
    default ToolMetadata getToolMetadata() {
        return ToolMetadata.builder().build();
    }

    /**
     * 抽象方法,必须由实现类提供具体逻辑。
     * 接收一个字符串输入(通常是 JSON 格式的参数),执行业务逻辑,并返回结果字符串。
     * 结果会传回给 AI 模型,供其继续推理或生成回复。
     */
    String call(String toolInput);

    /**
     * 带上下文的调用方法,也是默认实现。
     * 如果toolContext有值,则不支持,主要用于被子类重写支持上下文功能
     */
    default String call(String toolInput, @Nullable ToolContext toolContext) {
        if (toolContext != null && !toolContext.getContext().isEmpty()) {
            throw new UnsupportedOperationException("Tool context is not supported!");
        } else {
            return this.call(toolInput);
        }
    }

}

b) MethodToolCallback

MethodToolCallback 是 Spring AI 框架中实现 Tool Calling的核心机制之一。它用于将普通 Java 方法包装成 AI 模型可调用的"工具"(Tool)。

java 复制代码
public final class MethodToolCallback implements ToolCallback {

    //工具默认执行的转换器
    private static final ToolCallResultConverter DEFAULT_RESULT_CONVERTER = new DefaultToolCallResultConverter();
    //工具默认元数据
    private static final ToolMetadata DEFAULT_TOOL_METADATA = ToolMetadata.builder().build();


    //工具定义,也称为工具的元信息:名字、描述、参数列表等,告诉 AI"我能干啥"
    private final ToolDefinition toolDefinition;
    //工具额外元数据(如权限,分类)
    private final ToolMetadata toolMetadata;
    //真正要执行的那个 Java 方法对象(通过反射获取)
    private final Method toolMethod;
    //工具对象:如果方法不是静态的,需要这个对象来调用(即 object.method())
    @Nullable
    private final Object toolObject;
    //工具转换器:把方法返回值转成字符串,返回给 AI(默认用 JSON)
    private final ToolCallResultConverter toolCallResultConverter;

}

c) ToolDefinition

java 复制代码
public interface ToolDefinition {

    /**
     * 工具名称。在提供给模型的工具集中是唯一的
     */
    String name();

    /**
     * AI模型使用工具描述来确定工具的功能
     */
    String description();

    /**
     * 用于调用工具的参数的模式
     */
    String inputSchema();

}

ToolDefinition 实例

java 复制代码
ToolDefinition toolDefinition = ToolDefinition.builder()
    .name("currentWeather")
    .description("Get the weather in location")
    .inputSchema("""
        {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string"
                },
                "unit": {
                    "type": "string",
                    "enum": ["C", "F"]
                }
            },
            "required": ["location", "unit"]
        }
    """)
    .build();

d) ToolContext

Spring AI 支持通过 ToolContext API 向工具传递额外的上下文信息。此功能允许开发人员提供用户自定义的额外数据,这些数据可与 AI 模型传递的工具参数一起在工具执行过程中使用。

工具添加上下文 还是以获取当前时间为例

java 复制代码
 @Tool(description = "A tool to get the current date and time")
    public String getCurrentDateTime(ToolContext context) {
        System.out.println("userID: " + context.getContext().get("userID"));
        return LocalDateTime.now().atZone(java.time.ZoneId.systemDefault()).toString();
    }
java 复制代码
    @RequestMapping("/call")
    public String chat(String message) {
        return client.prompt()
                .user(message)
                .tools(new DateTimeTools()) //添加工具
                .toolContext(Map.of("userID", "12345")) //调用时给工具传入上下文信息
                .call()
                .content();
    }

测试结果

e) Return Direct

默认情况下,工具调用结果将作为响应返回模型,随后模型可利用该结果继续对话。

某些场景下,开发人员可能希望将结果直接返回调用方而非传回模型。例如:当构建依赖 RAG 工具的代理时,开发人员可能希望直接将检索结果返回调用方,而非传回模型进行不必要的后处理;

默认值为false

二者对比

4.工具的执行

工具执行是指使用提供的输入参数调用工具并返回结果的过程。

这个过程由 ToolCallingManager 接口处理,该接口是 Spring AI 中负责管理 "AI 调用工具" 全过程的核心组件,负责管理工具执行的完整生命周期。

它主要管控以下核心问题:

  • 哪些工具可用?
  • AI 请求调用工具时,怎么执行?
  • 执行结果如何返回给 AI 继续对话?
java 复制代码
public interface ToolCallingManager {

    /**
     * 从模型的工具调用选项中解析工具定义,告诉模型:我能用哪些工具
     */
    List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);

    /**
     * 执行模型所要求的工具调用:也就是真正的去"调工具"
     */
    ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);

}

流程图

流程图文字解释

  • 当需要向模型提供工具时,我们将其定义包含在聊天请求(Prompt)中,并调用 ChatModel API 将请求发送至 AI 模型。
  • 当模型决定调用工具时,它会发送包含工具名称及符合定义模式的输入参数的响应(ChatResponse)。
  • ChatModel 将工具调用请求发送至 ToolCallingManager API。
  • ToolCallingManager 负责识别需调用的工具并使用提供的输入参数执行该工具。
  • 工具调用结果返回至 ToolCallingManager
  • ToolCallingManager 将工具执行结果返回给 ChatModel
  • ChatModel 将工具执行结果返回 AI 模型(ToolResponseMessage)。
  • AI 模型利用工具调用结果作为附加上下文生成最终响应,并通过 ChatClient 将其返回调用方(ChatResponse

默认情况下,Spring AI 在每个 ChatModel 实现中透明地管理工具执行生命周期。但开发人员可选择退出此行为,自行控制工具执行。

相关推荐
青石路1 分钟前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
lincats1 小时前
Claude Code项目越写越乱?这套清理流程能救你
ai·ai agent·claude code
Java陈序员2 小时前
企业级!一个基于 Java 开发的开源 AI 应用开发平台!
spring boot·agent·mcp
像我这样帅的人丶你还3 小时前
Java 后端详解(五):Redis 缓存
java·后端·全栈
云燕实验室CloudLab5 小时前
《AI开始"抱团"思考了!多智能体 + 思维图到底有多强?》
ai·学习工具·智慧学伴
plainGeekDev5 小时前
GreenDAO → Room
android·java·kotlin
小七-七牛开发者5 小时前
论文解读:DeepSeek DSpark 在真实高并发推理服务中,如何保证 Token 生成又好又快?
ai·大模型·编程·ai coding
杨运交10 小时前
[041][公共模块]分布式唯一ID生成器设计与实现:一款灵活可扩展的雪花算法框架
spring boot
亦暖筑序10 小时前
Java 8老系统AI Workflow实战:把一次性AI对话升级成可恢复工作流
java·后端
敲代码的彭于晏11 小时前
Bean 生命周期完全图解:前端同学也能看懂的 Spring 核心机制
java·前端·后端