每日一个开源项目(第143篇):page-agent - 纯 JS 的网页 GUI Agent,无需截图、无需插件、无需后端

引言

"一行 script 标签,自然语言控制你的 Web 应用。不需要截图,不需要浏览器扩展,不需要后端服务,不需要 Python。"

这是"每日一个开源项目"系列的第143篇文章 。今天的主角是 page-agent------阿里巴巴开源的客户端 GUI Agent 库。

让 AI 自动操作浏览器,现有的主流方案几乎都走同一条路:截屏 → 多模态 LLM 识别页面元素 → 执行操作。这条路有两个代价:需要多模态模型(贵),需要在服务器端或无头浏览器里运行(复杂)。

page-agent 的回答是:把 DOM 树序列化成文本就够了。交互元素编上索引,LLM 读文本、返回"点击索引 3",在浏览器里直接执行。整个过程不离开页面,不需要截图,不需要多模态能力。

你将学到什么

  • 文本化 DOM 的原理:如何把页面变成 LLM 可读的带索引结构
  • ReAct 循环架构:Observe → Think(反思 + 行动)→ Act 的完整实现
  • 反思模型(Reflection-Before-Action):每步先评估上步效果再决策
  • 内置工具系统:点击、输入、滚动、执行 JS 的工具定义方式
  • 单包 vs 多包:page-agent(含 UI)和 @page-agent/core(纯核心)
  • Chrome 扩展 + MCP Server 的跨页面能力扩展

前置知识

  • 了解 LLM 的工具调用(Function Calling)机制
  • 了解 DOM 和浏览器事件的基本概念
  • 使用过 OpenAI SDK 或类似 API

项目背景

项目简介

page-agent 是一个纯客户端的 GUI Agent 库,把 LLM 的推理能力嵌入到网页里,让 AI 通过文本化 DOM 理解页面结构并执行操作。

核心设计决定:不用截图,用 DOM 文本。浏览器的 DOM 本身就完整地描述了页面上有什么元素、它们是什么类型、它们现在是否可交互。把这些信息序列化成文本,比截图更精确(像素级截图里按钮文字有时模糊),更便宜(不需要多模态模型),更快(DOM 读取是同步操作)。

项目致谢了 browser-use(Python 端服务器侧浏览器自动化库),page-agent 的定位是它的客户端版本:在页面内运行,而不是在服务器端控制无头浏览器。

作者/团队介绍

  • 组织: Alibaba(阿里巴巴)
  • 主要语言: TypeScript
  • License: MIT
  • npm 包 : page-agent(含 UI Panel)、@page-agent/core(纯核心逻辑)
  • 最新版本: v1.10.0

项目数据

  • 📄 License: MIT
  • 📦 npm: page-agent
  • 💻 技术栈: TypeScript + npm workspaces + Vite

主要功能

核心作用

复制代码
传统浏览器 GUI Agent(截图路线):
页面 → 截图 → 多模态 LLM 视觉理解 → 返回坐标/元素 → 执行点击
成本:多模态 API 费用高 + 服务器/无头浏览器基础设施

page-agent(文本 DOM 路线):
页面 → DOM 序列化为带索引文本 → 纯文本 LLM 推理 → 返回工具调用(click_element_by_index: 3)→ 直接在页面内执行
成本:纯文本 LLM(更便宜)+ 零后端(纯前端 JS)

使用场景

  1. SaaS 产品 AI Copilot:在你自己的产品里嵌入 AI 助手,用户说"帮我创建一个新项目,名字叫 X"------几行代码实现,不需要后端改动
  2. 智能表单填写:ERP、CRM、后台管理系统里的 20 步操作压缩成一句话
  3. 无障碍增强:为任意 Web 应用添加自然语言操作能力,语音命令、屏幕阅读器场景
  4. 跨标签页 Agent:配合 Chrome 扩展,Agent 可以跨页面工作(例如:从表格里读取数据,切换到另一个标签页填写)
  5. MCP 控制:通过 MCP Server(Beta)让外部 Agent 客户端控制浏览器

快速开始

一行接入(使用免费 Demo LLM,仅限技术评估)

html 复制代码
<script src="https://cdn.jsdelivr.net/npm/page-agent@1.10.0/dist/iife/page-agent.demo.js" crossorigin="true"></script>

加载后页面右下角出现 Agent 面板,可以直接输入自然语言命令。

NPM 安装(生产用法)

bash 复制代码
npm install page-agent
javascript 复制代码
import { PageAgent } from 'page-agent'

const agent = new PageAgent({
    model: 'qwen3.5-plus',
    baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
    apiKey: 'YOUR_API_KEY',
    language: 'zh-CN',
})

// 执行一个自然语言任务
const result = await agent.execute('点击登录按钮,然后填写用户名为 test@example.com')
console.log(result.success, result.message)

使用 @page-agent/core(无 UI 面板,嵌入式场景)

javascript 复制代码
import { PageAgentCore } from '@page-agent/core'
import { PageController } from '@page-agent/page-controller'

const pageController = new PageController({ enableMask: true })
const agent = new PageAgentCore({
    pageController,
    model: 'gpt-4o',
    apiKey: 'sk-...',
    maxSteps: 20,
    onAfterStep: async (agent, history) => {
        console.log('步骤完成', history.at(-1))
    }
})

const result = await agent.execute('查找产品列表中价格最低的商品并加入购物车')

支持的模型(OpenAI 兼容接口均可):

厂商 模型
OpenAI gpt-4o, gpt-4-turbo, gpt-5.2, gpt-5.4
Anthropic claude-opus-4.8, claude-sonnet-4, claude-haiku-3.5
阿里云 qwen3.5-plus, qwen3.6-max, qwen3.6-flash
DeepSeek deepseek-chat, deepseek-reasoner
Google gemini-2.0-flash(OpenAI 兼容端点)
本地 Ollama(qwen3:14b,RTX 3090 24GB 测试通过)

项目详细剖析

文本化 DOM:核心技术原理

page-agent 的 PageController 把 DOM 转换成一种带索引的简化文本结构:

ini 复制代码
原始 DOM(简化):
<div class="form-container">
  <input type="text" placeholder="用户名" />
  <input type="password" placeholder="密码" />
  <button class="btn-primary">登录</button>
  <a href="/register">注册账号</a>
</div>

序列化为 LLM 输入(带索引的交互元素):
URL: https://example.com/login
Title: 登录 - My App

[0]<input placeholder="用户名" />
[1]<input type="password" placeholder="密码" />
[2]<button>登录</button>
[3]<a>注册账号</a>

每个可交互元素分配一个数字索引 [N],LLM 只需要返回"点击 2"或"在 0 输入 admin@example.com"。

DOM 处理流水线

css 复制代码
实时 DOM
    ↓ dom_tree/ 模块
FlatDomTree(扁平化的树结构,含 DomNode 映射)
    ↓ 脱水(Dehydration)
带索引的简化 HTML 文本
    ↓
传入 LLM 上下文
    ↓
LLM 返回工具调用(如:click_element_by_index: {index: 2})
    ↓
PageController.clickElement(2) → 找到对应 HTMLElement → 触发点击

只有满足以下条件的元素才会被标注索引(避免噪声):

  • isVisible: true(元素在视口内或虽不在视口但可滚动到)
  • isInteractive: true(可点击/输入/选择的元素)
  • isTopElement: true(不被其他元素遮挡)

ReAct 循环架构

bash 复制代码
agent.execute("完成某个任务")
    ↓
┌─────────────────────────────────────────┐
│              主循环(最多 maxSteps 次)     │
│                                         │
│  ① Observe                              │
│     pageController.updateTree()         │
│     → 刷新 DOM,获取当前页面文本            │
│                                         │
│  ② Think(LLM 调用)                      │
│     输入:系统 prompt + 历史 + 当前 DOM     │
│     LLM 输出(反思模型):                  │
│     {                                   │
│       evaluation_previous_goal: "...",  │← 上一步做得怎么样?
│       memory: "...",                    │← 需要记住什么?
│       next_goal: "...",                 │← 下一步要做什么?
│       action: { click_element_by_index: {index: 2} }
│     }                                   │
│                                         │
│  ③ Act                                  │
│     执行 LLM 返回的工具调用               │
│     记录到 history(持久化,下步可见)      │
│                                         │
│  ④ 检查是否结束                           │
│     LLM 调用 done 工具 → 返回结果          │
│     或达到 maxSteps 上限                  │
└─────────────────────────────────────────┘
    ↓
ExecutionResult { success, message, history }

反思模型(Reflection-Before-Action)

每一步 LLM 调用前,会把上一步的执行结果和历史传给模型,要求它先"反思"再行动:

json 复制代码
{
  "evaluation_previous_goal": "成功点击了登录按钮,页面跳转到首页",
  "memory": "已完成登录,当前在首页,还需要找到设置页面",
  "next_goal": "找到导航栏中的设置或账户选项并点击",
  "action": {
    "click_element_by_index": { "index": 5 }
  }
}

evaluation_previous_goal 强制模型在执行下一步之前评估上一步效果,避免盲目继续(例如:点击了但页面没反应,应该换策略而不是再点一次)。

memory 字段是短期记忆机制------把关键进展压缩成 1-3 句话存入历史,让长任务里 LLM 不会"忘记"已经完成了什么。

内置工具系统

LLM 可以调用的工具(通过 Function Calling 机制):

工具 作用
click_element_by_index 点击指定索引的元素
input_text 在指定索引的输入框里输入文字
select_dropdown_option 选择下拉框选项(按文字内容匹配)
scroll 垂直滚动(支持按页数或像素)
scroll_horizontally 水平滚动
execute_javascript 执行任意 JS 代码(支持 AbortSignal 取消)
wait 等待 1-10 秒(等页面加载或动画完成)
ask_user 向用户提问(人机协作节点)
done 任务完成,返回结果

自定义工具(扩展默认行为):

javascript 复制代码
const agent = new PageAgent({
    model: 'gpt-4o',
    apiKey: '...',
    customTools: {
        // 添加自定义工具
        get_current_user: {
            description: '获取当前登录用户信息',
            inputSchema: z.object({}),
            execute: async function() {
                const user = await fetchCurrentUser()
                return JSON.stringify(user)
            }
        },
        // 设为 null 可以禁用内置工具
        execute_javascript: null
    }
})

Monorepo 结构

swift 复制代码
packages/
├── page-agent/        → 主包(npm: page-agent),包含 UI 面板
├── core/              → 纯核心逻辑(npm: @page-agent/core),无 UI
├── llms/              → LLM 客户端,支持多厂商
├── page-controller/   → DOM 操作和可视化反馈
├── ui/                → 控制面板 + 国际化,与核心解耦
├── extension/         → Chrome 扩展(WXT + React)
└── website/           → 文档网站(React)

核心的模块边界设计:

  • page-controller 不知道 LLM 的存在,只负责 DOM 操作
  • llms 不知道页面结构,只负责 LLM 通信
  • core 把两者组合成 ReAct 循环
  • page-agent(主包)在 core 上层加 UI 面板,两者可独立使用

跨页面能力:Chrome 扩展 + MCP

Chrome 扩展(适合跨标签页任务):

  • Agent 在一个扩展控制的上下文里运行,可以切换标签页、读取不同页面的 DOM
  • 适合"从 A 页面抓数据,填到 B 页面"这类需要跨页面的工作流

MCP Server(Beta)

  • 把 page-agent 暴露为 MCP 工具,让外部 Agent 客户端(如 Claude Desktop、Claude Code)远程控制浏览器
  • 适合需要把浏览器控制能力接入更大 Agent 工作流的场景

性能和可靠性设计

  • 步骤延迟:步骤间默认等待 400ms,给页面渲染和网络请求留出时间
  • 点击后等待:点击操作后等待 200ms,确保 DOM 更新完成
  • 并发保护 :防止 execute() 并发调用,避免竞态条件
  • AbortSignal 支持 :所有工具执行都接受取消信号,execute_javascript 也支持中断
  • 自动重试:LLM 调用失败(429 限流、500 服务器错误)自动指数退避重试
  • Token 用量追踪:每步记录 promptTokens、completionTokens,支持 Prompt Cache 命中统计

项目地址与资源

官方资源


总结

page-agent 的核心洞察是:浏览器自动化不一定需要"看图"。DOM 本身就是结构化的页面描述,把它序列化成带索引的文本,文本模型就能直接理解和操作------不需要多模态,不需要视觉理解,不需要坐标定位。

这个洞察把 GUI Agent 的门槛大幅降低:不需要服务器,不需要无头浏览器,不需要截图,一个 script 标签或一个 npm 包,在用户浏览器里直接运行。

反思模型(每步先评估上步 → 再规划下步)和持久化历史(关键信息压缩进 memory)是它处理多步骤任务的核心机制,保证 Agent 在长任务里不会迷失方向。

对于想给自己的 Web 产品加 AI Copilot 能力、或者想用 LLM 自动化内部工具操作的开发者,page-agent 是目前接入成本最低、对后端架构零侵入的选项之一。


探索 PrimeSkills ------ 精选 AI Agent 与技能的市场,每一个都经过真实企业工作流验证,去掉浮夸,留下真正有用的。

欢迎访问我的个人主页,发现更多有价值的见解和有趣的产品。

相关推荐
天渺工作室3 小时前
实现一个adblock/adblock plus等浏览器广告拦截器检测插件
前端·javascript
阳光是sunny4 小时前
Vue 项目怎么做用户行为全链路监控?轻量插件方案详解
前端·面试·架构
ZhengEnCi4 小时前
Q04-Vite禁用CSS代码分割-解决生产环境样式加载顺序混乱问题
前端·vue.js·vite
九酒4 小时前
AI Agent 开发踩坑记:口播功能非得用 APP 原生实现吗?
前端·人工智能·agent
蝎子莱莱爱打怪4 小时前
DSpark 讲透:DeepSeek 不换模型,硬把 V4 提速 85%,是怎么做到的?
人工智能·面试·程序员
Jackson__5 小时前
做了一段时间的AI coding后,我终于搞清了 CLI 和 MCP 的区别
前端·agent·ai编程
巫山老妖6 小时前
置身AI内
人工智能
IT_陈寒7 小时前
JavaScript项目实战经验分享
前端·人工智能·后端
用户47949283569158 小时前
6w star,GitHub 趋势第一的 Ponytail,这个agent插件到底在火什么
前端·后端
vanuan9 小时前
两个AI智能体第一次对话-A2A双Agent协作实战
人工智能