本页目录
Lesson 02 — 观察空间:页面怎么喂给模型
本课目标:设计并量化对比三种页面表示(原始 HTML / 元素编号列表 / 纯文本正文),理解观察空间设计就是上下文工程在 GUI 场景的延伸。手写
page_to_obs()提取可交互元素并编号,输出 token 对比表。
学完你能回答:「页面怎么表示给 LLM 才既省 token 又不丢操作能力?」——答案是元素编号列表(可访问性树的精简版),它比原始 HTML 省 一个数量级 token,又比纯文本多了「可点击」的交互信号。
0. 观察空间设计 = 决定上下文窗口里放什么
L01 让 agent 有了稳的手(能 goto/click/type)。但 agent 是 LLM 驱动的——它要先看见页面才能决定点哪。问题来了:一个网页动辄几万 token 的 HTML,怎么塞进上下文窗口?塞什么、不塞什么?
这就是观察-行动接口主线的观察空间半边,也是第七门课「上下文工程」母题在 GUI 场景的直接延伸:
上下文工程母题(第七门课) GUI 场景的观察空间(本课)
───────────────────────── ─────────────────────────
记忆按需 recall 进 prompt ←→ 页面元素按需提取进观察
RAG 只取 top-k 相关文档 ←→ 只取可交互元素 + 正文摘要
prompt 长了截断/滑动窗口 ←→ HTML 太长截断/编号精简
放什么 = 决定模型能感知什么 ←→ 放什么 = 决定 agent 能操作什么
🎯 核心认知:观察空间不是「把页面翻译成文本」这么简单——它是上下文工程决策。放原始 HTML,token 爆炸且噪音淹没信号;放纯文本,丢了交互性(agent 不知道哪能点)。设计观察空间 = 在「信息完整性」和「上下文预算」之间找平衡。
1. 流派对比:三种页面表示
| 表示 | 内容 | token 量 | 交互性 | 取舍 |
|---|---|---|---|---|
| ① 原始 HTML | 完整 DOM 字符串 | 巨大(数万 token) | ✅ 有标签结构 | ✅ 信息无损;🚫 token 爆炸、噪音大(style/script/nav 全是噪音)、LLM 难解析 |
| ② 元素编号列表(本课主路线) | 可交互元素 role+文本+编号 + 正文摘要 | 小(数百 token) | ✅ 编号即点击目标 | ✅ 便宜、结构保留、可操作;🚫 复杂布局可能漏元素 |
| ③ 纯文本正文 | inner_text() 全文 |
中 | ❌ 无交互信号 | ✅ 便宜、适合阅读;🚫 只配读不配操作(不知道哪能点) |
选 ② 的理由:GUI agent 的核心任务是操作(点击/输入),不是阅读。纯文本丢了「哪个元素可交互」的关键信号;原始 HTML 把信号埋在噪音里还贵。元素编号列表是「可访问性树(accessibility tree)」的精简版——只留可交互节点 + 编号,agent 看到编号就能 click(3)(L03 的 DSL 直接用这个编号)。
📖 Mind2Web(Deng et al. 2023, arXiv:2306.06070)证明了真实网站的 DOM 表示可以支撑跨网站泛化——它用元素属性(role/tag/text/context)做表示,多个 LLM 在其上能做动作预测。本课的元素编号列表是它的极简教学版。
2. 元素编号列表的设计
page_to_obs() 的核心是把页面上可交互的元素抽出来,每个分配一个编号:
[1] link "LangGraph 发布列表" (href=search.html?q=LangGraph)
[2] link "CrewAI 发布列表" (href=search.html?q=CrewAI)
[3] textbox "搜索框" (name=q)
[4] button "搜索"
正文摘要:本地搜索门户。这是 GUI Agent 课程的本地镜像首页...
为什么这样设计:
| 字段 | 作用 |
|---|---|
| 编号 | L03 DSL 的动作目标:click(1) 就点编号 1 的元素。编号 = 观察到行动的桥梁 |
| role | link/button/textbox——告诉模型元素是什么类型,决定能对它做什么动作 |
| 文本/value | 元素的可见文本或值——模型靠这个判断「该点哪个」 |
| 正文摘要 | 非交互的页面信息(标题、说明文字),让模型理解上下文但不占太多 token |
提取可交互元素的选择器
INTERACTIVE = 'a, button, input, select, textarea, [role="button"], [role="link"], [role="textbox"]'
涵盖链接、按钮、输入框、选择器、文本域 + ARIA role 标注的可交互元素。这是「可访问性树」思路的工程落地——可访问性树本身就是为辅助技术(屏幕阅读器)提取「能操作的元素」,agent 复用同一套语义。
3. 编号稳定性问题
一个隐蔽但致命的问题:每步操作后页面变了,重新扫描编号会漂移。
步 1 观察:[1]链接A [2]链接B [3]输入框
agent 决定 click(1)
步 2 页面跳转后重新扫描:[1]链接C [2]链接D [3]按钮E
↑ 编号 1 现在指的不是同一个元素了!
这是观察-行动接口的固有张力:观察是每步重新生成的快照,编号只在一轮 think-act 内有效。处理方式(本课实现 + L04 强化):
- 编号在单轮内有效:agent 看到观察→立刻决策→执行,不跨步引用旧编号。
- 重扫后编号重新分配:每步
page_to_obs()重新编号,agent 每步都看新观察。 - 用稳定属性做后备(L06 可靠性):当编号歧义时(两个元素文本一样),用 href/selector 做唯一标识。
🎯 这正是 L00 gap 表里「搜索 API 无观察空间」的对照——搜索给的是死字符串,浏览器给的观察是活的状态,每步都变。观察空间设计必须处理这种动态性。
4. token 估算
本课用最简单的字符数 ÷ 4 估 token(粗估,够做对比;真实场景用 tiktoken)。三种表示在同一页上的 token 差距是本课的核心实验产出——它直接决定 L04 agent 循环每步烧多少钱。
| 表示 | 一个典型搜索页 token | 说明 |
|---|---|---|
| 原始 HTML | ~3000-8000 | 含 style/script/nav 噪音 |
| 元素编号列表 | ~200-500 | 只留可交互元素 + 摘要 |
| 纯文本正文 | ~100-300 | 只有可见文字 |
元素编号列表比原始 HTML 省 一个数量级。L04 跑 10 步任务,原始 HTML 烧 3-8 万 token,编号列表只烧 2-5 千——这是文本派便宜的根本原因(流派对比 L00 的取舍落地)。
5. 落地清单
本课是观察空间设计课,无 research-assistant 代码改动(落地在 L09)。产出:
| 文件 | 说明 |
|---|---|
README.md(本文件) |
三表示流派对比 + 编号设计 + 稳定性 + token 估算 |
code.py |
page_to_obs() 实现 + 三表示 token 对比表 + 编号稳定性单测 |
exercise.md |
练习 |
验收
# 起 L00 本地服务(用 L00 的 search/detail 页做观察对象)
cd gui-agent-lessons/00_overview/test_pages && python -m http.server 8765
cd gui-agent-lessons/02_observation
python code.py
# 预期输出:
# - 同一页面三种表示各打印一段样例
# - token 对比表:原始HTML > 纯文本 > 元素编号列表(编号列表省一个数量级)
# - 编号稳定性单测:重扫同一页编号一致;操作后重扫编号重分配
⚠️ 需 playwright + L00 本地服务。未装/未起则跳过演示并提示。
6. 本课在两条主线上的位置
- 评估主线:本课产出 token 对比表——这是评估主线里「成本」维度的基线数据。L05 视觉路线(截图送 VLM)的 token 成本会用同一套估算方法对照,L11 收益表把「文本 vs 视觉」的 token 差异列进去。没有本课的 token 量化,成本对比就是拍脑袋。
- 观察-行动接口主线:本课是观察空间的正式开篇。L00 点了题(搜索 API 没观察空间),本课给出了三种观察表示并选定元素编号列表——L03 的行动 DSL 直接用这里的编号当动作目标,L04 的 agent 循环每步调用
page_to_obs()生成观察。观察→行动的桥梁(编号)在本课搭好。
🎯 面试话术
「页面表示我量化对比过三种——原始 HTML、元素编号列表、纯文本,token 差一个数量级。我选元素编号列表,它比 HTML 省一个数量级又比纯文本多了交互信号,编号直接当 L03 动作 DSL 的目标。观察空间设计就是上下文工程在 GUI 场景的延伸——放什么进窗口决定 agent 能感知能操作什么。编号稳定性我也处理了:单轮内有效、每步重扫重分配,不跨步引用旧编号。」