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)

方法内部会构造请求参数,包括用户查询 qapi_keyengine = 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 的区别

WebSearchToolWebScrapingTool 都是联网工具,但作用不同。

复制代码
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. 当前实现可以如何优化成更完整的联网 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. 我的理解

我认为 WebSearchToolWebScrapingToolResourceDownloadToolai_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 项目中的三个联网工具:WebSearchToolWebScrapingToolResourceDownloadToolWebSearchTool 通过 SearchAPI 调用百度搜索,接收搜索关键词并返回前 5 条自然搜索结果;WebScrapingTool 使用 Jsoup 根据 URL 抓取网页,并返回网页 HTML;ResourceDownloadTool 使用 Hutool 的 HttpUtil.downloadFile() 下载指定 URL 的资源,并保存到项目 tmp/download 目录下。三者配合后,可以形成"搜索候选网页---抓取网页内容---下载相关资源"的完整联网信息获取链路。

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

复制代码
WebSearchTool 负责找信息,WebScrapingTool 负责读网页,ResourceDownloadTool 负责存资源,它们共同构成了 ai_agent 的基础联网能力。

下一期可以继续分析:

AI Agent 项目学习笔记(十):文件操作、终端执行与 PDF 生成工具

下一期重点分析 FileOperationToolTerminalOperationToolPDFGenerationTool,理解智能体如何从"获取信息"进一步走向"处理本地文件、执行系统命令和生成交付物"。

相关推荐
wj3055853781 小时前
课程 1:WSL + uv + ComfyUI 环境选择说明
python·wsl·cuda·uv·comfyui
wj3055853781 小时前
课程 2:使用 uv 安装 ComfyUI
python·uv·comfyui
kels88991 小时前
实时外汇api的节假日交易时间表,能自动判断休市吗?
开发语言·经验分享·笔记·python·金融·区块链
辰海Coding1 小时前
MiniSpring框架学习-增加事件发布的简化 IoC 容器
java·学习·spring·java-ee
松☆1 小时前
torchair:昇腾PyTorch适配层生态协作深度解读
人工智能·pytorch·python
dhjabc_11 小时前
从零开发一个功能强大的 Markdown 预览器
python·开源软件
前端若水1 小时前
使用 IndexedDB 在客户端存储对话记录
java·前端·人工智能·python·机器学习
阳光九叶草LXGZXJ1 小时前
达梦数据库-学习-57-读写数据页超时告警排查(page[x,x,xxxxxx] disk write uses)-DSC集群版
linux·运维·服务器·数据库·sql·学习
xian_wwq1 小时前
【学习笔记】探讨大模型应用安全建设系列4——Agent 权限治理与工具调用安全
笔记·学习·安全