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指向项目根目录下的tmp,ResourceDownloadTool在此基础上使用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. 当前实现可以如何优化成更完整的联网 Agent?
21.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 抓取网页,并返回网页 HTML;ResourceDownloadTool使用 Hutool 的HttpUtil.downloadFile()下载指定 URL 的资源,并保存到项目tmp/download目录下。三者配合后,可以形成“搜索候选网页—抓取网页内容—下载相关资源”的完整联网信息获取链路。
这一期可以用一句话总结:
WebSearchTool 负责找信息,WebScrapingTool 负责读网页,ResourceDownloadTool 负责存资源,它们共同构成了 ai_agent 的基础联网能力。下一期可以继续分析:
AI Agent 项目学习笔记(十):文件操作、终端执行与 PDF 生成工具
下一期重点分析FileOperationTool、TerminalOperationTool和PDFGenerationTool,理解智能体如何从“获取信息”进一步走向“处理本地文件、执行系统命令和生成交付物”。
