本页目录
Lesson 03 — 行动空间:动作 DSL 与解析校验
本课目标:设计受限动作 DSL(
click(n)/type(n, text)/scroll(dir)/back()/finish(answer)),手写解析器+校验器+执行器,理解「模型能说什么」决定安全与可控的上限。非法动作不崩溃,转结构化错误回注重试。
学完你能回答:「GUI agent 的行动空间该怎么设计——自由代码还是受限 DSL?为什么浏览器场景必须用 DSL?」——答案是受限 DSL:可校验、可白名单,非法动作转结构化错误回注,不是崩溃。
0. 行动空间 = 决定模型能对世界施加什么
L02 解决了「agent 怎么看见页面」(观察空间),本课解决「agent 怎么表达动作」(行动空间)。这是观察-行动接口的另一半:
观察空间(L02) 行动空间(本课)
─────────── ───────────
页面 → 元素编号列表 元素编号 + 动作意图 → DSL → 执行
决定 agent 能感知什么 决定 agent 能操作什么
放什么进上下文窗口 允许说什么动作
🎯 核心认知:行动空间不是「让 LLM 自由发挥」这么简单——它是安全与可控的上限。行动空间越宽(自由代码),表达力越强但越危险;越窄(受限 DSL),越可控但越笨。浏览器场景里,一次错误的
click可能跳到恶意域、提交了不该提交的表单——所以这里可控优先于表达力。
1. 流派对比:三种行动空间
| 行动空间 | 形式 | 表达力 | 安全性 | 取舍 |
|---|---|---|---|---|
| ① 自由代码(CodeAct 式) | LLM 输出 Python/JS 直接执行 | 最强 | 🚫 最差——浏览器场景能干任何事(跳转/提交/下载执行),一个恶意 prompt 就完蛋 | 适合沙箱完好的 Code Agent(第七门课 L06),不适合直接控浏览器 |
| ② 受限 DSL(本课选它) | click(3) / type(3, "查询") / scroll(down) / back() / finish(答案) |
够用 | ✅ 可校验(编号存在吗/参数合法吗)+ 可白名单 + 可拦截敏感动作 | 表达力受限但浏览器任务用这几招就够 |
| ③ 视觉坐标点击(视觉派专属,L05 讲) | LLM 输出 (x, y) 坐标点击 |
所见即所得 | 🚫 难校验(坐标落在哪事先不知)、grounding 难 | 配合 SoM 标注变成「点编号」才可控 |
选 ② 的理由:
- 可校验:
click(99)当页只有 9 个元素时,校验器直接拒——编号不存在。自由代码做不到这种前置校验。 - 可白名单:动作集合固定(5 种),想加新动作必须改代码——天然限制了 LLM 的「越权冲动」。
- 可拦截敏感动作(L07 安全主线):DSL 是结构化的,
click的目标元素能查 href 是否在域名 allowlist——自由代码跳转了都不知道。
📖 SoM(Yang et al. 2023, arXiv:2310.11441)证明给图片画编号标注能让 VLM 精确指对象——视觉派的坐标点击一旦配合 SoM 编号,就退化成「点编号」,和本课的 DSL
click(n)同构。所以本课的 DSL 是文本/视觉两派的共同底座,L05 视觉路线会复用编号概念。
2. DSL 设计
5 个动作,覆盖浏览器任务的绝大多数操作:
| 动作 | 语法 | 语义 | 校验规则 |
|---|---|---|---|
click(n) |
click + 元素编号 | 点击编号 n 的元素 | n 是正整数且 ≤ 当前页元素数 |
type(n, text) |
type + 编号 + 文本 | 清空后输入 text 到编号 n | n 合法 + text 非空且 ≤ 长度上限 |
scroll(dir) |
scroll + up/down | 页面滚动 | dir ∈ |
back() |
back 无参 | 浏览器后退 | 无 |
finish(answer) |
finish + 答案文本 | 结束任务并提交答案 | answer 非空 |
为什么是这 5 个:click/type 是核心交互,scroll 处理长页面(观察空间只看到视口内元素,要 scroll 才能看到更多),back 是回退(L06 可靠性用),finish 是任务终止信号(agent 循环的退出条件)。不设 goto(url)——直接跳转任意 URL 是安全风险(L07 域名 allowlist 才允许),导航通过点击链接完成。
DSL 文法(正则解析)
CLICK = r'click\((\d+)\)' # click(3)
TYPE = r'type\((\d+),\s*"(.*)"\)' # type(3, "查询词")
SCROLL = r'scroll\((up|down)\)' # scroll(down)
BACK = r'back\(\)' # back()
FINISH = r'finish\((.*)\)' # finish(答案是...)
LLM 输出一行文本,解析器用正则匹配→生成结构化 Action 对象。匹配失败 = 非法动作。
3. 校验器 + 结构化错误回注
本课的灵魂设计:非法动作不是崩溃,是结构化错误回注给模型重试。
LLM 输出: click(99) (当前页只有 9 个元素)
↓ 解析: Action(type=click, idx=99)
↓ 校验: idx=99 > 9 → 非法
↓ 不抛异常,返回: {"ok": False, "error": "元素编号 99 不存在,当前页只有 1-9 号元素"}
↓ 错误信息喂回 LLM: 「上一步动作非法:...。请重新选择合法编号。」
↓ LLM 重试: click(3) ✅
为什么这样设计:
| 方案 | 后果 |
|---|---|
| 非法动作直接抛异常 | agent 循环崩溃,任务中断——一次幻觉就全盘失败 |
| 非法动作静默忽略 | LLM 不知道自己错了,继续犯同样的错 |
| 结构化错误回注(本课) | LLM 得到反馈,自我纠正——这是 ReAct「观察回注」思想在错误处理上的应用 |
🎯 这和第七门课 Reflexion 的精神一致:失败不可怕,把失败的语言化反馈喂回去,下一轮就能改进。本课把它用在动作校验上——非法动作 = 一次小失败,错误信息 = 语言化反馈。
4. 执行器
校验通过的动作交给执行器,执行器调 L01 的 BrowserSession 落地:
| 动作 | 执行 |
|---|---|
click(n) |
取 L02 elements[n-1] 的 selector → session.click(selector) |
type(n, text) |
session.type(selector, text) |
scroll(down/up) |
session.page.mouse.wheel(0, ±800) |
back() |
session.page.go_back() |
finish(answer) |
设 done=True,返回 answer |
执行器也要兜底——元素可能已 detach(页面变了),捕获异常转结构化错误(和校验错误同构),让 L04 agent 循环统一处理。
5. 落地清单
本课是行动空间设计课,无 research-assistant 代码改动(落地在 L09)。产出:
| 文件 | 说明 |
|---|---|
README.md(本文件) |
三种行动空间对比 + DSL 设计 + 校验回注 + SoM 导读 |
code.py |
DSL 解析器 + 校验器 + 执行器 + 单测(合法/非法/边界) |
exercise.md |
练习 |
验收
cd gui-agent-lessons/03_action
# DSL 单测(零 API,纯解析校验)
python code.py
# 预期输出:
# - 合法动作解析:click(3)/type(3,"x")/scroll(down)/back()/finish(ans) 全部解析成功
# - 非法动作:click(99)(编号越界)/foo(1)(未知动作)/click(abc)(参数非整数)→ 结构化错误
# - 执行演示(需 playwright + L00 服务):click 进详情、finish 提取答案
⚠️ 单测部分零依赖纯 Python 可跑;执行演示需 playwright + L00 本地服务。
6. 本课在两条主线上的位置
- 评估主线:本课不直接产指标,但「结构化错误回注」直接影响 L08 评估的「步数效率」——非法动作不崩、能重试,agent 才不会因一次幻觉就任务失败。L06 会量化「加固后非法动作重试成功率」,对照的就是本课的校验回注机制。
- 观察-行动接口主线:本课是行动空间的正式开篇。L02 给了编号(观察),本课让编号成为动作目标(
click(n))——观察→行动的桥梁在编号上合拢。L04 把 observe(L02)+ act(本课)装进循环,就是第一个完整 GUI agent。本课的「可校验可白名单」也是 L07 安全主线的伏笔——动作层拦截的前提是动作是结构化的 DSL 而非自由代码。
🎯 面试话术
「行动空间我用受限 DSL 而非自由代码——click(n)/type(n,text)/scroll/back/finish 五招,浏览器场景里可校验可白名单比表达力重要。非法动作不崩,转结构化错误回注重试,这是 Reflexion 的精神用在动作校验上。编号 n 直接用 L02 观察空间里的元素编号,观察→行动在编号上合拢。这层 DSL 也是 L07 安全的伏笔——动作结构化了才能在执行前拦截恶意跳转。」