【手搓 Agent 第2.2关】搭建 Agent 进阶能力:网络搜索工具(上)

上一章我们完整完结了 Stage 2 RAG 全模块,让 Agent 拥有了私有知识库查阅能力,可精准应答静态、本地化知识问题。但真实场景中存在大量时效性、公开性的外部信息,仅靠本地知识库完全无法满足需求,因此我们正式开启 Stage 2 第二大核心模块------自定义工具系统,本篇优先落地最核心的网络搜索工具。

我们摒弃成熟框架,从零手写原生百度爬虫,完成依赖配置、工具注册、爬虫函数编写、Agent 权限适配全流程,为基础 Agent 赋予联网能力。同时通过真实反爬报错场景,暴露原生智能体的致命缺陷:无法识别工具执行错误、只会机械重复调用工具,为后续 Agent 自主反思能力铺垫基础。

一、手写原生爬虫搜索工具

为了更好理解 Agent 的自我反思能力,我们在之前编写的代码的基础上先用"手写基础爬虫"作为网络搜索工具。这样可以帮助我们吃透工具全链路,深刻区分"普通脚本"和"Agent"。最后,完成爬虫全流程学习、跑通 LLM 自我修正闭环后,再替换专业的搜索 API,走向真实的生产环境。

前置准备:安装依赖包

bash 复制代码
pip install requests beautifulsoup4

1. 顶部导入区新增爬虫依赖

定位:文件最开头 import 合集处

追加依赖导入:

python 复制代码
import requests 
from bs4 import BeautifulSoup
  • requests(网络请求工具):联网获取网页原始数据,只负责通信,不解析 HTML
  • BeautifulSoup(HTML 解析工具):本地解析已有 HTML 文本,不联网、不发请求

问题:只导入 requests,能否提取网页正文?

结论: 理论上可以,但很难且无法精准高效提取。

理由: 可行是因为 requests 的 .text 属性会返回完整 HTML 字符串,仅用 Python 原生字符串方法(find、切片、split)或正则 re,可以暴力截取两段标签中间的正文文本。但缺点是 HTML 结构复杂,混杂 <script> ,很难编写截取规则;且页面只要微调代码就会失效,容错率低;无法区分广告、导航栏、正文,清晰成本高。

补充: 如果网页是 JS 动态渲染内容,哪怕同时导入两个库也拿不到数据,需要 Selenium/Playwright;仅静态 HTML 才适用 requests + BeautifulSoup 组合。

2. 全局 tools 数组新增工具

定位:代码中 tools = [ ... ] 列表,原有只有 query_knowledge_base,在列表末尾追加 web_search 工具。

示例代码:

python 复制代码
    {
        "type": "function",
        "function": {
            "name": "web_search",
            "description": "实时联网检索公开网络内容,仅用于知识库无的时效性/外部信息,静态常识无需调用",
            "parameters": {
                "type": "object",
                "required": ["query"],
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "需要联网检索的搜索关键词"
                    }
                }
            }
        }
    },

3. 新增裸爬虫函数

定位:def query_knowledge_base(query: str) -> str: 函数完整写完之后,新起一段写下面函数。

示例代码:

python 复制代码
def web_search(query: str) -> str:
    print(f"\n[执行工具] 正在联网搜索关键词:{query}")
    url = f"https://www.baidu.com/s?wd={query}"
    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"}
    try:
        resp = requests.get(url, timeout=10, headers=headers)
        resp.encoding = resp.apparent_encoding
        resp.raise_for_status()
        soup = BeautifulSoup(resp.text, "html.parser")
        raw_text = soup.get_text(strip=True, separator="\n")
        if "百度安全验证" in raw_text or "网络不给力" in raw_text:
            raise requests.exceptions.HTTPError("403 爬虫拦截,需要人机验证")
        short_text = raw_text[:2000]
        return f"【网络搜索结果】\n{short_text}"
    except requests.exceptions.RequestException as e:
        err_msg = f"搜索失败:{str(e)},无法获取网络内容"
        print(f"[爬虫内部捕获异常] {err_msg}")
        return err_msg
  • User-Agent(UA) :UA 是请求头里的一段身份文本,用来告诉服务器当前访问者是什么软件
    • 正常 Chrome 浏览器 UA 示例:Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0
    • 原生 requests 不自定义 UA 时,默认值:python-requests/2.31.0
  • 如果不加 UA 请求头,会被识别为爬虫代码,百度为避免服务器崩溃拒绝访问。

4. 工具映射字典追加关联

定位:generate_response 函数内部,找到 available_tools = { "query_knowledge_base": query_knowledge_base },扩展字典,新增一行映射。

示例代码:

python 复制代码
available_tools = {
    "query_knowledge_base": query_knowledge_base,
    "web_search": web_search
}

5. 修改系统提示词

定位:代码开头的 system_prompt 处。

示例代码:

python 复制代码
system_prompt = {
    "role": "system",
    "content": """你是严谨资料研究助手,严格遵循固定执行流程:
1. 第一步必须调用query_knowledge_base查询本地知识库;
2. 若知识库返回【知识库中未找到相关内容】,必须立刻调用web_search联网检索,禁止直接回答;
3. 时效性政策、最新行业数据、2026年新出台文件,本地库一定不存在,必须联网搜索;
4. 仅本地知识库存在有效资料,且无实时信息需求时,才可仅依靠知识库作答。"""
}

6. 预期结果

此时,模型反复调用工具,但持续搜索失败:

text 复制代码
[爬虫内部捕获异常] 搜索失败:403 爬虫拦截,需要人机验证,无法获取网络内容
工具返回结果:搜索失败:403 爬虫拦截,需要人机验证,无法获取网络内容

模型读到这段报错文本,但无法识别这是工具执行故障 ,只会认为 "本次搜索文本无有效信息"。因此,模型重复更换关键词,连续调用 3 次web_search,每次都拿到相同报错文字。

我们希望模型可以理解失败原因 , 从而产生自主修正策略

二、本篇总结 & 下期预告

本篇我们成功为 Agent 搭建了基础网络搜索工具,打通了智能体联网检索的基础链路,实现了本地知识库+网络搜索的双工具适配。但目前的 Agent 不具备故障识别能力,遇到爬虫拦截、网络报错等问题,会无脑重复调用工具,造成资源浪费、程序卡死。

下一篇 Stage 2-工具中篇,我们将引入 Agent 核心进阶思想:Error as Observation,重构全局异常处理逻辑,让模型读懂工具报错、自主判断故障原因,实现工具调用纠错、动态熔断的自主决策能力。