深度解析 Open-AutoGLM:让 AI 自己操作手机的技术实现
Open-AutoGLM:AI自主操作手机的视觉语言模型框架 摘要: Open-AutoGLM是智谱AI开源的基于多模态大模型的手机智能助理框架,支持通过自然语言指令实现手机自动化操作。该系统结合视觉理解(屏幕截图分析)和语言模型推理,采用AutoGLM-9B特化模型,支持50+主流中文应用的跨操作。技术架构包含ADB控制层、多模态模型推理层和任务规划层,通过坐标归一化、动作指令标准化等技术实现精
深度解析 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
)
推荐学习资源
论文
- AutoGLM: Autonomous Foundation Agents for GUIs (2024)
- MobileRL: Online Agentic Reinforcement Learning for Mobile GUI Agents (2025)
- 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% 内存 |
| 安全机制 | 敏感操作确认 + 人工接管 | 保障用户安全 |
| 性能优化 | 流式输出 + 智能缓冲 | 提升用户体验 |
项目亮点
- 真正的多模态Agent:结合视觉和文本,理解复杂界面
- 开箱即用:无需标注UI元素,直接自然语言控制
- 高度可扩展:清晰的模块化设计,易于二次开发
- 生产级质量:完善的错误处理、安全机制和性能优化
- 开源开放: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
更多推荐


所有评论(0)