Agent Engineering 课程阅读
首页/课程八 · GUI Agent/观察空间:页面怎么喂给模型

在 GitHub 查看原文

本页目录

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 能感知能操作什么。编号稳定性我也处理了:单轮内有效、每步重扫重分配,不跨步引用旧编号。」