AI Agent 项目学习笔记(九):网页搜索、网页抓取与资源下载工具
1. 本期目标上一篇文章分析了ai_agent项目的 Tool Calling 总体机制。我们已经知道项目通过Tool ↓ ToolRegistration ↓ ToolCallback[] ↓ LoveApp.doChatWithTools()把 Java 方法注册为大模型可调用的工具从而让智能体不只会回答问题还可以调用外部能力完成任务。这一期继续深入工具模块中的联网能力重点分析三个工具WebSearchTool WebScrapingTool ResourceDownloadTool这三个工具共同解决的是智能体如何获取外部网络信息其中WebSearchTool用于搜索信息WebScrapingTool用于抓取网页内容ResourceDownloadTool用于下载网络资源。项目的ToolRegistration会创建这三个工具并和文件操作、终端执行、PDF 生成、任务终止等工具一起转换为ToolCallback[]供LoveApp使用。(GitHub)本期主要解决几个问题1. 为什么 Agent 需要联网工具 2. WebSearchTool 如何通过搜索引擎获取信息 3. WebScrapingTool 如何抓取网页内容 4. ResourceDownloadTool 如何下载网络资源 5. 搜索、抓取、下载三类工具之间是什么关系 6. 这三个工具如何组成一个完整的信息获取链路 7. 当前实现有什么优点 8. 当前实现有哪些安全风险和优化方向2. 为什么要把这三个工具放在一起讲这三个工具都属于“外部信息获取能力”但作用不一样。可以简单理解为WebSearchTool 帮智能体找到信息在哪里。 WebScrapingTool 帮智能体读取网页里有什么。 ResourceDownloadTool 帮智能体把某个资源保存到本地。如果只搜索不抓取智能体只能得到搜索结果摘要无法深入分析网页正文。如果只抓取不搜索智能体需要用户直接提供 URL缺少主动发现信息的能力。如果只下载不搜索和抓取智能体虽然能保存资源但不知道应该下载什么。所以这三个工具组合起来才能形成一条较完整的联网信息处理链搜索关键词 ↓ 得到候选网页 ↓ 抓取网页内容 ↓ 分析网页信息 ↓ 必要时下载资源对于智能体来说这条链路非常重要。因为它让模型从“只能根据已有知识回答”扩展为可以查找外部资料 可以阅读网页内容 可以保存网络资源。3. ToolRegistration 中如何注册联网工具在ToolRegistration中项目创建了多个工具对象其中就包括WebSearchTool webSearchTool new WebSearchTool(searchApiKey); WebScrapingTool webScrapingTool new WebScrapingTool(); ResourceDownloadTool resourceDownloadTool new ResourceDownloadTool();随后项目通过ToolCallbacks.from(...)把这些工具和其他工具一起转换为ToolCallback[]。WebSearchTool的 API Key 来自配置项search-api.api-key这说明搜索工具需要外部搜索服务支持。(GitHub)可以理解为工具类本身 定义工具能做什么。 ToolRegistration 决定哪些工具会暴露给模型。 ToolCallback[] 最终交给 ChatClient 的工具列表。所以联网工具并不是自动可用的它们必须经过注册才能被模型在工具调用过程中选择。4. LoveApp 如何使用这些工具在LoveApp中项目通过Resource private ToolCallback[] allTools;注入所有工具然后在doChatWithTools()中调用.toolCallbacks(allTools)这表示当前这次模型调用可以使用allTools中注册的工具。doChatWithTools()同时传入ChatMemory.CONVERSATION_ID并额外加入MyLoggerAdvisor因此工具调用链路也可以结合会话记忆和日志观察。(GitHub)整体流程可以写成用户提出任务 ↓ LoveApp.doChatWithTools() ↓ 注入 conversationId ↓ 注入 MyLoggerAdvisor ↓ 注入 allTools ↓ 模型判断是否需要联网工具 ↓ 后端执行搜索 / 抓取 / 下载 ↓ 工具结果返回模型 ↓ 模型生成最终回答这就是联网工具接入 Agent 主链路的方式。5. WebSearchTool网页搜索工具WebSearchTool的作用是通过搜索 API 查询外部信息。源码中这个类定义了一个搜索接口地址private static final String SEARCH_API_URL https://www.searchapi.io/api/v1/search;工具方法是Tool(description Search for information from Baidu Search Engine) public String searchWeb( ToolParam(description Search query keyword) String query)方法内部会构造请求参数包括用户查询q、api_key和engine baidu然后使用 Hutool 的HttpUtil.get()发送请求。返回结果被解析成 JSON 后代码提取organic_results取前 5 条结果并把这些结果拼接成字符串返回。(GitHub)可以把它的流程理解为输入 query ↓ 构造搜索请求参数 ↓ 调用 SearchAPI ↓ 指定搜索引擎为 Baidu ↓ 解析 JSON 响应 ↓ 提取 organic_results 前 5 条 ↓ 拼接成字符串返回这个工具让智能体拥有了“主动搜索”的能力。6. WebSearchTool 的输入和输出6.1 输入WebSearchTool的输入只有一个query搜索关键词例如杭州 情侣 约会 地点 推荐 异地恋 沟通 方法 七夕 约会 活动 北京模型会根据用户问题生成合适的搜索关键词。例如用户说“帮我找几个适合周末约会的地方。”模型可能调用搜索工具并生成 query周末 情侣 约会 地点 推荐6.2 输出当前工具返回的是搜索结果 JSON 对象的字符串拼接。也就是说它不是返回一个结构化 Java 对象而是把前 5 条organic_results转成字符串后用逗号连接返回。(GitHub)可以理解为搜索结果 1 的 JSON 字符串, 搜索结果 2 的 JSON 字符串, 搜索结果 3 的 JSON 字符串, 搜索结果 4 的 JSON 字符串, 搜索结果 5 的 JSON 字符串这种实现比较简单模型可以从返回字符串中读取标题、链接、摘要等信息。不过从工程角度看这里后续还可以继续优化。7. WebSearchTool 的适用场景WebSearchTool适合处理需要外部信息的问题。例如用户帮我搜索一些适合情侣周末约会的地点。 用户找一些恋爱沟通技巧的参考资料。 用户查一下最近有什么适合情侣参加的城市活动。 用户帮我找一篇关于亲密关系沟通的文章。在这些场景中大模型自身知识可能不够新也不一定知道具体网页内容。搜索工具可以先提供候选信息来源。可以把它理解为搜索工具负责扩大信息来源 模型负责理解和整理搜索结果。8. WebSearchTool 当前实现的优点当前实现最大的优点是简单直接。它只做三件事接收关键词 调用搜索 API 返回前 5 条自然搜索结果这对于学习 Tool Calling 很友好。因为读者可以清楚看到模型生成 query ↓ 后端发起 HTTP 请求 ↓ 工具返回搜索结果 ↓ 模型继续组织答案而且它没有把搜索逻辑写在LoveApp中而是单独封装成WebSearchTool再通过ToolRegistration注册。这种结构符合工具模块化设计。(GitHub)9. WebSearchTool 可以改进的地方9.1 搜索结果结构化当前搜索结果是把 JSON 对象直接转成字符串拼接。(GitHub)这种方式虽然能用但可读性一般。后续可以整理成更清晰的格式标题xxx 摘要xxx 链接xxx 排名1这样模型更容易理解每条搜索结果也方便日志观察。9.2 处理 organic_results 不足 5 条的情况当前代码中有List objects organicResults.subList(0, 5);如果搜索结果少于 5 条可能会出现越界异常。虽然 catch 会返回错误信息但更好的做法是int limit Math.min(5, organicResults.size()); List objects organicResults.subList(0, limit);这样搜索结果不足 5 条时也能正常返回。9.3 增加搜索失败兜底信息当前工具失败时返回Error searching Baidu: ...这对调试有用但对用户不够友好。可以改成搜索失败原因是xxx。请稍后重试或换一个关键词。这样模型后续组织回答时也更容易给出自然提示。9.4 增加搜索结果来源字段后续可以让工具返回source baidu api searchapi query 用户查询词 time 调用时间这样更适合做工具调用审计。10. WebScrapingTool网页抓取工具WebScrapingTool的作用是抓取指定网页内容。源码中它提供了一个工具方法Tool(description Scrape the content of a web page) public String scrapeWebPage( ToolParam(description URL of the web page to scrape) String url)方法内部使用Document document Jsoup.connect(url).get(); return document.html();也就是说它通过 Jsoup 请求网页然后返回完整 HTML。如果发生异常就返回错误信息。(GitHub)可以把它的流程写成输入 URL ↓ Jsoup.connect(url) ↓ 请求网页 ↓ 获取 Document ↓ 返回 document.html()这个工具让智能体具备了“读取网页内容”的能力。11. WebScrapingTool 和 WebSearchTool 的区别WebSearchTool和WebScrapingTool都是联网工具但作用不同。WebSearchTool 输入关键词输出搜索结果列表。 WebScrapingTool 输入具体 URL输出网页 HTML 内容。例如用户问“帮我找一篇关于异地恋沟通的文章并总结要点。”可能的工具链是第一步调用 WebSearchTool 搜索“异地恋 沟通 方法” 第二步从搜索结果中选择一个 URL 第三步调用 WebScrapingTool 抓取网页内容 第四步模型总结网页要点所以搜索是“找入口”抓取是“读内容”。12. WebScrapingTool 的适用场景WebScrapingTool适合处理用户已经给出 URL或者搜索工具已经找到候选 URL 的情况。例如用户帮我总结这个网页的内容https://xxx 用户打开刚才搜索结果里的第一篇文章提炼约会建议。 用户抓取这个活动页面看一下适不适合情侣参加。在这些场景中模型需要的不只是搜索结果标题而是网页正文内容。抓取工具可以把网页内容拿回来再由模型做总结、提炼和判断。13. 当前 WebScrapingTool 的问题13.1 返回完整 HTML内容太杂当前工具直接返回document.html()这会包含HTML 标签 导航栏 脚本 样式 广告区域 页脚 无关链接这些内容进入模型后可能造成上下文污染。(GitHub)更适合模型处理的是正文文本而不是完整 HTML。后续可以改成return document.text();或者进一步提取title meta description 正文段落 主要链接这样更有利于模型理解网页内容。13.2 缺少超时设置当前使用Jsoup.connect(url).get()如果目标网站响应很慢可能导致工具调用阻塞。(GitHub)后续可以增加Jsoup.connect(url) .timeout(5000) .get();这样可以避免长时间等待。13.3 缺少 User-Agent部分网站会拒绝默认爬虫请求。后续可以增加.userAgent(Mozilla/5.0 ...)这样抓取成功率会更高。13.4 缺少最大内容长度限制网页 HTML 可能非常长。如果完整返回给模型可能导致上下文过长 token 成本增加 模型关注不到重点 甚至超过模型上下文窗口后续可以限制返回长度最多返回前 5000 个字符或者先做正文抽取再返回精简内容。13.5 需要防止 SSRF 风险网页抓取工具接收任意 URL。正式系统中如果不限制 URL可能被用来访问内网地址或敏感服务。例如http://localhost:8080 http://127.0.0.1 http://169.254.169.254 http://内网服务地址所以后续应该增加 URL 安全校验只允许 http / https 禁止 localhost 和内网 IP 禁止 file:// 等本地协议 设置域名白名单或黑名单这类联网工具在正式系统中必须慎重处理。14. ResourceDownloadTool资源下载工具ResourceDownloadTool的作用是从指定 URL 下载资源到本地。源码中它提供了一个工具方法Tool(description Download a resource from a given URL) public String downloadResource( ToolParam(description URL of the resource to download) String url, ToolParam(description Name of the file to save the downloaded resource) String fileName)方法内部先构造下载目录String fileDir FileConstant.FILE_SAVE_DIR /download; String filePath fileDir / fileName;然后创建目录并调用HttpUtil.downloadFile(url, new File(filePath));下载成功后返回文件保存路径。FileConstant.FILE_SAVE_DIR的值是项目根目录下的tmp因此资源下载目录实际是项目根目录/tmp/download。(GitHub)可以把流程写成输入 url fileName ↓ 确定保存目录 tmp/download ↓ 创建下载目录 ↓ 调用 HttpUtil.downloadFile() ↓ 保存到本地文件 ↓ 返回保存路径15. ResourceDownloadTool 和 WebScrapingTool 的区别这两个工具都接收 URL但目的不同。WebScrapingTool 读取网页内容返回给模型理解。 ResourceDownloadTool 下载资源文件保存到本地路径。例如网页文章 适合用 WebScrapingTool 抓取并总结。 图片、PDF、压缩包 适合用 ResourceDownloadTool 下载保存。可以这样理解抓取是“读网页” 下载是“存文件”两者经常配合使用但不能混为一谈。16. ResourceDownloadTool 的适用场景资源下载工具适合处理下载图片 下载 PDF 下载文档 下载网页附件 保存外部资源到本地例如用户帮我下载这个约会攻略 PDF。 用户把这个活动海报保存下来。 用户下载网页里的这个资源文件。下载后后续工具还可以继续处理。例如下载 PDF ↓ 保存到 tmp/download ↓ 后续读取或解析 ↓ 模型总结内容不过当前项目里还没有看到专门的 PDF 解析工具PDFGenerationTool主要用于生成 PDF而不是解析下载的 PDF。17. ResourceDownloadTool 当前实现的优点当前实现非常直接适合学习接收 URL 和文件名 创建 download 目录 下载文件 返回保存路径它还复用了项目统一的临时目录配置。FileConstant.FILE_SAVE_DIR指向项目根目录下的tmpResourceDownloadTool在此基础上使用tmp/download保存下载资源。(GitHub)这比直接把文件下载到项目根目录更合理。因为临时文件统一放在tmp/更方便后续清理和管理。18. ResourceDownloadTool 的安全风险资源下载工具的风险也比较明显。18.1 文件名路径穿越当前代码直接拼接String filePath fileDir / fileName;如果fileName包含../ ..\ / \就可能写到预期目录之外。(GitHub)后续应该限制文件名只能包含字母 数字 下划线 短横线 点号并且使用规范化路径检查确保最终路径仍然位于tmp/download目录下。18.2 文件大小不受限制当前工具直接下载文件没有看到文件大小限制。(GitHub)如果下载的是大文件可能导致磁盘被占满 请求长时间阻塞 服务性能下降后续应该限制最大下载大小例如最大 10 MB 最大 50 MB并在下载前检查响应头中的Content-Length。18.3 文件类型不受限制当前工具不限制下载文件类型。(GitHub)正式系统中可以限制只允许图片 只允许 PDF 只允许 txt / md / docx 禁止 exe / bat / sh / jar 等可执行文件这样可以降低被下载恶意文件的风险。18.4 URL 需要安全校验和网页抓取一样下载工具也需要防止访问内网资源。应该限制只允许 http / https 禁止 localhost 禁止 127.0.0.1 禁止内网 IP 禁止云元数据地址否则下载工具也可能变成 SSRF 攻击入口。19. 三个工具如何组成完整任务链假设用户提出帮我找一份适合情侣周末出游的攻略看看内容是否适合然后把有用的资源下载下来。智能体可能形成这样的工具调用链第一步WebSearchTool 搜索“情侣 周末 出游 攻略” 第二步WebScrapingTool 抓取搜索结果中的攻略网页 第三步模型分析 判断网页内容是否适合用户需求 第四步ResourceDownloadTool 下载网页中有用的 PDF、图片或附件 第五步模型总结 告诉用户资源已保存到哪里以及主要内容是什么这说明三个工具之间是递进关系搜索发现信息 抓取理解信息 下载保存资源这正是 Agent 联网能力的基本闭环。20. 和 RAG 的区别联网工具和 RAG 都是为了增强模型但它们的信息来源不同。RAG 从项目已有知识库中检索信息。 联网工具 从外部互联网获取信息。例如用户问恋爱中如何减少争吵这种问题适合先用 RAG因为本地知识库里可能已经有稳定答案。用户问这个周末北京有哪些情侣活动这种问题更适合联网搜索因为它依赖实时信息。所以可以这样分工稳定领域知识 优先 RAG。 实时外部信息 优先 WebSearchTool。 具体网页分析 使用 WebScrapingTool。 资源保存 使用 ResourceDownloadTool。21. 当前实现可以如何优化成更完整的联网 Agent21.1 搜索结果标准化可以定义一个搜索结果格式title snippet url source rank然后让工具返回标准化文本或 JSON。这样模型更容易判断哪个链接值得继续抓取。21.2 网页正文抽取可以把document.html()改成正文抽取。例如返回标题 正文前 3000 字 关键链接 页面描述这样比完整 HTML 更适合模型处理。21.3 搜索 抓取联动工具目前搜索和抓取是两个工具。后续可以封装一个更高层工具searchAndReadTopPages(query, topN)流程是搜索关键词 ↓ 取前 N 个 URL ↓ 抓取正文 ↓ 返回摘要化结果这样模型不需要多次规划工具调用任务链更稳定。21.4 下载前确认机制对于资源下载建议增加确认逻辑。例如模型先告诉用户我找到了 3 个可下载资源 1. xxx.pdf 2. yyy.jpg 3. zzz.docx 是否下载用户确认后再调用下载工具。这样可以避免模型自动下载大量无关文件。21.5 工具调用审计日志联网工具应该记录调用用户 conversationId 工具名称 URL 或 query 调用时间 返回状态 错误信息 下载文件路径 文件大小这样后续出现问题时可以定位。21.6 网络访问白名单正式系统中可以配置白名单例如只允许访问可信新闻网站 知识库域名 企业内部文档系统 指定资源服务器不建议让模型任意访问所有 URL。22. 当前项目的学习价值这一期的三个工具虽然代码都不复杂但学习价值很大。因为它们展示了 Agent 联网能力的三个基本动作Search Read Download这三个动作对应真实智能体任务中的三个阶段找资料 读资料 保存资料把这三个能力接入ChatClient后智能体就不再局限于模型自身知识和本地 RAG 知识库而是可以根据任务主动获取外部信息。23. 我的理解我认为WebSearchTool、WebScrapingTool和ResourceDownloadTool是ai_agent项目中从“知识型 Agent”走向“行动型 Agent”的关键工具。前面的 RAG 让智能体能够利用本地知识库。而这三个工具让智能体能够接触外部网络世界。可以这样理解RAG 让智能体会查自己的资料库。 WebSearchTool 让智能体会找外部资料。 WebScrapingTool 让智能体会读外部网页。 ResourceDownloadTool 让智能体会保存外部资源。所以这三个工具构成了 Agent 外部信息获取的基础能力。24. 本期重点理解这一期最重要的是理解三个联网工具的分工。可以总结为五点第一WebSearchTool 接收 query通过 SearchAPI 调用百度搜索并返回 organic_results 前 5 条结果。 第二WebScrapingTool 接收 URL通过 Jsoup 抓取网页并返回网页 HTML。 第三ResourceDownloadTool 接收 URL 和 fileName把资源下载到 tmp/download 目录。 第四搜索、抓取、下载三者可以组成“发现信息—读取内容—保存资源”的联网任务链。 第五联网工具必须重视 URL 校验、文件名安全、内容长度限制、下载大小限制和工具调用审计。一句话概括ai_agent 的联网工具模块让模型可以通过搜索发现外部信息通过网页抓取读取具体内容并通过资源下载把有用文件保存到本地。25. 本期小结本期主要分析了ai_agent项目中的三个联网工具WebSearchTool、WebScrapingTool和ResourceDownloadTool。WebSearchTool通过 SearchAPI 调用百度搜索接收搜索关键词并返回前 5 条自然搜索结果WebScrapingTool使用 Jsoup 根据 URL 抓取网页并返回网页 HTMLResourceDownloadTool使用 Hutool 的HttpUtil.downloadFile()下载指定 URL 的资源并保存到项目tmp/download目录下。三者配合后可以形成“搜索候选网页—抓取网页内容—下载相关资源”的完整联网信息获取链路。这一期可以用一句话总结WebSearchTool 负责找信息WebScrapingTool 负责读网页ResourceDownloadTool 负责存资源它们共同构成了 ai_agent 的基础联网能力。下一期可以继续分析AI Agent 项目学习笔记十文件操作、终端执行与 PDF 生成工具下一期重点分析FileOperationTool、TerminalOperationTool和PDFGenerationTool理解智能体如何从“获取信息”进一步走向“处理本地文件、执行系统命令和生成交付物”。