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,理解智能体如何从"获取信息"进一步走向"处理本地文件、执行系统命令和生成交付物"。