AI Agent 项目学习笔记(十一):TerminateTool、工具调用闭环与安全边界
2026/5/22 14:09:12 网站建设 项目流程

1. 本期目标

上一篇文章分析了FileOperationToolTerminalOperationToolPDFGenerationTool,重点理解了智能体如何读写文件、执行终端命令和生成 PDF。

这一期继续分析工具模块中最后一个比较特殊的工具:

TerminateTool

它不像搜索工具那样获取信息,也不像文件工具那样保存内容,更不像终端工具那样执行系统命令。它的作用是:

告诉智能体:任务已经结束,可以停止继续调用工具。

ai_agent项目的tool目录中包含TerminateTool.java,并且ToolRegistration会把它和文件、搜索、抓取、下载、终端、PDF 等工具一起注册到ToolCallback[]中,供LoveApp.doChatWithTools()使用。(GitHub)

本期主要解决几个问题:

1. TerminateTool 是做什么的? 2. 为什么智能体需要“终止工具”? 3. TerminateTool 和普通工具有什么区别? 4. 它在 ToolRegistration 中如何被注册? 5. 它在工具调用闭环中处于什么位置? 6. 工具调用为什么需要结束条件? 7. 当前 allTools 一次性注入全部工具有什么风险? 8. 如何设计更安全的工具分组和执行边界?

2. 为什么需要 TerminateTool?

在普通聊天中,模型生成一段回答就结束了。

流程很简单:

用户问题 ↓ 模型回答 ↓ 结束

但 Tool Calling 不一样。

当模型拥有多个工具时,它可能会形成一条任务链:

搜索网页 ↓ 抓取网页 ↓ 下载资源 ↓ 写入文件 ↓ 生成 PDF ↓ 返回结果

如果任务比较复杂,模型可能会连续调用多个工具。

这时系统就会遇到一个问题:

模型什么时候应该停止继续调用工具?

如果没有明确的终止机制,智能体可能出现两类问题:

第一,任务已经完成,但模型继续搜索、继续抓取、继续调用工具。 第二,任务无法继续推进,但模型仍然反复尝试其他工具。

所以,TerminateTool的作用就是给智能体一个明确的“结束动作”。


3. TerminateTool 的源码结构

TerminateTool的代码非常短。

它只有一个工具方法:

@Tool(description = """ Terminate the interaction when the request is met OR if the assistant cannot proceed further with the task. "When you have finished all the tasks, call this tool to end the work. """) public String doTerminate() { return "任务结束"; }

源码注释中也写明,它是“终止工具”,作用是让自主规划智能体能够合理地中断。这个工具没有输入参数,只返回一个固定文本“任务结束”。(GitHub)

可以把它理解为:

输入:无 输出:任务结束 作用:通知工具调用流程可以结束

4. TerminateTool 和其他工具的区别

前面几期讲过的工具大多属于两类。

第一类是信息获取工具:

WebSearchTool WebScrapingTool ResourceDownloadTool

它们解决的是:

从外部获取信息或资源。

第二类是本地执行工具:

FileOperationTool TerminalOperationTool PDFGenerationTool

它们解决的是:

在本地环境中保存、执行或生成结果。

TerminateTool不负责获取信息,也不负责生成文件。

它属于第三类:

流程控制工具

它解决的是:

智能体什么时候停止。

这就是它特殊的地方。


5. 为什么终止工具很重要?

对于一个简单问答机器人来说,终止不是问题。

因为模型回答完就结束。

但是对于一个任务型 Agent 来说,模型可能会不断规划下一步:

我需要先搜索资料 我需要再打开网页 我需要下载资源 我需要写入文件 我需要检查文件 我需要生成 PDF

这种能力让 Agent 更强,但也带来一个问题:

如果没有结束条件,工具调用可能变得不可控。

例如,用户要求:

帮我生成一份约会计划 PDF。

理想流程应该是:

生成计划内容 ↓ 生成 PDF ↓ 告诉用户保存路径 ↓ 结束

而不是:

生成 PDF ↓ 继续搜索约会地点 ↓ 继续抓取网页 ↓ 继续写文件 ↓ 继续检查目录

所以,TerminateTool的价值不是“做一件具体的业务事情”,而是让智能体知道:

任务完成后应该停下来。

6. TerminateTool 在 ToolRegistration 中的位置

TerminateTool并不是单独使用的。

ToolRegistration中,项目会创建它:

TerminateTool terminateTool = new TerminateTool();

然后把它和其他工具一起传入:

return ToolCallbacks.from( fileOperationTool, webSearchTool, webScrapingTool, resourceDownloadTool, terminalOperationTool, pdfGenerationTool, terminateTool );

这说明TerminateTool会和其他工具一起出现在allTools中。模型在doChatWithTools()中看到的不是单个工具,而是一整组工具。(GitHub)

可以理解为:

执行型工具: 负责推进任务。 TerminateTool: 负责结束任务。

7. LoveApp 中如何暴露 TerminateTool?

LoveApp中,项目通过:

@Resource private ToolCallback[] allTools;

注入ToolRegistration创建好的工具列表。

然后在doChatWithTools()中使用:

.toolCallbacks(allTools)

把所有工具暴露给当前模型调用。也就是说,只要用户调用的是doChatWithTools()这条链路,模型就可以看到包括TerminateTool在内的所有工具。(GitHub)

整体流程是:

ToolRegistration 创建 TerminateTool ↓ ToolCallbacks.from(...) 转成 ToolCallback[] ↓ LoveApp 注入 allTools ↓ doChatWithTools() 调用 .toolCallbacks(allTools) ↓ 模型可以选择调用 TerminateTool

8. Tool Calling 的执行闭环

Spring AI 文档中说明,Tool Calling 的基本流程是:应用把工具定义加入聊天请求,模型决定调用工具并提供工具名和参数,应用根据工具名执行对应工具,再把工具执行结果返回给模型,最后模型基于工具结果生成最终响应。工具由ToolCallback建模,ChatClientChatModel都可以接收工具列表。(Home)

结合ai_agent项目,可以把工具调用闭环写成:

用户提出任务 ↓ LoveApp.doChatWithTools() ↓ 注入 ChatMemory 会话 ID ↓ 注入 MyLoggerAdvisor 日志 ↓ 注入 allTools 工具列表 ↓ 模型判断需要哪些工具 ↓ 后端执行具体工具 ↓ 工具结果返回模型 ↓ 模型决定继续调用工具或结束任务 ↓ 必要时调用 TerminateTool ↓ 生成最终回复

所以,TerminateTool位于工具调用链路的末端。

它不是起点,也不是中间处理节点,而是:

任务闭环的结束标志。

9. 一个完整例子:生成 PDF 并终止任务

假设用户输入:

帮我生成一份“周末约会计划”的 PDF。

模型可能执行这样的工具链:

第一步:生成约会计划正文 第二步:调用 PDFGenerationTool 生成 date_plan.pdf 第三步:收到 PDF 保存路径 第四步:调用 TerminateTool 确认任务结束 第五步:回复用户 PDF 已生成,保存路径是 xxx

其中,PDFGenerationTool完成业务动作。

TerminateTool完成流程收尾。

两者的区别是:

PDFGenerationTool: 让任务产物生成出来。 TerminateTool: 让智能体停止继续寻找下一步。

10. 另一个例子:任务无法继续推进时终止

TerminateTool的描述里不只说“任务完成后终止”,还说当助手无法继续推进任务时也可以终止。源码中的 description 明确包含“when the request is met OR if the assistant cannot proceed further with the task”。(GitHub)

例如用户说:

帮我下载这个链接里的文件。

但链接不可访问,下载工具失败。

这时智能体不应该无限尝试。

合理流程是:

调用 ResourceDownloadTool ↓ 下载失败 ↓ 尝试判断是否能换链接 ↓ 没有新的有效链接 ↓ 调用 TerminateTool ↓ 告诉用户下载失败的原因

所以,终止不只代表“成功完成”。

它也可以代表:

当前信息不足或条件不满足,任务已经无法继续推进。

11. TerminateTool 和异常处理的区别

这里要注意,TerminateTool不是异常处理工具。

异常处理是:

工具执行过程中发生错误,例如网络失败、文件写入失败、命令执行失败。

TerminateTool是:

模型根据当前任务状态判断:不需要继续调用工具了。

例如:

ResourceDownloadTool 返回下载失败

这是工具执行结果。

模型看到失败结果后,可以选择:

换一个 URL 重新搜索 告诉用户失败 调用 TerminateTool 结束

所以两者关系是:

异常处理负责报告工具执行结果; TerminateTool 负责结束任务流程。

12. TerminateTool 和最终回答的区别

TerminateTool返回的是:

任务结束

但这并不等于用户最终看到的完整回答。

更合理的最终回答应该是模型组织出来的自然语言,例如:

我已经完成任务,PDF 文件已生成,保存路径是:tmp/pdf/date_plan.pdf。

或者:

我尝试下载该资源,但链接无法访问,因此任务已经停止。你可以换一个可访问链接后再试。

所以,TerminateTool更像一个内部流程信号。

它告诉智能体:

不用再继续调用其他工具了。

最终给用户看的内容,仍然应该由模型根据工具执行结果组织。


13. 当前 allTools 一次性注入的特点

当前LoveApp.doChatWithTools()使用的是:

.toolCallbacks(allTools)

这意味着所有注册工具都会一次性暴露给模型,包括:

文件读写 网页搜索 网页抓取 资源下载 终端执行 PDF 生成 任务终止

这种方式非常适合学习,因为它能让我们一次性观察完整的工具调用能力。

但它也有一个明显问题:

模型在任何工具调用场景下,都能看到所有工具。

其中最需要注意的是TerminalOperationTool,因为它可以执行终端命令。把它和普通文件、PDF、搜索工具一起默认暴露,适合实验,但不适合直接用于生产环境。


14. 为什么工具越多,越需要边界?

工具越多,模型的选择空间越大。

这既是优势,也是风险。

优势是:

模型可以根据任务自动组合工具。

风险是:

模型可能选择不必要的工具, 也可能调用高风险工具, 还可能生成不安全参数。

例如用户只是想让模型生成一段恋爱建议,理论上只需要普通对话。

但如果所有工具都暴露给模型,模型有可能尝试:

搜索网页 写入文件 生成 PDF 甚至执行终端命令

所以,工具调用系统要考虑一个核心原则:

只给当前任务必要的工具。

这就是最小权限原则在 Agent 工具系统中的体现。


15. 更合理的工具分组方式

当前项目中所有工具统一放在allTools中。

后续可以按风险和用途拆成几组:

basicTools: FileOperationTool PDFGenerationTool TerminateTool webTools: WebSearchTool WebScrapingTool ResourceDownloadTool TerminateTool systemTools: TerminalOperationTool TerminateTool

然后根据任务类型选择工具组。

例如:

普通文本整理任务: 只注入 basicTools 联网资料查询任务: 注入 webTools + basicTools 本地环境调试任务: 经过确认后才注入 systemTools

这样做的好处是:

第一,减少模型误调用工具的概率。 第二,降低高风险工具暴露范围。 第三,让每个任务的工具边界更清晰。 第四,方便后续做权限控制和审计。

16. TerminateTool 应该放在哪些工具组里?

TerminateTool比较特殊。

它不是高风险工具,而是流程控制工具。

因此,很多工具组都可以包含它。

例如:

basicTools + TerminateTool webTools + TerminateTool systemTools + TerminateTool

因为无论是哪种任务,智能体都需要知道什么时候结束。

可以这样理解:

业务工具负责推进任务; TerminateTool 负责结束任务。

所以,TerminateTool更像是每个工具组的通用收尾工具。


17. 当前实现还缺少什么?

TerminateTool本身很简单,但一个完整的工具执行系统还需要更多控制机制。

当前项目可以继续补充:

最大工具调用次数 最大执行时间 工具调用日志 工具失败重试次数 高风险工具人工确认 按用户权限选择工具组 按任务类型选择工具组 工具参数安全校验

这些机制和TerminateTool的关系是:

TerminateTool: 让模型主动结束。 最大次数 / 最大时间: 让系统强制兜底结束。 权限控制 / 参数校验: 让工具调用在安全边界内执行。

所以,终止工具不是唯一的安全机制。

它只是工具闭环中的一个组成部分。


18. 为什么还需要系统级强制终止?

因为模型不一定总能正确调用TerminateTool

有时模型可能忘记终止。

有时模型可能反复尝试工具。

有时工具结果不清晰,导致模型继续规划下一步。

所以,除了让模型拥有TerminateTool,系统还应该设置强制约束:

最多调用 5 次工具 单次工具超时 10 秒 整个任务最长执行 60 秒 连续失败 2 次后停止 终端工具必须人工确认

这类约束不依赖模型自觉,而是由后端系统执行。

可以理解为:

TerminateTool 是模型层面的结束动作; 系统限额是工程层面的安全兜底。

两者应该配合使用。


19. 工具调用日志为什么重要?

前面已经讲过MyLoggerAdvisor可以观察模型请求和响应。

但工具调用系统还需要专门记录工具日志。

尤其当工具会访问网络、写文件、下载资源或执行命令时,必须知道:

模型调用了哪个工具? 传入了什么参数? 工具执行是否成功? 生成了什么文件? 下载了什么资源? 执行了什么命令? 耗时多久? 最终结果是什么?

这类日志可以帮助我们排查问题:

回答错误是因为模型推理错了? 还是搜索结果错了? 还是网页抓取内容太杂? 还是文件写入失败? 还是 PDF 生成失败? 还是终端命令执行失败?

所以,工具日志是任务型 Agent 工程化中非常重要的一环。


20. 工具调用和安全边界的关系

Tool Calling 的本质是:

模型提出工具调用意图; 应用程序执行真实动作。

Spring AI 文档也强调,模型只能请求工具调用并提供输入参数,真正执行工具调用的是客户端应用程序,模型不会直接访问工具背后的 API。这个设计是重要的安全边界。(Home)

这说明安全责任主要在应用层。

应用层必须决定:

哪些工具可以暴露? 哪些参数可以接受? 哪些动作需要确认? 哪些结果可以返回给模型? 哪些日志必须记录?

所以,工具调用系统不能只相信模型。

更合理的设计是:

模型负责规划; 应用负责约束; 工具负责执行; 日志负责追踪; 系统负责兜底终止。

21. TerminateTool 的学习价值

TerminateTool代码很短,但它体现了 Agent 设计中的一个关键思想:

Agent 不只需要会做事,也需要会停下来。

很多初学者在做 Agent 时,容易只关注:

能不能搜索? 能不能读文件? 能不能写文件? 能不能调用命令?

但真正的任务执行系统还要考虑:

什么时候结束? 失败后怎么办? 是否继续尝试? 是否已经满足用户请求? 是否还需要调用下一个工具?

TerminateTool就是这个问题的一个最小实现。


22. 当前项目的工具调用闭环

到这一期为止,ai_agent的工具模块已经可以形成一个完整闭环:

WebSearchTool: 搜索外部资料。 WebScrapingTool: 抓取网页内容。 ResourceDownloadTool: 下载网络资源。 FileOperationTool: 保存和读取本地文本。 TerminalOperationTool: 执行本地命令。 PDFGenerationTool: 生成 PDF 交付物。 TerminateTool: 结束任务流程。

它们组合起来,构成了一个任务型 Agent 的基本能力:

找资料 ↓ 读资料 ↓ 保存资料 ↓ 处理资料 ↓ 生成文件 ↓ 检查结果 ↓ 结束任务

这说明ai_agent已经不只是一个聊天应用,而是具备了“工具执行链”的雏形。


23. 当前实现的优点

23.1 工具链完整

从搜索、抓取、下载,到文件读写、终端执行、PDF 生成,再到任务终止,项目覆盖了任务型 Agent 的基本工具链。

23.2 注册方式统一

所有工具都通过ToolRegistration集中创建,并通过ToolCallbacks.from(...)转换为ToolCallback[]。这样LoveApp只需要注入allTools,就可以把整组工具交给模型使用。(GitHub)

23.3 TerminateTool 让流程更完整

没有TerminateTool时,工具链主要强调“执行”。

加入TerminateTool后,工具链有了“结束”。

这让智能体更接近一个完整的任务执行循环。


24. 当前实现可以改进的地方

24.1 工具分组

把所有工具都放进allTools适合演示,但后续建议拆成多个工具组。

例如:

普通对话不注入工具 报告生成只注入 PDF 和文件工具 联网任务注入搜索和抓取工具 系统调试任务才注入终端工具

24.2 高风险工具确认

TerminalOperationTool这类工具应该增加人工确认。

例如模型想执行:

del rm format curl powershell

这类命令时,系统应该直接拦截或要求确认。


24.3 工具调用次数限制

可以增加类似:

maxToolCalls = 5

如果模型连续调用工具超过限制,就强制停止,并让模型总结已有结果。


24.4 工具参数校验

文件名、URL、命令、下载路径都应该做校验。

例如:

文件名不能包含 ../ URL 不能访问 localhost 下载文件不能超过大小限制 终端命令只能来自白名单 PDF 文件名必须以 .pdf 结尾

24.5 工具执行审计

每次工具调用都应该记录:

conversationId 工具名称 输入参数 执行结果 耗时 是否成功 错误信息 生成文件路径

这样后续才能定位问题和分析行为。


25. 我的理解

我认为TerminateTool看起来简单,但它补上了 Agent 工具链中非常关键的一环。

前面的工具解决的是:

如何做事?

TerminateTool解决的是:

什么时候不再继续做?

这两个问题同样重要。

一个真正可控的智能体,不应该只是不断调用工具,而应该具备完整的任务边界:

明确目标 选择工具 执行动作 观察结果 判断是否完成 必要时停止 给出最终回复

ai_agent项目通过一个很小的TerminateTool,展示了这个思想。


26. 本期重点理解

这一期最重要的是理解TerminateTool和工具调用闭环的关系。

可以总结为五点:

第一,TerminateTool 是流程控制工具,不负责获取信息或生成文件。 第二,它的 doTerminate() 方法没有参数,只返回“任务结束”。 第三,它通过 ToolRegistration 和其他工具一起注册到 allTools 中。 第四,LoveApp.doChatWithTools() 通过 .toolCallbacks(allTools) 把它暴露给模型。 第五,TerminateTool 适合作为模型主动结束任务的信号,但系统仍然需要最大调用次数、超时、权限和审计等工程级安全机制。

一句话概括:

TerminateTool 的作用,是让 ai_agent 的工具调用链不仅能启动和执行任务,也能在任务完成或无法继续时明确结束任务。

27. 本期小结

本期主要分析了ai_agent项目中的TerminateTool以及工具调用闭环。TerminateTool是一个流程控制工具,源码中只有一个doTerminate()方法,返回“任务结束”,用于在用户请求已经满足或任务无法继续推进时终止工具调用流程。它会在ToolRegistration中和其他工具一起注册为ToolCallback[],并在LoveApp.doChatWithTools()中通过.toolCallbacks(allTools)暴露给模型使用。与搜索、抓取、下载、文件读写、终端执行和 PDF 生成等业务工具相比,TerminateTool的价值在于为智能体提供一个明确的任务结束动作。

这一期可以用一句话总结:

TerminateTool 虽然代码最简单,但它让 ai_agent 的工具系统从“能执行动作”进一步变成“能完成闭环”的任务型 Agent。

下一期可以继续分析:

AI Agent 项目学习笔记(十二):Controller 接口层与应用入口

下一期重点分析controller目录和AiAgentApplication,理解项目如何把LoveApp的基础对话、结构化报告、RAG 问答和工具调用能力暴露成外部可访问的接口,以及前端或测试请求如何真正触发这些智能体能力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询