【保姆级图文详解】Spring AI 中的工具调用原理解析,工具开发:文件操作、联网搜索、网页抓取、资源下载、PDF生成、工具集中注册

目录

前言

若对您有帮助的话,请点赞收藏加关注哦,您的关注是我持续创作的动力!有问题请私信或联系邮箱:funian.gm@gmail.com

一、Spring AI 中的工具调用(Tool Calling)

1.1、概念

  • Spring AI 中的工具调用(Tool Calling)是指让 AI 大模型借助外部工具来完成自身无法直接处理的任务。这些外部工具可以是网页搜索、外部 API 调用、访问外部数据或执行特定代码等多种形式。例如,当用户询问成都最新天气时,AI 本身没有该实时数据,就可以调用 "查询天气工具" 来获取并返回结果。

1.2、工作原理

  • **工具调用的流程并非 AI 服务器直接调用工具或执行工具代码,而是由应用程序进行控制,这种设计的关键在于安全性,AI 模型无法直接接触 API 或系统资源,所有操作都必须通过应用程序执行,这样可以完全控制 AI 的行为。**具体流程如下:
    • 用户提出问题。
    • 程序将问题传递给大模型。
    • 大模型分析问题,判断是否需要使用工具以及使用何种工具,并确定所需参数。
    • 大模型输出工具名称和参数。
    • 程序接收工具调用请求,执行相应的工具操作。
    • 工具执行操作并返回结果数据。
    • 程序将抓取结果传回给大模型。
    • 大模型分析工具返回的内容,生成最终回答。
    • 程序将大模型的回答返回给用户。

1.3、技术选型

  • Spring AI 实现工具调用的流程包括工具定义、工具选择、返回意图、工具执行、结果返回和继续对话。为了简化开发,推荐使用 Spring AI、LangChain 等开发框架,部分 AI 大模型服务商提供的 SDK 也能起到简化代码的作用。需要注意的是,并非所有大模型都支持工具调用,可在 Spring AI 官方文档中查看各模型的支持情况。

  • 工具定义模式。在 Spring AI 中,定义工具主要有基于 Methods 方法和 Functions 函数式编程两种模式:

    • 基于 Methods 方法:使用 @Tool 和 @ToolParam 注解标记类方法,语法简单直观,支持大多数 Java 类型作为参数和返回类型,包括基本类型、POJO、集合等,几乎支持所有可序列化类型作为返回类型,包括 void,适合大多数新项目开发,支持按需注册和全局注册,自动处理类型转换,通过注解提供描述文档。
    • Functions 函数式编程:使用函数式接口并通过 Spring Bean 定义,语法较复杂,需要定义请求 / 响应对象,不支持基本类型、Optional、集合类型作为参数和返回类型,适合与现有函数式 API 集成,通常在配置类中预先定义,需要更多手动配置进行类型转换,通过 Bean 描述和 JSON 属性注解提供文档支持。
  • 一般推荐学习基于 Methods 方法来定义工具,因为其更容易编写和理解,支持的参数和返回类型更多。

1.4、原理解析

1.4.1、实现接口

  • 工具底层数据结构:Spring AI 工具调用的核心在于ToolCallback接口,它是所有工具实现的基础。该接口中:
    • getToolDefinition()提供工具的基本定义,包括名称、描述和调用参数,这些信息传递给 AI 模型,帮助模型了解何时调用工具及如何构造参数。

    • getToolMetadata()提供处理工具的附加信息,如是否直接返回结果等控制选项。

    • 两个call()方法是工具的执行入口,分别支持有上下文和无上下文的调用场景。

    • 工具定义类ToolDefinition包含名称、描述和调用工具的参数。

      public interface ToolCallback {

      复制代码
      /**
       * Definition used by the AI model to determine when and how to call the tool.
       */
      ToolDefinition getToolDefinition();
      
      /**
       * Metadata providing additional information on how to handle the tool.
       */
      ToolMetadata getToolMetadata();
      
      /**
       * Execute tool with the given input and return the result to send back to the AI model.
       */
      String call(String toolInput);
      
      /**
       * Execute tool with the given input and context, and return the result to send back to the AI model.
       */
      String call(String toolInput, ToolContext tooContext);

      }

  • 注解定义工具的原理:当使用注解定义工具时,Spring AI 会在幕后做大量工作,包括:

    • JsonSchemaGenerator解析方法签名和注解,自动生成符合 JSON Schema 规范的参数定义,作为ToolDefinition的一部分提供给 AI 大模型。
    • ToolCallResultConverter负责将各种类型的方法返回值统一转换为字符串,便于传递给 AI 大模型处理。
    • MethodToolCallback实现对注解方法的封装,使其符合ToolCallback接口规范。这种设计让开发者可以专注于业务逻辑实现,无需关心底层通信和参数转换的复杂细节。如果需要更精细的控制,可以自定义ToolCallResultConverter来实现特定的转换逻辑,例如对某些特殊对象的自定义序列化。
  • 工具上下文:在实际应用中,工具执行可能需要额外的上下文信息,如登录用户信息、会话 ID 或者其他环境参数,Spring AI 通过ToolContext提供了这一能力。

1.4.2、工具调用

  • 执行模式与核心组件:Spring AI 提供了框架控制的工具执行和用户控制的工具执行两种工具执行模式,这两种模式都依赖核心组件ToolCallingManager。

    • ToolCallingManager接口是管理 AI 工具调用全过程的核心组件,负责根据 AI 模型的响应执行对应的工具并返回执行结果给大模型,还支持异常处理,可统一处理工具执行过程中的错误情况。

      复制代码
      /**
       * Execute the tool call and return the response message. To ensure backward
       * compatibility, both {@link ToolCallback} and {@link FunctionCallback} are
       * supported.
       */
      private InternalToolExecutionResult executeToolCall(Prompt prompt, AssistantMessage assistantMessage,
      		ToolContext toolContext) {
      	List<FunctionCallback> toolCallbacks = List.of();
      	if (prompt.getOptions() instanceof ToolCallingChatOptions toolCallingChatOptions) {
      		toolCallbacks = toolCallingChatOptions.getToolCallbacks();
      	}
      	else if (prompt.getOptions() instanceof FunctionCallingOptions functionOptions) {
      		toolCallbacks = functionOptions.getFunctionCallbacks();
      	}
      
      	List<ToolResponseMessage.ToolResponse> toolResponses = new ArrayList<>();
      
      	Boolean returnDirect = null;
      
      	for (AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) {
      
      		logger.debug("Executing tool call: {}", toolCall.name());
      
      		String toolName = toolCall.name();
      		String toolInputArguments = toolCall.arguments();
      
      		FunctionCallback toolCallback = toolCallbacks.stream()
      			.filter(tool -> toolName.equals(tool.getName()))
      			.findFirst()
      			.orElseGet(() -> toolCallbackResolver.resolve(toolName));
      
      		if (toolCallback == null) {
      			throw new IllegalStateException("No ToolCallback found for tool name: " + toolName);
      		}
      
      		if (returnDirect == null && toolCallback instanceof ToolCallback callback) {
      			returnDirect = callback.getToolMetadata().returnDirect();
      		}
      		else if (toolCallback instanceof ToolCallback callback) {
      			returnDirect = returnDirect && callback.getToolMetadata().returnDirect();
      		}
      		else if (returnDirect == null) {
      			// This is a temporary solution to ensure backward compatibility with
      			// FunctionCallback.
      			// TODO: remove this block when FunctionCallback is removed.
      			returnDirect = false;
      		}
      
      		String toolResult;
      		try {
      			toolResult = toolCallback.call(toolInputArguments, toolContext);
      		}
      		catch (ToolExecutionException ex) {
      			toolResult = toolExecutionExceptionProcessor.process(ex);
      		}
      
      		toolResponses.add(new ToolResponseMessage.ToolResponse(toolCall.id(), toolName, toolResult));
      	}
      
      	return new InternalToolExecutionResult(new ToolResponseMessage(toolResponses, Map.of()), returnDirect);
      }
  • 其有两个核心方法,resolveToolDefinitions从模型的工具调用选项中解析工具定义,executeToolCalls执行模型请求对应的工具调用。如果使用任何 Spring AI 相关的 Spring Boot Starter,都会默认初始化一个DefaultToolCallingManager,其中包含工具观察器、工具解析器、工具执行异常处理器的定义。

  • 工具调用判断:ToolCallingManager通过从 AI 返回的toolCalls参数中获取要调用的工具来判断是否要调用工具,由于实现可能会更新,建议通过查看源码来分析。

  • 框架控制的工具执行:这是默认且最简单的模式,由 Spring AI 框架自动管理整个工具调用流程。在这种模式下,框架会自动检测模型是否请求调用工具,自动执行工具调用并获取结果,自动将结果发送回模型,管理整个对话流程直到得到最终答案。

二、工具调用(Tool Calling)开发

  • 自主开发前提:当在社区中找不到合适的工具时,就需要进行自主开发。同时要注意,对于 AI 自身能够实现的功能,通常没必要将其定义为额外的工具,因为这会增加一次额外的交互,工具应被用于解决 AI 无法直接完成的任务。
  • 在开发过程中,要格外注意工具描述的定义,因为它会影响 AI 决定是否使用该工具。

2.1、文件操作

2.1.1、概念描述

  • 功能:文件操作工具主要有保存文件和读取文件两大功能。
  • 存储目录:由于文件操作会影响系统资源,所以需要将文件统一存放到一个隔离的目录。在constant包下新建文件常量类,约定文件保存目录为项目根目录下的/tmp目录,相关代码通过public interface FileConstant定义,其中String FILE_SAVE_DIR = System.getProperty("user.dir") + "/tmp";指定了具体的保存路径。
  • 注意事项:建议将这个/tmp目录添加到.gitignore文件中,以避免提交隐私信息。
  • 后续任务:接下来需要编写文件操作工具类,并通过注解式定义工具。

2.1.2、概念描述

  • 1、定义文件保存目录

    package com.funian.agent.constant;

    /**

    • @Auther FuNian

    • @Major Computer Software
      */
      public interface FileConstant {

      /**

      • 文件保存目录
        */
        String FILE_SAVE_DIR = System.getProperty("user.dir") + "/tmp";
        }
  • 2、文件读取和保存。

    package com.funian.agent.tools;

    import cn.hutool.core.io.FileUtil;
    import com.funian.agent.constant.FileConstant;
    import org.springframework.ai.tool.annotation.Tool;
    import org.springframework.ai.tool.annotation.ToolParam;

    /**

    • @Auther FuNian
    • @Major Computer Software
      */

    /**

    • 文件操作工具类(提供文件读写功能)
      */
      public class FileOperationTool {

      private final String FILE_DIR = FileConstant.FILE_SAVE_DIR + "/file";

      @Tool(description = "Read content from a file")
      public String readFile(@ToolParam(description = "Name of a file to read") String fileName) {
      String filePath = FILE_DIR + "/" + fileName;
      try {
      return FileUtil.readUtf8String(filePath);
      } catch (Exception e) {
      return "Error reading file: " + e.getMessage();
      }
      }

      @Tool(description = "Write content to a file")
      public String writeFile(@ToolParam(description = "Name of the file to write") String fileName,
      @ToolParam(description = "Content to write to the file") String content
      ) {
      String filePath = FILE_DIR + "/" + fileName;

      复制代码
       try {
           // 创建目录
           FileUtil.mkdir(FILE_DIR);
           FileUtil.writeUtf8String(content, filePath);
           return "File written successfully to: " + filePath;
       } catch (Exception e) {
           return "Error writing to file: " + e.getMessage();
       }

      }
      }

  • 3、单元测试类。

    package com.funian.agent.tools;

    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;

    import static org.junit.jupiter.api.Assertions.*;

    /**

    • @Auther FuNian

    • @Major Computer Software
      */
      class FileOperationToolTest {

      @Test
      void readFile() {
      FileOperationTool fileOperationTool = new FileOperationTool();
      String fileName = "Spring AI.txt";
      String result = fileOperationTool.readFile(fileName);
      Assertions.assertNotNull(result);
      }

      @Test
      void writeFile() {
      FileOperationTool fileOperationTool = new FileOperationTool();
      String fileName = "Spring AI.txt";
      String content = "Spring AI交流社区";
      String result = fileOperationTool.writeFile(fileName, content);
      Assertions.assertNotNull(result);
      }
      }

  • 4、测试验证。

2.2、联网搜索

2.2.1、概念描述

  • 作用:联网搜索工具的作用是依据关键词来搜索网页列表。
  • 实现方式:可以使用专业的网页搜索 API,像 Search API,它能够实现从多个网站搜索内容,不过这类服务一般是按量计费的。另外,也能够直接使用 Google 或者 Bing 的搜索 API,甚至还可以通过爬虫和网页解析从某个搜索引擎获取内容。
  • 任务要求:需要阅读 Search API 的官方文档,重点关注 API 的请求参数和返回结果,并且从 API 返回的结果里,只提取关键部分。
复制代码
{
  "organic_results": [
    ...
    {
      "position": 2,
      "title": "【动物星球】动物星球商城_Animal Planet是什么牌子",
      "link": "https://pinpai.smzdm.com/59685/",
      "displayed_link": "什么值得买",
      "snippet": "实时推荐动物星球(Animal Planet)商城正品特价。结合动物星球评测与动物星球最新资讯,全方位介绍Animal Planet是什么牌子?什么值得买综合各类动物星球优惠信息,计算最优购买方案,帮您轻松搞定正品...",
      "snippet_highlighted_words": ["Animal", "Planet"],
      "thumbnail": "https://t8.baidu.com/it/u=1026803159,4238637210&fm=217&app=126&size=f242,150&n=0&f=JPEG&fmt=auto?s=01F65C9344640CAA12FCF17B0300D030&sec=1714842000&t=c3db150577185f3a818a8bbe73ddd2c4"
    },
    ...
  ]
}

2.2.2、开发步骤

  • 1、申请API_KEY:申请或者登录网站,申请Key。key千万别暴露。
  • 2、配置文件添加。

    searchApi

    search-api:
    api-key: 自己的 API Key

  • 3、撰写工具类。

    package com.funian.agent.tools;
    import cn.hutool.http.HttpUtil;
    import cn.hutool.json.JSONArray;
    import cn.hutool.json.JSONObject;
    import cn.hutool.json.JSONUtil;
    import org.springframework.ai.tool.annotation.Tool;
    import org.springframework.ai.tool.annotation.ToolParam;

    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;

    /**

    • @Auther FuNian
    • @Major Computer Software
      */

    /**

    • 网页搜索工具类
      */
      public class WebSearchTool {

      // SearchAPI 的搜索接口地址
      private static final String SEARCH_API_URL = "https://www.searchapi.io/api/v1/search";

      private final String apiKey;

      public WebSearchTool(String apiKey) {
      this.apiKey = apiKey;
      }

      @Tool(description = "Search for information from Baidu Search Engine")
      public String searchWeb(
      @ToolParam(description = "Search query keyword") String query) {
      Map<String, Object> paramMap = new HashMap<>();
      paramMap.put("q", query);
      paramMap.put("api_key", apiKey);
      paramMap.put("engine", "baidu");
      try {
      String response = HttpUtil.get(SEARCH_API_URL, paramMap);
      // 取出返回结果的前 10条
      JSONObject jsonObject = JSONUtil.parseObj(response);
      // 提取 organic_results 部分
      JSONArray organicResults = jsonObject.getJSONArray("organic_results");
      List<Object> objects = organicResults.subList(0, 10);
      // 拼接搜索结果为字符串
      String result = objects.stream().map(obj -> {
      JSONObject tmpJSONObject = (JSONObject) obj;
      return tmpJSONObject.toString();
      }).collect(Collectors.joining(","));
      return result;
      } catch (Exception e) {
      return "Error searching Baidu: " + e.getMessage();
      }
      }
      }

  • 4、单元测试类。

    package com.funian.agent.tools;

    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.test.context.SpringBootTest;

    import static org.junit.jupiter.api.Assertions.*;

    /**

    • @Auther FuNian
    • @ClassName:WebSearchToolTest
      */

    @SpringBootTest
    public class WebSearchToolTest {

    复制代码
      @Test
      public void testSearchWeb() {
          WebSearchTool tool = new WebSearchTool("oJQtz4cpK4QgbfjyGV7Vsw6g");
          String query = "Spring AI官网";
          String result = tool.searchWeb(query);
          assertNotNull(result);
      }

    }

  • 5、测试验证。

2.3、网页抓取

2.3.1、概念描述

  • 作用:网页抓取工具的作用是依据网址解析网页的内容。
  • 实现方式:使用 jsoup 库来实现网页内容抓取和解析。

2.3.2、开发步骤

  • 1、引入jsoup库。

    <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.19.1</version> </dependency>
  • 2、编写网页抓取工具类。

    package com.funian.agent.tools;

    /**

    • @Auther FuNian
    • @Major Computer Software
      */

    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.springframework.ai.tool.annotation.Tool;
    import org.springframework.ai.tool.annotation.ToolParam;

    /**

    • 网页抓取工具
      */
      public class WebScrapingTool {

      @Tool(description = "Scrape the content of a web page")
      public String scrapeWebPage(@ToolParam(description = "URL of the web page to scrape") String url) {
      try {
      Document document = Jsoup.connect(url).get();
      return document.html();
      } catch (Exception e) {
      return "Error scraping web page: " + e.getMessage();
      }
      }
      }

  • 3、单元测试。

    package com.funian.agent.tools;

    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;

    import static org.junit.jupiter.api.Assertions.*;

    /**

  • 4、验证。

2.4、资源下载

2.4.1、概念描述

  • 作用:资源下载工具的作用是通过链接将文件下载到本地。
  • 实现方式:使用 Hutool 的HttpUtil.downloadFile方法来实现资源下载,后续需要编写资源下载工具类的代码。

2.4.2、开发步骤

  • 1、引入Hutool包。

    复制代码
    <dependency>
              <groupId>cn.hutool</groupId>
              <artifactId>hutool-all</artifactId>
              <version>5.8.37</version>
          </dependency>
  • 2、编写下载类。

    package com.funian.agent.tools;

    import cn.hutool.core.io.FileUtil;
    import cn.hutool.http.HttpUtil;

    import com.funian.agent.constant.FileConstant;
    import org.springframework.ai.tool.annotation.Tool;
    import org.springframework.ai.tool.annotation.ToolParam;

    import java.io.File;

    /**

    • @Auther FuNian
    • @Major Computer Software
      */

    /**

    • 资源下载工具类
      */
      public class ResourceDownloadTool {

      @Tool(description = "Download a resource from a given URL")
      public String downloadResource(@ToolParam(description = "URL of the resource to download") String url, @ToolParam(description = "Name of the file to save the downloaded resource") String fileName) {
      String fileDir = FileConstant.FILE_SAVE_DIR + "/download";
      String filePath = fileDir + "/" + fileName;
      try {
      // 创建目录
      FileUtil.mkdir(fileDir);
      // 使用 Hutool 的 downloadFile 方法下载资源
      HttpUtil.downloadFile(url, new File(filePath));
      return "Resource downloaded successfully to: " + filePath;
      } catch (Exception e) {
      return "Error downloading resource: " + e.getMessage();
      }
      }
      }

  • 3、 编写单元测试类。

    @SpringBootTest
    public class ResourceDownloadToolTest {

    复制代码
      @Test
      public void testDownloadResource() {
          ResourceDownloadTool tool = new ResourceDownloadTool();
          String url = "https://home.console.aliyun.com/home/dashboard/Cost/logo.png";
          String fileName = "logo.png";
          String result = tool.downloadResource(url, fileName);
          assertNotNull(result);
      }

    }

  • 4、测试验证。

2.5、PDF生成

2.5.1、概念描述

  • 作用:PDF 生成工具的作用是根据文件名和内容生成 PDF 文档并保存。
  • 实现方式:可以使用 iText 库来实现 PDF 生成。需要注意的是,iText 对中文字体的支持需要额外配置,不同操作系统提供的字体也有所不同,如果要做生产级应用,建议自行下载所需字体。不过对于学习来说,不建议在此处浪费太多时间,可以使用内置中文字体(不引入 font-asian 字体依赖也可以使用)。
  • 拓展操作:上述代码为了实现方便,直接将 PDF 保存到本地文件系统。此外,还可以将生成的文件上传到对象存储服务,然后返回可访问的 URL 给 AI 去输出;或者将本地文件临时返回给前端,让用户直接访问。

2.5.2、开发步骤

  • 1、引入依赖itext。

    <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-core</artifactId> <version>9.1.0</version> <type>pom</type> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>font-asian</artifactId> <version>9.1.0</version> <scope>test</scope> </dependency>
  • 2、编写工具实现类。

    package com.funian.agent.tools;

    import cn.hutool.core.io.FileUtil;
    import com.funian.agent.constant.FileConstant;
    import com.itextpdf.kernel.font.PdfFont;
    import com.itextpdf.kernel.font.PdfFontFactory;
    import com.itextpdf.kernel.pdf.PdfDocument;
    import com.itextpdf.kernel.pdf.PdfWriter;
    import com.itextpdf.layout.Document;
    import com.itextpdf.layout.element.Paragraph;

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

    import java.io.IOException;

    /**

    • @Auther FuNian
    • @Major Computer Software
      */

    /**

    • PDF 生成工具
      */
      public class PDFGenerationTool {

      @Tool(description = "Generate a PDF file with given content", returnDirect = false)
      public String generatePDF(
      @ToolParam(description = "Name of the file to save the generated PDF") String fileName,
      @ToolParam(description = "Content to be included in the PDF") String content) {
      String fileDir = FileConstant.FILE_SAVE_DIR + "/pdf";
      String filePath = fileDir + "/" + fileName;
      try {
      // 创建目录
      FileUtil.mkdir(fileDir);
      // 创建 PdfWriter 和 PdfDocument 对象
      try (PdfWriter writer = new PdfWriter(filePath);
      PdfDocument pdf = new PdfDocument(writer);
      Document document = new Document(pdf)) {
      // 自定义字体(需要人工下载字体文件到特定目录)
      // String fontPath = Paths.get("src/main/resources/static/fonts/simsun.ttf")
      // .toAbsolutePath().toString();
      // PdfFont font = PdfFontFactory.createFont(fontPath,
      // PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
      // 使用内置中文字体
      PdfFont font = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H");
      document.setFont(font);
      // 创建段落
      Paragraph paragraph = new Paragraph(content);
      // 添加段落并关闭文档
      document.add(paragraph);
      }
      return "PDF generated successfully to: " + filePath;
      } catch (IOException e) {
      return "Error generating PDF: " + e.getMessage();
      }
      }
      }

  • 3、编写单元测试。

    package com.funian.agent.tools;

    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;

    import static org.junit.jupiter.api.Assertions.*;

    /**

    • @Auther FuNian

    • @ClassName:PDFGenerationToolTest
      */
      @SpringBootTest
      class PDFGenerationToolTest {

      @Test
      public void testGeneratePDF() {
      PDFGenerationTool tool = new PDFGenerationTool();
      String fileName = "Spring AI.pdf";
      String content = "Spring AI项目 https://docs.spring.io/spring-ai/reference/api/chat/comparison.html";
      String result = tool.generatePDF(fileName, content);
      assertNotNull(result);
      }
      }

  • 4、测试验证。

2.6、工具集中注册

2.6.1、概念描述

  • 集中注册:在开发好众多工具类后,结合自身需求,可以创建工具注册类,一次性给 AI 提供所有工具,让 AI 自行决定何时调用,这样方便统一管理和绑定所有工具。
  • 设计模式:相关代码暗含多种设计模式。有了这个注册类,添加或移除工具只需修改这一个类,更利于维护。
    • 工厂模式(allTools () 方法作为工厂方法创建和配置多个工具实例并包装成统一数组返回)。
    • 依赖注入模式(通过 @Value 注解注入配置值,将工具通过 Spring 容器注入到需要的组件中)。
    • 注册模式(该类作为中央注册点集中管理和注册所有可用工具)。
    • 适配器模式(ToolCallbacks.from 方法将不同工具类转换为统一的 ToolCallback 数组)。

2.6.2、开发步骤

  • 1、实现集中注册类。

    package com.funian.agent.tools;

    import org.springframework.ai.tool.ToolCallback;
    import org.springframework.ai.tool.ToolCallbacks;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    /**

    • @Auther FuNian
    • @Major Computer Software
      */

    /**

    • 集中的工具注册类
      */
      @Configuration
      public class ToolRegistration {

      @Value("${search-api.api-key}")
      private String searchApiKey;

      @Bean
      public ToolCallback[] allTools() {
      FileOperationTool fileOperationTool = new FileOperationTool();
      WebSearchTool webSearchTool = new WebSearchTool(searchApiKey);
      WebScrapingTool webScrapingTool = new WebScrapingTool();
      ResourceDownloadTool resourceDownloadTool = new ResourceDownloadTool();
      TerminalOperationTool terminalOperationTool = new TerminalOperationTool();
      PDFGenerationTool pdfGenerationTool = new PDFGenerationTool();
      TerminateTool terminateTool = new TerminateTool();
      return ToolCallbacks.from(
      fileOperationTool,
      webSearchTool,
      webScrapingTool,
      resourceDownloadTool,
      terminalOperationTool,
      pdfGenerationTool,
      terminateTool
      );
      }
      }

  • 2、使用集中工具。

    复制代码
    // AI 调用工具能力
      @Resource
      private ToolCallback[] allTools;
    
      /**
       * AI 旅游报告功能(支持调用工具)
       *
       * @param message
       * @param chatId
       * @return
       */
      public String doChatWithTools(String message, String chatId) {
          ChatResponse chatResponse = chatClient
                  .prompt()
                  .user(message)
                  .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                          .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 20))
                  // 开启日志,便于观察效果
                  .advisors(new LoggerAdvisor())
                  .tools(allTools)
                  .call()
                  .chatResponse();
          String content = chatResponse.getResult().getOutput().getText();
          log.info("content: {}", content);
          return content;
      }
  • 3、单元测试。

    @Test
    void doChatWithTools() {
    // 测试联网搜索问题的答案
    testMessage("周末想去成都,推荐几个适合的小众打卡地?");

    复制代码
          // 测试网页抓取:旅游案例分析
          testMessage("如何制定旅游攻略");
    
          // 测试资源下载:图片下载
          testMessage("下载一张成都SKP的照片");
    
          // 测试文件操作:保存用户档案
          testMessage("保存我的旅游攻略为文件");
    
          // 测试 PDF 生成
          testMessage("生成一份'成都旅游计划'PDF,包括具体路线、消费");
      }
    
      private void testMessage(String message) {
          String chatId = UUID.randomUUID().toString();
          String answer = TravelApp.doChatWithTools(message, chatId);
          Assertions.assertNotNull(answer);
      }
  • 4、测试验证。