目标:帮助大家理清Open AutoGLM的自动化链路、ReAct 思路落点、上下文/记忆处理方式、长任务不跑偏的提示词构建、截图采集与传输方式、是否存在“语义层”,以及给出代码架构与流程图。
1. 总览
Open AutoGLM是一个“视觉-语言模型驱动的手机自动化执行器”,核心思想是:
- 观察(Observation):每一步先通过 ADB 截取当前屏幕截图,并读取当前前台应用(current_app)。
- 思考(Reasoning / Think):把“任务目标 + 当前屏幕信息”发给多模态模型,让模型在
<think>...</think>输出简短推理。 - 行动(Act):模型在
<answer>...</answer>输出一行可执行的“伪代码/函数调用风格”指令(例如do(action="Tap", element=[x,y])),系统解析后调用 ADB 执行。 - 循环:执行完动作后进入下一步,直到
finish(message="...")或达到最大步数。
从工程角度,这属于典型的 ReAct(Reason + Act) 模式:用“显式思考 + 工具调用”把大模型从纯对话变成可控的行动体。
2. 代码结构与职责划分
核心模块(与 README 中描述一致):
-
phone_agent/agent.pyPhoneAgent:主循环 orchestrator(抓屏 → 请求模型 → 解析动作 → 执行动作 → 继续)AgentConfig:最大步数、语言、device_id、system_prompt、verboseStepResult:单步执行结果
-
phone_agent/model/client.pyModelClient:OpenAI-compatible Chat Completions 客户端MessageBuilder:构建 system/user/assistant messages,并做“剔除历史图片”
-
phone_agent/actions/handler.pyActionHandler:将模型输出动作映射到具体 ADB 操作parse_action:把模型输出字符串解析为 dict(目前通过eval解析 do(...))
-
phone_agent/adb/*screenshot.py:截图抓取、base64 编码device.py:点击/滑动/返回/主页/启动应用input.py:基于 ADB Keyboard 的文本输入(base64 广播)connection.py:ADB 设备连接/枚举(可用于远程/多设备)
-
phone_agent/config/*prompts_zh.py,prompts_en.py:系统提示词apps.py:应用名到包名映射(用于 Launch)i18n.py:UI 输出文案
3. ReAct 在本工程中的落地方式
3.1 ReAct 的三个关键点
在本实现中,ReAct 的典型链条落在如下位置:
- Reason(显式推理)
- 提示词强制模型输出
<think>...</think>,并在 verbose 模式下打印给用户。 - 工程侧通过
ModelClient._parse_response()从模型原始输出中拆分 thinking 与 action。
- Act(可执行动作)
- 提示词强制模型在
<answer>...</answer>中输出“一行可执行指令”,如:do(action="Launch", app="小红书")do(action="Tap", element=[500,100])finish(message="...")
- 工程侧通过
parse_action()解析这行指令并变成结构化 dict,然后ActionHandler.execute()执行。
- Observation(环境反馈)
- 每一步开头抓取新截图,并把
current_app写入“Screen Info”。 - 下一步模型会看到新的 UI 状态(截图),从而形成闭环。
注意:本项目的 Observation 主要来源于“新的截图 + current_app”,并没有额外的 OCR、控件树、可点击区域检测等结构化语义输入。
3.2 为什么这种 ReAct 结构可控
提示词里最关键的“可控性钩子”是:
- 固定输出格式:必须
<think>+<answer>,并且<answer>只允许一行操作。 - 动作白名单:提示词枚举了可用动作,并给出示例。
- 规则约束:如“先检查是否在目标 app,不在就 Launch;跑到无关页面先 Back;Wait 最多 3 次”等。
这使得模型的输出更接近“策略模块”,而不是自由文本。
4. 记忆层(Memory)如何处理
4.1 当前实现的“记忆”是什么
当前工程的记忆主要是:
PhoneAgent._context:一个 OpenAI messages 列表,包含 system / user / assistant 的历史对话。- 历史 assistant message 会以
"<think>...<answer>..."的形式被追加进 context(相当于轨迹/日志)。
也就是说:记忆 = 纯文本会话轨迹(短期记忆),用于让模型“知道自己之前做过什么”。
4.2 图片记忆如何处理(关键)
为了控制 token/图像数量,本实现对 context 做了一个重要的“记忆压缩策略”:
- 每一步请求模型时,会把当前截图放在最新的 user message 中。
- 请求完成后,会调用
MessageBuilder.remove_images_from_message()把这个 user message 里的 image content 删除,只保留文本。
结果是:
- 模型每一步只看到 1 张图(当前屏幕),不会在上下文里累计多张历史图。
- 轨迹仍保留“screen_info 文本 + 上一步 think/action 文本”,用于长任务延续。
4.3 是否有长期记忆/持久化
当前代码中:
- 没有看到“把任务状态写入磁盘”的实现(例如 JSON trajectory、检查点、可恢复会话)。
reset()会清空_context和_step_count。
因此严格来说:没有独立的长期记忆层/可恢复记忆层;只有进程内的对话上下文。
提示词里出现了
Note/Call_API动作,但在ActionHandler中它们是 placeholder(返回 success,不做真正的“记录/总结”),因此也未形成真正的“语义记忆/摘要记忆”。
5. 长任务如何保证延续性、避免跑偏
5.1 当前工程已具备的“不中断/不跑偏”机制
- 硬性步数上限
AgentConfig.max_steps限制最多执行多少步,避免无限循环。
- 每步提供可验证的 Observation
- 每一步都有当前截图 + current_app,模型必须对“最新状态”做出反应。
- 规则式约束(Prompt 内)
- 提示词有一组“必须遵循的规则”,典型包括:
- 不在目标 app → 先 Launch
- 无关页面 → Back / 关闭
- 页面未加载 → Wait 最多 3 次,否则 Back
- 点击/滑动不生效 → 调整点位、改变滑动距离、必要时跳过并在 finish 说明
这些规则是“防跑偏”的主要来源。
- 敏感操作确认
Tap动作带message字段时触发confirmation_callback,用户可确认/取消。- 这相当于在人类关键节点插入“监督断点”。
5.2 提示词如何构建(建议模板,贴合当前实现)
当前系统提示词已提供操作格式与大量经验规则。如果你要进一步增强“长任务延续性 + 不跑偏”,建议在 system prompt 中显式加入以下结构(保持仍是 <think> / <answer> 单行动作):
- 任务目标(Goal):一句话、不可歧义。
- 完成条件(Done Criteria):什么时候必须
finish(...)。 - 进度锚点(Progress Anchor):每一步在
<think>中用极短句标记“当前子目标/阶段”。 - 偏航检测(Drift Check):每一步必须回答:
- 当前 app 是否正确?
- 当前页面是否与子目标相关?
- 上一步动作是否生效?
- 失败策略(Recovery Policy):连续 N 次失败/无变化 → Back / Home / 重新 Launch。
由于工程侧不会执行多动作合并(只允许一行 action),所以这些内容应该主要体现在 <think> 的“决策标准”,而不是输出更复杂的动作。
6. 截图如何截取、压缩、传输给大模型
6.1 截图截取
截图在 phone_agent/adb/screenshot.py 完成:
- 通过 ADB 执行:
adb shell screencap -p /sdcard/tmp.png - 再
adb pull到本地临时目录 - 使用 Pillow 打开图片得到 width/height
6.2 截图压缩/尺寸控制
当前实现:
- 不做 resize
- 不做 JPEG 压缩
- 直接将图片保存为 PNG,再
base64.b64encode传给模型
因此:
- 画面清晰但体积可能较大(尤其高分辨率设备)
- token/带宽消耗更高
6.3 给大模型的传输方式
MessageBuilder.create_user_message() 会把 base64 拼到 OpenAI vision 输入格式:
{"type": "image_url", "image_url": {"url": "data:image/png;base64,<...>"}}
并与文本一起组成一个 user message 的多段 content。
6.4 敏感页面处理
如果截图命令输出包含 Status: -1 或 Failed,代码会返回一个“纯黑 PNG”并标记 is_sensitive=True。
注意:当前 PhoneAgent 不会把 is_sensitive 额外写入 screen_info,也不会改变策略;只是让模型收到一张黑图(并可能因此无法继续视觉定位)。
7. 有没有专门的语义层?
以“分层架构”的角度看:
- 动作层(Action Layer):存在,且很清晰——
ActionHandler+adb/*。 - 语义层(Semantic Layer):当前实现没有独立的语义层模块。
- 输入端:除了截图,只有
current_app被编码为 JSON 字符串。 - 没有 OCR、UI tree、控件可点击候选、元素检测、文本抽取等。
Note/Call_API在 prompt 中存在,但 handler 未实现真正的“语义记录/摘要 API”。
- 输入端:除了截图,只有
换句话说:本工程把“语义理解/页面解析/元素定位”主要交给多模态模型在 <think> 中隐式完成。
8. 核心流程图
8.1 端到端主循环(PhoneAgent)
flowchart TD
A[Start task / step] --> B[Capture screenshot via ADB]
B --> C[Get current_app via dumpsys window]
C --> D[Append user message text image]
D --> E[ModelClient.request messages]
E --> F[Parse response: thinking + action string]
F --> G[parse_action: action dict]
G --> H[Remove image from last user message]
H --> I[ActionHandler.execute action]
I --> J[Append assistant message: x]
J --> K{finished?}
K -- yes --> L[Return final message]
K -- no --> B
8.2 时序图:模型交互与动作执行
sequenceDiagram
participant U as User
participant A as PhoneAgent
participant S as ADB Screenshot
participant D as ADB Device/Input
participant M as ModelClient (OpenAI API)
participant H as ActionHandler
U->>A: run(task)
loop each step
A->>S: get_screenshot()
S-->>A: base64 PNG + w/h
A->>D: get_current_app()
D-->>A: app_name
A->>M: chat.completions.create(messages: text+image)
M-->>A: raw_content
A->>A: parse thinking/action
A->>H: execute(action)
H->>D: tap/swipe/type/back/home/launch
D-->>H: done
H-->>A: ActionResult
end
A-->>U: result message
8.3 动作分发(ActionHandler)
flowchart TD
A[action dict] --> B{_metadata}
B -- finish --> C[Return should_finish=True]
B -- do --> D{action name}
D --> E[Map to handler method]
E --> F[Convert coords 0-1000 -> pixels]
F --> G[Call adb.*]
G --> H[Return ActionResult]
9. 工程上的关键实现细节(容易被忽略)
- 坐标系统
- 提示词要求坐标为 (0,0)~(999,999) 的相对坐标。
ActionHandler._convert_relative_to_absolute()会按屏幕宽高换算到像素。
- 文本输入
- 使用 ADB Keyboard,通过广播
ADB_INPUT_B64发送 base64 文本。 - 每次输入前会切换到 ADB Keyboard,清空文本,再输入,然后恢复原键盘。
- 历史图片剔除
- 只保留最新一步的图像输入,避免长任务上下文膨胀。
Note/Call_API/Interact
- 提示词描述了这些动作,但 handler 目前是占位逻辑,未实现真正的记录/总结/交互。
- 动作解析使用
eval
parse_action()对以do...开头的 action 直接eval。- 这对“非可信模型输出”存在潜在安全风险(如果模型输出被注入恶意表达式)。
10. 如果要补齐“记忆层 / 语义层”的工程化方向(可选建议)
若你希望系统在更复杂、超长任务中更稳,可以考虑:
- 语义层:引入 OCR(例如只对局部区域 OCR)、控件检测(按钮/输入框)、或 Android 无障碍树(Accessibility tree),把结构化语义加入到
screen_info。 - 记忆层:实现
Note动作写入一个结构化 memory(例如 JSON:{step, app, page_summary, entities}),并在每 N 步做一次摘要注入 system 或 user。 - 可恢复执行:把
_context或关键状态(目标、子目标、最近 K 步、失败计数)落盘,允许崩溃后继续。
参考入口
- 主循环:
phone_agent/agent.py - 模型客户端与 message 构建:
phone_agent/model/client.py - 动作执行与解析:
phone_agent/actions/handler.py - 截图:
phone_agent/adb/screenshot.py - ADB 操作:
phone_agent/adb/device.py,phone_agent/adb/input.py - 系统提示词:
phone_agent/config/prompts_zh.py,phone_agent/config/prompts_en.py