基于 DOM 信息进行页面操作的 page-agent
使用 AI Agent 进行页面操作的实践已经挺多了,但更多的是以浏览器插件形式存在的。最近阿里发布了 alibaba/page-agent: JavaScript in-page GUI agent. Control web interfaces with natural language. 可以直接在页面内嵌入脚本来进行 AI Agent 的页面操作。
关于 page-agent 的实现,我们简单过一下执行流程,然后重点关注两个功能:一个是如何让 Agent 准确理解页面内容,另一个是如何让 Agent 可以准确的对页面进行操作。
执行流程
page-agent 的执行流程与普通的 agent 区别不大:
- 业务代码创建
new PageAgent(config) PageAgent内部创建PageControllerPageAgent继承PageAgentCorePageAgentCore.execute(task)启动任务循环:- 每一步先通过
pageController.getBrowserState()获取当前页面状态 - Core 组装 system prompt + user prompt
LLM.invoke()调用模型,并强制模型调用宏工具AgentOutput- 宏工具里包了一层“反思 + 动作”结构
- Core 从动作中找到具体 tool
- tool 再调用
PageController的 DOM 方法执行动作 - 执行结果进入 history,继续下一步,直到
done
- 每一步先通过
对应关系如下:
1 | PageAgent |
页面内容提取
首先,我们来看页面内容的提取。现在主流实现 browser use 有两种方向:
- 一种是对页面进行截图,让多模态模型通过视觉理解返回要操作的坐标位置,进行对应的操作。类似的有字节的 Midscene - Vision-Driven UI Automation
- 另一种是直接获取 DOM 的信息,文本化后传给模型。这么做的好处是不依赖多模态模型,任何一个纯文本模型都可以进行识别和操作。但坏处就是它只能理解 DOM 信息,对于复杂的交互,比如 Canvas、SVG 这种就不是很好好操作了。并且如果一个页面它的布局跟 DOM 流不是严格对应的,比如用了过多的 absolute 和 fix 布局,那么通过 DOM 信息来获取的页面内容可能会不太准确。
page-agent 是通过将 DOM 信息文本化来提取页面内容的,这使得它可以方便的集成在页面中,同时也不要求必须使用多模态模型。
我们使用以下例子来快速了解 page-agent 是如何文本化 DOM 信息的,假设原页面是一个带顶部导航、筛选表单、结果列表和分页器的后台页面,文本化后的内容大致为:
1 | [0]<a aria-label=Company Home /> |
可以看到 page-agent 以文本形式结构化了 DOM 信息,每个元素前面都加上了固定的 ID,其内部会维护每个 ID 与实际 DOM 元素的对应关系。
同时我们还注意到 page-agent 会把 DOM 的信息添加在文本信息中,比如是否已经选中了,是否可以滚动等等。
操作动作执行
模型每一步不是直接输出动作,而是必须调用一个宏工具,输入结构类似:
1 | { |
也就是说,模型被要求:
- 先评估上一动作效果
- 再写短期记忆
- 再说明下一目标
- 最后选择一个实际动作
packages/core/src/tools/index.ts 定义了内置工具集合,本质上是:
PageAgentToolMap<string, PageAgentTool>
目前主要工具有:
donewaitask_userclick_element_by_indexinput_textselect_dropdown_optionscrollscroll_horizontallyexecute_javascript
这些工具会对 DOM 元素进行精细的操作。
Prompt
从实现上看,每一步发给模型的是两条 message:
systemuser
system message 来源有两种:
- 默认的
packages/core/src/prompts/system_prompt.md - 或
config.customSystemPrompt完全覆盖默认 prompt
system prompt 负责定义 Agent 的全局行为规则,而不是当前页面状态。
user message 由 #assembleUserPrompt() 拼装,包含四大块:
<instructions>这是可选块,由
#getInstructions()生成,可能包含:<system_instructions>来自config.instructions.system<page_instructions>来自config.instructions.getPageInstructions(url)<llms_txt>如果开启 experimentalLlmsTxt,会抓当前站点 /llms.txt
这块的作用是给默认 system prompt 追加“部署方自己的规则”和“页面级特殊说明”。
这块描述当前任务状态,主要包含:
<user_request>用户原始任务文本<step_info>当前是第几步,最大允许步数,当前时间
它解决的是“当前任务在什么阶段”这个问题。
` 这块是 Agent 的短期记忆。
对于之前的每个 step,会写入:
Evaluation of Previous StepMemoryNext GoalAction Results
此外还会写入系统 observation,例如:
- 页面 URL 变化
- 剩余 step 太少
- 等待时间过长
- 用户 takeover
这是重点,当前页面快照,来自
pageController.getBrowserState(),包括:header- 当前页面标题和 URL
- viewport/page 尺寸
- 当前滚动位置
- 页面上下方还剩多少内容
content- 文本化 DOM 主体
footer- 底部滚动提示
如果配置了
transformPageContent,则content会在送进 prompt 前先被改写。
最终可以把每一步发给模型的内容抽象成这样:
1 | system: |
总结
1 | User Task |