深度解析 Open-AutoGLM:让 AI 自己操作手机的技术实现

本文基于智谱AI开源项目 Open-AutoGLM 的源码分析,深入剖析这个多模态手机 Agent 的核心技术原理。

引言

想象一下,你只需要对手机说"打开微信,找到张三,给他发消息说明天见",手机就能自动完成这一系列操作。这不是科幻,而是基于视觉语言模型的 Phone Agent 正在实现的技术。

最近深入研究了智谱AI开源的 Open-AutoGLM 项目,它是一个基于多模态大模型的手机端智能助理框架。通过分析其核心代码,我发现了许多值得分享的技术亮点。本文将从架构设计、核心技术到实现细节,全面解析这个项目是如何让 AI "看懂"并"操作"手机的。

项目概述

项目简介

Open-AutoGLM (Phone Agent) 是由智谱AI开发的一个基于视觉语言模型的手机端智能助理框架。它能够:

  • 理解自然语言指令:用户用自然语言描述需求(如"打开小红书搜索美食")
  • 视觉理解屏幕内容:通过视觉语言模型(VLM)分析手机屏幕截图
  • 自动化操作执行:通过ADB自动执行点击、滑动、输入等操作
  • 智能任务规划:AI自主规划完成任务所需的操作步骤

核心价值

  • 多模态理解:结合视觉和文本信息理解手机界面
  • 自主决策:AI根据当前状态自动决定下一步操作
  • 跨应用操作:支持50+款主流中文应用(微信、淘宝、美团等)
  • 人机协同:支持敏感操作确认和人工接管机制

技术栈概览

前端/控制层:Python 3.10+
AI模型:AutoGLM-Phone-9B (视觉语言模型)
设备控制:ADB (Android Debug Bridge)
模型接口:OpenAI-compatible API
图像处理:Pillow (PIL)
推理引擎:vLLM / SGLang(可选,用于本地部署)

技术架构

整体架构图

┌─────────────────────────────────────────────────────────┐
│                      用户层                               │
│              (自然语言指令输入)                            │
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────┐
│                   Phone Agent                            │
│  ┌─────────────┐  ┌──────────────┐  ┌───────────────┐  │
│  │  Agent.py   │  │ Model Client │  │ Action Handler│  │
│  │  (核心控制)  │◄─┤  (AI推理)    │  │  (动作执行)    │  │
│  └─────────────┘  └──────────────┘  └───────────────┘  │
│         │                │                   │          │
│         └────────────────┴───────────────────┘          │
└─────────────────────────────────────────────────────────┘
          │                  │                    │
          ▼                  ▼                    ▼
  ┌──────────────┐  ┌──────────────┐   ┌──────────────┐
  │ ADB 截图模块  │  │  AI 模型服务  │   │  ADB 控制模块 │
  │ (screenshot)  │  │  (OpenAI API)│   │  (device.py) │
  └──────────────┘  └──────────────┘   └──────────────┘
          │                  │                    │
          └──────────────────┴────────────────────┘
                             │
                             ▼
                    ┌────────────────┐
                    │  Android 设备   │
                    │  (通过 ADB)     │
                    └────────────────┘

核心工作流程

1. 初始化
   ├─ 检查 ADB 连接
   ├─ 检查模型服务
   └─ 配置 Agent 参数

2. 执行循环 (每个步骤)
   ├─ 截取当前屏幕
   ├─ 获取当前应用信息
   ├─ 构建多模态消息 (图片 + 文本)
   ├─ 调用 AI 模型推理
   │   ├─ 思考过程 (thinking)
   │   └─ 动作指令 (action)
   ├─ 解析动作
   ├─ 执行动作 (通过 ADB)
   └─ 检查是否完成

3. 终止条件
   ├─ finish() 指令
   ├─ 达到最大步数
   └─ 发生错误

目录结构

Open-AutoGLM/
├── phone_agent/              # 核心包
│   ├── __init__.py          # 导出主要接口
│   ├── agent.py             # PhoneAgent 核心类
│   ├── model/               # AI 模型交互
│   │   ├── client.py        # OpenAI 客户端封装
│   │   └── __init__.py
│   ├── actions/             # 动作处理
│   │   ├── handler.py       # 动作执行器
│   │   └── __init__.py
│   ├── adb/                 # ADB 工具集
│   │   ├── connection.py    # 连接管理
│   │   ├── screenshot.py    # 截图功能
│   │   ├── input.py         # 文本输入
│   │   ├── device.py        # 设备控制
│   │   └── __init__.py
│   └── config/              # 配置文件
│       ├── apps.py          # 应用包名映射
│       ├── prompts_zh.py    # 中文提示词
│       ├── prompts_en.py    # 英文提示词
│       └── i18n.py          # 国际化
├── main.py                  # CLI 入口
├── examples/                # 示例代码
├── scripts/                 # 工具脚本
├── requirements.txt         # 依赖清单
└── setup.py                 # 安装配置

核心技术分析

多模态AI模型

模型架构

AutoGLM-Phone-9B 是一个9B参数的视觉语言模型:

  • 基础模型:GLM-4.1V-9B-Thinking
  • 特化训练:针对手机应用场景优化
  • 输入:屏幕截图(图像) + 任务描述(文本) + 历史上下文
  • 输出:思考过程(thinking) + 动作指令(action)
推理过程(代码:phone_agent/model/client.py)
# 核心推理流程
def request(self, messages: list[dict[str, Any]]) -> ModelResponse:
    # 1. 流式调用模型
    stream = self.client.chat.completions.create(
        messages=messages,  # 多模态消息(图像+文本)
        model=self.config.model_name,
        temperature=0.0,    # 确定性输出
        stream=True         # 流式返回
    )

    # 2. 解析思考和动作
    # thinking: AI的推理过程
    # action: 具体的操作指令(如 do(action="Tap", element=[500,300]))
    thinking, action = self._parse_response(raw_content)

    return ModelResponse(thinking=thinking, action=action)
Prompt Engineering(代码:phone_agent/config/prompts_zh.py)

系统提示词包含:

SYSTEM_PROMPT = """
今天的日期是: {formatted_date}
你是一个智能体分析专家,可以根据操作历史和当前状态图执行一系列操作来完成任务。

输出格式:
<think>{think}</think>
<answer>{action}</answer>

操作指令:
- do(action="Launch", app="xxx")      # 启动应用
- do(action="Tap", element=[x,y])     # 点击坐标
- do(action="Type", text="xxx")       # 输入文本
- do(action="Swipe", start=[x1,y1], end=[x2,y2])  # 滑动
- finish(message="xxx")                # 完成任务

必须遵循的规则:
1. 检查当前app是否是目标app
2. 进入无关页面先执行Back
3. 页面未加载最多Wait三次
...(共18条规则)
"""

关键技术点

  • 坐标归一化:使用0-999的相对坐标系,与屏幕分辨率无关
  • 思考链(Chain of Thought):AI先思考再行动
  • 规则约束:通过Prompt约束AI行为,避免错误操作

ADB设备控制

ADB连接管理(代码:phone_agent/adb/connection.py)

支持两种连接方式:

# 1. USB连接
adb devices  # 列出设备

# 2. 无线连接(WiFi)
adb tcpip 5555              # 启用TCP/IP模式
adb connect 192.168.1.100:5555  # 连接远程设备
屏幕截图(代码:phone_agent/adb/screenshot.py)
def get_screenshot(device_id: str | None = None) -> Screenshot:
    # 1. 在设备上截图
    adb shell screencap -p /sdcard/tmp.png

    # 2. 拉取到本地
    adb pull /sdcard/tmp.png temp_path

    # 3. 读取并转为base64
    img = Image.open(temp_path)
    base64_data = base64.b64encode(buffered.getvalue())

    return Screenshot(base64_data=base64_data, width=width, height=height)

敏感页面检测

  • 支付、密码等页面截图会失败(返回黑屏)
  • 系统自动检测并标记 is_sensitive=True
  • 触发人工接管机制
设备操作(代码:phone_agent/adb/device.py)
# 点击
def tap(x: int, y: int, device_id=None):
    adb shell input tap {x} {y}

# 滑动
def swipe(start_x, start_y, end_x, end_y, duration_ms):
    adb shell input swipe {start_x} {start_y} {end_x} {end_y} {duration_ms}

# 输入文本(使用ADB Keyboard)
def type_text(text, device_id=None):
    # 1. 切换到ADB Keyboard
    adb shell ime set com.android.adbkeyboard/.AdbIME
    # 2. 通过广播发送文本
    adb shell am broadcast -a ADB_INPUT_TEXT --es msg "{text}"
    # 3. 恢复原输入法
    restore_keyboard(original_ime)

# 启动应用
def launch_app(app_name: str):
    package = APP_PACKAGES[app_name]
    adb shell monkey -p {package} -c android.intent.category.LAUNCHER 1

技术要点

  • 坐标转换:相对坐标(0-999) → 绝对像素坐标
  • 中文输入:ADB Keyboard通过广播发送UTF-8文本
  • 自动延迟:操作后自动等待(避免操作过快)

动作解析与执行(代码:phone_agent/actions/handler.py)

动作解析

使用AST(抽象语法树)安全解析,避免eval:

def parse_action(response: str) -> dict[str, Any]:
    # 输入: "do(action='Tap', element=[500, 300])"
    # 输出: {"_metadata": "do", "action": "Tap", "element": [500, 300]}

    tree = ast.parse(response, mode="eval")
    call = tree.body
    action = {"_metadata": "do"}
    for keyword in call.keywords:
        key = keyword.arg
        value = ast.literal_eval(keyword.value)  # 安全解析
        action[key] = value
    return action
动作执行
class ActionHandler:
    def execute(self, action: dict, screen_width: int, screen_height: int):
        action_type = action.get("_metadata")  # "do" or "finish"

        if action_type == "finish":
            return ActionResult(success=True, should_finish=True)

        action_name = action.get("action")  # "Tap", "Swipe", etc.
        handler_method = self._get_handler(action_name)

        return handler_method(action, screen_width, screen_height)

    def _handle_tap(self, action, width, height):
        element = action.get("element")  # [x, y] 相对坐标
        x, y = self._convert_relative_to_absolute(element, width, height)
        tap(x, y, self.device_id)
        return ActionResult(success=True, should_finish=False)

支持的动作类型

  • Launch - 启动应用
  • Tap - 点击
  • Type / Type_Name - 文本输入
  • Swipe - 滑动
  • Back / Home - 系统按键
  • Double Tap / Long Press - 特殊点击
  • Wait - 等待
  • Take_over - 人工接管
  • Interact - 用户交互
  • Note / Call_API - 内容记录/总结

关键模块详解

PhoneAgent 核心类(phone_agent/agent.py)

class PhoneAgent:
    """AI驱动的手机自动化Agent"""

    def __init__(self, model_config, agent_config,
                 confirmation_callback=None,
                 takeover_callback=None):
        self.model_client = ModelClient(model_config)
        self.action_handler = ActionHandler(device_id, callbacks...)
        self._context = []  # 对话历史
        self._step_count = 0

    def run(self, task: str) -> str:
        """执行任务的主循环"""
        self._context = []
        self._step_count = 0

        # 第一步:初始化任务
        result = self._execute_step(task, is_first=True)
        if result.finished:
            return result.message

        # 循环执行直到完成或达到最大步数
        while self._step_count < self.agent_config.max_steps:
            result = self._execute_step(is_first=False)
            if result.finished:
                return result.message

        return "Max steps reached"

    def _execute_step(self, user_prompt=None, is_first=False) -> StepResult:
        """执行单步操作"""
        self._step_count += 1

        # 1. 获取当前状态
        screenshot = get_screenshot(self.device_id)
        current_app = get_current_app(self.device_id)

        # 2. 构建消息
        if is_first:
            # 添加系统提示词
            self._context.append(
                MessageBuilder.create_system_message(system_prompt)
            )
            # 添加用户任务 + 屏幕截图
            self._context.append(
                MessageBuilder.create_user_message(
                    text=f"{user_prompt}\n\n{screen_info}",
                    image_base64=screenshot.base64_data
                )
            )
        else:
            # 后续步骤:只添加新的屏幕状态
            self._context.append(
                MessageBuilder.create_user_message(
                    text=f"** Screen Info **\n\n{screen_info}",
                    image_base64=screenshot.base64_data
                )
            )

        # 3. AI推理
        response = self.model_client.request(self._context)

        # 4. 解析动作
        action = parse_action(response.action)

        # 5. 移除图像(节省上下文空间)
        self._context[-1] = MessageBuilder.remove_images_from_message(
            self._context[-1]
        )

        # 6. 执行动作
        result = self.action_handler.execute(
            action, screenshot.width, screenshot.height
        )

        # 7. 添加AI响应到上下文
        self._context.append(
            MessageBuilder.create_assistant_message(
                f"<think>{response.thinking}</think>"
                f"<answer>{response.action}</answer>"
            )
        )

        # 8. 检查是否完成
        finished = (action.get("_metadata") == "finish" or
                   result.should_finish)

        return StepResult(
            success=result.success,
            finished=finished,
            action=action,
            thinking=response.thinking,
            message=result.message
        )

关键设计

  • 状态管理:维护对话上下文(_context)和步数计数(_step_count)
  • 图像优化:执行后删除图像,只保留文本历史(节省内存和token)
  • 错误处理:捕获异常并优雅降级
  • 可扩展性:支持自定义回调函数

消息构建器(phone_agent/model/client.py:MessageBuilder)

class MessageBuilder:
    """OpenAI格式的消息构建器"""

    @staticmethod
    def create_user_message(text: str, image_base64: str | None = None):
        """构建用户消息(支持多模态)"""
        content = []

        if image_base64:
            # 图像内容(base64编码)
            content.append({
                "type": "image_url",
                "image_url": {
                    "url": f"data:image/png;base64,{image_base64}"
                }
            })

        # 文本内容
        content.append({
            "type": "text",
            "text": text
        })

        return {"role": "user", "content": content}

    @staticmethod
    def build_screen_info(current_app: str, **extra_info) -> str:
        """构建屏幕信息JSON"""
        info = {"current_app": current_app, **extra_info}
        return json.dumps(info, ensure_ascii=False)

    @staticmethod
    def remove_images_from_message(message: dict) -> dict:
        """移除消息中的图像(节省空间)"""
        if isinstance(message.get("content"), list):
            message["content"] = [
                item for item in message["content"]
                if item.get("type") == "text"
            ]
        return message

多模态消息格式

{
  "role": "user",
  "content": [
    {
      "type": "image_url",
      "image_url": {
        "url": "data:image/png;base64,iVBORw0KGgoAAAAN..."
      }
    },
    {
      "type": "text",
      "text": "打开微信\n\n{\"current_app\": \"System Home\"}"
    }
  ]
}

应用包名映射(phone_agent/config/apps.py)

APP_PACKAGES: dict[str, str] = {
    # 社交通讯
    "微信": "com.tencent.mm",
    "QQ": "com.tencent.mobileqq",
    "微博": "com.sina.weibo",

    # 电商购物
    "淘宝": "com.taobao.taobao",
    "京东": "com.jingdong.app.mall",
    "拼多多": "com.xunmeng.pinduoduo",

    # 生活服务
    "美团": "com.sankuai.meituan",
    "大众点评": "com.dianping.v1",
    "饿了么": "me.ele",

    # 视频娱乐
    "bilibili": "tv.danmaku.bili",
    "抖音": "com.ss.android.ugc.aweme",
    "快手": "com.smile.gifmaker",

    # ... 共50+个应用
}

def get_current_app(device_id: str | None = None) -> str:
    """获取当前聚焦的应用名称"""
    result = subprocess.run(
        adb_prefix + ["shell", "dumpsys", "window"],
        capture_output=True, text=True
    )
    output = result.stdout

    # 解析窗口焦点信息
    for line in output.split("\n"):
        if "mCurrentFocus" in line or "mFocusedApp" in line:
            for app_name, package in APP_PACKAGES.items():
                if package in line:
                    return app_name

    return "System Home"

技术要点

  • 双向映射:应用名称 ⇄ 包名
  • 模糊匹配:支持多种别名(如"12306"和"铁路12306")
  • 动态检测:通过dumpsys window实时获取前台应用

安全机制

敏感操作确认
def _handle_tap(self, action, width, height):
    element = action.get("element")
    x, y = self._convert_relative_to_absolute(element, width, height)

    # 检查是否有敏感操作标记
    if "message" in action:
        if not self.confirmation_callback(action["message"]):
            return ActionResult(
                success=False,
                should_finish=True,
                message="User cancelled sensitive operation"
            )

    tap(x, y, self.device_id)
    return ActionResult(success=True, should_finish=False)

使用示例

# AI输出
do(action="Tap", element=[500, 300], message="确认支付")

# 触发确认回调
response = input("Sensitive operation: 确认支付\nConfirm? (Y/N): ")
if response != "Y":
    # 取消操作
人工接管机制
def _handle_takeover(self, action, width, height):
    """处理人工接管请求(登录、验证码等)"""
    message = action.get("message", "User intervention required")
    self.takeover_callback(message)
    return ActionResult(success=True, should_finish=False)

# 默认实现
@staticmethod
def _default_takeover(message: str):
    input(f"{message}\nPress Enter after completing manual operation...")

触发场景

  • 登录页面(需要输入密码)
  • 验证码识别
  • 人脸识别
  • 敏感页面(黑屏)

性能监控(phone_agent/model/client.py)

# 记录推理性能
time_to_first_token: float | None  # 首token延迟
time_to_thinking_end: float | None  # 思考完成时间
total_time: float                   # 总推理时间

# 打印性能指标
print(f"  Performance Metrics:")
print(f"Time to first token: {time_to_first_token:.3f}s")
print(f"Time to thinking end: {time_to_thinking_end:.3f}s")
print(f"Total inference time: {total_time:.3f}s")

输出示例

  Performance Metrics:
Time to first token: 0.234s
Time to thinking end: 1.567s
Total inference time: 2.891s

记忆与上下文管理

上下文管理策略

对话历史维护(代码:phone_agent/agent.py)

PhoneAgent 维护一个完整的对话历史列表:

class PhoneAgent:
    def __init__(self):
        self._context: list[dict[str, Any]] = []  # 对话历史
        self._step_count = 0

    def run(self, task: str):
        # 重置上下文(开始新任务)
        self._context = []
        self._step_count = 0

    def reset(self):
        """手动重置(交互模式下)"""
        self._context = []
        self._step_count = 0
上下文结构

对话历史按照标准的 OpenAI 消息格式组织:

self._context = [
    # 1. 系统提示词(只在第一步添加)
    {
        "role": "system",
        "content": "今天的日期是: 2025年12月14日...\n你是一个智能体分析专家..."
    },

    # 2. 第一步:用户任务 + 屏幕截图
    {
        "role": "user",
        "content": [
            {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}},
            {"type": "text", "text": "打开微信\n\n{\"current_app\": \"System Home\"}"}
        ]
    },

    # 3. AI 第一步响应
    {
        "role": "assistant",
        "content": "<think>当前在桌面,需要启动微信</think><answer>do(action='Launch', app='微信')</answer>"
    },

    # 4. 第二步:新的屏幕状态(图片已删除)
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "** Screen Info **\n\n{\"current_app\": \"微信\"}"}
        ]
    },

    # 5. AI 第二步响应
    {
        "role": "assistant",
        "content": "<think>微信已打开,现在需要...</think><answer>do(action='Tap', element=[500,300])</answer>"
    },

    # ... 循环继续
]

图像内存优化

问题:图像占用大量内存

每张屏幕截图的 base64 编码约为 1-2MB,如果保留所有历史截图:

  • 10 步 = 10-20MB
  • 50 步 = 50-100MB
  • 100 步 = 100-200MB

这会导致:

  • 内存溢出:Python 进程占用过多内存
  • Token 超限:发送给模型的上下文过大
  • 推理变慢:模型处理大量图像耗时增加
解决方案:执行后删除图像
def _execute_step(self, user_prompt=None, is_first=False):
    # 1. 添加带图像的消息
    self._context.append(
        MessageBuilder.create_user_message(
            text=f"** Screen Info **\n\n{screen_info}",
            image_base64=screenshot.base64_data  # 包含图像
        )
    )

    # 2. AI 推理(使用当前图像)
    response = self.model_client.request(self._context)

    # 3. 解析并执行动作
    action = parse_action(response.action)

    # 4.  关键:执行后立即删除图像
    self._context[-1] = MessageBuilder.remove_images_from_message(
        self._context[-1]
    )

    # 5. 添加 AI 响应
    self._context.append(
        MessageBuilder.create_assistant_message(
            f"<think>{response.thinking}</think><answer>{response.action}</answer>"
        )
    )

删除后的消息

# 删除前
{
    "role": "user",
    "content": [
        {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}},  # ~1.5MB
        {"type": "text", "text": "** Screen Info **\n\n{\"current_app\": \"微信\"}"}
    ]
}

# 删除后
{
    "role": "user",
    "content": [
        {"type": "text", "text": "** Screen Info **\n\n{\"current_app\": \"微信\"}"}  # ~100B
    ]
}
优化效果
  • 内存节省:100 步从 100MB → 10KB(减少 99.99%)
  • Token 节省:只有当前步骤的图像会被发送给模型
  • 推理速度:历史步骤只保留文本描述,不影响推理

上下文窗口管理

历史信息的价值

虽然删除了图像,但文本历史仍然保留:

# AI 可以看到完整的操作历史
[
    {"role": "user", "content": "打开微信"},
    {"role": "assistant", "content": "<think>启动微信</think><answer>do(action='Launch', app='微信')</answer>"},
    {"role": "user", "content": "** Screen Info **\n\n{\"current_app\": \"微信\"}"},
    {"role": "assistant", "content": "<think>点击搜索</think><answer>do(action='Tap', element=[500,100])</answer>"},
    # ... 历史操作记录
]

历史信息的作用

  • 避免重复操作:AI 知道已经做过什么
  • 任务连贯性:理解当前步骤在整体任务中的位置
  • 错误恢复:如果操作失败,可以根据历史调整策略
最大步数限制
@dataclass
class AgentConfig:
    max_steps: int = 100  # 默认最大 100 步

def run(self, task: str) -> str:
    while self._step_count < self.agent_config.max_steps:
        result = self._execute_step(is_first=False)
        if result.finished:
            return result.message

    return "Max steps reached"  # 超过限制

为什么需要限制

  • 防止死循环:AI 陷入重复操作
  • 成本控制:每次推理都有成本
  • 用户体验:任务不应该无限运行

多轮对话的记忆机制

单任务执行(run 模式)
agent = PhoneAgent(model_config, agent_config)
result = agent.run("打开微信搜索张三")  # 独立任务
  • 上下文范围:仅限当前任务
  • 任务结束后:上下文保留,但通常不会再使用
  • 下次调用 run():上下文会被重置
交互模式(多任务连续执行)
agent = PhoneAgent(model_config, agent_config)

while True:
    task = input("Enter your task: ")
    if task == "quit":
        break

    result = agent.run(task)
    print(f"Result: {result}")

    agent.reset()  #  重置上下文,准备下一个任务

每个任务独立

  • 任务 1:“打开微信” → 完成 → reset()
  • 任务 2:“打开淘宝” → 完成 → reset()
  • 两个任务之间没有记忆共享
跨任务记忆(未实现,但可扩展)

如果需要跨任务记忆,可以修改设计:

class PhoneAgent:
    def __init__(self):
        self._global_memory = []  # 全局记忆
        self._task_context = []    # 当前任务上下文

    def run(self, task: str, use_memory: bool = False):
        self._task_context = []

        if use_memory:
            # 将全局记忆注入到任务上下文
            self._task_context.extend(self._global_memory[-10:])  # 最近10条

        # ... 执行任务

        # 任务结束后,更新全局记忆
        self._global_memory.append({
            "task": task,
            "result": result,
            "key_actions": extracted_actions
        })

当前应用状态追踪

除了对话历史,系统还追踪设备状态

def _execute_step(self):
    # 每一步都获取最新状态
    screenshot = get_screenshot(self.device_id)      # 当前屏幕
    current_app = get_current_app(self.device_id)   # 当前应用

    # 构建状态信息
    screen_info = MessageBuilder.build_screen_info(current_app)
    # 输出: {"current_app": "微信"}

状态信息的作用

  • 定位当前位置:AI 知道在哪个应用
  • 验证操作结果:检查上一步操作是否成功
  • 调整策略:根据当前状态决定下一步

上下文管理的技术要点总结

方面 策略 效果
图像存储 执行后立即删除 节省 99.99% 内存
文本历史 完整保留(直到任务结束) 保持任务连贯性
步数限制 默认 100 步 防止死循环
任务隔离 每个任务独立上下文 避免任务间干扰
状态同步 每步实时获取设备状态 确保信息准确

核心思想

  • 只保留必要信息:图像只在当前步骤使用,历史只保留文本
  • 平衡记忆与性能:完整的文本历史 + 当前的图像 = 最优组合
  • 状态驱动决策:每一步都基于最新的屏幕状态,而不是依赖记忆

技术难点与解决方案

坐标归一化问题

问题描述

不同手机屏幕分辨率差异巨大:

  • 低端机:720x1280
  • 中端机:1080x2400
  • 高端机:1440x3200
  • 折叠屏:2208x1840

如果AI直接输出绝对坐标,模型需要知道当前屏幕分辨率,增加复杂度。

解决方案

使用 0-999 归一化坐标系

# AI输出(归一化坐标)
do(action="Tap", element=[500, 300])  # 相对坐标

# 转换为绝对坐标
def _convert_relative_to_absolute(element, screen_width, screen_height):
    x = int(element[0] / 1000 * screen_width)   # 500 / 1000 * 1080 = 540
    y = int(element[1] / 1000 * screen_height)  # 300 / 1000 * 2400 = 720
    return x, y

优势

  • AI不需要知道具体分辨率
  • 训练数据可以跨设备共享
  • 坐标具有语义含义(500, 500 = 屏幕中心)

中文输入问题

问题描述

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

adb shell input text "你好"  #  输出乱码或失败
解决方案

使用 ADB Keyboard 第三方输入法:

def type_text(text, device_id=None):
    # 1. 检测并切换到 ADB Keyboard
    original_ime = detect_and_set_adb_keyboard(device_id)

    # 2. 清空现有文本
    clear_text(device_id)

    # 3. 通过广播发送 UTF-8 文本
    adb_prefix = _get_adb_prefix(device_id)
    encoded_text = text.replace(" ", "%s")  # 空格转义
    subprocess.run(
        adb_prefix + ["shell", "am", "broadcast",
                     "-a", "ADB_INPUT_TEXT",
                     "--es", "msg", encoded_text]
    )

    # 4. 恢复原输入法
    restore_keyboard(original_ime, device_id)

技术要点

  • ADB Keyboard 通过 Android 广播接收文本
  • 支持完整 UTF-8(中文、emoji、特殊字符)
  • 自动切换输入法,用户无感知

应用识别问题

问题描述

需要知道当前运行的是哪个应用,但不同应用包名各异。

解决方案

通过 dumpsys window 解析窗口焦点:

def get_current_app(device_id=None):
    result = subprocess.run(
        ["adb", "shell", "dumpsys", "window"],
        capture_output=True, text=True
    )

    # 解析输出,查找焦点窗口
    for line in result.stdout.split("\n"):
        if "mCurrentFocus" in line or "mFocusedApp" in line:
            # mCurrentFocus=Window{abc com.tencent.mm/com.tencent.mm.ui.LauncherUI}
            for app_name, package in APP_PACKAGES.items():
                if package in line:  # 匹配包名
                    return app_name

    return "System Home"

配置映射表(phone_agent/config/apps.py):

APP_PACKAGES = {
    "微信": "com.tencent.mm",
    "淘宝": "com.taobao.taobao",
    "美团": "com.sankuai.meituan",
    # ... 50+ 应用
}

敏感页面处理

问题描述

支付、密码等敏感页面,Android 系统会阻止截图:

adb shell screencap -p /sdcard/tmp.png
# 输出: Status: -1  # 截图失败
解决方案

检测截图失败并返回黑屏 + 敏感标记

def get_screenshot(device_id=None):
    # 执行截图命令
    result = subprocess.run(
        ["adb", "shell", "screencap", "-p", "/sdcard/tmp.png"],
        capture_output=True, text=True
    )

    # 检测失败
    if "Status: -1" in result.stdout or "Failed" in result.stdout:
        return _create_fallback_screenshot(is_sensitive=True)

    # 正常返回
    return Screenshot(base64_data=..., is_sensitive=False)

def _create_fallback_screenshot(is_sensitive):
    # 返回纯黑色图像
    black_img = Image.new("RGB", (1080, 2400), color="black")
    base64_data = base64.b64encode(...)
    return Screenshot(base64_data=base64_data, is_sensitive=is_sensitive)

AI行为

  • 收到黑屏时,AI会输出 do(action="Take_over", message="请手动完成支付")
  • 触发人工接管回调

动作解析安全性

问题描述

AI输出的动作字符串需要解析,使用 eval() 存在安全风险。

解决方案

使用 **AST(抽象语法树)**安全解析:

def parse_action(response: str):
    # 输入: "do(action='Tap', element=[500, 300])"

    #  不安全:eval(response)

    #  安全:AST 解析
    tree = ast.parse(response, mode="eval")
    if not isinstance(tree.body, ast.Call):
        raise ValueError("Expected a function call")

    call = tree.body
    action = {"_metadata": "do"}

    for keyword in call.keywords:
        key = keyword.arg
        value = ast.literal_eval(keyword.value)  # 只允许字面量
        action[key] = value

    return action

安全保证

  • ast.literal_eval() 只允许字面量(字符串、数字、列表等)
  • 不允许函数调用、变量引用等危险操作
  • 防止代码注入攻击

流式输出优化

问题描述

AI 推理耗时较长(2-3秒),用户体验差。

解决方案

使用流式输出 + 智能缓冲

def request(self, messages):
    stream = self.client.chat.completions.create(
        messages=messages,
        stream=True  # 启用流式
    )

    raw_content = ""
    buffer = ""
    action_markers = ["finish(message=", "do(action="]
    in_action_phase = False

    for chunk in stream:
        content = chunk.choices[0].delta.content
        raw_content += content

        if in_action_phase:
            continue  # 动作部分不打印

        buffer += content

        # 检测是否进入动作阶段
        for marker in action_markers:
            if marker in buffer:
                # 打印 marker 之前的内容(thinking)
                thinking_part = buffer.split(marker, 1)[0]
                print(thinking_part, end="", flush=True)
                in_action_phase = True
                break

        # 智能缓冲:避免截断 marker
        if not is_potential_marker(buffer):
            print(buffer, end="", flush=True)
            buffer = ""

效果

  • 用户实时看到 AI 的思考过程
  • 首token延迟通常 < 0.3s
  • 体验类似 ChatGPT 的流式输出

配置选项

环境变量
export PHONE_AGENT_BASE_URL="http://localhost:8000/v1"
export PHONE_AGENT_MODEL="autoglm-phone-9b"
export PHONE_AGENT_API_KEY="EMPTY"
export PHONE_AGENT_MAX_STEPS="100"
export PHONE_AGENT_DEVICE_ID="emulator-5554"
export PHONE_AGENT_LANG="cn"
自定义回调
def my_confirmation(message: str) -> bool:
    """敏感操作确认"""
    print(f"  Sensitive operation: {message}")
    response = input("Confirm? (Y/N): ")
    return response.upper() == "Y"

def my_takeover(message: str) -> None:
    """人工接管"""
    print(f" Takeover required: {message}")
    input("Press Enter after completing manual operation...")

agent = PhoneAgent(
    confirmation_callback=my_confirmation,
    takeover_callback=my_takeover
)

推荐学习资源

论文
  1. AutoGLM: Autonomous Foundation Agents for GUIs (2024)
  2. MobileRL: Online Agentic Reinforcement Learning for Mobile GUI Agents (2025)
  3. Screen2Words: Automatic Mobile UI Summarization (ACL 2021)
相关技术
  • ADB 官方文档:https://developer.android.com/tools/adb
  • OpenAI API 规范:https://platform.openai.com/docs/api-reference
  • vLLM 文档:https://docs.vllm.ai/
相关项目
  • GLM-V:https://github.com/zai-org/GLM-V (模型部署指南)
  • AppAgent:微软的手机 Agent 项目
  • UFO:微软的 Windows UI Agent

总结

核心技术总结

技术领域 核心技术 关键价值
AI 推理 Vision-Language Model 理解屏幕内容和用户意图
Prompt 工程 Chain-of-Thought + 规则约束 引导 AI 正确决策
设备控制 ADB + 坐标归一化 跨设备通用控制
文本输入 ADB Keyboard + 广播 支持中文输入
内存优化 图像删除策略 节省 99% 内存
安全机制 敏感操作确认 + 人工接管 保障用户安全
性能优化 流式输出 + 智能缓冲 提升用户体验

项目亮点

  1. 真正的多模态Agent:结合视觉和文本,理解复杂界面
  2. 开箱即用:无需标注UI元素,直接自然语言控制
  3. 高度可扩展:清晰的模块化设计,易于二次开发
  4. 生产级质量:完善的错误处理、安全机制和性能优化
  5. 开源开放:Apache 2.0 协议,社区友好

技术挑战

挑战 当前解决方案 局限性
坐标精度 归一化坐标 + AI视觉定位 小元素可能点击不准
动态界面 实时截图 + 状态检测 动画过程中可能误判
应用适配 包名映射表 需手动维护50+应用
推理速度 流式输出 仍需2-3秒/步
任务记忆 文本历史 跨任务无记忆

适用场景

适合

  • 重复性手机操作自动化
  • UI 自动化测试
  • 数据采集和爬虫
  • 研究和学习 AI Agent

不适合

  • 实时性要求极高的场景(如游戏竞技)
  • 需要100%准确率的关键业务
  • 复杂的逻辑推理任务
  • 需要深度交互的创意工作

附录

A. 常用命令速查

# 设备管理
adb devices              # 列出设备
adb connect IP:PORT      # 连接远程设备
adb disconnect           # 断开所有设备

# Agent 运行
python main.py "任务描述"                    # 单次任务
python main.py --list-apps                  # 列出支持的应用
python main.py --device-id XXX "任务"       # 指定设备

# 模型部署
python -m vllm.entrypoints.openai.api_server --model MODEL_PATH --port 8000

# 调试
python main.py --verbose "任务"  # 详细输出

B. 目录导航

文件/目录 功能 重要度
phone_agent/agent.py 核心Agent类
phone_agent/model/client.py AI推理客户端
phone_agent/actions/handler.py 动作执行器
phone_agent/adb/device.py 设备控制
phone_agent/config/prompts_zh.py 中文Prompt
phone_agent/config/apps.py 应用映射表
main.py CLI入口

C. 常见问题

Q1: 为什么截图是黑屏?
A: 可能是敏感页面(支付/密码),系统阻止截图。Agent会自动触发人工接管。

Q2: 中文输入变成乱码?
A: 检查是否安装并启用了 ADB Keyboard。

Q3: 点击位置不准确?
A: 尝试调整坐标,或在Prompt中添加更精确的描述。

Q4: 任务卡住不动?
A: 检查 max_steps 是否达到上限,或查看日志是否有错误。

Q5: 能否支持iOS设备?
A: 当前仅支持Android。iOS需要通过 WebDriverAgent,架构需调整。


总结

通过对 Open-AutoGLM 源码的深入分析,我们可以看到这是一个设计精良、技术先进的多模态 Agent 项目。从多模态 AI 模型到 ADB 设备控制,从内存优化到安全机制,每个技术细节都经过深思熟虑。

对于想要学习 AI Agent 开发的开发者来说,这个项目提供了完整的实践案例。无论是架构设计、Prompt Engineering,还是工程化实践,都值得深入研究。

项目地址:https://github.com/zai-org/Open-AutoGLM

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐