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 实现中透明地管理工具执行生命周期。但开发人员可选择退出此行为,自行控制工具执行。

相关推荐
222you2 小时前
Java 并发编程(1)
java·开发语言
一起来学吧2 小时前
【OpenClaw系列教程】第二篇:OpenClaw 是什么? 开源AI智能体平台
人工智能·ai·openclaw
岁岁种桃花儿2 小时前
kubenetes从入门到上天系列第十九篇:Kubernetes安装Nginx ingress controller
java·nginx·kubernetes
L-影2 小时前
从野蛮生长到精耕细作:AI中的Scaling Law正在开启新篇章(下篇)
人工智能·ai·scaling law
做一个AK梦2 小时前
RedisForValueService.setIfAbsent()
java·分布式
Yvonne爱编码2 小时前
JAVA数据结构 DAY8-堆
java·数据结构·python
一起来学吧2 小时前
【OpenClaw系列教程】第四篇:OpenClaw安装配置指南 - 开始养你的数字“龙虾“
人工智能·ai·openclaw
dovens2 小时前
Spring Boot(快速上手)
java·spring boot·后端
唠玖馆3 小时前
c++ 类和对象(全)
java·开发语言·c++