1. 本期目标
上一篇文章分析了 ai_agent 项目中的三个联网工具:
WebSearchTool
WebScrapingTool
ResourceDownloadTool
它们主要解决的是:
智能体如何从外部网络获取信息?
这一期继续分析工具模块中的另一类能力:
本地执行与结果交付能力
重点分析三个工具:
FileOperationTool
TerminalOperationTool
PDFGenerationTool
这三个工具让智能体不只是"查资料"和"回答问题",还可以进一步完成:
读写本地文件
执行终端命令
生成 PDF 文件
Spring AI 官方文档中也说明,Tool Calling 主要用于两类场景:一类是信息检索,另一类是执行动作;模型只负责请求工具调用并提供参数,真实工具执行由应用程序完成。这个项目中的文件读写、终端命令和 PDF 生成,正属于"执行动作"这一类工具能力。(Home)
2. 为什么要分析这三个工具?
前面讲的联网工具主要用于:
找信息
读网页
下载资源
而本期的三个工具主要用于:
处理本地数据
执行本地操作
生成最终交付物
可以这样理解:
FileOperationTool:
让智能体能把内容保存下来,或者读取已有文件。
TerminalOperationTool:
让智能体能调用系统命令,完成更底层的本地操作。
PDFGenerationTool:
让智能体能把最终内容整理成 PDF 文件,形成可交付结果。
三者结合起来,就形成了一条从"生成内容"到"保存内容"再到"交付内容"的链路:
模型生成文本
↓
写入本地文件
↓
必要时执行命令处理
↓
生成 PDF 报告
↓
返回文件路径
这也是 Agent 从"聊天机器人"走向"任务执行助手"的关键一步。
3. 这三个工具在项目中的注册位置
这三个工具和其他工具一样,都在 ToolRegistration 中统一注册。
ToolRegistration 会创建 FileOperationTool、TerminalOperationTool、PDFGenerationTool 等工具对象,并通过 ToolCallbacks.from(...) 转换成 ToolCallback[],最后提供给 LoveApp 使用。也就是说,模型能够调用这些工具,是因为它们被统一放入了 allTools 这个工具列表中。(GitHub)
整体关系可以写成:
FileOperationTool
TerminalOperationTool
PDFGenerationTool
↓
ToolRegistration.allTools()
↓
ToolCallback[]
↓
LoveApp.doChatWithTools()
↓
模型可选择调用这些工具
所以,工具类本身只是定义能力;真正让模型看到这些能力的是 ToolRegistration。
4. 文件保存根目录:FileConstant
在分析具体工具之前,需要先看一个基础常量:
public interface FileConstant {
String FILE_SAVE_DIR = System.getProperty("user.dir") + "/tmp";
}
项目把所有工具生成或保存的文件统一放到项目根目录下的 tmp 目录中。FileOperationTool 会使用 tmp/file,ResourceDownloadTool 会使用 tmp/download,PDFGenerationTool 会使用 tmp/pdf。这种设计比把文件散落在项目根目录更清晰,也方便后续统一清理临时文件。(GitHub)
可以理解为:
项目根目录
↓
tmp
├─ file
├─ download
└─ pdf
本期重点关注:
tmp/file
tmp/pdf
以及终端命令执行工具。
5. FileOperationTool:文件操作工具
FileOperationTool 提供两个核心能力:
readFile
writeFile
它内部定义的文件目录是:
private final String FILE_DIR = FileConstant.FILE_SAVE_DIR + "/file";
也就是说,所有通过该工具读写的普通文本文件,默认都位于:
项目根目录/tmp/file
源码中可以看到,readFile() 使用 Hutool 的 FileUtil.readUtf8String(filePath) 读取 UTF-8 文本,writeFile() 会先创建目录,再使用 FileUtil.writeUtf8String(content, filePath) 写入内容。(GitHub)
6. readFile:读取文件内容
readFile() 的方法定义是:
@Tool(description = "Read content from a file")
public String readFile(
@ToolParam(description = "Name of the file to read") String fileName)
它的输入是:
fileName:要读取的文件名
内部流程可以理解为:
接收 fileName
↓
拼接路径:tmp/file/fileName
↓
读取 UTF-8 文本内容
↓
返回文件内容
如果读取失败,它会返回:
Error reading file: 具体错误信息
这个工具适合让智能体读取之前保存的文本内容,例如计划、摘要、报告草稿等。(GitHub)
7. writeFile:写入文件内容
writeFile() 的方法定义是:
@Tool(description = "Write content to a file")
public String writeFile(String fileName, String content)
它有两个输入:
fileName:要写入的文件名
content:要写入的内容
内部流程是:
接收 fileName 和 content
↓
拼接路径:tmp/file/fileName
↓
创建 tmp/file 目录
↓
以 UTF-8 形式写入文本
↓
返回保存路径
写入成功后,工具会返回:
File written successfully to: 文件路径
这意味着模型可以先生成一段内容,再调用该工具把内容保存成本地文件。(GitHub)
8. FileOperationTool 的使用场景
这个工具适合处理以下任务:
保存用户计划
保存模型生成的建议
保存中间分析结果
读取之前保存的草稿
继续修改某个文件内容
例如用户说:
帮我把刚才的约会计划保存成 plan.txt。
模型可以调用:
writeFile(fileName = "plan.txt", content = "具体计划内容")
之后用户再说:
读取 plan.txt,帮我改得更正式一点。
模型可以调用:
readFile(fileName = "plan.txt")
然后基于读取内容继续修改。
所以,FileOperationTool 提供的是一种简单的"本地工作区记忆"。
9. FileOperationTool 的价值
多轮对话记忆主要保存的是聊天上下文。
而文件工具保存的是明确的任务产物。
两者区别是:
ChatMemory:
保存对话上下文,服务于模型理解前后文。
FileOperationTool:
保存文件内容,服务于任务结果沉淀和后续处理。
例如:
ChatMemory 记住:
用户前面说过想要周末约会计划。
FileOperationTool 保存:
一份完整的周末约会计划文本。
这说明文件工具不是简单的辅助功能,而是让 Agent 有了"工作区"的雏形。
10. FileOperationTool 当前可以改进的地方
当前实现比较适合学习,但正式系统中需要注意几个问题。
第一,fileName 直接拼接到路径中:
String filePath = FILE_DIR + "/" + fileName;
如果文件名包含 ../ 这类路径片段,就可能带来路径穿越风险。后续应该限制文件名只允许字母、数字、下划线、短横线和安全扩展名,并检查最终路径仍然位于 tmp/file 目录内。(GitHub)
第二,当前没有文件大小限制。模型如果写入很长内容,可能导致磁盘占用增长。后续可以限制单个文件最大大小,例如 1 MB 或 5 MB。
第三,当前写入会覆盖已有文件。正式系统中可以增加:
是否允许覆盖
自动生成版本号
写入前备份
返回文件大小
这样更安全,也更适合真实任务场景。
11. TerminalOperationTool:终端执行工具
TerminalOperationTool 是项目中风险最高、能力也最强的工具。
它提供的方法是:
@Tool(description = "Execute a command in the terminal")
public String executeTerminalCommand(String command)
输入是:
command:要执行的终端命令
源码中,它使用:
new ProcessBuilder("cmd.exe", "/c", command)
启动系统命令。这说明当前实现更偏向 Windows 环境,因为它使用的是 cmd.exe /c。工具会读取命令标准输出,并在进程退出码非 0 时追加失败退出码信息。(GitHub)
12. TerminalOperationTool 的执行流程
这个工具的执行流程可以写成:
接收 command
↓
构造 ProcessBuilder("cmd.exe", "/c", command)
↓
启动进程
↓
读取标准输出
↓
等待命令结束
↓
如果 exitCode != 0,追加错误码
↓
返回命令输出
例如用户让智能体查看当前目录,模型可能调用:
executeTerminalCommand(command = "dir")
工具执行后返回命令输出。
这类工具让 Agent 具备了真实操作本地环境的能力,但也意味着需要非常严格的安全控制。
13. TerminalOperationTool 的适用场景
学习阶段,它可以用于演示:
查看目录
运行简单命令
检查文件是否存在
执行脚本
调用本地程序
例如:
查看 tmp 目录下有哪些文件。
模型可能调用:
dir tmp
再例如:
帮我检查生成的 PDF 文件是否存在。
模型可能调用:
dir tmp\pdf
从能力上看,它让智能体从"只能调用特定 Java 工具"扩展为"可以调用系统命令"。这是一种更通用的执行能力。
14. TerminalOperationTool 的高风险点
这个工具必须重点讨论安全问题。
因为当前实现把模型给出的 command 直接交给:
cmd.exe /c
执行。也就是说,如果缺少限制,模型可能触发删除文件、访问敏感目录、执行恶意命令等危险操作。(GitHub)
正式系统中不能直接暴露无限制终端工具,至少需要增加:
命令白名单
危险命令黑名单
工作目录限制
执行超时
输出长度限制
低权限运行
沙箱隔离
人工确认机制
工具调用审计日志
比较安全的思路是:
不让模型直接执行任意 command,
而是提供受控命令工具。
例如,不提供:
executeTerminalCommand(command)
而是提供更窄的工具:
listFiles(directory)
checkFileExists(fileName)
runPredefinedScript(scriptName)
这样可以减少模型执行任意命令的风险。
15. TerminalOperationTool 的跨平台问题
当前工具写死了:
cmd.exe /c
这适合 Windows,但在 Linux 或 macOS 环境下不能直接运行。(GitHub)
如果后续要做跨平台支持,可以根据操作系统判断:
Windows:
cmd.exe /c command
Linux / macOS:
/bin/sh -c command
不过,仅仅适配跨平台还不够。终端执行本身是高风险能力,跨平台支持应该和安全控制一起设计。
16. PDFGenerationTool:PDF 生成工具
PDFGenerationTool 用于把模型生成的内容写入 PDF 文件。
它的方法定义是:
@Tool(description = "Generate a PDF file with given content", returnDirect = false)
public String generatePDF(String fileName, String content)
它接收两个参数:
fileName:生成的 PDF 文件名
content:写入 PDF 的内容
源码中,它会把 PDF 保存到:
FileConstant.FILE_SAVE_DIR + "/pdf"
也就是:
项目根目录/tmp/pdf
工具内部使用 iText 创建 PdfWriter、PdfDocument 和 Document,并用 STSongStd-Light 与 UniGB-UCS2-H 创建中文字体,最后把 content 放进一个 Paragraph 写入 PDF。(GitHub)
17. PDFGenerationTool 的执行流程
可以把 PDF 生成流程理解为:
接收 fileName 和 content
↓
拼接路径:tmp/pdf/fileName
↓
创建 tmp/pdf 目录
↓
创建 PdfWriter
↓
创建 PdfDocument
↓
创建 Document
↓
设置中文字体
↓
把 content 放入 Paragraph
↓
写入 PDF
↓
返回 PDF 文件路径
生成成功后,工具返回:
PDF generated successfully to: 文件路径
这让 Agent 具备了生成可交付文件的能力。(GitHub)
18. returnDirect = false 的理解
PDFGenerationTool 的 @Tool 注解中设置了:
returnDirect = false
这表示工具结果不会直接作为最终回复返回给用户,而是会先返回给模型,让模型再组织最终回答。源码中该注解写在 generatePDF() 方法上。(GitHub)
可以理解为:
工具返回:
PDF generated successfully to: xxx
模型最终回答:
我已经为你生成了 PDF 文件,保存路径是 xxx。
这样用户看到的回答会更自然,而不是直接看到工具原始返回值。
19. PDFGenerationTool 的使用场景
这个工具适合把模型生成的最终内容整理成正式文件。
例如:
生成恋爱沟通建议报告
生成约会计划 PDF
生成咨询总结 PDF
生成用户问题分析报告
生成行动清单 PDF
用户可以说:
把刚才的建议整理成一份 PDF。
模型就可以调用:
generatePDF(
fileName = "love_advice.pdf",
content = "整理后的建议内容"
)
工具执行后返回 PDF 保存路径,模型再把路径告诉用户。
20. PDFGenerationTool 当前实现的优点
当前实现有三个优点。
第一,目录统一。PDF 文件统一保存到 tmp/pdf,与 FileConstant.FILE_SAVE_DIR 保持一致。(GitHub)
第二,支持中文。源码中使用了 STSongStd-Light 和 UniGB-UCS2-H 创建字体,这说明作者考虑到了中文内容写入 PDF 的问题。(GitHub)
第三,实现简单。它只把一段内容写成一个段落,适合快速验证"模型生成内容 → 工具生成 PDF"的完整链路。
21. PDFGenerationTool 当前可以改进的地方
当前工具只写入一个普通段落,生成的 PDF 比较基础。
后续可以改进为:
支持标题
支持小标题
支持列表
支持分页
支持表格
支持页眉页脚
支持生成时间
支持用户名称
支持报告编号
此外,fileName 同样需要安全校验,避免路径穿越和非法扩展名。因为当前工具也是直接把 fileName 拼接到 filePath 中。(GitHub)
更稳妥的做法是:
只允许 .pdf 扩展名
自动补全 .pdf 后缀
清理特殊字符
确保最终路径位于 tmp/pdf 目录下
22. 三个工具如何组合成完整任务链?
这三个工具单独看都很简单,但组合起来就能完成更完整的任务。
例如用户提出:
帮我生成一份恋爱沟通建议报告,并保存成 PDF。
可能的工具链是:
第一步:模型生成报告正文
第二步:FileOperationTool.writeFile
把报告草稿保存成 txt 文件
第三步:PDFGenerationTool.generatePDF
把报告正文生成 PDF
第四步:TerminalOperationTool.executeTerminalCommand
检查 tmp/pdf 目录下文件是否生成成功
第五步:模型返回最终说明
告诉用户 PDF 文件保存路径
也就是说:
FileOperationTool 负责保存中间文本
PDFGenerationTool 负责生成交付文件
TerminalOperationTool 负责检查或执行辅助命令
这就是任务型 Agent 的基本执行链。
23. 和前一期联网工具的关系
前一期讲的三个联网工具是:
WebSearchTool
WebScrapingTool
ResourceDownloadTool
本期三个本地工具是:
FileOperationTool
TerminalOperationTool
PDFGenerationTool
它们可以组成更长的任务链:
WebSearchTool 搜索资料
↓
WebScrapingTool 抓取网页内容
↓
模型总结和改写
↓
FileOperationTool 保存草稿
↓
PDFGenerationTool 生成 PDF
↓
TerminalOperationTool 检查文件
所以,联网工具偏向"获取资料",本地工具偏向"处理资料和生成结果"。
24. 这三个工具和 ChatMemory 的区别
这三个工具也容易和 ChatMemory 混淆。
它们的区别是:
ChatMemory:
保存对话上下文,让模型理解前后文。
FileOperationTool:
保存明确的文件内容,让任务产物可以落盘。
PDFGenerationTool:
生成正式文件,让结果可以交付。
TerminalOperationTool:
调用系统命令,让 Agent 能执行更底层的操作。
例如:
ChatMemory 让模型知道:
用户之前想要一份约会计划。
FileOperationTool 让系统保存:
plan.txt
PDFGenerationTool 让系统生成:
plan.pdf
TerminalOperationTool 让系统检查:
plan.pdf 是否存在
所以,它们不是替代关系,而是处在不同层次。
25. 当前实现的整体优点
25.1 能力闭环比较完整
这三个工具补齐了 Agent 的本地处理能力:
读文件
写文件
执行命令
生成 PDF
配合前面的联网工具,项目已经具备了从资料获取到结果交付的基本闭环。
25.2 工具设计简单直观
每个工具类都围绕一个明确职责展开:
FileOperationTool:
文件读写
TerminalOperationTool:
终端执行
PDFGenerationTool:
PDF 生成
这对学习 Tool Calling 很友好。
25.3 和项目统一临时目录结合
文件和 PDF 都放在 FileConstant.FILE_SAVE_DIR 下,也就是项目根目录的 tmp 目录中。这样路径比较集中,后续清理和管理都更方便。(GitHub)
26. 当前实现最需要注意的问题
这一期最需要强调的是安全边界。
文件写入、终端执行、PDF 生成都属于"会改变本地环境"的工具。
尤其是终端执行工具,当前实现直接执行模型给出的命令。这个能力非常强,但正式系统中必须加限制。(GitHub)
建议按照风险等级管理工具:
低风险:
PDF 生成
普通文件读取
中风险:
文件写入
资源下载
高风险:
终端执行
高风险工具最好不要默认暴露给模型,而是:
需要用户确认
需要权限校验
需要沙箱隔离
需要审计日志
需要限制命令范围
27. 更合理的工具分组设计
当前 ToolRegistration 会把所有工具统一放入 allTools 中。(GitHub)
学习阶段这样写很方便,但后续可以分组:
basicTools:
FileOperationTool
PDFGenerationTool
TerminateTool
webTools:
WebSearchTool
WebScrapingTool
ResourceDownloadTool
systemTools:
TerminalOperationTool
然后在不同任务中只注入需要的工具。
例如:
普通报告生成:
只注入 basicTools
联网资料整理:
注入 basicTools + webTools
本地环境调试:
经过确认后才注入 systemTools
这样比"一次性给模型所有工具"更安全。
28. 我的理解
我认为这一期的核心是理解 Agent 的"落地执行能力"。
前面的 RAG 和联网搜索让智能体可以获得更多信息。
而本期的三个工具让智能体可以把信息变成结果:
FileOperationTool:
把内容沉淀为文件。
TerminalOperationTool:
把模型决策转化为系统命令。
PDFGenerationTool:
把文本答案转化为可交付文档。
这说明一个完整 Agent 不应该只停留在"生成回答",而应该逐步具备:
获取信息
处理信息
保存信息
生成交付物
执行受控动作
ai_agent 项目已经把这些能力用简单的 Java 工具类串了起来。
29. 本期重点理解
这一期最重要的是理解三个本地工具的分工。
可以总结为五点:
第一,FileOperationTool 提供 readFile 和 writeFile,用于读取和写入 tmp/file 目录下的文本文件。
第二,TerminalOperationTool 使用 ProcessBuilder("cmd.exe", "/c", command) 执行终端命令,当前更偏 Windows 环境。
第三,PDFGenerationTool 使用 iText 把内容写入 PDF,并保存到 tmp/pdf 目录下。
第四,FileConstant 统一规定工具文件保存根目录为项目根目录下的 tmp。
第五,文件写入和终端执行属于高风险工具,正式系统中必须加入路径校验、权限控制、沙箱隔离和审计日志。
一句话概括:
FileOperationTool 负责保存和读取任务文本,TerminalOperationTool 负责执行本地命令,PDFGenerationTool 负责把模型生成内容转化为可交付 PDF 文件。
30. 本期小结
本期主要分析了 ai_agent 项目中的三个本地执行工具。FileOperationTool 提供 readFile() 和 writeFile() 两个方法,用于读取和写入 tmp/file 目录下的 UTF-8 文本文件;TerminalOperationTool 使用 ProcessBuilder("cmd.exe", "/c", command) 执行终端命令,并返回命令输出;PDFGenerationTool 使用 iText 创建 PDF 文件,将模型生成内容写入 tmp/pdf 目录,并通过内置中文字体支持中文内容。三个工具配合后,可以让智能体完成"生成内容---保存草稿---执行辅助命令---生成 PDF 交付物"的任务链路。
这一期可以用一句话总结:
这三个工具让 LoveApp 从"能获取信息、能回答问题",进一步变成"能保存结果、执行本地操作、生成正式文件"的任务型智能体。
下一期可以继续分析:
AI Agent 项目学习笔记(十一):工具调用安全与 TerminateTool 任务终止机制
下一期重点分析 TerminateTool、工具权限分级、终端执行风险、文件路径安全、联网工具 SSRF 风险、工具调用审计,以及如何把当前项目的工具系统改造成更安全的 Agent 执行框架。