AI05——工具调用 - 实践
可以理解为让 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
);
}
}
该代码包含的设计模式:
工厂模式:allTools() 方法作为一个工厂方法,负责创建和配置多个工具实例,然后将它们包装成统一的数组返回。这符合工厂模式的核心思想 - 集中创建对象并隐藏创建细节。
依赖注入模式:通过
@Value注解注入配置值,以及将创建好的工具通过 Spring 容器注入到需要它们的组件中。注册模式:该类作为一个中央注册点,集中管理和注册所有可用的工具,使它们能够被系统其他部分统一访问。
适配器模式的应用: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 会做大量幕后工作:
JsonSchemaGenerator会解析方法签名和注解,自动生成符合 JSON Schema 规范的参数定义,作为 ToolDefinition 的一部分提供给 AI 大模型ToolCallResultConverter负责将各种类型的方法返回值统一转换为字符串,便于传递给 AI 大模型处理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();

浙公网安备 33010602011771号