Webwright — 架构与原理
30 秒导读: Webwright 是微软开源的浏览器 agent 框架。它不让模型「一步点一个按钮」, 而是让模型像工程师写自动化脚本一样:写一段 Playwright 代码、跑一遍、看截图和日志、 改代码再跑——浏览器只是被临时拉起又丢弃的环境,持久成果是工作区里那份可复跑的脚本。 核心逻辑集中在几个文件:主循环单文件 ≈450 行、实时浏览器环境 ≈570 行、CLI ≈150 行。
1. 这是什么(零基础也能懂)
一句话定义: Webwright 给大模型一个终端 + 工作区,让它通过写代码来操作浏览器完成网页任务 (搜航班、比价、填表、抓招聘信息……)。
它想解决的问题。 今天大多数网页 agent 把「浏览器会话本身」当成工作台:每一步模型收到
当前页面状态,只预测下一个动作(点这里、输那里、选某个 DOM)。这套「一次一个动作」的
框子在模型弱时有用;模型强到会写会调代码后,它反而成了瓶颈——啰嗦、脆弱、错误会在长任务里累积
(动机见 README.md「Motivation」折叠块)。
Webwright 的立场:把 agent 和浏览器分开。 浏览器是 agent 可以随手启动、检查、丢弃的东西; 真正留下来的「状态」是本地工作区里的代码与日志。用一句类比:
别的 agent 把「浏览器」当内存,状态一断就没;Webwright 把「工作区(代码+截图+日志)」当磁盘, 浏览器只是一个跑完就关的进程。
它能做什么(功能):
- 用自然语言描述一个网页任务,自动写出并执行 Playwright 脚本完成它。
- 三种模型后端可插拔:OpenAI(Responses API)、Anthropic(Claude)、OpenRouter。
- 两种运行姿态:工作区模式(每步跑一次性无状态浏览器,产出可复跑脚本)与 实时浏览器模式(每步驱动同一个活的浏览器页面)。
- 每次运行把轨迹、逐步脚本、截图、日志全部落盘,方便复盘。
- 可作为 Claude Code / Codex / OpenClaw / Hermes 的插件/skill 使用(
skills/webwright/)。
用起来什么样。 一条命令就能跑(引自 README.md「Quick Start」):
python -m webwright.run.cli \
-c base.yaml -c model_openai.yaml \
-t "Search for flights from SEA to JFK on 2026-08-15 to 2026-08-20" \
--start-url https://www.google.com/flights \
--task-id demo_openai \
-o outputs/default
-c 叠配置、-t 给任务、--start-url 给起始页、-o 指定输出目录。跑完 outputs/ 下会出现
一个带时间戳的文件夹,里面有 trajectory.json(完整对话轨迹)、script.py(逐步拼起来的代码)、
screenshots/、debug/。
本节不碰底层细节。记住一句就够:Webwright = 让「会写代码的模型」用终端和临时浏览器干网页活。
2. 顶层全景(它大概怎么转)
2.1 三个主角 + 一条循环
Webwright 的运行时就三块拼在一起,由一条循环串起来:
┌──────────────────────────────────────────────┐
配置(YAML) │ DefaultAgent(中央循环) │
base.yaml │ │
model_*.yaml├──▶ ① 渲染 system+instance 提示词 │
local_*.yaml│ │
│ ┌──────────── while True ──────────────┐ │
│ │ ② model.query(messages) → 一段 JSON │ │
│ │ ③ 解析出 thought + 动作 + done │ │
│ │ ④ env.execute(动作) → 观察(observation)│ │
│ │ ⑤ 把观察转成 user 消息喂回 ⑥ │ │
│ └───────────────────────────────────────┘ │
│ done 且过门禁 → 退出,吐 final_response │
└──────────────────────────────────────────────┘
▲ │
│ 一段 JSON 动作 ▼ 落地到真实目标
┌──────┴───────┐ ┌───────────────────┐
│ Model │ │ Environment │
│ (OpenAI / │ │ 工作区跑 bash 脚本 │
│ Anthropic / │ │ 或 驱动实时 page │
└──────────────┘ └───────────────────┘
怎么读这张图: 中间 DefaultAgent 是唯一的循环体;左边 Model 负责「想」(产出一段严格 JSON),
右边 Environment 负责「做」(把 JSON 里的动作落到真实浏览器/终端),观察结果再喂回循环。配置只在
启动时把这三块拼出来,并渲染提示词。
2.2 部件一句话职责
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
DefaultAgent | 中央循环:调用模型→执行动作→喂回观察,管步数上限、历史压缩、done 门禁 | src/webwright/agents/default.py |
LocalWorkspaceEnvironment | 工作区模式:把模型给的 bash_command 当 shell 命令跑,浏览器由脚本自己临时拉起 | src/webwright/environments/local_workspace.py |
LocalBrowserEnvironment | 实时浏览器模式:持有活的 Playwright page,把 python_code 当一步动作 exec 掉 | src/webwright/environments/local_browser.py |
BaseModel 及子类 | 与厂商无关的模型后端:拼 payload、发 HTTP、解析严格 JSON、重试、算 token | src/webwright/models/base.py 等 |
| 配置系统 | YAML 堆叠 + 递归合并 + 内联覆盖,并把合并结果快照落盘 | src/webwright/config/__init__.py |
self_reflection / image_qa | 工作区模式下的自我校验工具:截图裁判、视觉问答 | src/webwright/tools/ |
cli.run_one | 入口:解析参数→拼配置→造三主角→跑一次任务→收尾落盘 | src/webwright/run/cli.py |
2.3 两种模式(先建立这个心智模型,后面处处用到)
Webwright 同一套循环能跑出两 种截然不同的姿态,区别只在挂哪个环境、哪个动作字段:
| 维度 | 工作区模式(默认) | 实时浏览器模式 |
|---|---|---|
| 配置 | base.yaml(可叠 task_showcase.yaml) | base.yaml -c local_browser.yaml |
| 环境类 | LocalWorkspaceEnvironment | LocalBrowserEnvironment |
| 动作字段 | bash_command(一条 shell) | python_code(一步异步 Playwright) |
| 浏览器状态 | 无状态:每次脚本自己 launch 新会话 | 有状态:同一个 page 跨步复用 |
| 持久产物 | 工作区里的 final_script.py + 截图 + 日志 | 无工作区,答案直接放 final_response |
| 完成校验 | self_reflection 截图裁判必须判 success 才准 done | 模型自己判 done |
| 典型场景 | 跑基准/SOTA、产出可复跑脚本 | 需要人工登录(CDP 连真实 Chrome) |
这就是 Webwright 最核心的设计:「code-as-action」——动作不是「点/输」的枚举,而是一段代码。 工作区模式把这点推到极致(整个浏览历史 = 一份代码文件)。详见 02-environments.md。
2.4 主线走一遍(高层,不进代码)
以默认工作区模式跑一个「按条件筛选商品」任务为例:
用户任务 ──▶ cli.run_one 拼配置、造 Agent ──▶ Agent 渲染提示词(含工作区路径、规则)
│
▼
模型第 1 步:thought="先把任务拆成 critical points 写进 plan.md",bash_command="cat > plan.md ..."
│ env 跑这条 shell → 观察(命令输出、工作区文件清单)喂回
▼
模型第 N 步:写 final_script.py,跑一次 → 存截图到 final_runs/run_001/screenshots/
│
▼
模型某步:跑 self_reflection 截图裁判 → 写 self_reflect_result.json(predicted_label=1)
│
▼
模型 下一步:done=true → Agent 门禁检查 predicted_label==1 通过 → 退出,吐 final_response
关键点:done 不是模型说了算。工作区模式下,DefaultAgent 会拦住 done=true,去磁盘上核对
最新一次运行的 self_reflect_result.json 是否 predicted_label==1,不满足就把动作打回、注入一条
纠错消息继续跑(见 04-completion-gate.md)。
3. 阅读地图(建议顺序)
这个项目虽小但设计密度高,拆成 4 章由浅入深。建议顺序:
- 01-agent-loop.md — 中央循环。 先看清那条
while True到底怎么转: JSON 动作协议、步数上限、done 如何触发退出、上下文怎么被压缩和裁剪。这是骨架。 - 02-environments.md — 两种环境与 code-as-action。 Webwright 的灵魂在这: 为什么「浏览器是一次性的」、工作区环境怎么跑 shell、实时环境怎么 exec 一步 Python。
- 03-model-backends.md — 模型后端。 一个与厂商无关的基类如何用严格 JSON schema 逼模型只吐结构化动作,以及解析重试、观察格式化、限速重试等工程细节。
- 04-completion-gate.md — 完成门禁与自我校验。 工作区模 式独有的 「截图裁判」如何做到「模型说完成、但要另一个模型看截图签字」,以及配置快照如何让工具复用同一个模型。
给 AI agent 的路由提示: 想改「循环/退出/压缩」逻辑看 §01;想改「浏览器怎么被驱动/两种模式」看 §02; 想加一个模型厂商或调「JSON 解析/重试」看 §03;想改「完成判定/自校验/配置」看 §04。
4. 一句话记住它
别的浏览器 agent 让模型「操作浏览器」;Webwright 让模型「写一个操作浏览器的程序」。 浏览器可丢弃,程序留下来——这就是它在长任务上更稳、更省、更可复用的原因。