版本:1.0
本文档定义了 ko-browser 输出的 AX Tree 快照文本格式。
该格式是 ko-browser 的核心差异化设计,面向 LLM/AI Agent 场景做了极致的 token 优化。
ko-browser 的快照是对浏览器 Accessibility Tree(无障碍树)的结构化文本表示。每个可交互或有意义的页面元素被分配一个递增序号,LLM 可以直接使用该序号来执行操作。
Page: "百度一下,你就知道"
1: link "新闻"
2: link "hao123"
3: link "地图"
4: textbox "搜索" focused
5: button "百度一下"
6: list
7: listitem
8: link "关于百度"
9: listitem
10: link "About Baidu"
11: heading "热搜榜"
12: link "植树造林 总书记强调要"利民""
13: img "百度Logo" value="https://www.baidu.com/img/logo.png"
每个元素占一行,格式为:
<indent><id>: <role> "<name>" [value="<value>"] [<state1> <state2> ...]
| 部分 | 说明 | 是否必须 |
|---|---|---|
<indent> |
2 空格 × 层级深度,表示树的层次关系 | 是(根层级无缩进) |
<id> |
递增整数序号(从 1 开始),唯一标识该元素 | 是 |
: |
冒号分隔符,紧跟序号后面 | 是 |
<role> |
元素的 ARIA 角色,如 link、button、textbox |
是 |
"<name>" |
元素的可访问名称,用双引号包裹 | 否(无名称时省略) |
value="<value>" |
元素的值(仅在与名称不同时显示) | 否 |
<states> |
元素状态,空格分隔,如 focused、disabled、checked |
否 |
4: textbox "搜索" focused
│ │ │ │
│ │ │ └── 状态:当前聚焦
│ │ └────────── 名称:搜索
│ └────────────────── 角色:文本输入框
└───────────────────── 序号 4,可用 b.Click(4) 操作
页面的根文档节点使用特殊格式:
Page: "页面标题"
- 根节点不分配序号(不可交互)
- 后面紧跟一个空行,再开始列出子元素
- 如果页面无标题,则不输出此行
| 规则 | 说明 |
|---|---|
| 从 1 开始 | 第一个非根元素编号为 1 |
| 递增不跳号 | 严格按深度优先遍历顺序 +1 |
| 根节点不编号 | Page: 行不占用序号 |
| 每次快照重新编号 | 序号是临时的,下次 Snapshot() 会重新分配 |
| 引用方式 | CLI: ko-browser click 5;Library: b.Click(5) |
序号在每次快照时重新生成。页面 DOM 变化后,同一个元素的序号可能不同。因此:
- ✅ 获取快照 → 立即使用序号操作
- ❌ 缓存序号跨多次快照使用
使用 2 个空格 表示一级层次:
1: navigation "主导航"
2: link "首页"
3: link "产品"
4: link "产品A"
5: link "产品B"
6: link "关于"
- 根层级(顶层元素):无缩进
- 每深一层:+2 空格
- 缩进仅用于视觉展示层级关系,不影响序号分配
- 用双引号包裹:
"搜索" - 超过 80 字符时截断并加
...:"这是一段很长的文本内容会被截断到八十个字符以内..." - 无名称时省略(仅显示
id: role)
- 仅当值不等于名称时才显示
- 格式:
value="内容" - 超过 50 字符时截断
- 典型场景:表单输入框的当前输入值
4: textbox "用户名" value="admin"
5: textbox "密码"
状态紧跟在名称/值之后,空格分隔:
| 状态 | 含义 | 场景 |
|---|---|---|
focused |
当前聚焦 | 输入框、按钮 |
disabled |
不可用 | 灰色按钮 |
checked |
已勾选 | 复选框、单选框 |
expanded |
已展开 | 下拉菜单、手风琴 |
collapsed |
已折叠 | 折叠面板 |
selected |
已选中 | 标签页、列表项 |
required |
必填 | 表单字段 |
readonly |
只读 | 不可编辑的输入框 |
multiline |
多行 | textarea |
3: textbox "邮箱" required focused
7: checkbox "记住我" checked
9: button "提交" disabled
以下是快照中常见的 ARIA 角色:
| 角色 | 说明 | 典型操作 |
|---|---|---|
link |
超链接 | Click |
button |
按钮 | Click |
textbox |
文本输入框 | Type / Fill |
checkbox |
复选框 | Check / Uncheck |
radio |
单选框 | Click |
combobox |
下拉选择框 | Select |
slider |
滑块 | 暂不支持 |
tab |
标签页 | Click |
menuitem |
菜单项 | Click |
searchbox |
搜索框 | Type / Fill |
spinbutton |
数字步进器 | Type |
switch |
开关 | Click |
| 角色 | 说明 |
|---|---|
heading |
标题(h1-h6) |
navigation |
导航区域 |
list |
列表 |
listitem |
列表项 |
table |
表格 |
row |
表格行 |
cell |
表格单元格 |
img |
图片 |
banner |
页头 |
main |
主内容区 |
complementary |
侧边栏 |
contentinfo |
页脚 |
dialog |
对话框 |
alert |
警告提示 |
status |
状态信息 |
| 工具 | 格式 | 示例 | 引用方式 |
|---|---|---|---|
| agent-browser | @eN role "name" |
@e5 button "Submit" |
click @e5 |
| ko-browser v0 (旧) | [N] role "name" |
[5] button "Submit" |
click 5 |
| ko-browser v1 (当前) | N: role "name" |
5: button "Submit" |
click 5 |
1. Token 效率最高
对于 LLM token 计算,每个字符都很重要:
| 格式 | 文本 | 额外字符数 |
|---|---|---|
@e5 |
@e5 button "Submit" |
3 (@, e, 空格) |
[5] |
[5] button "Submit" |
3 ([, ], 空格) |
5: |
5: button "Submit" |
1 (:) |
在一个典型页面快照中(100+ 元素),id: 格式比 [id] 格式节省约 200 个字符(~50-100 token)。
2. 引用最简洁
用户(或 LLM)引用元素时,直接使用数字即可:
ko-browser click 5 # ← 最简洁
ko-browser click @e5 # ← agent-browser 风格
ko-browser click [5] # ← 需要转义方括号在 Library API 中同样简洁:
b.Click(5) // ← 数字即可3. 对 LLM 友好
- 数字是所有 LLM tokenizer 最高效编码的内容
- 冒号
:是常见分隔符,不会造成 tokenizer 意外切分 - 无需特殊前缀(
@e)或包裹字符([]),减少 LLM 出错概率 - LLM 生成
click 5比生成click @e5或click [5]更不容易出错
4. 阅读不模糊
冒号 : 是天然的键值分隔符,5: button "Submit" 的语义清晰:
5是编号button是角色"Submit"是名称
snap, _ := b.Snapshot()
// snap.Text 就是本文档定义的格式化文本
fmt.Println(snap.Text)
// 输出:
// Page: "百度一下,你就知道"
//
// 1: link "新闻"
// 2: link "hao123"
// 3: textbox "搜索" focused
// 4: button "百度一下"
// snap.IDMap 是 id → BackendDOMNodeID 的映射
// 当你调用 b.Click(3) 时,内部通过 IDMap 查找到 CDP 节点 ID
// snap.Nodes 是完整的过滤后树结构,供需要自定义处理的用户使用<snapshot> ::= [<page-line> "\n\n"] <element-lines>
<page-line> ::= 'Page: "' <title> '"'
<element-lines> ::= (<element-line> "\n")*
<element-line> ::= <indent> <id> ": " <role> [" " <quoted-name>] [" " <value>] [" " <states>]
<indent> ::= (" ")*
<id> ::= [1-9][0-9]*
<role> ::= [a-z]+
<quoted-name> ::= '"' <text-80> '"'
<value> ::= 'value="' <text-50> '"'
<states> ::= <state> (" " <state>)*
<state> ::= "focused" | "disabled" | "checked" | "expanded" | "collapsed"
| "selected" | "required" | "readonly" | "multiline"
<text-80> ::= .{1,80} | .{1,77} "..."
<text-50> ::= .{1,50} | .{1,47} "..."
Page: "Google"
1: combobox "搜索" focused
2: button "Google 搜索"
3: button "手气不错"
4: link "Gmail"
5: link "图片"
Page: "登录 - GitHub"
1: link "GitHub"
2: heading "登录到 GitHub"
3: textbox "用户名或邮箱" required
4: textbox "密码" required
5: link "忘记密码?"
6: button "登录"
7: link "创建账户"
Page: "Hacker News"
1: link "Hacker News"
2: link "new"
3: link "past"
4: link "comments"
5: table
6: row
7: link "Show HN: A tool I built"
8: link "example.com"
9: link "42 points"
10: link "username"
11: link "15 comments"
12: row
13: link "Why Rust is awesome"
14: link "blog.example.com"
15: link "128 points"
16: link "another_user"
17: link "67 comments"
18: link "More"
LLM 收到快照:
Page: "Google"
1: combobox "搜索" focused
2: button "Google 搜索"
3: button "手气不错"
LLM 决策: 在搜索框输入并搜索
LLM 输出:
fill 1 "Go 语言教程"
click 2
→ ko-browser 执行:
b.Fill(1, "Go 语言教程")
b.Click(2)
| 版本 | 日期 | 变更 |
|---|---|---|
| 1.0 | 2026-03-31 | 初始版本:确定 id: role "name" states 格式 |
| — | (之前) | 旧格式 [id] role "name" states,因 [ ] 浪费 token 而废弃 |