可以理解为让 AI 大模型 借用外部工具 来完成它自己做不到的事情。 工具调用是一种让大型语言模型(LLM)能够与外部工具和服务进行交互的功能机制

工具调用的工作原理——并不是 AI 服务器自己调用这些工具、,它只提出要求,而真正执行工具的是我们自己的应用程序,执行后再把结果告诉 AI,让它继续工作。

AI 模型永远无法直接接触你的 API 或系统资源,所有操作都必须通过你的程序来执行,这样你可以完全控制 AI 做什么——安全性

工具的本质就是一种插件(能不自己写,就不写),可以直接在网上找现成的工具实现,例如在 GitHub 社区找到官方提供的 工具源码

通过Spring AI 实现工具调用

定义工具
工具定义模式

基于 Methods 方法(容易编写、更容易理解、支持的参数和返回类型更多)

Methods 模式:通过 @Tool 注解定义工具,通过 tools 方法绑定工具

 class WeatherTools {
     @Tool(description = "Get current weather for a location")
     public String getWeather(@ToolParam(description = "The city name") String city) {
         return "Current weather in " + city + ": Sunny, 25°C";
     }
 }
 ​
 // 使用方式
 ChatClient.create(chatModel)
     .prompt("What's the weather in Beijing?")
     .tools(new WeatherTools())
     .call();

基于functions 函数式编程(不推荐,不好用)

通过 @Bean 注解定义工具,通过 functions 方法绑定工具

定义工具

Spring AI 提供了两种定义工具的方法 —— 注解式编程式

Spring AI 支持大多数常见的 Java 类型作为参数和返回值

1)注解式:

@Tool 注解标记普通 Java 方法,就可以定义工具了,简单直观。

@ToolParam注解描述参数含义

每个工具最好都添加详细清晰的描述,帮助 AI 理解何时应该调用这个工具。

示例代码:

 class WeatherTools {
     @Tool(description = "获取指定城市的当前天气情况")
     String getWeather(@ToolParam(description = "城市名称") String city) {
         // 获取天气的实现逻辑
         return "北京今天晴朗,气温25°C";
     }
 }

2)编程式:如果想在运行时动态创建工具,可以选择编程式来定义工具,更灵活。

编程式就是把注解式的那些参数,改成通过调用方法来设置了而已

先定义工具类:

 class WeatherTools {
     String getWeather(String city) {
         // 获取天气的实现逻辑
         return "北京今天晴朗,气温25°C";
     }
 }
 ​

然后将工具类转换为 ToolCallback 工具定义类,之后就可以把这个类绑定给 ChatClient,从而让 AI 使用工具了。

 Method method = ReflectionUtils.findMethod(WeatherTools.class, "getWeather", String.class);
 ​
 //ReflectionUtils.findMethod():这是 Spring 框架提供的反射工具类方法,用于查找指定类中的方法
 ​
 ​
 ToolCallback toolCallback = MethodToolCallback.builder()//创建工具回调的建造者对象
     .toolDefinition(ToolDefinition.builder(method)//设置工具定义信息
             .description("获取指定城市的当前天气情况")
             .build())
     .toolMethod(method)
     .toolObject(new WeatherTools())
     .build();

集成工具注册

开发了很多工具类后,结合自己的需求,我们可以创建 工具注册类,可以给 AI 一次性提供所有工具,让它自己决定何时调用。方便统一管理和绑定所有工具

 @Configuration  // @Configuration注解:标记当前类是一个配置类,Spring会扫描并加载其中的配置
 ​
 /**
  * 集中的工具注册类
  */
 ​
 ​
 //有了这个注册类,如果需要添加或移除工具,只需修改这一个类即可,更利于维护。
 public class ToolRegistration {
 ​
     @Value("${search-api.api-key}")
     private String searchApiKey;
 ​
     // @Bean注解:将该方法的返回值注册为Spring容器中的Bean,其他组件可通过依赖注入使用
     // 方法返回值为ToolCallback[]:Spring AI要求的工具数组类型
     @Bean
     public ToolCallback[] allTools() {
         // 创建文件操作工具实例
         FileOperationTool fileOperationTool = new FileOperationTool();
         // 创建网络搜索工具实例,传入从配置注入的API密钥
         WebSearchTool webSearchTool = new WebSearchTool(searchApiKey);
         // 创建网页内容提取工具实例
         WebScrapingTool webScrapingTool = new WebScrapingTool();
         // 创建资源下载工具实例
         ResourceDownloadTool resourceDownloadTool = new ResourceDownloadTool();
         // 创建终端操作工具实例
         TerminalOperationTool terminalOperationTool = new TerminalOperationTool();
         // 创建PDF生成工具实例
         PDFGenerationTool pdfGenerationTool = new PDFGenerationTool();
         // 创建终止工具实例(可能用于终止AI流程)
         TerminateTool terminateTool = new TerminateTool();
 ​
 ​
         //把自己写的方法转换为可以给springAi用的工具
         // 使用ToolCallbacks.from()方法:将多个工具实例转换为统一的ToolCallback数组
         // 该方法起到适配器作用,使不同工具类能以统一格式被Spring AI框架处理
         return ToolCallbacks.from(
                 fileOperationTool,
                 webSearchTool,
                 webScrapingTool,
                 resourceDownloadTool,
                 terminalOperationTool,
                 pdfGenerationTool,
                 terminateTool
         );
     }
 ​
 ​
 }

该代码包含的设计模式:

  1. 工厂模式:allTools() 方法作为一个工厂方法,负责创建和配置多个工具实例,然后将它们包装成统一的数组返回。这符合工厂模式的核心思想 - 集中创建对象并隐藏创建细节。

  2. 依赖注入模式:通过 @Value 注解注入配置值,以及将创建好的工具通过 Spring 容器注入到需要它们的组件中。

  3. 注册模式:该类作为一个中央注册点,集中管理和注册所有可用的工具,使它们能够被系统其他部分统一访问。

  4. 适配器模式的应用:ToolCallbacks.from 方法可以看作是一种适配器,它将各种不同的工具类转换为统一的 ToolCallback 数组,使系统能够以一致的方式处理它们。

Spring AI要求的工具数组类型——ToolCallback[] ToolCallbacks.from()方法:将多个工具实例转换为统一的ToolCallback数组

使用工具

定义好工具后,Spring AI 提供了多种灵活的方式将工具提供给 ChatClient,让 AI 能够在需要时调用这些工具。

1)按需使用:这是最简单的方式,直接在构建 ChatClient 请求时通过 tools() 方法附加工具。

 String response = ChatClient.create(chatModel)
     .prompt("北京今天天气怎么样?")
     .tools(new WeatherTools())  // 在这次对话中提供天气工具
     .call()
     .content();

2)全局使用:如果某些工具需要在所有对话中都可用,可以在构建 ChatClient 时注册默认工具。

 ChatClient chatClient = ChatClient.builder(chatModel)
     .defaultTools(new WeatherTools(), new TimeTools())  // 注册默认工具
     .build();

3)更底层的使用方式:除了给 ChatClient 绑定工具外,也可以给更底层的 ChatModel 绑定工具(毕竟工具调用是 AI 大模型支持的能力)

 // 先得到工具对象
 ToolCallback[] weatherTools = ToolCallbacks.from(new WeatherTools());
 // 绑定工具到对话
 ChatOptions chatOptions = ToolCallingChatOptions.builder()
     .toolCallbacks(weatherTools)
     .build();
 // 构造 Prompt 时指定对话选项
 Prompt prompt = new Prompt("北京今天天气怎么样?", chatOptions);
 chatModel.call(prompt);

4)批量绑定(复用集成的所有工具)

过 tools 方法绑定所有已注册的工具:

 @Resource
 private ToolCallback[] allTools;
 ​
 public String doChatWithTools(String message, String chatId) {
     ChatResponse response = chatClient
             .prompt()
             .user(message)
             .tools(allTools)
             .call()
             .chatResponse();
     
     String content = response.getResult().getOutput().getText();
     return content;
 }

在使用工具时,Spring AI 会自动处理工具调用的全过程:

AI 模型决定调用工具 => 执行工具方法 => 将结果返回给模型 => 模型基于工具生成回答。

工具进阶知识

工具底层数据结构

AI 怎么知道要如何调用工具?

Spring AI 工具调用的核心在于 ToolCallback 接口,它是所有工具实现的基础

为什么我们刚刚定义工具时,直接通过注解就能把方法变成工具呢?

当使用注解定义工具时,Spring AI 会做大量幕后工作:

  1. JsonSchemaGenerator 会解析方法签名和注解,自动生成符合 JSON Schema 规范的参数定义,作为 ToolDefinition 的一部分提供给 AI 大模型

  2. ToolCallResultConverter 负责将各种类型的方法返回值统一转换为字符串,便于传递给 AI 大模型处理

  3. MethodToolCallback 实现了对注解方法的封装,使其符合 ToolCallback 接口规范

工具上下文

假如做了一个用户自助退款功能,如果已登录用户跟 AI 说:”我要退款“,AI 就不需要再问用户 “你是谁?”,让用户自己输入退款信息了;而是直接从系统中读取到 userId,在工具调用时根据 userId 操作退款即可。

工具执行可能需要额外的上下文信息,比如登录用户信息、会话 ID 或者其他环境参数。Spring AI 通过 ToolContext 提供了这一能力。

我们在调用 AI 大模型时,用.toolContext(Map.of())传递上下文参数。

 // 从已登录用户中获取用户名称
 String loginUserName = getLoginUserName();
 ​
 String response = chatClient
         .prompt("帮我查询用户信息")
         .tools(new CustomerTools())
         .toolContext(Map.of("userName", "我是我"))
         .call()
         .content();
 ​
 System.out.println(response);
 class CustomerTools {
 ​
     @Tool(description = "Retrieve customer information")
     Customer getCustomerInfo(Long id, ToolContext toolContext) {
         return customerRepository.findById(id, toolContext.getContext().get("userName"));
     }
 ​
 }

ToolContext 本质上就是一个 Map: 它可以携带任何与当前请求相关的信息,但这些信息 不会传递给 AI 模型,只在应用程序内部使用。这样做既增强了工具的安全性

可观测性

工具调用所有主要操作的记录日志(可观测性的功能之一)

 logging:
   level:
     org.springframework.ai: DEBUG

可以在配置文件中设置 org.springframework.ai 包的日志级别为 DEBUG

立即返回

工具执行的结果不需要再经过 AI 模型处理,而是希望直接返回给用户(比如生成pdf文档)

使用注解式:

@Tool(description = "Retrieve customer information", returnDirect = true) 定义工具时,将 returnDirect 属性设为 true

使用编程式:

手动构造 ToolMetadata 对象:设置 .returnDirect(true)

 ToolMetadata toolMetadata = ToolMetadata.builder()
     .returnDirect(true)
     .build();
 ​
 Method method = ReflectionUtils.findMethod(CustomerTools.class, "getCustomerInfo", Long.class);
 ToolCallback toolCallback = MethodToolCallback.builder()
     .toolDefinition(ToolDefinition.builder(method)
             .description("Retrieve customer information")
             .build())
     .toolMethod(method)
     .toolObject(new CustomerTools())
     .toolMetadata(toolMetadata)
     .build();

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐