AI Agent 项目学习笔记(八):Tool Calling 工具调用机制总览

AI Agent 项目学习笔记(八):Tool Calling 工具调用机制总览 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.javaGitHub 目录中也能看到这些文件说明项目当前已经覆盖了搜索、网页抓取、资源下载、文件读写、终端执行、PDF 生成和任务终止等工具能力。(GitHub)从功能上可以分成四类信息获取类 WebSearchTool WebScrapingTool 文件与资源类 FileOperationTool ResourceDownloadTool PDFGenerationTool 系统执行类 TerminalOperationTool 流程控制类 TerminateTool这说明项目中的 Tool Calling 不是只做一个简单示例而是已经形成了一组可组合工具。5. ToolRegistration集中注册工具项目中负责统一注册工具的是ToolRegistration它被标注为Configuration说明这是一个 Spring 配置类。在这个类中项目定义了一个 BeanBean 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 queryFileOperationTool中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 content13. 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/filereadFile根据文件名读取 UTF-8 文本内容writeFile根据文件名和内容写入文本文件写入前会创建目录。源码中可以看到这两个方法都通过 Hutool 的FileUtil完成文件读写。(GitHub)可以理解为writeFile 把模型生成的内容保存成本地文件。 readFile 把本地文件内容读出来交给模型。例如用户可以说把刚才的约会计划保存成 plan.txt 读取 plan.txt 并帮我优化一下这个工具让 Agent 具备了简单的文件状态管理能力。15. PDFGenerationToolPDF 生成工具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. 当前项目中暂未重点使用 ToolContextSpring 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 如何通过联网工具获取外部信息并讨论搜索结果清洗、网页正文抽取、下载安全和工具调用审计等问题。