传统企业自动化面临一个残酷的现实:你想要的API根本不存在

当业务团队要求"自动从供应商门户抓取实时库存"时,工程师的回应往往是"他们没有开放接口"。于是,项目陷入死局——要么等待供应商开发API(通常以季度计),要么部署脆弱的CSS选择器爬虫(一次改版就全线崩溃)。

2024年,这一困局正在被一种全新的技术范式打破:Vision-Language Model(VLM)驱动的智能浏览器Agent。它不再依赖预设的DOM选择器,而是像人类一样"看懂"网页、理解语义、自主决策。这不是渐进式改良,而是自动化领域的范式转移


一、技术范式的断裂:从Selector到Semantic

传统RPA和Web自动化的核心脆弱性在于Selector依赖。无论是Selenium的XPath、Puppeteer的CSS选择器,还是Playwright的Locator,本质上都是在用硬编码规则匹配DOM树。一旦前端框架升级、A/B测试启动、或动态class名变化,整个自动化链路即刻瘫痪。

数据不会撒谎:根据 UiPath 2023 Annual Report 披露,企业RPA项目的平均维护成本占初始开发的35%-45%,其中超过60%的维护工作源于UI变更导致的选择器失效。

范式转移

🟢 VLM Agent范式(鲁棒)

视觉+语义理解

Accessibility Tree解析

SoM标注

VLM推理决策

结构变化?

自适应重规划

执行成功

🔴 传统RPA范式(脆弱)

硬编码Selector

DOM树匹配

结构变化?

💥 链路断裂

执行成功

人工修复选择器

VLM Agent的核心突破在于:它不再"记住"元素位置,而是"理解"页面意图。即便按钮从页面左侧移到右侧、class从.btn-submit变成.primary-action,只要语义标签仍然是"提交订单",Agent就能准确定位。


二、核心技术栈解构:从DOM到Cognition

构建一个生产级VLM Browser Agent,需要四个核心模块的精密协作:

2.1 Accessibility Tree:被忽视的语义金矿

大多数开发者对Accessibility Tree(a11y tree)的认知仅停留在"无障碍支持"。但在VLM Agent语境下,它是连接视觉像素与DOM语义的桥梁

与完整的DOM树(通常包含数千节点)不同,Accessibility Tree是经过浏览器精简的语义化结构,仅保留对辅助技术有意义的元素——按钮、链接、表单、标题等。这意味着:

  • 信息密度提升10-50倍:典型电商页面DOM节点约3000-5000个,Accessibility Tree仅100-300个
  • 噪声自动过滤<div>嵌套地狱、装饰性元素被自动剔除
  • 角色语义明确:每个节点自带rolenamevalue等ARIA属性
// Playwright 获取 Accessibility Tree 的标准方法
const accessibilitySnapshot = await page.accessibility.snapshot();
// 输出结构示例:
// {
//   role: 'WebArea',
//   name: 'Amazon Shopping Cart',
//   children: [
//     { role: 'button', name: 'Proceed to Checkout', focused: true },
//     { role: 'link', name: 'Continue Shopping' }
//   ]
// }

2.2 Set-of-Mark (SoM):视觉-语言对齐的Rosetta Stone

微软研究院2023年论文 “Set-of-Mark: Visual Prompting for Large Vision-Language Models” 提出了一种革命性的视觉标注方法:在截图的每个交互元素上叠加数字标记,让VLM能够精确引用。

方法 定位精度 多元素区分 成本 鲁棒性
纯文本描述
Bounding Box坐标
Set-of-Mark 极高
纯VLM视觉定位

SoM的工作流:

视觉语言模型 SoM标注器 元素检测器 无头浏览器 视觉语言模型 SoM标注器 元素检测器 无头浏览器 页面截图 + DOM快照 提取可交互元素 元素列表+边界框 叠加数字标记 标注后截图 + 任务Prompt 视觉推理 + 决策生成 Action(id=23, type="click")

2.3 Action Space:从意图到执行

一个设计良好的Action Space需要在表达力可控性之间取得平衡。参考 Browser Use(2024年最活跃的Web Agent开源项目)的设计:

# Browser Use Action Space 定义(简化版)
class BrowserAction(Enum):
    CLICK = "click"           # 参数: element_id: int
    TYPE = "type"             # 参数: element_id: int, text: str
    SCROLL = "scroll"         # 参数: direction: "up" | "down"
    NAVIGATE = "navigate"     # 参数: url: str
    EXTRACT = "extract"       # 参数: element_id: int
    WAIT = "wait"             # 参数: seconds: int
    COMPLETE = "complete"     # 任务完成信号

关键设计原则:

  1. 原子化:每个Action只做一件事,便于VLM精确控制
  2. 可组合:复杂操作通过Action序列实现
  3. 可观测:每个Action执行后返回页面状态变化

2.4 状态管理与记忆:LangGraph的状态机范式

生产级Agent不能是无状态的——它需要记住"我在哪"、“我做过什么”、“下一步该做什么”。LangGraph 提供了基于状态图的优雅解决方案:

接收任务

初始化浏览器

执行Action

任务完成

执行失败

错误分析

重新规划

返回结果

获取页面快照

VLM决策

Init

Observe

Reason

ParseContext

GenerateThought

SelectAction

Act

Complete

Error

Recover


三、实战架构:构建自动驾驶级RPA引擎

以下是一个生产可用的VLM Browser Agent核心架构:

💾 记忆层

⚡ 执行层

👁️ 感知层

🧠 认知决策层

🎯 任务输入层

VLM选型

GPT-4o Vision

Claude 3.5 Sonnet

Gemini 1.5 Pro

Qwen2-VL / LLaVA

自然语言任务描述

约束条件: 超时/重试/白名单

Prompt Engineering

VLM推理引擎

Action Planner

Playwright无头浏览器

Accessibility Tree提取

截图+SoM标注

DOM Diff检测

Action Validator

Human-in-the-Loop网关

浏览器操作执行

执行日志与回放

短期: 当前会话状态

中期: 任务轨迹缓存

长期: 向量知识库

3.1 核心代码骨架

以下是融合SoM与Accessibility Tree的Agent核心逻辑:

"""
VLM Browser Agent - 核心引擎
参考项目: browser-use, webagent, omne-agentic-workflow
"""

import asyncio
from dataclasses import dataclass
from typing import Optional, List, Dict, Any
from enum import Enum
from playwright.async_api import async_playwright, Page, Browser
from openai import AsyncOpenAI
import json


class ActionType(Enum):
    CLICK = "click"
    TYPE = "type"
    SCROLL = "scroll"
    NAVIGATE = "navigate"
    EXTRACT = "extract"
    WAIT = "wait"
    COMPLETE = "complete"


@dataclass
class BrowserAction:
    type: ActionType
    element_id: Optional[int] = None
    text: Optional[str] = None
    url: Optional[str] = None
    direction: Optional[str] = None
    seconds: Optional[int] = None


@dataclass
class ElementInfo:
    id: int
    role: str
    name: str
    bounding_box: Dict[str, float]
    is_interactive: bool


class SoMAnnotator:
    """Set-of-Mark 标注器"""
    
    def __init__(self):
        self.counter = 0
        self.elements: Dict[int, ElementInfo] = {}
    
    def build_element_map(self, accessibility_tree: dict) -> Dict[int, ElementInfo]:
        """从Accessibility Tree构建元素映射"""
        self.elements.clear()
        self.counter = 0
        self._traverse_tree(accessibility_tree)
        return self.elements
    
    def _traverse_tree(self, node: dict, depth: int = 0):
        """递归遍历Accessibility Tree"""
        interactive_roles = {
            'button', 'link', 'textbox', 'checkbox', 'radio',
            'combobox', 'menuitem', 'tab', 'searchbox'
        }
        
        role = node.get('role', '')
        name = node.get('name', '')
        
        if role in interactive_roles and name:
            self.counter += 1
            self.elements[self.counter] = ElementInfo(
                id=self.counter,
                role=role,
                name=name,
                bounding_box=node.get('boundingBox', {}),
                is_interactive=True
            )
        
        for child in node.get('children', []):
            self._traverse_tree(child, depth + 1)
    
    def generate_som_screenshot(self, page: Page, output_path: str) -> str:
        """生成带SoM标注的截图"""
        # 注入Canvas覆盖层绘制数字标记
        js_code = f"""
        (elements) => {{
            const canvas = document.createElement('canvas');
            canvas.style.position = 'fixed';
            canvas.style.top = '0';
            canvas.style.left = '0';
            canvas.style.width = '100%';
            canvas.style.height = '100%';
            canvas.style.zIndex = '999999';
            canvas.style.pointerEvents = 'none';
            document.body.appendChild(canvas);
            
            const ctx = canvas.getContext('2d');
            ctx.font = 'bold 14px Arial';
            
            Object.entries(elements).forEach(([id, el]) => {{
                const bbox = el.bounding_box;
                if (bbox) {{
                    ctx.fillStyle = 'rgba(255, 165, 0, 0.8)';
                    ctx.beginPath();
                    ctx.arc(bbox.x + 12, bbox.y + 12, 12, 0, 2 * Math.PI);
                    ctx.fill();
                    ctx.fillStyle = 'black';
                    ctx.fillText(id, bbox.x + 6, bbox.y + 17);
                }}
            }});
            
            return canvas.toDataURL();
        }}
        """
        # 执行标注(实际实现需要更复杂的坐标映射)
        return output_path


class VLMAgent:
    """VLM驱动的浏览器Agent"""
    
    SYSTEM_PROMPT = """你是一个专业的Web自动化Agent。你的任务是根据页面截图和元素信息,选择最合适的操作来完成任务。

可用操作:
- click(element_id): 点击指定元素
- type(element_id, text): 在指定元素中输入文本
- scroll(direction): 滚动页面
- navigate(url): 导航到指定URL
- extract(element_id): 提取元素内容
- wait(seconds): 等待指定秒数
- complete(): 任务完成

响应格式(JSON):
{"action": "click", "element_id": 23, "reasoning": "点击提交按钮完成订单"}
"""

    def __init__(
        self,
        model: str = "gpt-4o",
        api_key: Optional[str] = None,
        base_url: Optional[str] = None
    ):
        self.model = model
        self.client = AsyncOpenAI(api_key=api_key, base_url=base_url)
        self.annotator = SoMAnnotator()
        self.max_steps = 30
        self.conversation_history: List[Dict] = []
    
    async def run(self, task: str, start_url: str) -> Dict[str, Any]:
        """执行自动化任务"""
        async with async_playwright() as p:
            browser = await p.chromium.launch(headless=False)
            page = await browser.new_page()
            
            # 导航到起始页
            await page.goto(start_url)
            await page.wait_for_load_state('networkidle')
            
            result = {"success": False, "steps": 0, "data": None}
            
            for step in range(self.max_steps):
                # 1. 感知:获取页面状态
                accessibility_tree = await page.accessibility.snapshot()
                element_map = self.annotator.build_element_map(accessibility_tree)
                screenshot_path = f"/tmp/step_{step}.png"
                await page.screenshot(path=screenshot_path)
                
                # 2. 构建 SoM 增强的 Prompt
                element_description = self._format_elements(element_map)
                
                # 3. VLM 推理
                action = await self._decide_action(
                    task=task,
                    element_description=element_description,
                    screenshot_path=screenshot_path
                )
                
                result["steps"] = step + 1
                
                # 4. 执行动作
                if action.type == ActionType.COMPLETE:
                    result["success"] = True
                    break
                
                await self._execute_action(page, action, element_map)
                await asyncio.sleep(1)  # 等待页面响应
            
            await browser.close()
            return result
    
    async def _decide_action(
        self,
        task: str,
        element_description: str,
        screenshot_path: str
    ) -> BrowserAction:
        """调用VLM进行决策"""
        
        with open(screenshot_path, "rb") as f:
            screenshot_data = f.read()
        
        messages = [
            {"role": "system", "content": self.SYSTEM_PROMPT},
            {"role": "user", "content": [
                {"type": "text", "text": f"任务: {task}\n\n可用元素:\n{element_description}"},
                {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{screenshot_data.hex()}"}}
            ]}
        ]
        
        response = await self.client.chat.completions.create(
            model=self.model,
            messages=messages,
            max_tokens=256,
            temperature=0.1
        )
        
        return self._parse_action(response.choices[0].message.content)
    
    def _format_elements(self, elements: Dict[int, ElementInfo]) -> str:
        """格式化元素列表为文本描述"""
        lines = []
        for eid, el in elements.items():
            lines.append(f"[{eid}] {el.role}: \"{el.name}\"")
        return "\n".join(lines)
    
    def _parse_action(self, response: str) -> BrowserAction:
        """解析VLM响应为Action对象"""
        try:
            data = json.loads(response)
            return BrowserAction(
                type=ActionType(data.get("action", "wait")),
                element_id=data.get("element_id"),
                text=data.get("text"),
                url=data.get("url"),
                direction=data.get("direction"),
                seconds=data.get("seconds", 2)
            )
        except:
            return BrowserAction(type=ActionType.WAIT, seconds=2)
    
    async def _execute_action(
        self,
        page: Page,
        action: BrowserAction,
        elements: Dict[int, ElementInfo]
    ):
        """执行浏览器动作"""
        if action.type == ActionType.CLICK and action.element_id:
            el = elements.get(action.element_id)
            if el:
                # 使用语义定位而非选择器
                await page.get_by_role(el.role, name=el.name).click()
        
        elif action.type == ActionType.TYPE and action.element_id:
            el = elements.get(action.element_id)
            if el and action.text:
                await page.get_by_role(el.role, name=el.name).fill(action.text)
        
        elif action.type == ActionType.SCROLL:
            direction = action.direction or "down"
            await page.evaluate(f"window.scrollBy(0, {'500' if direction == 'down' else '-500'})")
        
        elif action.type == ActionType.NAVIGATE and action.url:
            await page.goto(action.url)
        
        elif action.type == ActionType.WAIT:
            await asyncio.sleep(action.seconds or 2)


# 使用示例
async def main():
    agent = VLMAgent(model="gpt-4o")
    result = await agent.run(
        task="在Amazon搜索'wireless earbuds',找到价格最低的商品,加入购物车",
        start_url="https://www.amazon.com"
    )
    print(f"任务完成: {result}")

if __name__ == "__main__":
    asyncio.run(main())

四、开源生态与选型指南

2024年,VLM Browser Agent领域迎来了爆发式增长。以下是目前最值得关注的开源项目横向对比:

项目 Stars 核心特点 VLM支持 生产就绪度 维护活跃度
browser-use 45k+ LangChain集成、多模型支持 GPT-4o/Claude/Gemini/本地 ⭐⭐⭐⭐⭐
Open Interpreter 58k+ 自然语言操作系统 多模型 ⭐⭐⭐⭐
Omne 3k+ 多Agent协作框架 模块化 ⭐⭐⭐
WebVoyager 2k+ 学术基准测试 研究导向 ⭐⭐
Agent-E 1k+ 企业级Web Agent 多模型 ⭐⭐⭐⭐

选型建议

快速原型/研究

企业生产环境

本地化/隐私

高精度/无预算限制

成本敏感

完全本地化

需求分析

使用场景

browser-use

Agent-E / 自研

Qwen2-VL + browser-use

VLM选型

GPT-4o / Claude 3.5

Gemini 1.5 Flash

Qwen2-VL-72B / LLaVA-OneVision


五、成本与性能的工程权衡

VLM调用的Token消耗是生产部署的核心考量。以下是典型场景的成本分析:

5.1 Token消耗估算

场景 页面复杂度 单步Token 平均步数 单任务成本(GPT-4o)
简单表单填写 ~1,500 3-5 $0.03-0.05
电商搜索+下单 ~2,500 8-12 $0.15-0.25
复杂多页流程 ~4,000 15-25 $0.50-0.80

5.2 优化策略

  1. 视觉压缩:将截图缩放至512x512或768x768,减少视觉Token
  2. 元素预过滤:仅传递可交互元素给VLM,过滤装饰性内容
  3. 缓存策略:对静态页面元素缓存SoM标注
  4. 模型分层:简单动作用小模型,复杂决策用大模型
# 成本优化:分层模型策略
class HierarchicalAgent:
    def __init__(self):
        self.fast_model = "gpt-4o-mini"  # 简单决策
        self.smart_model = "gpt-4o"      # 复杂推理
    
    async def decide(self, context: dict) -> BrowserAction:
        complexity_score = self._assess_complexity(context)
        
        if complexity_score < 0.3:
            # 简单场景:快速模型
            return await self._call_model(self.fast_model, context)
        else:
            # 复杂场景:智能模型
            return await self._call_model(self.smart_model, context)

六、安全与伦理边界

自动驾驶级RPA的威力越大,责任越重。必须在架构层面内嵌安全约束:

📋 审计追踪

⚖️ 伦理约束

🛡️ 安全防护层

URL白名单验证

敏感操作确认

速率限制

沙箱隔离

服务条款合规

反爬虫策略尊重

数据最小化原则

全量操作日志

截图存证

决策链回放

Agent

核心安全规则

# agent_security_config.yaml
safety_rules:
  url_whitelist:
    - "https://internal.company.com/*"
    - "https://portal.supplier-a.com/*"
  
  action_blacklist:
    - type: "click"
      target_pattern: "*Delete*"
      require_confirmation: true
    - type: "type"
      target_pattern: "*password*"
      log_content: false
  
  rate_limits:
    max_actions_per_minute: 30
    max_actions_per_session: 500
  
  sandbox:
    disable_file_access: true
    disable_network_override: true
    credential_isolation: true

七、未来展望:从RPA到Agentic Workflow

VLM Browser Agent不是终点,而是Agentic Workflow时代的序章。我们正在见证三个趋势的交汇:

  1. 多模态统一:GPT-4o、Gemini 1.5等模型已实现文本、图像、视频、音频的原生理解
  2. 工具使用能力:模型不再只是"聊天",而是主动调用API、操作软件、编排工作流
  3. 长期记忆:向量数据库与RAG让Agent能够积累经验、持续学习

预测:到2025年底,70%的企业Web自动化场景将通过VLM Agent实现,传统Selector-based RPA将退守至极少数高吞吐、低延迟的专用场景。


核心资源与引用

开源项目:

  • Browser Use: https://github.com/browser-use/browser-use
  • Open Interpreter: https://github.com/OpenInterpreter/open-interpreter
  • Playwright: https://github.com/microsoft/playwright
  • LangGraph: https://github.com/langchain-ai/langgraph

关键论文:

  • Set-of-Mark Visual Prompting (Microsoft Research, 2023): https://arxiv.org/abs/2310.11441
  • WebVoyager: Building an End-to-End Web Agent with Large Multimodal Models: https://arxiv.org/abs/2401.13919
  • SeeAct: GPT-4V as Web Navigation Agent: https://arxiv.org/abs/2401.01614

技术规范:

  • WAI-ARIA Accessibility Tree: https://www.w3.org/TR/wai-aria/
  • Playwright Accessibility API: https://playwright.dev/python/docs/api/class-accessibility

API未死,但它正在变成"编译目标"而非"编程接口"。 未来的开发者不再手写HTTP请求,而是用自然语言描述意图,让VLM Agent自动生成执行路径。这不是科幻,而是正在发生的现实。

如果你正在评估VLM Agent在业务中的落地,建议从低风险、高价值的内部流程开始——供应商门户数据同步、竞品价格监控、报表自动下载——逐步积累经验后再扩展到生产环境。

技术的浪潮已经涌来,你准备好冲浪了吗?

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐