1. 本期目标
前几期主要分析了 ai_agent 项目的对话主链路、Advisor、多轮记忆和 RAG 检索增强。到目前为止,智能体已经具备了这些能力:
能够和用户多轮对话
能够记住当前会话上下文
能够参考本地知识库回答
能够通过 RAG 检索增强回答质量
但是这些能力主要还是围绕"回答问题"展开的。
这一期开始分析 Agent 中非常关键的另一类能力:
Tool Calling 工具调用
Spring AI 官方文档中说明,Tool Calling 是 AI 应用中的常见模式,它允许模型和一组 API 或工具交互,从而扩展模型能力;工具主要用于两类场景:一类是信息检索,例如查询外部信息、数据库、文件系统或搜索引擎;另一类是执行动作,例如发送邮件、创建记录、提交表单或触发工作流。工具调用的关键点是:模型本身并不直接访问 API,而是由应用程序提供工具定义、执行工具调用,并把工具结果返回给模型。(Home)
本期主要解决几个问题:
1. 为什么 AI Agent 需要 Tool Calling?
2. Tool Calling 和普通聊天有什么区别?
3. ai_agent 项目中有哪些工具?
4. ToolRegistration 如何统一注册工具?
5. ToolCallback[] 是什么?
6. @Tool 和 @ToolParam 分别起什么作用?
7. LoveApp 如何把工具注入模型调用?
8. 当前工具链路的完整执行流程是什么?
9. Tool Calling 有哪些安全风险和改进方向?
2. 为什么需要 Tool Calling?
普通大模型对话可以理解为:
用户输入问题
↓
模型生成回答
这种方式能完成很多文本任务,但它有天然限制:
模型不能自己访问实时网页
模型不能自己读取本地文件
模型不能自己下载资源
模型不能自己执行终端命令
模型不能自己生成真实 PDF 文件
如果用户提出的任务是:
帮我搜索一些约会地点
抓取这个网页的内容
把这段建议保存成文件
帮我生成一份 PDF 报告
执行一个命令查看目录
普通聊天模型只能"建议你怎么做",而不能真正帮你完成。
Tool Calling 的作用就是让模型从:
只会回答
进一步变成:
可以调用外部工具完成任务
所以,Tool Calling 是 Agent 从"问答系统"走向"任务执行系统"的关键能力。
3. Tool Calling 不是模型直接执行工具
这里需要先澄清一个重要问题。
Tool Calling 并不是模型真的拥有了操作系统权限,也不是模型自己去执行 Java 方法。
Spring AI 文档中明确说明:模型只能请求调用某个工具,并给出输入参数;应用程序负责根据工具名称和参数执行真实工具,再把工具执行结果返回给模型。模型永远不会直接访问工具背后的 API,这也是一个关键安全点。(Home)
也就是说,真实流程是:
模型判断需要工具
↓
模型生成工具调用请求
↓
Spring AI / 应用程序找到对应 ToolCallback
↓
后端执行 Java 方法
↓
工具结果返回给模型
↓
模型根据工具结果生成最终回答
所以,Tool Calling 的本质是:
模型负责决策和组织语言
应用负责执行真实动作
这一点非常重要。
因为如果以为"模型直接执行命令",就容易忽略后端工具注册、参数校验、权限控制和安全边界。
4. 项目中的工具目录结构
ai_agent 项目的工具代码主要位于:
src/main/java/com/ai/aiagent/tool
当前该目录下包含这些工具类:
FileOperationTool.java
PDFGenerationTool.java
ResourceDownloadTool.java
TerminalOperationTool.java
TerminateTool.java
ToolRegistration.java
WebScrapingTool.java
WebSearchTool.java
GitHub 目录中也能看到这些文件,说明项目当前已经覆盖了搜索、网页抓取、资源下载、文件读写、终端执行、PDF 生成和任务终止等工具能力。(GitHub)
从功能上可以分成四类:
信息获取类:
WebSearchTool
WebScrapingTool
文件与资源类:
FileOperationTool
ResourceDownloadTool
PDFGenerationTool
系统执行类:
TerminalOperationTool
流程控制类:
TerminateTool
这说明项目中的 Tool Calling 不是只做一个简单示例,而是已经形成了一组可组合工具。
5. ToolRegistration:集中注册工具
项目中负责统一注册工具的是:
ToolRegistration
它被标注为:
@Configuration
说明这是一个 Spring 配置类。
在这个类中,项目定义了一个 Bean:
@Bean
public ToolCallback[] allTools()
这个方法会创建多个工具对象,包括 FileOperationTool、WebSearchTool、WebScrapingTool、ResourceDownloadTool、TerminalOperationTool、PDFGenerationTool 和 TerminateTool,然后通过 ToolCallbacks.from(...) 转换成 ToolCallback[] 返回。WebSearchTool 还会使用配置项中的 search-api.api-key 作为搜索 API Key。(GitHub)
可以理解为:
ToolRegistration
↓
创建所有工具对象
↓
ToolCallbacks.from(...)
↓
生成 ToolCallback[]
↓
交给 LoveApp 使用
所以 ToolRegistration 的作用是:
集中管理哪些工具可以暴露给模型调用
6. ToolCallback[] 是什么?
Spring AI 文档中说明,工具是 Tool Calling 的构建块,底层通过 ToolCallback 接口进行建模;ChatClient 和 ChatModel 都可以接收一组 ToolCallback,使这些工具对模型可用。(Home)
在项目里:
@Resource
private ToolCallback[] allTools;
LoveApp 通过 @Resource 注入了 ToolRegistration 中创建的 allTools。随后在工具调用方法中使用:
.toolCallbacks(allTools)
把这些工具注入到本次模型调用中。(GitHub)
可以理解为:
ToolCallback[] = 当前允许模型调用的工具列表
模型不是天然知道项目有哪些工具。
只有当后端把 ToolCallback[] 放进本次 ChatClient 调用时,模型才可以根据工具描述决定是否调用这些工具。
7. @Tool:把 Java 方法声明成工具
项目中的每个工具方法基本都使用了:
@Tool(description = "...")
Spring AI 文档说明,可以通过 @Tool 注解把一个方法声明为工具;@Tool 的 name 用于标识工具,未提供时默认使用方法名;description 用于帮助模型理解工具的用途和调用时机,官方也强调应该提供详细描述,否则模型可能不会在需要时调用工具,或者错误调用工具。(Home)
例如 WebSearchTool 中的方法是:
@Tool(description = "Search for information from Baidu Search Engine")
public String searchWeb(String query)
这表示模型可以把 searchWeb 看成一个可调用工具,用于通过百度搜索引擎检索信息。项目源码中,该工具会调用 SearchAPI 接口,并设置 engine = baidu,最后提取 organic_results 的前 5 条搜索结果返回。(GitHub)
所以 @Tool 的作用可以概括为:
告诉模型:
这个 Java 方法可以作为工具使用,
它能做什么,
什么时候适合调用它。
8. @ToolParam:描述工具参数
除了 @Tool,项目中还大量使用了:
@ToolParam(description = "...")
它用于描述工具参数。
例如 WebSearchTool 中:
@ToolParam(description = "Search query keyword") String query
FileOperationTool 中:
@ToolParam(description = "Name of the file to write") String fileName
@ToolParam(description = "Content to write to the file") String content
这些参数描述会帮助模型理解工具调用时应该填什么参数。项目中的搜索、网页抓取、文件读写、下载资源、执行终端命令、生成 PDF 等工具都使用了 @ToolParam 对参数进行说明。(GitHub)
可以理解为:
@Tool:
描述这个工具能做什么。
@ToolParam:
描述调用这个工具时需要哪些参数。
工具描述越清楚,模型越容易正确选择工具和生成参数。
9. LoveApp 如何调用工具?
LoveApp 中的工具调用入口是:
public String doChatWithTools(String message, String chatId)
核心代码可以简化为:
ChatResponse chatResponse = chatClient
.prompt()
.user(message)
.advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, chatId))
.advisors(new MyLoggerAdvisor())
.toolCallbacks(allTools)
.call()
.chatResponse();
这里最关键的是:
.toolCallbacks(allTools)
它把前面注册好的所有工具注入到当前模型调用中。源码中也可以看到,doChatWithTools() 同时保留了 ChatMemory.CONVERSATION_ID,说明工具调用链路也支持多轮对话记忆;它还添加了 MyLoggerAdvisor,方便观察请求和响应。(GitHub)
所以 doChatWithTools() 的完整能力是:
用户输入
↓
多轮记忆
↓
日志观察
↓
工具列表注入
↓
模型决定是否调用工具
↓
返回最终回答
10. Tool Calling 完整执行流程
结合 Spring AI 的 Tool Calling 机制和项目源码,可以把完整流程画成:
用户提出任务
↓
LoveApp.doChatWithTools(message, chatId)
↓
ChatClient.prompt()
↓
注入 ChatMemory.CONVERSATION_ID
↓
注入 MyLoggerAdvisor
↓
注入 ToolCallback[] allTools
↓
模型看到可用工具定义
↓
模型判断是否需要调用工具
↓
如果需要:生成工具名和参数
↓
Spring AI 根据 ToolCallback 找到对应 Java 方法
↓
后端执行工具方法
↓
工具结果返回给模型
↓
模型结合工具结果生成最终回答
↓
LoveApp 返回文本结果
这一流程对应 Spring AI 文档中的 Tool Calling 生命周期:应用把工具定义放入聊天请求,模型决定调用工具并给出参数,应用执行工具并把结果返回给模型,最后模型基于工具结果生成最终响应。(Home)
一句话理解:
模型负责"想调用什么工具、传什么参数",后端负责"真正执行工具"。
11. WebSearchTool:网页搜索工具
WebSearchTool 的作用是:
通过搜索 API 从百度搜索引擎检索信息
它的工具方法是:
@Tool(description = "Search for information from Baidu Search Engine")
public String searchWeb(String query)
内部逻辑大致是:
接收 query
↓
构造 SearchAPI 请求参数
↓
设置 engine = baidu
↓
发送 HTTP GET 请求
↓
解析返回 JSON
↓
提取 organic_results 前 5 条
↓
拼接成字符串返回
源码中可以看到,该工具使用 HttpUtil.get() 请求 https://www.searchapi.io/api/v1/search,并从返回 JSON 中提取 organic_results 的前 5 条结果。(GitHub)
这个工具适合处理:
需要实时外部信息的问题
需要搜索网页资料的问题
需要先找候选链接的问题
例如:
帮我搜索适合情侣约会的餐厅推荐
帮我找一些七夕约会活动
帮我搜索某个城市的约会地点
12. WebScrapingTool:网页抓取工具
WebScrapingTool 的作用是:
抓取指定网页的 HTML 内容
它的工具方法是:
@Tool(description = "Scrape the content of a web page")
public String scrapeWebPage(String url)
源码中,该工具使用 Jsoup 连接目标 URL,并返回 document.html();如果抓取失败,则返回错误信息。(GitHub)
可以理解为:
输入 URL
↓
Jsoup 访问网页
↓
获取网页 HTML
↓
返回给模型
这个工具适合和 WebSearchTool 配合使用:
第一步:搜索得到候选网页
第二步:抓取某个网页内容
第三步:模型总结网页信息
不过当前实现直接返回完整 HTML,内容可能比较长,也可能包含大量导航、脚本和无关标签。后续可以优化成:
只提取正文文本
过滤 script/style 标签
限制最大返回长度
提取 title、description、main content
13. ResourceDownloadTool:资源下载工具
ResourceDownloadTool 的作用是:
根据 URL 下载资源并保存到本地
它的工具方法接收两个参数:
url:资源地址
fileName:保存文件名
源码中,它会把文件保存到:
FileConstant.FILE_SAVE_DIR + "/download"
而 FileConstant.FILE_SAVE_DIR 定义为:
项目根目录/tmp
所以下载目录实际是:
项目根目录/tmp/download
工具内部会创建目录,然后调用 Hutool 的 HttpUtil.downloadFile() 下载资源。(GitHub)
这个工具适合处理:
下载图片
下载网页资源
下载文档
保存外部文件
但它也需要注意安全问题,比如 URL 白名单、文件大小限制、文件类型限制和文件名安全检查。
14. FileOperationTool:文件读写工具
FileOperationTool 提供两个工具方法:
readFile
writeFile
它们都操作:
FileConstant.FILE_SAVE_DIR + "/file"
也就是:
项目根目录/tmp/file
readFile 根据文件名读取 UTF-8 文本内容;writeFile 根据文件名和内容写入文本文件,写入前会创建目录。源码中可以看到,这两个方法都通过 Hutool 的 FileUtil 完成文件读写。(GitHub)
可以理解为:
writeFile:
把模型生成的内容保存成本地文件。
readFile:
把本地文件内容读出来交给模型。
例如,用户可以说:
把刚才的约会计划保存成 plan.txt
读取 plan.txt 并帮我优化一下
这个工具让 Agent 具备了简单的文件状态管理能力。
15. PDFGenerationTool:PDF 生成工具
PDFGenerationTool 的作用是:
把给定内容生成 PDF 文件
它的工具方法是:
@Tool(description = "Generate a PDF file with given content", returnDirect = false)
public String generatePDF(String fileName, String content)
源码中,它会把 PDF 保存到:
FileConstant.FILE_SAVE_DIR + "/pdf"
也就是:
项目根目录/tmp/pdf
实现上,它使用 iText 创建 PdfWriter、PdfDocument 和 Document,并使用 STSongStd-Light 与 UniGB-UCS2-H 字体配置来支持中文内容写入。(GitHub)
这个工具让智能体可以从"生成文字建议"进一步变成:
生成一份真实 PDF 报告
例如:
帮我生成一份周末约会计划 PDF
帮我把恋爱沟通建议整理成 PDF
当前方法返回的是 PDF 文件保存路径,而不是直接返回文件流。因此如果后续接入 Web 接口,还需要额外提供文件下载接口。
16. TerminalOperationTool:终端执行工具
TerminalOperationTool 的作用是:
执行终端命令
它的工具方法接收一个参数:
command
源码中使用:
new ProcessBuilder("cmd.exe", "/c", command)
启动命令,因此当前实现更偏 Windows 环境。它会读取标准输出,如果退出码非 0,则追加错误退出码信息。(GitHub)
这个工具能力很强,但风险也最大。
它意味着模型可能触发系统命令执行。
在学习项目中,它可以用于验证 Agent 的自动执行能力;但在正式系统中,不能直接暴露无限制终端执行。
至少需要增加:
命令白名单
工作目录限制
危险命令拦截
超时时间限制
输出长度限制
权限隔离
沙箱环境
人工确认机制
否则会带来严重安全风险。
17. TerminateTool:任务终止工具
TerminateTool 是一个比较特殊的工具。
它不是获取信息,也不是执行系统动作,而是用于流程控制。
源码中它的描述是:当请求已经满足,或者助手无法继续推进任务时,调用该工具终止交互;工具方法 doTerminate() 返回"任务结束"。(GitHub)
可以理解为:
让自主规划型智能体知道什么时候停下来
在复杂 Agent 中,模型可能会循环执行:
搜索
抓取
分析
再搜索
再抓取
再分析
如果没有终止机制,智能体可能不知道什么时候结束任务。
TerminateTool 的作用就是给模型一个明确的结束动作。
18. 当前工具能力之间如何组合?
这些工具单独看都比较简单,但真正有价值的是组合。
例如用户提出:
帮我做一份周末约会计划,并保存成 PDF。
模型可能形成这样的工具调用链:
1. 调用 WebSearchTool 搜索约会地点
2. 调用 WebScrapingTool 抓取相关网页内容
3. 整理出约会路线和建议
4. 调用 PDFGenerationTool 生成 PDF
5. 调用 TerminateTool 结束任务
再比如用户提出:
把这份计划保存到文件里,之后我再修改。
工具链可能是:
1. 模型生成计划文本
2. 调用 FileOperationTool.writeFile 写入 plan.txt
3. 后续调用 FileOperationTool.readFile 读取 plan.txt
4. 模型继续优化内容
所以 Tool Calling 的价值不在于某个工具本身,而在于:
模型可以根据任务目标,动态选择和组合多个工具。
19. Tool Calling 和 RAG 的区别
Tool Calling 和 RAG 都能增强模型能力,但它们解决的问题不同。
RAG:
从知识库中检索已有知识,增强回答依据。
Tool Calling:
调用外部工具获取信息或执行动作。
举个例子:
用户问:异地恋如何维持关系?
适合 RAG:
检索本地恋爱知识库中的"异地恋沟通建议"。
用户问:帮我搜索这个周末北京有什么适合情侣的活动。
适合 Tool Calling:
调用 WebSearchTool 搜索最新网页信息。
也可以组合:
RAG 提供稳定领域知识
Tool Calling 获取实时外部信息
模型整合两者生成回答
所以两者不是替代关系,而是互补关系。
20. Tool Calling 和 Advisor 的区别
前面几期分析过 Advisor。
这里也要区分:
Advisor:
增强模型调用流程。
Tool Calling:
让模型可以调用外部能力。
例如:
MessageChatMemoryAdvisor:
负责把历史对话加入上下文。
QuestionAnswerAdvisor:
负责把检索文档加入上下文。
MyLoggerAdvisor:
负责记录请求和响应。
ToolCallback[]:
负责暴露一组可以被模型调用的工具。
在 LoveApp 中,Advisor 是这样接入的:
.advisors(...)
工具是这样接入的:
.toolCallbacks(allTools)
源码中 doChatWithTools() 同时使用了这两者:它用 Advisor 注入会话 ID 和日志能力,用 toolCallbacks(allTools) 注入工具能力。(GitHub)
所以二者处于不同层次:
Advisor 是对话链路增强器
Tool 是模型可调用的外部执行能力
21. 当前实现的优点
21.1 工具集中注册
所有工具都在 ToolRegistration 中集中创建,并统一转换成 ToolCallback[]。
这样 LoveApp 不需要逐个 new 工具,只需要注入:
@Resource
private ToolCallback[] allTools;
这让工具管理更清晰。(GitHub)
21.2 工具类型覆盖较全
当前项目已经包含:
搜索
抓取
下载
文件读写
终端执行
PDF 生成
任务终止
这些能力覆盖了一个 Agent 从信息获取到结果交付的基本流程。(GitHub)
21.3 与 ChatClient 主链路结合自然
工具调用没有另起一套系统,而是直接挂在:
chatClient.prompt()
链路上。
这说明项目仍然保持了统一的智能体调用结构:
ChatClient 主链路
+ ChatMemory
+ MyLoggerAdvisor
+ ToolCallback[]
21.4 可扩展性强
新增工具时,只需要:
1. 新建一个工具类
2. 写一个带 @Tool 的方法
3. 在 ToolRegistration 中注册
这样模型就可以在工具列表中看到这个新工具。
22. 当前实现可以改进的地方
22.1 工具权限需要分级
当前 doChatWithTools() 一次性注入了所有工具:
.toolCallbacks(allTools)
这意味着模型在该模式下可以看到全部工具,包括终端执行工具。(GitHub)
更安全的方式是按场景分组:
基础工具组:
文件读写、PDF 生成
联网工具组:
网页搜索、网页抓取、资源下载
高风险工具组:
终端执行
流程工具组:
任务终止
不同任务只注入必要工具。
22.2 TerminalOperationTool 需要沙箱
当前终端工具直接执行:
cmd.exe /c command
这在正式环境中风险很高。(GitHub)
建议增加:
命令白名单
禁止 rm/del/format 等危险命令
限制工作目录
限制执行时间
限制输出长度
以低权限用户运行
Docker 沙箱隔离
高风险命令人工确认
22.3 文件名需要安全校验
FileOperationTool、ResourceDownloadTool 和 PDFGenerationTool 都根据用户提供的 fileName 拼接本地路径。(GitHub)
这需要防止:
../ 路径穿越
特殊字符
覆盖关键文件
超长文件名
非法扩展名
可以把文件名限制为:
字母、数字、下划线、短横线、点号
并且强制所有文件只能写入 tmp 子目录。
22.4 网页抓取结果需要清洗
WebScrapingTool 当前返回的是完整 HTML。(GitHub)
后续可以改成:
只返回正文文本
过滤 script/style
限制最大字符数
提取标题和段落
保留链接来源
这样可以减少无关内容进入模型上下文。
22.5 搜索结果需要结构化
WebSearchTool 当前把前 5 条搜索结果的 JSON 对象拼接成字符串返回。(GitHub)
后续可以把结果整理成更清晰的格式:
标题
摘要
链接
来源
排名
这样模型更容易使用搜索结果。
22.6 工具调用需要审计日志
当前有 MyLoggerAdvisor 观察模型请求和响应,但工具执行本身也应该有独立日志。
建议记录:
工具名称
调用参数
调用时间
执行耗时
执行结果摘要
是否成功
错误信息
调用用户
会话 ID
尤其是文件写入、资源下载和终端执行这类工具,必须有审计记录。
23. 当前项目中暂未重点使用 ToolContext
Spring AI 中除了普通工具参数外,还可以使用一些更高级的工具上下文机制。但从当前 tool 目录下这些工具类的源码看,项目主要使用的是 @Tool 和 @ToolParam,没有明显看到工具方法中使用 ToolContext 传递用户 ID、会话 ID 或其他运行时上下文。(GitHub)
后续如果要做正式 Agent,可以考虑引入工具上下文,用于传递:
当前用户 ID
当前会话 ID
当前权限级别
当前工作目录
当前任务 ID
是否允许联网
是否允许执行高风险工具
这样工具执行就可以结合业务身份和权限控制,而不是只依赖模型生成的参数。
24. 我的理解
我认为 ai_agent 项目的 Tool Calling 设计,最值得学习的是它展示了一个 Agent 从"问答"到"执行"的基本扩展方式。
前面的 ChatClient、Advisor、ChatMemory、RAG 主要解决:
如何让模型更好地回答?
而 Tool Calling 解决的是:
如何让模型借助外部工具完成任务?
在这个项目中,工具调用链路可以总结为:
Tool 类定义能力
@Tool 描述能力
ToolRegistration 注册能力
ToolCallback[] 暴露能力
LoveApp 注入能力
模型选择能力
后端执行能力
模型整合结果
这就是 Agent 的基本执行闭环。
25. 本期重点理解
这一期最重要的是理解 Tool Calling 的基本机制。
可以总结为五点:
第一,Tool Calling 让模型可以请求调用外部工具,从而突破纯文本回答的限制。
第二,模型本身不直接执行工具,真实执行由应用程序完成。
第三,项目通过 @Tool 和 @ToolParam 把 Java 方法声明为可调用工具。
第四,ToolRegistration 负责统一创建工具对象,并转换成 ToolCallback[]。
第五,LoveApp 通过 .toolCallbacks(allTools) 把工具列表注入当前 ChatClient 调用。
一句话概括:
ai_agent 的 Tool Calling 机制,是通过 ToolRegistration 把一组 Java 工具方法注册为 ToolCallback[],再由 LoveApp 注入模型调用,使模型能够根据任务需要调用搜索、抓取、下载、文件、终端和 PDF 等外部能力。
26. 本期小结
本期主要分析了 ai_agent 项目中的 Tool Calling 工具调用机制。项目在 tool 目录下实现了多个工具类,包括网页搜索、网页抓取、资源下载、文件读写、终端执行、PDF 生成和任务终止。每个工具方法通过 @Tool 声明为可调用工具,并通过 @ToolParam 描述参数含义。ToolRegistration 作为集中注册类,创建所有工具对象,并通过 ToolCallbacks.from(...) 转换为 ToolCallback[]。在 LoveApp 的 doChatWithTools() 方法中,系统通过 .toolCallbacks(allTools) 把工具列表注入到当前模型调用中,使模型能够根据用户任务自动选择工具、生成参数,并在工具执行结果返回后组织最终回答。
这一期可以用一句话总结:
Tool Calling 让 LoveApp 从"能够回答恋爱问题的智能体",进一步变成"能够搜索信息、读写文件、下载资源、执行命令并生成 PDF 的任务型智能体"。
下一期可以继续分析:
AI Agent 项目学习笔记(九):网页搜索、网页抓取与资源下载工具
下一期重点分析 WebSearchTool、WebScrapingTool 和 ResourceDownloadTool 的源码实现,理解 Agent 如何通过联网工具获取外部信息,并讨论搜索结果清洗、网页正文抽取、下载安全和工具调用审计等问题。