Open-AutoGLM 深度解析

让 AI 帮你完成手机上的任务

December 14, 2024·10 min read·Yimin
#AI#Agent#Android#VLM

本文深入剖析 Open-AutoGLM 的架构设计与实现原理,带你理解 AI 如何「看懂」手机屏幕并自动完成任务。

🎯 这是什么?

Open-AutoGLM 是智谱 AI 开源的手机 Agent 框架。你只需要用自然语言描述任务:

"打开微信发消息给文件传输助手:测试成功"

AI 就能帮你在安卓手机上一步步完成这个任务。

核心能力:给 AI 一双眼睛(截图)+ 一双手(ADB)+ 一个大脑(视觉语言模型)= 自动化操作手机。


🏗️ 整体架构

┌─────────────────────────────────────────────────────────────────┐
│                        主控制循环                               │
│                                                                 │
│    ┌──────────┐     ┌──────────┐     ┌──────────┐              │
│    │  截屏    │ ──► │  AI 理解  │ ──► │  执行    │              │
│    │ (ADB)   │     │  (VLM)   │     │ (ADB)    │              │
│    └──────────┘     └──────────┘     └──────────┘              │
│         ▲                                   │                   │
│         └───────────────────────────────────┘                   │
│                      循环直到任务完成                           │
└─────────────────────────────────────────────────────────────────┘

整个系统分为三个核心部分:

模块职责技术实现
截屏模块获取手机当前画面ADB + Pillow
AI 决策模块理解画面,决定下一步视觉语言模型 (VLM)
动作执行模块执行点击、滑动、输入等ADB Shell

🔄 核心执行流程

每一步都是「看 → 想 → 做」的循环:

def _execute_step(self):
    # 1️⃣ 截取当前屏幕
    screenshot = get_screenshot()
    
    # 2️⃣ 发送给 AI 模型
    response = self.model_client.request([
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": [截图, 任务描述]}
    ])
    
    # 3️⃣ 解析 AI 返回的动作
    action = parse_action(response.action)
    # 例如: {"action": "Tap", "element": [500, 300]}
    
    # 4️⃣ 执行动作
    self.action_handler.execute(action)
    
    # 5️⃣ 检查是否完成,否则继续循环

一个真实任务的执行过程

任务:「打开微信发消息给文件传输助手:测试成功」

Step 1: 
  📸 截图 → 看到桌面
  🧠 AI: "我看到桌面有微信图标,应该先启动微信"
  🎯 执行: Launch("微信")

Step 2:
  📸 截图 → 微信首页
  🧠 AI: "微信打开了,我需要点击搜索按钮"
  🎯 执行: Tap([826, 86])

Step 3:
  📸 截图 → 搜索页面
  🧠 AI: "搜索框已激活,输入文件传输助手"
  🎯 执行: Type("文件传输助手")

... 继续执行直到完成 ...

Step N:
  📸 截图 → 消息已发送
  🧠 AI: "消息发送成功,任务完成"
  🎯 执行: finish(message="任务完成!")

🧠 AI 是如何「看懂」屏幕的?

视觉语言模型 (VLM)

Open-AutoGLM 使用的是 AutoGLM-Phone-9B,一个专门为手机操控训练的视觉语言模型。

┌─────────────────────────────────────────────────────────────────┐
│                    VLM 的工作方式                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  输入:                                                          │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │ 📷 手机截图 (Base64 编码)                                 │ │
│  │ 📝 任务描述: "打开微信发消息..."                          │ │
│  │ 📋 System Prompt: 角色定义 + 动作说明 + 规则              │ │
│  └───────────────────────────────────────────────────────────┘ │
│                          │                                      │
│                          ▼                                      │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │               AutoGLM-Phone-9B                            │ │
│  │                                                           │ │
│  │   • 识别屏幕上的 UI 元素(按钮、文字、图标)              │ │
│  │   • 理解元素的位置坐标                                    │ │
│  │   • 根据任务目标决定下一步动作                            │ │
│  └───────────────────────────────────────────────────────────┘ │
│                          │                                      │
│                          ▼                                      │
│  输出:                                                          │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │ 💭 思考: "我看到搜索按钮在右上角,应该点击它"             │ │
│  │ 🎯 动作: do(action="Tap", element=[826, 86])              │ │
│  └───────────────────────────────────────────────────────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

System Prompt 的关键作用

System Prompt 告诉 AI:

  1. 你是谁:智能体分析专家
  2. 能做什么:15 种动作类型
  3. 怎么做:18 条行为规则
SYSTEM_PROMPT = """
你是一个智能体分析专家,可以根据操作历史和当前状态图执行一系列操作来完成任务。

操作指令及其作用如下:
- do(action="Launch", app="xxx")      # 启动 APP
- do(action="Tap", element=[x,y])     # 点击坐标
- do(action="Type", text="xxx")       # 输入文字
- do(action="Swipe", start=[...], end=[...])  # 滑动
- do(action="Back")                   # 返回键
- do(action="Home")                   # 主屏幕键
- do(action="Wait", duration="x seconds")  # 等待
- do(action="Take_over", message="xxx")  # 人工接管
- finish(message="xxx")               # 完成任务
...

必须遵循的规则:
1. 在执行任何操作前,先检查当前app是否是目标app
2. 如果进入到了无关页面,先执行 Back
3. 如果页面未加载出内容,最多连续 Wait 三次
...
"""

📐 坐标系统设计

AI 输出的是 0-999 的相对坐标,而不是实际像素:

┌─────────────────────────────────────────────────────────────────┐
│                     坐标归一化设计                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   AI 使用的相对坐标                转换后的实际像素              │
│   ┌─────────────────┐             ┌─────────────────┐          │
│   │ (0,0)    (999,0)│             │ (0,0)  (1080,0) │          │
│   │                 │      →      │                 │          │
│   │    (500,500)    │             │   (540,1200)    │          │
│   │                 │             │                 │          │
│   │ (0,999) (999,999│             │ (0,2400)(1080,..)          │
│   └─────────────────┘             └─────────────────┘          │
│                                                                 │
│   转换公式:                                                     │
│   pixel_x = (ai_x / 1000) * screen_width                       │
│   pixel_y = (ai_y / 1000) * screen_height                      │
│                                                                 │
│   优点:                                                         │
│   • AI 不需要知道具体分辨率                                    │
│   • 适配所有手机 (720p, 1080p, 2K, 4K...)                      │
│   • 简化模型训练                                               │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

⌨️ 中文输入的巧妙解决方案

ADB 原生的 input text 命令不支持中文

adb shell input text "hello"  # ✅ 可以
adb shell input text "你好"    # ❌ 乱码

Open-AutoGLM 的解决方案是使用 ADB Keyboard

┌─────────────────────────────────────────────────────────────────┐
│                   中文输入流程                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  "测试成功"                                                     │
│       │                                                         │
│       ▼ Base64 编码                                            │
│  "5rWL6K+V5oiQ5Yqf"                                            │
│       │                                                         │
│       ▼ 通过广播发送                                           │
│  adb shell am broadcast -a ADB_INPUT_B64 --es msg "5rWL..."    │
│       │                                                         │
│       ▼ ADB Keyboard 接收                                      │
│  手机上的 ADB Keyboard APP 解码并输入                          │
│       │                                                         │
│       ▼                                                         │
│  输入框显示: "测试成功" ✅                                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

🎮 支持的 15 种动作

动作说明示例
Launch启动 APPdo(action="Launch", app="微信")
Tap点击do(action="Tap", element=[500, 300])
Type输入文字do(action="Type", text="你好")
Swipe滑动do(action="Swipe", start=[500,800], end=[500,200])
Long Press长按do(action="Long Press", element=[500,500])
Double Tap双击do(action="Double Tap", element=[500,500])
Back返回键do(action="Back")
Home主屏幕do(action="Home")
Wait等待do(action="Wait", duration="2 seconds")
Take_over人工接管do(action="Take_over", message="请完成验证码")
Note记录内容do(action="Note", message="...")
Call_API调用总结do(action="Call_API", instruction="...")
Interact用户选择do(action="Interact")
Type_Name输入人名do(action="Type_Name", text="张三")
finish完成任务finish(message="任务完成")

🤖 反应式设计:边看边做

Open-AutoGLM 采用 ReAct (Reasoning + Acting) 模式,而不是预先规划:

┌─────────────────────────────────────────────────────────────────┐
│                  规划式 vs 反应式                               │
├───────────────────────────────┬─────────────────────────────────┤
│       ❌ 规划式               │       ✅ 反应式 (Open-AutoGLM)  │
├───────────────────────────────┼─────────────────────────────────┤
│                               │                                 │
│  先制定完整计划:               │  每步根据当前状态决策:          │
│  1. 打开微信                  │                                 │
│  2. 点击搜索                  │  截图 → "我看到桌面"            │
│  3. 输入联系人                │        → Launch 微信            │
│  4. 点击联系人                │                                 │
│  5. 输入消息                  │  截图 → "微信打开了"            │
│  6. 点击发送                  │        → Tap 搜索               │
│                               │                                 │
│  然后按计划执行               │  截图 → "搜索框激活"            │
│  ❌ 界面变化会导致失败         │        → Type 联系人            │
│                               │                                 │
│                               │  ... 继续循环 ...               │
│                               │  ✅ 能处理弹窗、广告等意外      │
│                               │                                 │
└───────────────────────────────┴─────────────────────────────────┘

优点

  • 适应性强:界面变化、弹窗、广告都能处理
  • 容错性好:一步失败可以重试
  • 通用性高:同一套代码处理所有任务

🧠 上下文记忆管理

AI Agent 需要「记住」之前做过什么,才能做出连贯的决策。Open-AutoGLM 通过维护一个 对话上下文列表 来实现:

class PhoneAgent:
    def __init__(self, ...):
        self._context = []  # 存储对话历史
        self._step_count = 0
    
    def run(self, task: str):
        self._context = []  # 新任务清空历史
        self._step_count = 0
        # ... 执行任务 ...

上下文结构

每一步都会往 _context 中追加消息:

┌─────────────────────────────────────────────────────────────────┐
│                      对话上下文示例                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  [0] System: "你是智能体分析专家..."     ← 系统提示(固定)     │
│                                                                 │
│  [1] User: [截图1] + "打开微信发消息..."  ← 第一步:任务+截图   │
│  [2] Assistant: "<think>看到桌面</think>                        │
│                  <answer>Launch(微信)</answer>"                 │
│                                                                 │
│  [3] User: [截图2] + "继续执行任务"       ← 第二步:新截图      │
│  [4] Assistant: "<think>微信打开了</think>                      │
│                  <answer>Tap([826,86])</answer>"                │
│                                                                 │
│  [5] User: [截图3] + "继续执行任务"       ← 第三步...           │
│  ...                                                            │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

内存优化:清除历史截图

截图数据很大(Base64 编码后约 1-2MB),如果保留所有历史截图会导致:

  • 内存占用飙升
  • API 请求变慢
  • Token 超限

Open-AutoGLM 的解决方案:只保留最新截图,历史截图从消息中移除

def _execute_step(self, ...):
    # 1. 截图并构建消息
    screenshot = get_screenshot()
    self._context.append(user_message_with_image)
    
    # 2. 调用 AI
    response = self.model_client.request(self._context)
    
    # 3. 立即移除刚才的截图,只保留文字
    self._context[-1] = MessageBuilder.remove_images_from_message(self._context[-1])
    
    # 4. 追加 AI 的回复
    self._context.append(assistant_message)

这样 AI 仍然能看到:

  • ✅ 当前屏幕截图(最新的)
  • ✅ 历史操作记录(文字)
  • ✅ 历史思考过程(文字)

但不会:

  • ❌ 保留历史截图(节省内存)
  • ❌ 让 context 无限增长

为什么需要历史记录?

虽然是「反应式」设计,但历史记录仍然重要:

场景作用
重复操作检测AI 看到"我刚点过这里",避免死循环
错误恢复AI 知道"刚才点错了",可以尝试其他方案
任务连贯性AI 记得"我在找文件传输助手",不会迷失方向
步骤计数达到 max_steps 时强制结束,防止无限执行

🔒 特殊场景处理

敏感页面(支付、银行)

当截图失败时(系统禁止截图),返回黑屏占位图:

def _create_fallback_screenshot(is_sensitive: bool) -> Screenshot:
    """Create a black fallback image when screenshot fails."""
    black_img = Image.new("RGB", (1080, 2400), color="black")
    return Screenshot(
        base64_data=encode(black_img),
        is_sensitive=is_sensitive  # 标记为敏感页面
    )

AI 看到黑屏后会尝试等待或返回。

人工接管(验证码、登录)

当 AI 遇到无法自动完成的操作时:

def _handle_takeover(self, action, ...):
    message = action.get("message", "需要人工操作")
    # 阻塞等待用户完成
    input(f"{message}\n完成后按 Enter 继续...")
    return ActionResult(True, should_finish=False)  # 继续执行

用户完成操作后按 Enter,Agent 继续执行。


📊 与传统自动化的对比

特性Appium/UIAutomatorOpen-AutoGLM
需要 APP 源码❌ 需要了解结构✅ 不需要
需要写脚本❌ 需要编程✅ 自然语言
界面变化适应❌ 脚本失效✅ AI 自动适应
跨 APP 操作❌ 困难✅ 轻松
理解语义❌ 只能按规则✅ 理解意图
处理异常❌ 需预设✅ 智能应对

🎯 总结

Open-AutoGLM 的核心创新:

  1. 视觉理解:用 VLM 直接「看图」理解界面,不依赖 Accessibility API
  2. 自然语言:用户说人话,AI 自己规划和执行
  3. 反应式架构:每步都根据当前状态决策,适应动态环境
  4. ADB Keyboard:巧妙解决中文输入问题
  5. 坐标归一化:0-999 相对坐标适配所有分辨率
一句话总结:
给 AI 一双眼睛(截图)+ 一双手(ADB)+ 一个大脑(VLM)= 自动化操作手机

📚 扩展阅读


本文基于 Open-AutoGLM v0.1.0 源码分析

Open-AutoGLM 深度解析