# browseruse-bench 分层架构重构设计文档

> **核心目标**: 将现有"SDK 黑盒调用"架构转变为"源码拆解 + 自有 core 重写"架构。每个 Agent Framework 的核心逻辑被拆解到独立目录中，共享统一的 `BaseReActAgent` 协议和 `core/` 基础设施，彻底消除对外部 SDK 运行时的依赖。

---

## 1. 现状与问题

### 1.1 当前架构

```
browseruse-bench (当前)
├── agents/
│   ├── base.py              ← BaseAgent ABC: run_task() → AgentResult
│   ├── browser_use.py       ← 调用 browser-use SDK (Agent, Browser, ChatOpenAI...)
│   ├── skyvern.py           ← 调用 skyvern SDK (Skyvern.local(), PostgreSQL 依赖)
│   └── agent_tars.py        ← 调用 agent-tars CLI (纯 subprocess, 无 Python SDK)
├── browsers/
│   ├── base.py              ← BrowserBackend ABC: open() → BrowserSessionContext
│   └── providers/            ← lexmount, agentbay, local, cloudnative
├── schemas/                  ← AgentResult, EvalResult, EvalSummary
├── cli/                      ← bubench run / eval / leaderboard
└── utils/                    ← config_loader, data_loader, llm_cost, ...
```

每个 agent 实现（`browser_use.py`, `skyvern.py`, `agent_tars.py`）本质上是对外部 SDK 的**薄包装**：
- 导入 SDK 的类 → 传入配置 → 调 SDK 的 `run()` → 包装返回 `AgentResult`
- Agent 内部的 LLM 调用、DOM 处理、动作执行、prompt 构建全部由 SDK 内部完成，对我们完全不透明

### 1.2 这套架构的核心问题

| 问题 | 具体表现 |
|------|---------|
| **SDK 黑盒** | 无法控制 agent 内部的 LLM 调用策略、DOM 序列化方式、action 执行逻辑 |
| **环境隔离成本高** | 每个 SDK 有独立依赖，需要 `.venvs/` 多虚拟环境隔离，增加部署复杂度 |
| **无法共享基础设施** | 三个 SDK 各自实现了 CDP 连接、LLM 调用、DOM 处理，存在大量重复 |
| **无法统一对比** | 不同 SDK 的 step 定义、action 粒度、prompt 策略不一致，benchmark 对比不公平 |
| **扩展新 agent 困难** | 添加新 agent 必须找到或开发新 SDK，无法基于已有基础设施快速搭建 |
| **LLM Provider 分散** | 每个 SDK 内部各自适配 LLM，切换模型需要改多处代码 |

### 1.3 重构的核心思想

**不再把 Agent Framework 当作外部依赖来调用，而是拆解各 SDK 的源码，提取核心逻辑，用自己的 `core/` 基础设施重写。**

具体来说：
1. **Agent 屏蔽 LLM 语义** — LLM Provider 实现在 `core/llm/` 内部，对 agent 只暴露 `ChatBaseModel` 接口
2. **Agent 屏蔽浏览器语义** — 浏览器供应商、CDP、DOM 处理在 `core/` 内部，对 agent 只暴露 `BrowserSession` 接口
3. **共享 ReAct 协议** — `BaseReActAgent` 定义统一的 observe → think → act 循环，各 agent 只需实现差异化部分（prompt 模板、action 解析、特有策略）
4. **AgentRunner 统一编排** — 一个 runner 负责拉起、并发执行、收集结果，不再是每个 agent 自己管生命周期

---

## 2. 目标架构总览

### 2.1 分层视图

```
┌─────────────────────────────────────────────────────────────────────┐
│  agents/                        (各 Agent 实现 — 独立目录)           │
│  ├─ base_react_agent.py         共享 ReAct 循环协议                  │
│  ├─ runner.py                   AgentRunner (拉起/并发/收集结果)      │
│  ├─ browser_use/                从 browser-use SDK 拆解重写          │
│  ├─ skyvern/                    从 skyvern SDK 拆解重写              │
│  └─ agent_tars/                 从 agent-tars 拆解重写               │
│                                                                      │
│  Agent 只看到: ChatBaseModel + BrowserSession                        │
│  Agent 不感知: 具体 LLM Provider / 浏览器供应商 / CDP 细节           │
├─────────────────────────────────────────────────────────────────────┤
│  core/                          (基础设施 — Agent 以外的一切)         │
│  ├─ llm/                        LLM Provider 统一层                  │
│  │   └─ 对外暴露 ChatBaseModel   内部: OpenAI/Anthropic/Azure/...    │
│  ├─ providers/                  云浏览器供应商抽象                    │
│  │   └─ BrowserProvider Protocol  Lexmount/AgentBay/Local/...        │
│  ├─ session/                    浏览器会话 + Capability 调度          │
│  │   └─ BrowserSession           Capability 优先 → CDP fallback      │
│  ├─ cdp/                        CDP 底层实现                         │
│  │   └─ Client, Actor, DOM 采集, Screenshot, Storage                 │
│  ├─ dom/                        DOM 过滤 + 序列化 (纯算法)           │
│  │   └─ Filters + Serializers    可插拔策略                          │
│  └─ config/                     配置加载                             │
├─────────────────────────────────────────────────────────────────────┤
│  layer-registry (PyPI)          (自动注册 + config typing — 依赖图根节点) │
│  ├─ registration/  create_registrar() + LayerRegistrar + discover()  │
│  └─ config/        extract → build union → codegen (.py + .json)     │
├─────────────────────────────────────────────────────────────────────┤
│  schemas/                       (跨模块契约 — Shared Kernel)          │
│  ├─ config/                     所有配置 schema                      │
│  └─ results/                    所有过程 input/output schema          │
├─────────────────────────────────────────────────────────────────────┤
│  benchmark_data/                (数据集 — 自有 + 第三方)              │
│  ├─ lexbench_browser/           自有 benchmark                       │
│  ├─ online_mind2web/            第三方 benchmark                     │
│  └─ browsecomp/                 第三方 benchmark                     │
├─────────────────────────────────────────────────────────────────────┤
│  evaluator/                     (评估引擎 — 按 benchmark 特化)        │
│  ├─ base.py                     BaseEvaluator Protocol               │
│  └─ evaluators/                 LexBench / Mind2Web / BrowseComp     │
├─────────────────────────────────────────────────────────────────────┤
│  cli/                           (命令行入口)                          │
│  leaderboard/                   (Web UI)                             │
│  utils/                         (纯工具函数, 无业务语义)              │
└─────────────────────────────────────────────────────────────────────┘
```

### 2.2 依赖规则

```
agents/ ──→ schemas/ ←── evaluator/
  │            │              │
  ↓            ↓              ↓
core/  ──→ layer-registry (PyPI) ←── cli/
  │
  ↓
(外部: playwright, CDP, LLM API)
```

- `layer-registry` (PyPI 包) 和 `schemas/` 是依赖图的**根节点**，不依赖任何业务模块，也互不依赖
- `core/` 依赖 `schemas/` 和 `layer-registry`，不依赖 `agents/`
- `agents/` 依赖 `core/`、`schemas/`、`layer-registry`，不依赖 `evaluator/`
- `evaluator/` 依赖 `schemas/` 和 `layer-registry`，不依赖 `agents/` 或 `core/`
- **禁止循环依赖**

### 2.3 关键设计决策

| 决策 | 理由 |
|------|------|
| LLM 在 `core/` 而非 `agents/` | Agent 应屏蔽 LLM 供应商差异，切换模型不需要改 agent 代码 |
| `BaseReActAgent` 是文件不是目录 | 它是协议/基线，不是独立模块，所有 agent 共享这一个文件 |
| `schemas/` 独立于所有模块 | 跨模块契约 (Shared Kernel)，防止循环依赖，见第 8 节讨论 |
| 每个 agent 是目录 | 各 agent 有自己的 prompt 模板、action 解析、特有策略，需要内聚 |
| Agent 可被并发执行 | `runner.py` 支持同时跑多个不同 agent，用于 benchmark 对比 |
| 统一自动注册机制 | `layer-registry` PyPI 包提供 `create_registrar()` 一键创建注册器；各 layer 通过 metaclass 继承即注册，外部类通过 `@runtime_checkable Protocol` 校验后手动注册。详见 [registry-auto-registration.md](./registry-auto-registration.md) |

---

## 3. agents/ — Agent 实现层

### 3.1 BaseReActAgent (共享协议)

所有 agent 的基类，定义统一的 ReAct 循环。**各 agent 通过 override 差异化方法来实现自己的逻辑。**

```python
class BaseReActAgent(ABC):
    """所有 Agent 共享的 ReAct 循环协议。

    职责:
      - 管理 observe → think → act 主循环
      - step 计数、error handling、retry、超时
      - history 记录、loop detection
      - 调用 ChatBaseModel (不感知具体 LLM Provider)
      - 调用 BrowserSession (不感知具体浏览器供应商)
    """

    def __init__(
        self,
        task: str,
        llm: ChatBaseModel,            # 从 core/llm/ 获取, agent 不关心是 OpenAI 还是 Anthropic
        session: BrowserSession,        # 从 core/session/ 获取, agent 不关心是 Lexmount 还是 Local
        config: AgentConfig,
    ):
        self.task = task
        self.llm = llm
        self.session = session
        self.config = config
        self._history: list[StepRecord] = []
        self._step_count = 0

    async def run(self) -> AgentResult:
        """主循环 — 子类一般不需要 override"""
        for step in range(self.config.max_steps):
            try:
                result = await self.step()
                if result.is_done:
                    break
            except Exception as e:
                if not self._handle_error(e):
                    break
        return self._build_result()

    async def step(self) -> StepResult:
        """单步执行 — 子类一般不需要 override"""
        # 1. observe: 获取浏览器状态
        state = await self.observe()

        # 2. think: 构建消息 → 调 LLM
        messages = self.build_messages(state)
        llm_output = await self.llm.ainvoke(messages, output_format=self.get_output_model())

        # 3. act: 解析 + 执行动作
        actions = self.parse_llm_output(llm_output)
        results = []
        for action in actions:
            result = await self.execute_action(action)
            results.append(result)
            if result.is_done or result.error:
                break

        # 4. record
        self._history.append(StepRecord(step=self._step_count, ...))
        return StepResult(...)

    # ── 子类必须实现的差异化方法 ──

    @abstractmethod
    def build_system_prompt(self) -> str:
        """各 agent 有自己的 prompt 模板"""
        ...

    @abstractmethod
    def parse_llm_output(self, output: Any) -> list[Action]:
        """各 agent 有自己的 LLM 输出解析逻辑"""
        ...

    @abstractmethod
    def get_output_model(self) -> type[BaseModel]:
        """各 agent 定义自己的 structured output schema"""
        ...

    # ── 子类可选 override 的扩展点 ──

    async def observe(self) -> BrowserState:
        """默认: CDP DOM 采集 + 截图, 子类可 override 使用其他策略"""
        dom = await self.session.extract_dom()
        screenshot = await self.session.screenshot()
        return BrowserState(dom=dom, screenshot=screenshot)

    async def execute_action(self, action: Action) -> ActionResult:
        """默认: 通过 BrowserSession 执行, 子类可 override"""
        return await self.session.execute_action(action.name, action.params)

    def build_messages(self, state: BrowserState) -> list[BaseMessage]:
        """默认: system + history + current state, 子类可 override 消息压缩策略"""
        ...
```

### 3.2 各 Agent 目录 (拆解自 SDK 源码)

每个目录是从对应 SDK 中**提取核心逻辑后用 core/ 重写**的结果。

```
agents/
├── base_react_agent.py              # 共享 ReAct 协议 (上述代码)
├── runner.py                        # AgentRunner
│
├── browser_use/                     # 源自 browser-use SDK
│   ├── agent.py                     # BrowserUseAgent(BaseReActAgent)
│   ├── prompts/                     # browser-use 风格的 prompt 模板
│   │   ├── system_default.md
│   │   └── system_flash.md
│   ├── output_model.py              # browser-use 的 structured output 定义
│   ├── message_manager.py           # browser-use 特有的消息压缩策略
│   └── controller.py                # browser-use 19 个 action 的解析/映射
│
├── skyvern/                         # 源自 skyvern SDK
│   ├── agent.py                     # SkyvernAgent(BaseReActAgent)
│   ├── prompts/                     # skyvern 风格的 prompt 模板
│   ├── output_model.py
│   ├── workflow.py                  # skyvern 特有的 workflow 编排
│   └── credential.py               # skyvern 特有的凭证注入
│
└── agent_tars/                      # 源自 agent-tars
    ├── agent.py                     # AgentTarsAgent(BaseReActAgent)
    ├── prompts/                     # agent-tars 风格的 prompt 模板
    ├── output_model.py
    └── planner.py                   # agent-tars 特有的多步规划
```

**各 agent 的 `agent.py` 示例**:

```python
# agents/browser_use/agent.py

class BrowserUseAgent(BaseReActAgent):
    """从 browser-use SDK 拆解重写的 agent。

    差异化:
      - 19 种 action 的 structured output (multi-action per step)
      - 特有的消息压缩策略 (保留最近 N 步 + LLM 摘要)
      - 支持 flash_mode (低 token 消耗)
    """

    def build_system_prompt(self) -> str:
        template = self._load_prompt("system_default.md")
        return template.format(actions=self.action_registry.to_description())

    def parse_llm_output(self, output: BrowserUseOutput) -> list[Action]:
        return [Action(name=a.action_name, params=a.params) for a in output.actions]

    def get_output_model(self) -> type[BaseModel]:
        return BrowserUseOutput  # 定义在 output_model.py

    def build_messages(self, state: BrowserState) -> list[BaseMessage]:
        # browser-use 特有: 超过 token 预算时用 LLM 压缩历史
        return self._message_manager.build(
            system=self.build_system_prompt(),
            state=state,
            history=self._history,
        )
```

### 3.3 AgentRunner (编排层)

```python
class AgentRunner:
    """拉起并管理 agent 执行。

    职责:
      - 根据 config 实例化: LLM (from core/llm) + BrowserSession (from core/session) + Agent
      - 支持并发执行多个不同 agent (用于 benchmark 对比)
      - 统一超时、重试、结果收集
    """

    async def run_single(self, task: TaskDefinition, agent_name: str, config: RunConfig) -> AgentResult:
        # 1. 从 core 获取基础设施
        llm = create_chat_model(config.llm)           # core/llm/factory.py
        provider = get_provider(config.provider)       # core/providers/registry.py
        cdp_session = await provider.open(config.provider)
        session = BrowserSession(provider, CDPClient(cdp_session.cdp_url))

        # 2. 实例化 agent
        agent = get_agent(agent_name)(task=task.task_text, llm=llm, session=session, config=config.agent)

        # 3. 执行
        try:
            return await asyncio.wait_for(agent.run(), timeout=config.timeout)
        finally:
            await provider.close(cdp_session)

    async def run_parallel(self, task: TaskDefinition, agent_names: list[str], config: RunConfig) -> dict[str, AgentResult]:
        """并发执行多个 agent, 用于对比评测"""
        tasks = [self.run_single(task, name, config) for name in agent_names]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        return dict(zip(agent_names, results))
```

---

## 4. 自动注册机制 (PyPI: `layer-registry`)

> 完整设计见 [registry-auto-registration.md](./registry-auto-registration.md)

注册机制由外部 PyPI 包 `layer-registry` 提供（`pip install layer-registry`），不再是项目内部的 `registry/` 目录。各 layer 的 `_registrar.py` 通过 `from layer_registry import create_registrar` 使用。

**核心 API**:

- `create_registrar(name, protocol, discriminator_field=)` — **一键创建 layer registrar**（推荐）
- `LayerRegistry[P]` — 泛型注册表，底层存储
- `LayerRegistrar[P]` — 各 layer 的专属注册器基类（高级场景手动继承）
- `discover()` — 递归导入指定包，触发 import-time 注册

**两条注册路径**:

- **继承自动注册 (免检)** — 子类继承基类 → metaclass 拦截 → `registry.add(key, cls, check=False)`
- **手动注册 (runtime_check)** — `@XxxRegistrar.register("key")` → `issubclass(cls, Protocol)` 校验后注册

每个 layer 定义一个 `_registrar.py`，一行 `create_registrar()` 即可搭建：

```python
# 以 Agent layer 为例 — agents/_registrar.py

from layer_registry import create_registrar

@runtime_checkable
class AgentProtocol(Protocol):
    async def run(self) -> AgentResult: ...
    def build_system_prompt(self) -> str: ...
    def parse_llm_output(self, output: Any) -> list: ...
    def get_output_model(self) -> type: ...

AgentRegistrar = create_registrar("agent", AgentProtocol, discriminator_field="name")
```

之后该 layer 的基类使用 `metaclass=AgentRegistrar.Meta`，所有具体子类定义时自动注册。config.yaml 中的 `agent.name: browser_use` 直接作为注册表查找 key: `AgentRegistrar.get("browser_use")`。

---

## 5. core/ — 基础设施层

### 5.1 core/llm/ — LLM Provider 统一层

**对外接口**: 只暴露 `ChatBaseModel` 和 `create_chat_model()` 工厂函数。
**对内实现**: 封装各 LLM 供应商的差异（消息序列化、tool calling 格式、特殊参数）。

```python
# core/llm/base.py — 这是 agent 唯一能看到的接口

@runtime_checkable
class ChatBaseModel(Protocol):
    """LLM 统一接口。Agent 只依赖此 Protocol。"""

    @property
    def model_name(self) -> str: ...

    async def ainvoke(
        self,
        messages: list[BaseMessage],
        output_format: type[T] | None = None,
        **kwargs,
    ) -> ChatCompletion[T]: ...
```

```python
# core/llm/factory.py — 根据 config 创建具体实现（通过注册表，零 match/case）

from core.llm._registrar import LLMRegistrar

def create_chat_model(config: LLMConfig) -> ChatBaseModel:
    """Agent 调这个函数获取 LLM, 不需要知道具体 Provider"""
    cls = LLMRegistrar.get(config.provider)
    return cls(config)
```

```
core/llm/                          # 内部实现 (agent 不直接 import)
├── base.py                        # ChatBaseModel Protocol
├── factory.py                     # config → 实例
├── messages.py                    # BaseMessage, UserMessage, ...
├── schema_optimizer.py            # tool calling schema 统一生成
├── providers/
│   ├── openai.py                  # function calling, response_format
│   ├── anthropic.py               # cache_control, system 消息分离
│   ├── azure.py                   # Responses API, reasoning 参数
│   ├── deepseek.py                # prefix continuation
│   ├── google.py                  # Gemini API, thinking_level
│   └── bedrock.py                 # URL→bytes, camelCase
└── serializers/
    ├── openai.py                  # OpenAI 消息格式
    ├── anthropic.py               # Anthropic 消息格式
    └── deepseek.py                # DeepSeek 消息格式
```

### 5.2 core/providers/ — 云浏览器供应商

管理供应商 session 生命周期，返回 CDP URL，声明供应商特有能力。

```python
# core/providers/base.py

@runtime_checkable
class BrowserProvider(Protocol):
    """所有云浏览器供应商的统一接口"""

    @property
    def provider_id(self) -> str: ...

    async def open(self, config: ProviderConfig) -> CDPSession: ...
    async def close(self, session: CDPSession) -> None: ...
    def get_capability(self, cap_type: type[T]) -> T | None: ...
```

```
core/providers/
├── base.py                        # BrowserProvider Protocol
├── capabilities.py                # ContextCapability, FingerprintCapability, ...
├── registry.py                    # provider_id → Provider 实例化
├── lexmount.py                    # connect_url, Contexts, Fingerprint
├── agentbay.py                    # observe/act 原生能力
├── local.py                       # 本地 Chrome / Playwright
└── cloudnative.py                 # 通用 CloudNative
```

**供应商 Capability 矩阵**:

| Capability | Lexmount | AgentBay | Local | CDP Fallback |
|------------|----------|----------|-------|-------------|
| `ContextCapability` | Contexts API | Context module | - | cookies + localStorage |
| `FingerprintCapability` | 创建时配置 | Generator | - | Emulation.* |
| `DOMExtractionCapability` | - | observe() | - | DOM+AX+Snapshot 三协议 |
| `ActionCapability` | - | act() | - | Input.dispatch* |

### 5.3 core/session/ — 浏览器会话

```python
# core/session/browser_session.py

class BrowserSession:
    """封装 CDP 连接 + Capability 调度。Agent 通过此接口操作浏览器。

    策略: 供应商原生 Capability 优先 → CDP fallback
    """

    def __init__(self, provider: BrowserProvider, cdp: CDPClient): ...

    async def extract_dom(self) -> DOMState: ...
    async def screenshot(self, full_page: bool = False) -> bytes: ...
    async def execute_action(self, action: str, params: dict) -> ActionResult: ...
    async def save_context(self, name: str) -> None: ...
    async def apply_fingerprint(self, config: FingerprintConfig) -> None: ...
```

```
core/session/
├── browser_session.py             # Capability 调度
├── session_state.py               # 状态追踪
└── watchdog.py                    # 健康监控 (超时/崩溃检测)
```

### 5.4 core/cdp/ — CDP 底层实现

从各 SDK 源码中提取的 CDP 操作实现，作为所有供应商的 fallback。

```
core/cdp/
├── client.py                      # CDP WebSocket 连接封装
├── dom_extractor.py               # DOM+AX+DOMSnapshot 三协议并行采集
├── actor.py                       # click/input/scroll/navigate 的 CDP 实现
├── screenshot.py                  # Page.captureScreenshot
└── storage.py                     # cookies/localStorage 导入导出
```

CDP Actor 的实现来源（用于追溯和参考，不构成运行时依赖）:

| 功能 | 参考源 | CDP 协议 |
|------|--------|---------|
| DOM 采集 | browser-use `dom/service.py` | DOM.getDocument, Accessibility.getFullAXTree, DOMSnapshot.captureSnapshot |
| 元素点击 | browser-use `actor/element.py` | Input.dispatchMouseEvent |
| 文本输入 | browser-use `actor/element.py` | Input.dispatchKeyEvent |
| 页面导航 | browser-use `actor/page.py` | Page.navigate |
| 截图 | 通用 CDP | Page.captureScreenshot |
| Storage | 通用 CDP | Network.getCookies, Runtime.evaluate |

### 5.5 core/dom/ — DOM 过滤 + 序列化

纯算法层，与供应商无关。将 CDP 采集的原始 DOM 树过滤、序列化为 LLM 可读格式。

```
core/dom/
├── models.py                      # DOMElementNode, DOMState (core 内部模型)
├── filters/
│   ├── pipeline.py                # FilterPipeline 可配置链
│   ├── visibility.py              # display:none, 超出视口
│   ├── interactivity.py           # 标记可交互元素
│   ├── paint_order.py             # 遮挡检测
│   └── disabled_elements.py       # 移除 script/style/head
└── serializers/
    ├── tree.py                    # Agent 主循环用 (带 [i_N] 索引)
    ├── eval.py                    # 评估用 (完整结构)
    └── markdown.py                # 内容提取用
```

### 5.6 core/config/ — 配置加载

```
core/config/
└── loader.py                      # YAML + .env 合并, CLI override
```

---

## 6. schemas/ — 跨模块契约

### 6.1 设计原则

`schemas/` 是模块间的 **Shared Kernel**（共享内核），定义所有跨模块的数据契约。

**什么放 `schemas/`**: 被 2 个以上模块使用的数据结构（如 `AgentResult` 被 agents、evaluator、cli、leaderboard 共同使用）。

**什么不放 `schemas/`**: 模块内部的数据结构（如 `DOMElementNode` 只在 `core/dom/` 内部使用，放 `core/dom/models.py`）。

```
schemas/
├── __init__.py
├── enums.py                       # EnvironmentStatus, AgentDoneStatus, ...
├── config/
│   ├── provider_config.py         # ProviderConfig, CDPSession
│   ├── llm_config.py              # LLMConfig
│   ├── agent_config.py            # AgentConfig (max_steps, vision, ...)
│   ├── eval_config.py             # EvalConfig
│   └── benchmark_config.py        # BenchmarkMeta, TaskDefinition
└── results/
    ├── agent_result.py            # AgentResult, AgentMetrics, AgentUsage
    ├── step_record.py             # StepRecord, ActionResult
    ├── eval_result.py             # EvalResult, FailureClassification
    └── eval_summary.py            # EvalSummary, MetricStats, CostSummary
```

---

## 7. benchmark_data/ — 数据集管理

```
benchmark_data/
├── __init__.py
├── registry.py                    # Benchmark 注册表 (name → meta + loader)
├── loader.py                      # 统一数据加载 (JSONL/JSON)
├── lexbench_browser/              # 自有 benchmark
│   ├── meta.yaml                  # 元信息: 名称、版本、任务数、评估方式
│   └── data/
│       ├── tasks.jsonl
│       ├── l1.jsonl ... l3-security.jsonl
│       └── tasks_private.jsonl
├── online_mind2web/               # 第三方 benchmark
│   ├── meta.yaml
│   └── data/
│       ├── Online_Mind2Web.json
│       └── hard.json
└── browsecomp/                    # 第三方 benchmark
    ├── meta.yaml
    └── data/
        └── tasks.jsonl
```

---

## 8. evaluator/ — 评估引擎

```
evaluator/
├── __init__.py
├── base.py                        # BaseEvaluator Protocol
├── registry.py                    # Evaluator 注册表
├── evaluators/
│   ├── lexbench.py                # LexBench 评估逻辑
│   ├── mind2web.py                # Mind2Web 评估逻辑
│   └── browsecomp.py              # BrowseComp 评估逻辑
├── judge.py                       # LLM-as-Judge
├── metrics.py                     # 指标计算
├── cost.py                        # Token/费用统计
├── stats.py                       # 聚合统计
└── failure.py                     # 失败分类
```

---

## 9. 完整目录结构

```
browseruse_bench/
│
│   # 注册机制由 PyPI 包 layer-registry 提供 (pip install layer-registry)
│   # 详见 registry-auto-registration.md
│
├── schemas/                       # Shared Kernel — 跨模块数据契约
│   ├── enums.py
│   ├── config/
│   │   ├── provider_config.py
│   │   ├── llm_config.py
│   │   ├── agent_config.py
│   │   ├── eval_config.py
│   │   └── benchmark_config.py
│   └── results/
│       ├── agent_result.py
│       ├── step_record.py
│       ├── eval_result.py
│       └── eval_summary.py
│
├── core/                          # 基础设施 (Agent 以外的一切)
│   ├── llm/
│   │   ├── _registrar.py          # ChatModelProtocol + LLMRegistrar
│   │   ├── base.py                # ChatBaseModel (metaclass=LLMRegistrar.Meta)
│   │   ├── factory.py             # config → ChatBaseModel 实例
│   │   ├── messages.py
│   │   ├── schema_optimizer.py
│   │   ├── providers/
│   │   │   ├── openai.py
│   │   │   ├── anthropic.py
│   │   │   ├── azure.py
│   │   │   ├── deepseek.py
│   │   │   ├── google.py
│   │   │   └── bedrock.py
│   │   └── serializers/
│   │       ├── openai.py
│   │       ├── anthropic.py
│   │       └── deepseek.py
│   ├── providers/
│   │   ├── _registrar.py          # BrowserProviderProtocol + ProviderRegistrar
│   │   ├── base.py                # BrowserProvider (metaclass=ProviderRegistrar.Meta)
│   │   ├── capabilities.py
│   │   ├── lexmount.py
│   │   ├── agentbay.py
│   │   ├── local.py
│   │   └── cloudnative.py
│   ├── session/
│   │   ├── browser_session.py
│   │   ├── session_state.py
│   │   └── watchdog.py
│   ├── cdp/
│   │   ├── client.py
│   │   ├── dom_extractor.py
│   │   ├── actor.py
│   │   ├── screenshot.py
│   │   └── storage.py
│   ├── dom/
│   │   ├── models.py              # DOMElementNode (core 内部模型)
│   │   ├── filters/
│   │   │   ├── _registrar.py      # DOMFilterProtocol + DOMFilterRegistrar
│   │   │   ├── pipeline.py
│   │   │   ├── visibility.py
│   │   │   ├── interactivity.py
│   │   │   ├── paint_order.py
│   │   │   └── disabled_elements.py
│   │   └── serializers/
│   │       ├── tree.py
│   │       ├── eval.py
│   │       └── markdown.py
│   └── config/
│       └── loader.py
│
├── agents/                        # Agent 实现层
│   ├── _registrar.py              # AgentProtocol + AgentRegistrar
│   ├── base_react_agent.py        # BaseReActAgent (metaclass=AgentRegistrar.Meta)
│   ├── runner.py                  # AgentRunner — AgentRegistrar.get() 查询
│   ├── browser_use/               # 从 browser-use SDK 拆解
│   │   ├── agent.py
│   │   ├── prompts/
│   │   ├── output_model.py
│   │   ├── message_manager.py
│   │   └── controller.py
│   ├── skyvern/                   # 从 skyvern SDK 拆解
│   │   ├── agent.py
│   │   ├── prompts/
│   │   ├── output_model.py
│   │   ├── workflow.py
│   │   └── credential.py
│   └── agent_tars/                # 从 agent-tars 拆解
│       ├── agent.py
│       ├── prompts/
│       ├── output_model.py
│       └── planner.py
│
├── benchmark_data/                # 数据集 (自有 + 第三方)
│   ├── _registrar.py              # BenchmarkLoaderProtocol + BenchmarkRegistrar
│   ├── loader.py
│   ├── lexbench_browser/
│   ├── online_mind2web/
│   └── browsecomp/
│
├── evaluator/                     # 评估引擎
│   ├── _registrar.py              # EvaluatorProtocol + EvaluatorRegistrar
│   ├── base.py                    # BaseEvaluator (metaclass=EvaluatorRegistrar.Meta)
│   ├── evaluators/
│   ├── judge.py
│   ├── metrics.py
│   ├── cost.py
│   ├── stats.py
│   └── failure.py
│
├── cli/                           # 命令行入口
│   ├── __init__.py
│   ├── run.py
│   ├── eval.py
│   ├── leaderboard.py
│   └── server.py
│
├── leaderboard/                   # Web UI
│   ├── generator.py
│   ├── server.py
│   └── templates/
│
└── utils/                         # 纯工具函数 (无业务语义)
    ├── logger.py
    ├── json_io.py
    ├── image_utils.py
    └── venv.py
```

---

## 10. 迁移路径

按以下顺序实施，每个 Phase 完成后可独立测试。

### Phase 0: layer-registry PyPI 包 — 自动注册 + config typing 基础设施

- 实现 `registration/` 子包: `LayerRegistry`, `create_auto_registrar`, `LayerRegistrar`, `create_registrar()`, `discover()`
- 实现 `config/` 子包: `extract_config_schema`, `build_layer_config`, `codegen`, `json_schema`
- 发布为独立 PyPI 包 `layer-registry`，消费者通过 `pip install layer-registry` 使用
- **测试**: metaclass 自动注册、手动注册 + runtime_check 校验、key 推断、重复 key 报错、config 提取 + 生成

### Phase 1: schemas/ — 建立契约

- 将现有 `schemas/` 扩展为包含 config schema 的完整 Shared Kernel
- 现有 `AgentResult`, `EvalResult`, `EvalSummary` 迁移到新位置
- 新增 `ProviderConfig`, `LLMConfig`, `AgentConfig`, `TaskDefinition` 等
- **测试**: 所有 schema 的序列化/反序列化、向后兼容

### Phase 2: core/providers/ — 云浏览器统一

- 将现有 `browsers/providers/` 迁移到 `core/providers/`，实现 `BrowserProvider` Protocol
- 建立 Capability 体系
- **测试**: 每个 provider 能 open/close session，返回有效 cdp_url

### Phase 3: core/llm/ — LLM Provider 统一

- 从各 SDK 源码中提取 LLM 调用逻辑，统一到 `core/llm/`
- 实现 `ChatBaseModel` Protocol 和各 provider
- 实现 `create_chat_model()` 工厂
- **测试**: 各 provider 能正确调用 API，structured output 正常

### Phase 4: core/cdp/ + core/dom/ — 浏览器操作 + DOM 处理

- 从各 SDK 源码中提取 CDP 操作实现
- 实现 DOM 三协议采集、Filter Pipeline、Serializer
- 实现 BrowserSession 的 Capability 调度
- **测试**: 通过 CDP 能点击、输入、导航、截图；DOM 序列化输出正确

### Phase 5: agents/ — 拆解重写各 Agent

- 建立 `BaseReActAgent` 协议
- 从 browser-use SDK 拆解核心逻辑到 `agents/browser_use/`
- 从 skyvern SDK 拆解核心逻辑到 `agents/skyvern/`
- 从 agent-tars 拆解核心逻辑到 `agents/agent_tars/`
- 实现 `AgentRunner`
- **测试**: 每个 agent 能基于 core/ 基础设施端到端执行 benchmark task

### Phase 6: benchmark_data/ + evaluator/ — 数据与评估

- 将现有 `benchmarks/` 数据迁移到 `benchmark_data/`
- 将现有 `utils/eval.py`, `utils/eval_failure.py` 等迁移到 `evaluator/`
- 建立 benchmark 注册表和评估器注册表
- **测试**: 各 benchmark 数据加载正常，评估结果与迁移前一致

### Phase 7: 清理遗留

- 删除旧 `agents/`, `browsers/` 目录
- 删除不再需要的 `.venvs/` 多虚拟环境
- 更新 CLI 入口指向新模块
- 更新文档和示例

---

## 11. Config 示例

```yaml
# config.yaml

# 云浏览器供应商
provider:
  id: lexmount                       # lexmount / agentbay / local
  browser_mode: normal               # 供应商 specific
  # api_key 从 .env 读取

# LLM (在 core 内部处理, agent 不感知)
llm:
  provider: openai                   # openai / anthropic / azure / deepseek / google
  model: gpt-4o
  temperature: 0.0
  max_tokens: 4096

# Agent
agent:
  name: browser_use                  # browser_use / skyvern / agent_tars
  max_steps: 100
  max_failures: 3
  use_vision: true

# DOM (在 core 内部处理)
dom:
  filter_pipeline:
    - DisabledElementFilter
    - VisibilityFilter:
        viewport_threshold: 1000
    - InteractivityDetector
    - PaintOrderFilter
  serializer: TreeSerializer

# Benchmark
benchmark:
  name: LexBench-Browser
  subset: l1                         # 可选子集

# 评估
eval:
  use_judge: true
  judge_model: gpt-4o
```
