agent 框架的浏览器操作模块:会话管理、工具体系、内容提取、Cookie 管理。
浏览器操作的核心实现位于 agent/tools/builtin/browser/,采用适配器模式将第三方库 browser-use 的能力封装为 agent 框架的标准工具。
agent/tools/builtin/browser/
├── __init__.py # 统一导出所有浏览器工具
├── baseClass.py # 核心实现(~2200行):会话管理 + 27个工具函数
└── sync_mysql_help.py # 同步 MySQL 辅助类(Cookie 查询)
agent/skill/skills/
└── browser.md # 浏览器工具使用指南(Skill 注入到 LLM system prompt)
关键依赖关系:
agent 框架 (@tool 装饰器, ToolResult)
↓ 适配
browser-use (BrowserSession, Tools, ActionResult)
↓ 底层
Chrome DevTools Protocol (CDP)
不直接依赖 Playwright,完全基于 CDP 协议与浏览器通信。
使用模块级全局变量维护唯一的浏览器会话,避免重复创建/销毁浏览器实例:
# baseClass.py:98-101
_browser_session: Optional[BrowserSession] = None
_browser_tools: Optional[Tools] = None
_file_system: Optional[FileSystem] = None
_last_browser_type: str = "local"
_last_headless: bool = True
_live_url: Optional[str] = None
通过 init_browser_session(browser_type=...) 初始化,支持三种运行模式:
| 模式 | browser_type | 底层实现 | 适用场景 |
|---|---|---|---|
| 本地浏览器 | "local" |
启动本地 Chrome,通过 user_data_dir 持久化 profile |
开发调试,速度最快 |
| 云浏览器 | "cloud" |
连接 browser-use 云服务,通过 cdp_url 远程控制 |
生产环境,不占本地资源 |
| 容器浏览器 | "container" |
调用远程 API 创建 Docker 容器,内含 Chrome,通过 CDP 连接 | 隔离性好,支持预配置账户 |
初始化流程(baseClass.py:230-322):
init_browser_session()
├─ local: 检测 macOS Chrome 路径 → 创建 user_data_dir → BrowserSession(is_local=True)
├─ cloud: BrowserSession(use_cloud=True) → 解析 cdp_url 生成 live_url
└─ container: create_container() API → 等待浏览器启动 → BrowserSession(cdp_url=...)
容器模式通过 HTTP API 与远程容器管理服务交互(baseClass.py:105-228):
步骤 1.1: POST /api/v1/container/create
→ 返回 container_id, vnc, cdp 地址
→ 等待 5 秒让容器内浏览器启动
步骤 1.2: POST /api/v1/browser/page/create
→ 传入 container_id, url, account_name
→ 返回 connection_id
→ 内置重试机制(最多 3 次)
get_browser_session()(baseClass.py:330-372)在每次工具调用前检查 CDP 连接是否存活:
# 通过 CDP 执行 Runtime.evaluate('1+1') 探测连接
cdp_session = await _browser_session.get_or_create_cdp_session()
await asyncio.wait_for(
cdp_session.cdp_client.send.Runtime.evaluate(
params={'expression': '1+1'},
session_id=cdp_session.session_id
),
timeout=3.0,
)
如果连接断开(WebSocket 超时等),自动 cleanup 并重新初始化。
init_browser_session() → 创建会话(幂等,已存在则直接返回)
get_browser_session() → 获取会话(自动健康检查 + 重连)
cleanup_browser_session() → 优雅停止(session.stop())
kill_browser_session() → 强制终止(session.kill())
所有工具通过 @tool() 装饰器注册到 agent 框架的 ToolRegistry,LLM 可直接调用。共 27 个工具,分为 8 类。
| 工具 | 功能 | 底层调用 |
|---|---|---|
browser_navigate_to_url(url, new_tab) |
导航到 URL | tools.navigate() |
browser_search_web(query, engine) |
搜索引擎搜索(支持 bing/google/duckduckgo) | tools.search() |
browser_go_back() |
浏览器后退 | tools.go_back() |
browser_wait(seconds) |
等待指定秒数 | tools.wait() |
browser_get_live_url() |
获取云浏览器实时画面链接 | 读取全局 _live_url |
| 工具 | 功能 | 特殊处理 |
|---|---|---|
browser_click_element(index) |
点击元素 | 挂载日志监听器自动捕获下载链接 |
browser_input_text(index, text, clear) |
输入文本 | 支持清除已有内容 |
browser_send_keys(keys) |
发送键盘按键/快捷键 | 支持组合键如 Control+A |
browser_upload_file(index, path) |
上传文件 | 需要绝对路径 |
browser_click_element 的下载链接捕获机制(baseClass.py:711-846):
# 1. 挂载自定义 logging.Handler 到 browser_use 命名空间
capture_handler = DownloadLinkCaptureHandler()
logger = logging.getLogger("browser_use")
logger.addHandler(capture_handler)
# 2. 执行点击
result = await tools.click(index=index, browser_session=browser)
# 3. 检查是否捕获到下载链接(通过正则匹配日志中的 URL)
if capture_handler.captured_url:
# 将链接注入到 ToolResult.output,LLM 可以看到并决定下一步
| 工具 | 功能 | 特殊处理 |
|---|---|---|
browser_scroll_page(down, pages, index) |
滚动页面 | 通过 CDP 检测 scrollY 变化,判断是否到达边界;限制单次最大 10 页 |
browser_find_text(text) |
查找文本并滚动到位 | tools.find_text() |
browser_screenshot() |
截图 | tools.screenshot() |
browser_get_visual_selector_map() |
获取带元素索引标注的截图 + 交互元素列表 | 使用 create_highlighted_screenshot_async 生成标注图 |
browser_get_selector_map() |
获取交互元素索引映射(纯文本) | 通过 BrowserStateRequestEvent 触发 DOM 更新 |
browser_get_visual_selector_map(baseClass.py:1066-1161)是最核心的观察工具:
1. 触发 BrowserStateRequestEvent(include_dom=True, include_screenshot=True)
2. 等待浏览器返回完整状态(DOM 树 + 截图)
3. 从 DOM 状态提取 selector_map(所有可交互元素的索引)
4. 调用 create_highlighted_screenshot_async 在截图上标注元素索引号
5. 构建元素列表(tag, aria-label, href, type, role 等属性)
6. 返回 ToolResult(images 字段含标注截图,output 含元素列表)
| 工具 | 功能 |
|---|---|
browser_switch_tab(tab_id) |
切换标签页(4 字符 ID) |
browser_close_tab(tab_id) |
关闭标签页 |
| 工具 | 功能 |
|---|---|
browser_get_dropdown_options(index) |
获取下拉框选项 |
browser_select_dropdown_option(index, text) |
选择下拉框选项 |
| 工具 | 功能 | 底层 |
|---|---|---|
browser_extract_content(query, extract_links) |
LLM 驱动的结构化数据提取 | tools.extract() + Qwen LLM |
browser_read_long_content(goal, source) |
智能长内容读取(自动检测 PDF) | tools.read_long_content() |
browser_get_page_html() |
获取完整 HTML | CDP Runtime.evaluate |
browser_download_direct_url(url, save_name) |
HTTP 直链下载 | httpx.AsyncClient 流式下载 |
extraction_adapter(baseClass.py:1387-1409)将 browser-use 的 LangChain Runnable 接口适配为 Qwen LLM 调用:
async def extraction_adapter(input_data):
response = await qwen_llm_call(messages=[{"role": "user", "content": prompt}])
content = response["content"]
# 自动清洗搜索引擎重定向 URL(Bing Base64 解码、Google url 参数提取)
urls = re.findall(r'https?://[^\s<>"\']+', content)
for original_url in urls:
clean_url = scrub_search_redirect_url(original_url)
if clean_url != original_url:
content = content.replace(original_url, clean_url)
return Namespace(completion=content)
_detect_and_download_pdf_via_cdp(baseClass.py:1458-1550):
1. 检查 URL 是否以 .pdf 结尾
2. 如果不明显,通过 CDP 检查 document.contentType
3. 确认是 PDF 后,通过浏览器内 fetch API 下载(自动携带 cookies/session)
4. 将 data URL 中的 base64 解码为 PDF 文件保存到本地
5. 将 source 参数改为本地文件路径,交给 pypdf 解析
| 工具 | 功能 |
|---|---|
browser_export_cookies(name, account) |
导出当前域名 Cookie 到 .cache/.cookies/ |
browser_load_cookies(url, name) |
自动匹配 Cookie 文件并注入浏览器 |
browser_ensure_login_with_cookies(cookie_type, url) |
检查登录状态,需要时从 MySQL 查询 Cookie 注入 |
browser_load_cookies(baseClass.py:2046-2177):
1. 精确匹配:{domain}.json(如 xiaohongshu.com.json)
2. 前缀匹配:{domain}*.json
3. 模糊匹配:{主域名}*.json(如 xiaohongshu*.json)
4. 未找到时:根据 auto_navigate 参数决定是否直接导航到目标页面
browser_ensure_login_with_cookies 流程:
1. 导航到目标 URL
2. 执行 JS 检测登录状态(查找登录按钮/用户头像)
3. 如果需要登录:
a. 从 agent_channel_cookies 表查询 Cookie
b. 解析 Cookie(支持 JSON 数组、JSON 对象、分号分隔字符串)
c. 通过 CDP _cdp_set_cookies 注入
d. 刷新页面
| 工具 | 功能 |
|---|---|
browser_evaluate(code) |
在页面执行任意 JavaScript |
browser_wait_for_user_action(message, timeout) |
暂停等待用户手动操作(如验证码) |
browser_done(text, success) |
标记任务完成 |
所有工具的返回值统一为 agent 框架的 ToolResult,通过 action_result_to_tool_result() 将 browser-use 的 ActionResult 转换:
# baseClass.py:407-431
def action_result_to_tool_result(result: ActionResult, title: str = None) -> ToolResult:
if result.error:
return ToolResult(title=..., output="", error=result.error,
long_term_memory=result.long_term_memory or result.error)
return ToolResult(title=..., output=result.extracted_content or "",
long_term_memory=..., metadata=result.metadata or {})
ToolResult 支持双层记忆管理(agent/tools/models.py):
output:完整内容,可配置 include_output_only_once=True 只给 LLM 看一次long_term_memory:简短摘要,永久保留在对话历史中images:截图等图片数据(base64)scrub_search_redirect_url()(baseClass.py:1347-1385)自动解析搜索引擎重定向链接:
| 引擎 | 处理方式 |
|---|---|
| Bing | 提取 u 参数,去掉 a1 前缀,Base64 解码 |
提取 url 参数,URL 解码 |
|
| 通用 | 检查 target/dest/destination/link 参数 |
所有浏览器产生的文件统一存储在工作目录下:
.cache/
├── .browser_use_files/ # 浏览器下载、截图、PDF 等临时文件
└── .cookies/ # Cookie 持久化文件({domain}_{account}.json)
agent/skill/skills/browser.md 作为 Skill 注入到 LLM 的 system prompt,指导 LLM 正确使用浏览器工具。核心规则:
browser_get_visual_selector_map 获取元素索引browser_waitbrowser_load_cookies,首次登录需请求人类协助extract_content/read_long_content)而非手动解析 DOM