Spring AI 集成(3)-使用工具

0 阅读4分钟

集成工具

工具调用也称为函数调用,是指在与模型交互时,模型可以调用外部函数或API来获取数据或执行操作。Spring AI提供了对工具调用的支持,使得模型可以在对话中动态地调用外部函数。

尽管我们通常将工具调用视为一种模型能力,但实际上提供工具调用逻辑的是客户端应用程序。模型只能请求工具调用并提供输入参数,而应用程序则负责根据输入参数执行工具调用并返回结果。模型永远无法访问作为工具提供的任何 API,这是一个关键的安全考量。Spring AI 提供了便捷的 API 来定义工具、解析来自模型的工具调用请求并执行工具调用.

工具用途

根据Spring AI 文档,工具调用的主要用于:

  • 信息检索. 此类工具可用于从外部源(如数据库、网络服务、文件系统或网络搜索引擎)检索信息。其目标是增强模型的知识,使其能够回答原本无法回答的问题。因此,它们可用于检索增强生成(RAG)场景。例如,工具可用于检索给定地点的当前天气、获取最新新闻文章,或查询数据库以获取特定记录。

  • 执行操作. 此类工具可用于在软件系统中执行操作,例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。其目标是自动化那些原本需要人工干预或显式编程的任务。例如,工具可用于为与聊天机器人交互的客户预订航班、在网页上填写表单,或在代码生成场景中基于自动化测试(TDD)实现一个Java类。

将方法作为工具

在Spring AI中,我们可以将Java方法作为工具来使用。Spring AI会自动将方法转换为工具,并在模型请求工具调用时执行该方法。 我们可以通过在方法上添加@Tool注解来将其标记为工具。以下是一个示例:

src/main/java/com/example/canaan/tool目录下创建一个新的工具类 UserTools.java,并添加以下内容:

package com.chestnut.canaan.tool;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;

import java.util.Objects;

@Service
public class UserTools {

    @Tool(
        name = "getUserFrom",
        description = "根据用户名获取用户来源"
    )
    public String getUserFrom(@ToolParam(description = "用户名称") String userName) {
        if (userName == null || userName.isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        // 模拟获取用户来源的逻辑
        if (Objects.equals(userName, "Canaan")) {
            return "中国西北";
        }
        if (Objects.equals(userName, "Tifa")) {
            return "中国东北";
        }
        return "未知来源";
    }
}

在这个示例中,我们定义了一个名为 getUserFrom 的工具方法,它接受一个用户名作为参数,并返回该用户的来源。我们使用 @Tool 注解来标记这个方法为工具,并提供了工具的名称和描述。 接下来,我们需要修改一下 src/main/java/com/example/canaan/config/ai/ChatConfiguration.java,使其支持工具调用:

package com.chestnut.canaan.config.ai;

import com.chestnut.canaan.tool.UserTools;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatConfiguration {

    @Resource
    private ChatMemory chatMemory;

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder
                .defaultTools(new UserTools())
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
                .defaultSystem("you are a friendly assistant!").build();
    }
}

在这个配置中,我们使用 defaultTools 方法将 UserTools 类注册为工具类。这样,当模型根据描述命中到 getUserFrom 工具时,Spring AI将能够调用该方法。

现在,我们可以通过访问 /ai/chat?input=用户输入 来与模型进行交互,并使用工具获取用户来源。例如,输入 Canaan此人来自哪里 将返回 中国西北

将函数作为工具

我们可以通过 FunctionToolCallback 将函数式类型(Function、Supplier、Consumer 或 BiFunction)转换为工具,例如:

package com.chestnut.canaan.config.ai;

import com.chestnut.canaan.tool.UserTools;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.function.Function;


@Configuration
public class ChatConfiguration {

    @Resource
    private ChatMemory chatMemory;

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        ToolCallback weatherTool = FunctionToolCallback
                .builder("currentWeather", (Function<WeatherRequest, WeatherResponse>) _ -> new WeatherResponse(25.0, Unit.C))
                .description("Get the weather in location")
                .inputType(WeatherRequest.class)
                .build();
        return builder
                .defaultTools(new UserTools())
                .defaultToolCallbacks(weatherTool)
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
                .defaultSystem("you are a friendly assistant!").build();
    }
}


enum Unit { C, F }

record WeatherRequest(@ToolParam(description = "位置名称,例如:上海") String location, Unit unit) {}

record WeatherResponse(double temp, Unit unit) {}


currentWeather-result.png

注意

如果使用函数作为工具时,必须确保函数的输入和输出可以是Void或POJO。输入和输出的POJO必须是可序列化的,因为结果将被序列化并发送回模型。以上述示例为例,WeatherRequestWeatherResponse 都是可序列化的 POJO。简单来说,如果序列化为json,则必须是用{}包裹的对象。单个的字符串或数字等基本类型不能作为输入或输出。


// 可以
(Function<WeatherRequest, WeatherResponse>) _ -> new WeatherResponse(25.0, Unit.C)
// 不可以
(Function<String, String>) _ -> "25.0"

额外说明

Spring AI官方文档中,DeepSeek的start包并不支持工具调用。

ablity-support.png

但是在上述尝试中可以发现,无论是使用方法还是使用函数作为工具时,DeepSeek模型仍然能够正确调用工具并返回结果。