Spring AI 工具回调开发实战:文件操作、联网搜索与 PDF 生成
Spring AI 工具调用技术允许大模型借用外部工具完成任务。文章详解了工具调用的工作原理、流程及安全性设计,对比了 Methods 与 Functions 定义模式。通过注解式开发实战展示了文件操作、联网搜索、网页抓取等工具的实现,涵盖 ChatClient 绑定方式及工具生态资源,帮助开发者构建具备执行能力的智能体应用。

Spring AI 工具调用技术允许大模型借用外部工具完成任务。文章详解了工具调用的工作原理、流程及安全性设计,对比了 Methods 与 Functions 定义模式。通过注解式开发实战展示了文件操作、联网搜索、网页抓取等工具的实现,涵盖 ChatClient 绑定方式及工具生态资源,帮助开发者构建具备执行能力的智能体应用。

以 Spring AI 框架为例,学习 AI 应用开发的核心特性——工具调用,大幅增强 AI 的能力,并实战主流工具的开发,熟悉工具的原理和高级特性。
具体内容包括:
@Tool 和 @ToolParam 注解标记类方法之前我们通过 RAG 技术让 AI 应用具备了根据外部知识库来获取信息并回答的能力,但是直到目前为止,AI 应用还只是个'知识问答助手'。本节我们可以利用工具调用特性,实现更多需求。
而且这些需求还可以进行组合,比如用户先让 AI 联网搜索约会地点、再下载约会地点的图片、最后将获取到的内容组合生成 PDF、并保存到本地,一条龙服务。
如果 AI 能够完成上述需求,就不再只是一个有知识的'大脑',而是有手有脚,会利用工具完成任务的'智能体'了。
下面我们就来学习下实现上述需求的关键——工具调用技术。
工具调用(Tool Calling)可以理解为让 AI 大模型借用外部工具来完成它自己做不到的事情。
跟人类一样,如果只凭手脚完成不了工作,那么就可以利用工具箱来完成。
工具可以是任何东西,比如网页搜索、对外部 API 的调用、访问外部数据、或执行特定的代码等。
比如用户提问'帮我查询上海最新的天气',AI 本身并没有这些知识,它就可以调用'查询天气工具',来完成任务。
目前工具调用技术发展的已经比较成熟了,几乎所有主流的、新出的 AI 大模型和 AI 应用开发平台都支持工具调用。
其实,工具调用的工作原理非常简单,并不是 AI 服务器自己调用这些工具、也不是把工具的代码发送给 AI 服务器让它执行,它只能提出要求,表示'我需要执行 XX 工具完成任务'。而真正执行工具的是我们自己的应用程序,执行后再把结果告诉 AI,让它继续工作。
举个例子,假如用户提问'编程导航网站有哪些热门文章?',就需要经历下列流程:
虽然看起来是 AI 在调用工具,但实际上整个过程是由我们的应用程序控制的。AI 只负责决定什么时候需要用工具,以及需要传递什么参数,真正执行工具的是我们的程序。
你可能会好奇,为啥要这么设计呢?这样不是要让程序请求 AI 多次么?为啥不让 AI 服务器直接调用工具程序?
有这个想法很正常,但如果让你自己设计一个 AI 大模型服务,你就能理解了。很关键的一点是安全性,AI 模型永远无法直接接触你的 API 或系统资源,所有操作都必须通过你的程序来执行,这样你可以完全控制 AI 能做什么、不能做什么。
举个例子,你有一个爆破工具,用户像 AI 提了需求'我要拆这栋房子',虽然 AI 表示可以用爆破工具,但是需要经过你的同意,才能执行爆破。反之,如果把爆破工具植入给 AI,AI 觉得自己能炸了,就炸了,不需要再问你的意见。而且这样也给 AI 服务器本身增加了压力。
大家可能看到过 Function Calling(功能调用)这个概念,别担心,其实它和 Tool Calling(工具调用)完全是同一概念!只是不同平台或每个人习惯的叫法不同而已。
Spring AI 工具调用文档的开头就说明了这一点。
煮波更喜欢'工具调用'这个说法,因为 Function 这个词更像是计算机行业的术语,不如工具更形象易懂、更具普适性。
我们先来梳理一下工具调用的流程:
通过上述流程,我们会发现,程序需要和 AI 多次进行交互、还要能够执行对应的工具,怎么实现这些呢?我们当然可以自主开发,不过还是更推荐使用 Spring AI、LangChain 等开发框架。此外,有些 AI 大模型服务商也提供对应的 SDK,都能够简化代码编写。
本教程后续部分将以 Spring AI 为例,带大家实战工具调用开发。
需要注意的是,不是所有大模型都支持工具调用。有些基础模型或早期版本可能不支持这个能力。可以在 Spring AI 官方文档中查看各模型支持情况。
首先我们通过 Spring AI 官方提供的图片来理解 Spring AI 在实现工具调用时都帮我们做了哪些事情?
下面是一个较早版本的流程图,也能帮助我们理解这个过程。
在 Spring AI 中,定义工具主要有两种模式:基于 Methods 方法或者 Functions 函数式编程。
记结论就行了,我们只用学习基于 Methods 方法来定义工具,另外一种了解即可。原因是 Methods 方式更容易编写、更容易理解、支持的参数和返回类型更多。
二者的详细对比:
| 特性 | Methods 方式 | Functions 方式 |
|---|---|---|
| 定义方式 | 使用 @Tool 和 @ToolParam 注解标记类方法 | 使用函数式接口并通过 Spring Bean 定义 |
| 语法复杂度 | 简单,直观 | 较复杂,需要定义请求/响应对象 |
| 支持的参数类型 | 大多数 Java 类型,包括基本类型、POJO、集合等 | 不支持基本类型、Optional、集合类型 |
| 支持的返回类型 | 几乎所有可序列化类型,包括 void | 不支持基本类型、Optional、集合类型等 |
| 使用场景 | 适合大多数新项目开发 | 适合与现有函数式 API 集成 |
| 注册方式 | 支持按需注册和全局注册 | 通常在配置类中预先定义 |
| 类型转换 | 自动处理 | 需要更多手动配置 |
| 文档支持 | 通过注解提供描述 | 通过 Bean 描述和 JSON 属性注解 |
举个例子来对比这两种定义模式:
@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();
@Bean 注解定义工具,通过 functions 方法绑定工具@Configuration
public class ToolConfig {
@Bean
@Description("Get current weather for a location")
public Function<WeatherRequest, WeatherResponse> weatherFunction() {
return request -> new WeatherResponse("Weather in " + request.getCity() + ": Sunny, 25°C");
}
}
// 使用方式
ChatClient.create(chatModel).prompt("What's the weather in Beijing?").functions("weatherFunction").call();
显然 Methods 模式的开发量更少,更推荐这种方式,所以下面重点讲解这种方式。
Spring AI 提供了两种定义工具的方法——注解式和编程式。
只需使用 @Tool 注解标记普通 Java 方法,就可以定义工具了,简单直观。
每个工具最好都添加详细清晰的描述,帮助 AI 理解何时应该调用这个工具。对于工具方法的参数,可以使用 @ToolParam 注解提供额外的描述信息和是否必填。
示例代码:
class WeatherTools {
@Tool(description = "获取指定城市的当前天气情况")
String getWeather(@ToolParam(description = "城市名称") String city) {
// 获取天气的实现逻辑
return "北京今天晴朗,气温 25°C";
}
}
如果想在运行时动态创建工具,可以选择编程式来定义工具,更灵活。
先定义工具类:
class WeatherTools {
String getWeather(String city) {
// 获取天气的实现逻辑
return "北京今天晴朗,气温 25°C";
}
}
然后将工具类转换为 ToolCallback 工具定义类,之后就可以把这个类绑定给 ChatClient,从而让 AI 使用工具了。
Method method = ReflectionUtils.findMethod(WeatherTools.class, "getWeather", String.class);
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(method).description("获取指定城市的当前天气情况").build())
.toolMethod(method)
.toolObject(new WeatherTools())
.build();
其实你会发现,编程式就是把注解式的那些参数,改成通过调用方法来设置了而已。
在定义工具时,需要注意方法参数和返回值类型的选择。Spring AI 支持大多数常见的 Java 类型作为参数和返回值,包括基本类型、复杂对象、集合等。而且返回值需要是可序列化的,因为它将被发送给 AI 大模型。
以下类型目前不支持作为工具方法的参数或返回类型:
定义好工具后,Spring AI 提供了多种灵活的方式将工具提供给 ChatClient,让 AI 能够在需要时调用这些工具。
tools() 方法附加工具。这种方式适合只在特定对话中使用某些工具的场景。String response = ChatClient.create(chatModel)
.prompt("北京今天天气怎么样?")
.tools(new WeatherTools()) // 在这次对话中提供天气工具
.call().content();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new WeatherTools(), new TimeTools()) // 注册默认工具
.build();
// 先得到工具对象
ToolCallback[] weatherTools = ToolCallbacks.from(new WeatherTools());
// 绑定工具到对话
ChatOptions chatOptions = ToolCallingChatOptions.builder().toolCallbacks(weatherTools).build();
// 构造 Prompt 时指定对话选项
Prompt prompt = new Prompt("北京今天天气怎么样?", chatOptions);
chatModel.call(prompt);
ToolCallbackResolver 在运行时动态解析工具。这种方式特别适合工具需要根据上下文动态确定的场景,比如从数据库中根据工具名搜索要调用的工具。在本节的工具进阶知识中会讲到,先了解到有这种方式即可。总结一下,在使用工具时,Spring AI 会自动处理工具调用的全过程:
这整个过程对开发者来说是透明的,我们只需专注于实现工具的业务逻辑即可。
那么,怎么实现工具呢?
首先,工具的本质就是一种插件。能不自己写的插件,就尽量不要自己写。我们可以直接在网上找一些优秀的工具实现,比如 Spring AI Alibaba 官方文档中提到了社区插件。
虽然文档里只提到了屈指可数的插件数,但我们可以顺藤摸瓜,在 GitHub 社区找到官方提供的更多工具源码,包含大量有用的工具!比如翻译工具、网页搜索工具、爬虫工具、地图工具等。
这种搜集资源的能力,希望大家也能够掌握,尤其是学新技术的时候,即使官方文档写的不够清晰完善,我们也可以从开源社区中获取到一手信息。
如果社区中没找到合适的工具,我们就要自主开发。需要注意的是,AI 自身能够实现的功能通常没必要定义为额外的工具,因为这会增加一次额外的交互,我们应该将工具用于 AI 无法直接完成的任务。
下面我们依次来实现需求分析中提到的 6 大工具,开发过程中我们要格外注意工具描述的定义,因为它会影响 AI 决定是否使用工具。
先在项目根包下新建 tools 包,将所有工具类放在该包下;并且工具的返回值尽量使用 String 类型,让结果的含义更加明确。
文件操作工具主要提供 2 大功能:保存文件、读取文件。
由于会影响系统资源,所以我们需要将文件统一存放到一个隔离的目录进行存储:
在 constant 包下新建文件常量类,约定文件保存目录为项目根目录下的 /tmp 目录中。
/**
* 文件常量
*/
public interface FileConstant {
/**
* 文件保存目录
*/
String FILE_SAVE_DIR = System.getProperty("user.dir") + "/tmp";
}
建议同时将这个目录添加到 .gitignore 文件中,避免提交隐私信息。
编写文件操作工具类,通过注解式定义工具,代码如下:
/**
* 文件操作工具类 (提供文件读写功能)
*/
public class FileTools {
// 工具实现逻辑...
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online