banner
约 4,200 字
14 分钟

OpenManus 开发实战:从 ReAct 工具循环到动态网页浏览器自动化

摘要

本文以动态航班检索为主线,拆解 OpenManus 的 ReAct 工具循环、浏览器状态建模、站点知识注入和 GUI-Plus 视觉操作。重点不在复述框架能力,而在分析动态网页自动化为什么会失败,以及不同改造分别解决了什么问题。

OpenManus 开发实战:从 ReAct 工具循环到动态网页浏览器自动化

写在前面

OpenManus 的价值不在于把大模型接到浏览器上,而在于把一次自然语言任务拆成可循环执行的推理、工具调用、环境观测和结果终止。浏览器自动化是观察这一机制最直接的场景:网页状态不断变化,模型必须知道当前页面有什么、下一步能做什么,以及何时已经完成任务。

本文以一个动态航班检索任务为主线。用户输入出发城市、到达城市和日期,Agent 需要完成网页操作并给出航班信息。实现依次经历三个阶段:

阶段

主要问题

核心改动

结论

基线浏览器 Agent

动态日期控件导致元素编号变化,步骤预算容易耗尽

DOM 元素编号与 ReAct 工具循环

能完成稳定页面操作,但对动态控件不稳

站点知识增强

模型不了解航班网站的 URL 规则和任务结束条件

元素分类、站点知识、当前时间和终止提示

缩短部分路径,但当前实现不是完整 RAG

GUI-Plus 视觉操作

DOM 语义不足以表达复杂控件的视觉位置

截图、视觉模型、原子动作 JSON 和调试记录

能观察和操作页面,端到端稳定性仍需验证

后文先解释 OpenManus 的执行闭环,再说明浏览器工具如何将网页转成模型可读状态,最后分别分析三种实现的有效边界。核心结论是:动态网页自动化并不是单一的点击问题,而是状态表示、领域知识、动作执行和完成判定四个问题的组合。

1. OpenManus 的执行闭环

OpenManus 采用 ReAct,Reasoning and Acting,推理与行动,作为主循环。模型根据当前记忆形成下一步工具调用;工具在网页、文件、代码环境或搜索服务中执行;执行结果重新写入记忆,成为下一轮推理的依据。只有满足明确的完成条件时,Agent 才应调用 terminate 结束任务。

OpenManus 的工具调用闭环(图由AI辅助绘制)
OpenManus 的工具调用闭环(图由AI辅助绘制)

这个结构中有四个关键对象。

对象

在本项目中的作用

不可替代的职责

Manus / ToolCallAgent

协调模型推理、工具调用和终止

决定下一步是否需要工具

Memory

保存用户请求、模型消息和工具观测

为模型提供连续上下文

BrowserUseTool

打开页面、读取 DOM、点击、输入、滚动和提取内容

将浏览器状态转化为可操作接口

Terminate

显式结束任务

防止已得到答案后继续无效循环

代码中的执行循环可以概括为以下逻辑。BaseAgent.run 控制步数和状态,ToolCallAgent 将模型输出的工具调用转换为实际执行,Manus 在浏览器场景下额外向模型补充当前页面状态。

Python
while current_step < max_steps and state != FINISHED:
    current_step += 1

    should_act = await agent.think()
    if should_act:
        await agent.act()

    if agent.is_stuck():
        agent.handle_stuck_state()

这段循环说明了 Agent 的边界。大模型不直接控制浏览器,而是提出工具意图;浏览器工具执行后返回页面观测;模型基于观测继续决策。因此,模型能力只决定推理质量,工具返回的信息质量同样决定最终成功率。

1.1 工具与执行环境

当前实现提供浏览器、搜索、Python、文件等工具。浏览器工具适合获取网页信息和完成交互;Python 与文件工具适合数据处理;隔离执行环境适合运行不可信代码。三者解决的不是同一个问题。

需求

适合的能力

需要验证的结果

查询实时页面并完成表单操作

浏览器工具

URL、页面字段和结果列表是否正确

处理本地数据或生成表格

Python 工具

程序输出、文件内容和异常信息

执行来源不完全可信的代码

Daytona 沙箱

进程隔离、文件范围和资源限制

Daytona 在此处提供的是执行隔离,而非更强的推理能力。SandboxManus 可以将代码任务放入远程沙箱,但浏览器动态控件是否被正确选择,仍取决于状态观察和动作验证。

2. 浏览器自动化的核心难点

浏览器工具并不会把完整网页交给模型,而是把可交互元素压缩为文本表示。例如 Document Object Model,文档对象模型,DOM 中的按钮、输入框和链接会被转成类似下面的状态:

纯文本
当前页面:航班检索页
[0]<input>出发城市</input>
[1]<input>到达城市</input>
[2]<input>出发日期</input>
[3]<button>搜索</button>

这种表示有两个优势:输入长度远小于整页 HTML,模型可以通过文本语义选择元素。BrowserContextHelper 会将 URL、页面标题、标签页、滚动状态和元素列表组合成下一轮提示词,使模型不必凭空猜测网页结构。

但动态页面会破坏这一假设。日期选择器、城市候选框和异步结果列表都可能在一次点击后重新渲染。旧编号随后指向不同元素,甚至消失。航班网站的日期控件还具有月份切换、禁用日期和遮罩层等状态,单靠元素编号很难保证操作正确。

动态日期控件的实际页面状态
动态日期控件的实际页面状态

上图中日期控件与航班列表同时存在。对于人来说,日期语义来自控件的视觉布局;对于仅依赖 DOM 编号的 Agent,日期可能只是多个相似的数字元素。这也是基线方案在精确设置日期时容易反复尝试的根因。

2.1 从页面状态到动作验证

可靠的浏览器 Agent 不应只判断是否点击成功,而应验证点击是否改变了目标状态。航班检索至少需要验证以下事实:

阶段

需要确认的状态

推荐证据

城市输入

出发地和目的地已写入对应字段

输入框文本或页面 URL

日期选择

日期字段与查询参数一致

日期控件文本和 depdate 参数

提交查询

页面已进入结果页

URL、标题或结果容器

结果提取

返回目标日期和目标航线的航班

航班号、起降时间、价格等结构化字段

任务结束

已向用户返回足够信息

调用 terminate,不再继续浏览

下面的流程将三种策略放到同一条执行链上。优先使用稳定的 DOM 语义;站点知识补充日期、URL 和任务结束规则;只有 DOM 无法稳定表达目标时,才引入视觉模型。最后必须回到页面状态验证,不能将视觉点击本身当作成功。

浏览器策略选择与验证流程(图由AI辅助绘制)
浏览器策略选择与验证流程(图由AI辅助绘制)

3. 代表性案例:动态航班检索

案例输入是单程航班查询,目标是从上海到北京并指定出发日期。它包含城市输入、日期选择、查询提交和结果读取四类动作,足以暴露浏览器 Agent 的主要问题。

3.1 基线方案:DOM 编号与步骤预算

基线版本由 BrowserUseTool 提供 go_to_urlclick_elementinput_text、滚动和内容提取等动作。模型在每轮推理后选择一个工具,浏览器返回最新元素列表,再进入下一轮。

它的优点是行为可审计。每一步都有工具参数和观测结果,可以定位模型到底是选错了元素、页面没有加载,还是没有结束任务。其局限也很明确:编号只是当前 DOM 快照的临时索引,不是元素的持久身份。

在这一类任务中,步骤上限是实际约束。早期运行记录使用 max_steps=20;当前代码中 ToolCallAgent 的默认值为 30。两者属于不同版本或配置,不能直接将某次成功或失败归因于模型能力。工程上应将步数、超时和重试策略统一写入任务配置,并记录每步轨迹。

基线方案适合结构稳定、页面交互少的任务,例如固定表单、后台检索页或有明确 idname 的控件。面对动态日历,它需要更强的页面语义和明确的完成标准。

3.2 站点知识增强:改善信息不足,而非替代浏览器

第二个版本增加了两类改动。

第一类是 ElementClassifier。它根据标签、文本、classidroletype 等特征给交互元素打上类别,例如 INPUTBUTTONLINKTABSELECTCALENDAR。日期数字在特定元素结构中会优先归为 CALENDAR,并给出置信度。它的作用是把杂乱的元素列表转换为更容易推理的语义候选集。

Python
if tag in interactive_tags and text.isdigit() and 1 <= int(text) <= 31:
    return ElementCategory.CALENDAR, 95

return ElementCategory.OTHER, 0

第二类是知识注入。实现将 knowledge/travel_tools.txt 中的航班 URL 形式、城市代码和操作注意事项在启动时加入系统提示词,同时注入当前时间,并要求模型优先从元素列表获取信息、避免重复调用 extract_content,在已经拿到查询结果后及时结束任务。

这里需要区分概念。代码读取知识目录下的文本并整体拼接到系统提示词中,没有根据用户问题执行向量检索、排序和上下文选择。因此它更准确地说是静态领域上下文注入,而不是完整的 Retrieval-Augmented Generation,检索增强生成,RAG。真正的 RAG 至少还应包含按任务检索相关片段,以及必要的重排序和引用追踪。

一次运行记录显示,Agent 已构造出包含目标日期的结果页 URL,并识别到 99 个可交互元素;元素分类结果包括 DATEINPUTBUTTONLINKTAB 等。说明领域知识和元素语义确实缩短了到达结果页的路径。

知识增强版本的结果提取与终止记录
知识增强版本的结果提取与终止记录

但日志也暴露出更重要的问题:模型在已经获得航班信息后仍继续向用户询问偏好,没有立即终止,造成重复上下文累积。也就是说,信息获取成功不等于任务完成。知识注入改善了导航,却没有自动形成可靠的完成判定。

改动

解决的问题

不能解决的问题

元素分类

让日期、输入框和按钮更容易区分

不能保证元素编号在重渲染后仍有效

URL 与城市代码知识

减少模型对网站规则的猜测

站点改版后容易失效

当前时间注入

避免日期语义与系统时间脱节

不会自动选择正确日期控件

终止提示

降低重复浏览概率

不能代替可验证的任务完成条件

如果要将该实现升级为真正可维护的 RAG,应把站点知识拆成可检索片段,并按任务类型召回。例如航班查询只检索城市代码、日期规则和结果字段定义;页面改版后只更新对应文档。检索到的知识需要带来源和版本,避免静态提示词不断膨胀。

3.3 GUI-Plus:用视觉补足 DOM 的盲区

第三个版本增加了图形用户界面,Graphical User Interface,GUI,视觉操作能力。浏览器先截取当前可视区域,再将截图和任务描述交给 GUI-Plus;模型返回一个严格的 JavaScript Object Notation,JavaScript 对象表示法,JSON,原子动作,例如点击坐标、输入文本、滚动或结束。浏览器执行动作后保存截图和标记,供后续定位问题。

简化后的输入输出可以表示为:

JSON
{
  "thought": "日期控件已展开,目标日期位于当前月份网格中",
  "action": "CLICK",
  "params": {
    "x": 652,
    "y": 537
  }
}

这种方式的优势不是替代所有 DOM 操作,而是能处理 DOM 文本难以表达的视觉结构,例如日期格、画布控件、悬浮遮罩和复杂布局。下图保存了元素编号覆盖层和视觉点击标记,可用于检查视觉动作是否落在预期区域。

GUI-Plus 调试截图:页面元素编号与视觉点击标记
GUI-Plus 调试截图:页面元素编号与视觉点击标记

现有实现同时保留了基于 Locator、JavaScript 查找和视觉模型的多种定位路径,以提高实验兼容性。但这类隐式切换在生产系统中需要谨慎:如果系统悄悄从 DOM 操作转为坐标点击,失败原因会变得不透明,也难以复现。

更稳妥的做法是将策略选择显式化:先判断页面是否提供稳定语义;视觉模型只在指定条件下启用;每次视觉操作后必须检查目标字段、URL 或结果容器是否变化。当前 GUI 路径的运行日志仍出现日期参数错误和重复答复,说明视觉模型已能参与页面操作,但尚不能据此认定端到端任务稳定完成。

4. 三种方案应该如何选择

三种实现不是替代关系,而是逐层增加对复杂状态的表达能力。

方案

适用场景

优势

主要风险

DOM 编号操作

静态表单、内部系统、结构稳定页面

低成本、可审计、结果可复现

动态重渲染后索引失效

领域知识增强

URL 规则明确、重复任务多、站点相对稳定

减少探索步骤,补齐模型不知道的业务规则

静态知识过期;若无检索机制,提示词持续膨胀

GUI-Plus 视觉操作

日期面板、画布、复杂弹窗、DOM 语义缺失的页面

能直接利用视觉布局

坐标偏移、分辨率变化、成本和响应时间更高

工程上推荐以 DOM 方案作为主路径,以领域知识减少无效探索,以视觉能力处理明确列出的难点控件。无论走哪条路径,最终都必须由可观察状态证明成功。

5. 从实现中得到的工程改进

5.1 将任务完成条件写成可验证状态

当前 terminate 更多依赖模型自觉调用。对于查询任务,应先定义结构化完成条件,再允许终止。例如:

纯文本
城市字段 = 上海、北京
日期参数 = 目标日期
结果页 = 已加载
结果列表 = 至少包含一条目标航线记录
输出字段 = 航班号、起降时间、价格或无结果说明

这比在提示词中要求任务完成后结束更可靠。模型可以负责规划,但完成与否应尽可能由页面状态、URL 参数或结构化结果验证。

5.2 检测语义循环,而不只检测重复文本

当前 is_stuck() 主要检测相同的助手消息是否重复。动态网页中的失败循环往往不是完全相同的文本,而是反复点击同类控件、反复提取同一页面或在已获得结果后继续对话。

更合理的检测对象是工具轨迹:连续多次页面 URL 不变、目标字段未变化、同一类工具调用未带来新观测,都应触发重新规划或人工介入。这样可以减少无效 token 消耗,也让失败原因更明确。

5.3 将站点知识改为按需检索

静态知识文件适合快速验证,但不适合长期积累。后续可以按网站、任务类型和页面版本拆分知识,检索时返回最小必要片段。例如查询航班时只注入城市代码、日期参数和结果字段;处理改签时再检索订单页规则。这样既能控制上下文长度,也便于追踪规则来源和过期时间。

5.4 将视觉操作纳入可复现的调试链路

视觉模型输出坐标后,至少需要保存三类证据:操作前截图、模型动作 JSON、操作后截图。对于点击操作,还应记录页面尺寸、设备缩放比例和目标元素状态。现有调试截图已经提供了一个良好起点,但文件名中的 URL 等动态内容需要统一编码或清洗,避免在不同操作系统上造成路径问题。

5.5 用测试集而不是单次演示评估 Agent

单次成功只能说明某条轨迹可行,不能说明系统稳定。浏览器 Agent 至少应覆盖以下测试:

测试维度

示例

页面状态

首次加载、弹窗出现、异步刷新、登录失效

日期逻辑

当月、跨月、跨年、不可选日期

输入歧义

城市同名、机场多选、往返与单程

结果验证

有航班、无航班、价格缺失、页面改版

失败处理

步数耗尽、视觉模型无效 JSON、工具超时

测试结果应保存任务、工具轨迹、页面截图、最终 URL、结构化输出和失败类型。只有这样,后续的模型替换、提示词修改或工具升级才有可比较的依据。

小结

这次实践说明,OpenManus 的核心是一个受工具观测驱动的 ReAct 循环。浏览器自动化的真正难点不在于让模型发出点击指令,而在于持续提供正确的页面状态,并用可验证的条件判断任务是否完成。

DOM 编号方案适合稳定页面;领域知识增强能够减少盲目探索,但当前实现仍属于静态上下文注入;GUI-Plus 为复杂控件补上视觉信息,却不能跳过动作后的状态验证。将三者放在明确的策略选择、验证和评估框架中,浏览器 Agent 才能从一次可演示的流程,逐步变成可维护的工程能力。

END