安卓智能体:MobileAgent分析及体验

近期,大模型领域的发展突飞猛进,模型推理能力的增强、多模态领域的突破、Agent形式的火热发展,让很多事情变得可能。可以预见的是,很多传统的交互方式都将发生改变。其中,手机/电脑作为我们生活中不可或缺的一部分,也一定会发生翻天覆地的变化。以后可能只需要通过自然语言的方式就可以让我们的设备自己完成很多复杂的操作。

目前github上已经有几个开源的移动端智能体的项目了,本文主要对安卓平台下的智能体项目:MobileAgent进行体验分析。

项目地址:https://github.com/X-PLUG/MobileAgent

Mobile-Agent是一款多模态AI手机操作助手。采用纯视觉方案,通过视觉感知工具和操作工具完成智能体在手机上的操作,无需依赖任何系统级别的UI文件。得益于这种智能体中枢模型的强大,Mobile-Agent实现了即插即用,无需进行额外的训练和探索。

MobileAgent有好几个分支,其中最新的是MobileAgent-E

MobileAgent-E 具有自我进化功能,专注于需要推理和多应用程序交互的复杂、长期任务。通过其独特的自我进化机制,系统可以借鉴过去的经验并不断改进其性能。

官方文档还是通过conda,然而conda比较臃肿,且安装速度慢。这里给出使用uv替换的例子:

1
2
3
4
5
6
7
uv venv 
source .venv/bin/activate
uv pip install -r requirements.txt

export ADB_PATH="/usr/local/bin/adb"
export QWEN_API_KEY="sk-xxx"
bash scripts/run_task.sh

安装过程并不是很顺利,遇到了第一个报错

1
2
3
4
5
6
7
8
....
  File "/Users/yzddmr6/project/MobileAgent/Mobile-Agent-E/.venv/lib/python3.10/site-packages/modelscope/models/base/base_head.py", line 5, in <module>
    from modelscope.models.base.base_model import Model
  File "/Users/yzddmr6/project/MobileAgent/Mobile-Agent-E/.venv/lib/python3.10/site-packages/modelscope/models/base/base_model.py", line 16, in <module>
    from modelscope.utils.plugins import (register_modelhub_repo,
  File "/Users/yzddmr6/project/MobileAgent/Mobile-Agent-E/.venv/lib/python3.10/site-packages/modelscope/utils/plugins.py", line 18, in <module>
    import pkg_resources
ModuleNotFoundError: No module named 'pkg_resources'

查一下Gemini:

1
uv pip install --upgrade pip setuptools

接着又出现另一个报错:

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749375510925-63c17f2f-57d1-4706-b95a-1aa75a1a948c.png

发现是我环境里numpy版本太新了,而项目里没有对版本进行约束。

1
2
pip uninstall numpy
pip install numpy==1.26.4 # 或者 1.26.x 的最新稳定版本

然后是第三个错误:

1
ImportError cannot import name 'get_metadata_patterns' from 'datasets.data_files

解决方法:

1
pip install "datasets<3"

这些问题issue里其实有人反馈了,但是官方并没有处理:https://github.com/X-PLUG/MobileAgent/issues/120

初始化的时候项目会下载一个本地的OCR模型

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749376214336-f3487fa1-4540-4b62-9ee9-25715659c496.png

这里项目代码默认支持以下三种模型:OpenAI, Gemini, Claude

1
2
3
4
## Reasoning model configs
BACKBONE_TYPE = os.environ.get("BACKBONE_TYPE", default="OpenAI") # "OpenAI" or "Gemini" or "Claude"
assert BACKBONE_TYPE in ["OpenAI", "Gemini", "Claude"], "Unknown BACKBONE_TYPE"
print("### Using BACKBONE_TYPE:", BACKBONE_TYPE)

model是写死的,不能通过参数传入,那就直接改代码吧。

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749478587812-c3a008a2-b442-4ca3-a64c-12e904a227f7.png

MobileAgent采用的是ADB截图->云端模型图像识别->本地模型OCR->云端推理模型分析->ADB Keyboard执行指令的方案

调用流程图如下

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749604788292-3455f4ce-1196-49a8-9f05-40de35c4d980.png

采用的MultiAgent架构

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749604894925-d8c0ca26-c916-4c15-b944-060956e7783d.png

就不人工写了,AI总结一下

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
该项目(Mobile-Agent-E)是一个自动化移动设备操作智能体系统,能够通过感知、推理、执行和反思等模块,自动完成手机上的复杂任务。下面将详细分析其执行逻辑,并给出详细的执行流程图和各主要函数的调用关系。

---

## 一、整体执行流程

### 1. 启动入口

- **入口文件**:`run.py`
- **主函数**:`main()`
  - 解析命令行参数(如任务、日志目录、推理模型等)
  - 根据参数决定是单任务还是多任务模式
  - 调用 `run_single_task()`(单任务)或循环调用(多任务)

---

### 2. 单任务执行主流程

#### 主要函数:`run_single_task()`(inference_agent_E.py)

#### 详细流程:

1. **日志与环境初始化**
   - 创建日志目录、截图目录、临时目录
   - 读取/初始化 tips 和 shortcuts
   - 初始化 InfoPool(信息池,贯穿任务始终,记录所有状态)

2. **感知模型与Agent初始化**
   - 初始化 Perceptor(感知器,负责屏幕内容感知)
   - 初始化 Manager、Operator、Notetaker、ActionReflector、ExperienceReflectorShortCut、ExperienceReflectorTips 等智能体

3. **主循环(每一步迭代)**
   - **感知(Perception)**
     - `perceptor.get_perception_infos()`:获取当前屏幕的文本、图标等信息
     - 记录到 InfoPool
   - **高层规划(Planning)**
     - `manager.get_prompt()`:生成高层计划提示词
     - `add_response()`:构造多模态对话历史
     - `get_reasoning_model_api_response()`:调用大模型推理,得到计划
     - `manager.parse_response()`:解析计划、当前子目标
     - 更新 InfoPool
   - **经验反思(Experience Reflection)**(仅在任务结束时)
     - `exp_reflector_shortcuts.get_prompt()`、`exp_reflector_tips.get_prompt()`:生成反思提示
     - `get_reasoning_model_api_response()`:推理生成新 shortcut 和 tips
     - `exp_reflector_shortcuts.add_new_shortcut()`:添加新 shortcut
     - 更新 InfoPool
   - **执行(Action Decision & Execution)**
     - `operator.get_prompt()`:生成操作提示
     - `add_response()`:构造多模态对话历史
     - `get_reasoning_model_api_response()`:推理生成 action
     - `operator.parse_response()`:解析 action
     - `operator.execute()`:执行 action(包括原子操作和快捷操作)
     - 更新 InfoPool
   - **再感知(Perception)**
     - 再次调用 `perceptor.get_perception_infos()`,获取执行后的屏幕状态
     - 更新 InfoPool
   - **反思(Action Reflection)**
     - `action_reflector.get_prompt()`:生成反思提示
     - `add_response_two_image()`:构造前后截图的多模态对话历史
     - `get_reasoning_model_api_response()`:推理判断 action 成功与否
     - `action_reflector.parse_response()`:解析结果
     - 更新 InfoPool
   - **重要内容记录(Notetaking)**
     - 如果 action 成功,调用 `notetaker.get_prompt()`、`add_response()`、`get_reasoning_model_api_response()`、`notetaker.parse_response()`,记录重要内容
     - 更新 InfoPool
   - **终止条件判断**
     - 达到最大步数、连续失败、重复操作、任务完成等条件时,调用 `finish()` 结束任务

---

## 二、主要函数调用关系

### 1. run.py
- `main()`  
  └── `run_single_task()`(或多次)

### 2. inference_agent_E.py
- `run_single_task()`
  - 初始化各 Agent
  - 主循环
    - `perceptor.get_perception_infos()`
      - `get_screenshot()`
      - `ocr()`
      - `det()`
      - `merge_text_blocks()`
      - `draw_coordinates_on_image()`
      - `crop()`
      - `generate_api()`/`generate_local()`
    - `manager.get_prompt()`
    - `add_response()`
    - `get_reasoning_model_api_response()`
      - `inference_chat()`
    - `manager.parse_response()`
    - `operator.get_prompt()`
    - `add_response()`
    - `get_reasoning_model_api_response()`
    - `operator.parse_response()`
    - `operator.execute()`
      - `execute_atomic_action()`(tap、swipe、type、back、home、switch_app、enter、wait等)
      - `save_screenshot_to_file()`
    - `action_reflector.get_prompt()`
    - `add_response_two_image()`
    - `get_reasoning_model_api_response()`
    - `action_reflector.parse_response()`
    - `notetaker.get_prompt()`
    - `add_response()`
    - `get_reasoning_model_api_response()`
    - `notetaker.parse_response()`
    - `finish()`

---

## 三、执行流程图

---mermaid
flowchart TD
    A[main() in run.py] --> B{单任务 or 多任务}
    B -- 单任务 --> C[run_single_task()]
    B -- 多任务 --> C[run_single_task() 循环]
    C --> D[初始化日志/目录/InfoPool]
    D --> E[初始化 Perceptor & Agents]
    E --> F{主循环: 每步迭代}
    F --> G[Perceptor 感知 get_perception_infos]
    G --> H[Manager 规划 get_prompt → 推理 → parse_response]
    H --> I{是否任务完成}
    I -- 否 --> J[Operator 执行 get_prompt → 推理 → parse_response → execute]
    J --> K[Perceptor 再感知 get_perception_infos]
    K --> L[ActionReflector 反思 get_prompt → 推理 → parse_response]
    L --> M{Action 成功?}
    M -- 是 --> N[Notetaker 记录 get_prompt → 推理 → parse_response]
    M -- 否 --> O[跳过 Notetaker]
    N --> P[更新 InfoPool & 日志]
    O --> P
    P --> F
    I -- 是 --> Q[ExperienceReflector 反思经验]
    Q --> R[finish() 结束]
---

---

## 四、各核心函数说明

### 1. 感知相关
- `get_perception_infos()`:调用截图、OCR、图标检测、图标描述,合并文本块,返回所有可交互元素信息。
- `ocr()`:OCR 检测与识别,返回文本及其坐标。
- `det()`:图标检测,返回图标坐标。
- `generate_api()/generate_local()`:对图标图片进行描述。

### 2. 规划与推理
- `get_prompt()`(Manager/Operator/ActionReflector/Notetaker):根据 InfoPool 生成多模态提示词。
- `add_response()/add_response_two_image()`:构造多模态对话历史。
- `get_reasoning_model_api_response()`:调用大模型 API 进行推理。
- `parse_response()`:解析模型输出,提取计划、action、反思结果等。

### 3. 执行与操作
- `execute()`(Operator):解析 action,调用原子操作或快捷操作。
- `execute_atomic_action()`:具体执行 tap、swipe、type、back、home、switch_app、enter、wait 等操作。
- `save_screenshot_to_file()`:保存执行后截图。

### 4. 反思与自我进化
- `ActionReflector`:判断 action 是否成功,分析失败原因。
- `Notetaker`:记录重要内容。
- `ExperienceReflectorShortCut/ExperienceReflectorTips`:反思生成新 shortcut 和 tips。

### 5. 终止与总结
- `finish()`:保存最终 tips、shortcuts,总结任务过程。

分析代码发现,项目里总共有三个地方使用到了模型:

  1. 感知部分:在 inference_agent_E.py 中,CAPTION_CALL_METHOD 可以设置为 "api""local"。如果为 "api",则图标描述等多模态理解会通过如 Qwen-VL-Plus 等云端API完成。如果为 "local",则会下载本地 Qwen-VL-Chat 模型权重,并用本地推理(需有显卡和环境支持)。默认是调用API。
  2. 推理部分:推理模型(如 GPT-4o、Gemini、Claude)全部通过API调用,不支持本地大模型推理。
  3. OCR和图标提取:OCR 和图标检测等视觉感知部分,使用的是 ModelScope 的本地模型。

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749876522859-91e3c0ea-9e9a-4bfd-99d4-8dc15bc2a2c2.png

这里就有一个问题了,都是图像识别,为啥还要拉一个本地的OCR模型,不直接用Qwen-VL-Plus识别呢?

找到了项目作者之前的PPT解释了这一点,总结一下就是当前模型的多模态能力不行,因此要再用一个本地的OCR作为补充。

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749875491315-970f0cf5-cb8a-4507-b2aa-1d95a6e516f6.png

同时也问了一下AI的看法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Qwen-VL-Plus 等云端 API 模型和本地 OCR 识别模型在项目中的作用有明显区别:

---

### 1. Qwen-VL-Plus(云端多模态大模型)的作用
- 主要用于**图标描述**(icon captioning)和复杂多模态理解。
- 输入是裁剪下来的图标图片,输出是对图标的形状、颜色等自然语言描述。
- 适合需要理解图片内容、场景、复杂语义的任务。
- 在代码中,`generate_api`、`process_image` 等函数会调用 Qwen-VL-Plus API,对图标图片进行描述。

### 2. 本地 OCR 识别模型的作用
- 专门用于**屏幕文本的检测和识别**,即提取屏幕上的所有可见文字及其坐标。
- 由 ModelScope 的 OCR 检测和识别 pipeline 实现,速度快、资源消耗低、无需联网。
- 在代码中,`ocr()` 函数会调用本地 OCR pipeline,直接返回文本内容和位置。

---

## 为什么不直接用 Qwen-VL-Plus 做 OCR 识别?

1. **专业性与准确率**  
   - Qwen-VL-Plus 是通用多模态大模型,虽然能理解图片,但在纯 OCR 任务(尤其是小字体、复杂背景、中文等)上,准确率远低于专用 OCR 模型。
   - 专业 OCR 模型针对文本检测和识别优化,效果更好。

2. **效率与成本**  
   - 本地 OCR 推理速度快、无需网络、无 API 调用费用。
   - Qwen-VL-Plus 作为云端大模型,推理慢且每次调用都消耗 API 额度,成本高。

3. **工程分工明确**  
   - 项目采用“专用模型做专用事”:OCR 负责文本,Qwen-VL-Plus 负责图标/复杂图片理解,互补协作,提升整体性能和效率。

4. **API 限制与稳定性**  
   - 频繁调用云端大模型做 OCR,容易受限于 API 速率、网络波动,影响系统稳定性。

---

## 总结

- **本地 OCR**:高效、准确地提取屏幕文本。
- **Qwen-VL-Plus**:理解和描述图标、复杂图片内容。
- **分工协作**,各司其职,提升系统整体效果和效率。

这也是业界常见的多模态系统工程实践。

下面是对核心页面元素识别函数get_perception_infos 的逐行解释:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def get_perception_infos(self, screenshot_file, temp_file=TEMP_DIR):
        # 通过ADB获取当前屏幕截图
        get_screenshot(self.adb_path)
        
        # 打开截图,获取图片宽高
        width, height = Image.open(screenshot_file).size
        
        # 对截图进行OCR,获取文本和对应坐标
        text, coordinates = ocr(screenshot_file, self.ocr_detection, self.ocr_recognition)
        # 合并相邻的文本块
        text, coordinates = merge_text_blocks(text, coordinates)
        
        # 计算每个文本块的中心点坐标
        center_list = [[(coordinate[0]+coordinate[2])/2, (coordinate[1]+coordinate[3])/2] for coordinate in coordinates]
        # 在图片上绘制中心点
        draw_coordinates_on_image(screenshot_file, center_list)
        
        # 构建感知信息列表,每个元素包含文本和坐标
        perception_infos = []
        for i in range(len(coordinates)):
            perception_info = {"text": "text: " + text[i], "coordinates": coordinates[i]}
            perception_infos.append(perception_info)
            
        # 检测图标(icon),获取其坐标
        coordinates = det(screenshot_file, "icon", self.groundingdino_model)
        
        # 将检测到的icon加入感知信息列表
        for i in range(len(coordinates)):
            perception_info = {"text": "icon", "coordinates": coordinates[i]}
            perception_infos.append(perception_info)
            
        # 筛选所有icon的坐标和对应的索引
        image_box = []
        image_id = []
        for i in range(len(perception_infos)):
            if perception_infos[i]['text'] == 'icon':
                image_box.append(perception_infos[i]['coordinates'])
                image_id.append(i)

        # 将每个icon区域裁剪出来,保存到临时文件夹
        for i in range(len(image_box)):
            crop(screenshot_file, image_box[i], image_id[i], temp_file=temp_file)

        # 获取临时文件夹下所有图片文件
        images = get_all_files_in_folder(temp_file)
        if len(images) > 0:
            # 按文件名中的数字排序
            images = sorted(images, key=lambda x: int(x.split('/')[-1].split('.')[0]))
            # 提取图片对应的id
            image_id = [int(image.split('/')[-1].split('.')[0]) for image in images]
            icon_map = {}
            # 设定描述icon的提示词
            prompt = 'This image is an icon from a phone screen. Please briefly describe the shape and color of this icon in one sentence.'
            if CAPTION_CALL_METHOD == "local":
                # 本地模型生成icon描述
                for i in range(len(images)):
                    image_path = os.path.join(temp_file, images[i])
                    icon_width, icon_height = Image.open(image_path).size
                    # 过滤掉过大的icon
                    if icon_height > 0.8 * height or icon_width * icon_height > 0.2 * width * height:
                        des = "None"
                    else:
                        des = generate_local(self.vlm_tokenizer, self.vlm_model, image_path, prompt)
                    icon_map[i+1] = des
            else:
                # API方式批量生成icon描述
                for i in range(len(images)):
                    images[i] = os.path.join(temp_file, images[i])
                icon_map = generate_api(images, prompt, caption_model=CAPTION_MODEL)
            # 将icon描述填充回感知信息
            for i, j in zip(image_id, range(1, len(image_id)+1)):
                if icon_map.get(j):
                    perception_infos[i]['text'] = "icon: " + icon_map[j]

        # 将所有感知信息的坐标转为中心点坐标(整数)
        for i in range(len(perception_infos)):
            perception_infos[i]['coordinates'] = [int((perception_infos[i]['coordinates'][0]+perception_infos[i]['coordinates'][2])/2), int((perception_infos[i]['coordinates'][1]+perception_infos[i]['coordinates'][3])/2)]
            
        # 返回感知信息、图片宽度和高度
        return perception_infos, width, height

有两种运行模式:单任务模式和批量多任务模式。多任务模式看起来是支持“evolution”的

1
The agent can be run in both `individual` (performing a standalone task) or `evolution` (performing a sequence of tasks with evolution) settings. We provide example shell scripts as follows:

先跑一下单任务的试试

1
2
3
4
python run.py \
    --run_name "testing" \
    --setting "individual" \
    --instruction "Find me at least two survery papers on Large Language Models. Check the detailed abstract of the most cited one. And then create a new note in Notes and add the titles the papers you found. Also include the abstract of the most cited paper."

屏幕会把每个Agent的分析过程打印出来。

注意看截图里有很多sleeping,这个地方后面会提到。

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749478658535-abcd867e-588e-4ef0-870c-05c082c3b25b.png

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749478722288-94be523a-7fa3-4f03-9cb2-17c9f50fcbda.png

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749478840990-89b04f6d-c389-47ca-90fd-20581be203bc.png

然后会把每一步的截图,保存到logs文件夹下面

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749478890370-fff3c4e4-85d3-4b48-ba74-1bdac20ae976.png

贴一下部分日志

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

### Manager ... ###

Thought: The current subgoal is to open the Chrome browser. I can see the Chrome icon on the home screen. I will tap on the Chrome icon to open the browser.
Overall Plan: 1. Open the Chrome browser. 2. Navigate to the blog `yzddmr6.com`. 3. Read the content of the first three articles on the blog. 4. Generate a summary of the content from the three articles. 5. Open the file manager (MT管理器). 6. Create a new text file named `blog.txt` and save the generated summary into it.
Current Subgoal: 1. Open the Chrome browser.

### Operator ... ###

Executing atomic action:  Tap {'x': 745, 'y': 1963}
        Atomic Operation Screenshot saved to logs/gemini-2.5-flash-preview-05-20/mobile_agent_E/testing/20250614-105247/screenshots/2__Tap.png
Action Thought: My previous attempt to open Chrome using `Open_App` failed, indicating that the app name "Chrome" might not be recognized or directly available for that action. Looking at the screenshot, I can see the Chrome browser icon at coordinates [745, 1963]. To open the browser, I will directly tap on this icon.
Action Description: Tap the Chrome browser icon to open the application. This is a direct way to launch the app when `Open_App` fails or the app name is not explicitly listed.
Action: {'name': 'Tap', 'arguments': {'x': 745, 'y': 1963}}

### Perceptor ... ###

SupervisionWarnings: annotate is deprecated: `BoxAnnotator` is deprecated and will be removed in `supervision-0.22.0`. Use `BoundingBoxAnnotator` and `LabelAnnotator` instead
Perception Infos: [{'text': 'text: 91%', 'coordinates': [956, 60]}, {'text': 'text: @', 'coordinates': [335, 62]}, {'text': 'text: 10:54', 'coordinates': [246, 61]}, {'text': 'text: 40', 'coordinates': [753, 62]}, {'text': 'text: e', 'coordinates': [801, 62]}, {'text': 'text: O', 'coordinates': [63, 167]}, {'text': 'text: i', 'coordinates': [1014, 166]}, {'text': 'text: 0', 'coordinates': [881, 167]}, {'text': 'text: Google', 'coordinates': [535, 336]}, {'text': 'text: 搜索或输入网址', 'coordinates': [283, 560]}, {'text': 'text: f', 'coordinates': [834, 851]}, {'text': 'text: 虎', 'coordinates': [613, 852]}, {'text': 'text: 虎扑', 'coordinates': [612, 965]}, {'text': 'text: 百度', 'coordinates': [187, 966]}, {'text': 'text: Gitl', 'coordinates': [1012, 965]}, {'text': 'text: YouTube', 'coordinates': [400, 966]}, {'text': 'text: Facebook', 'coordinates': [827, 966]}, {'text': 'text: 探索', 'coordinates': [125, 1186]}, {'text': 'text: 8', 'coordinates': [937, 1194]}, {'text': 'text: X', 'coordinates': [915, 1330]}, {'text': 'text: 获得更合平需求的内容', 'coordinates': [539, 1485]}, {'text': 'text: 登录即可获取系统根据您的兴趣推荐的内容', 'coordinates': [539, 1573]}, {'text': 'text: 以6的身份继续', 'coordinates': [540, 1694]}, {'text': 'text: 选择其他账号', 'coordinates': [536, 1825]}, {'text': 'text: 用了几十年才刚知道,\n塑料椅中间的小洞是这', 'coordinates': [361, 2074]}, {'text': 'text: 么用的,涨知识了', 'coordinates': [303, 2172]}, {'text': 'text: i', 'coordinates': [966, 2266]}, {'text': 'text: 阿波罗新闻网·1日', 'coordinates': [290, 2269]}, {'text': 'icon: The icon is a blue square with a white paw print in the center.', 'coordinates': [186, 847]}, {'text': 'icon: The icon is a square with rounded corners, featuring a red background and a white play button in the center.', 'coordinates': [399, 847]}, {'text': 'icon: The icon is a teal circle with the number 6 in white.', 'coordinates': [538, 1356]}, {'text': 'icon: The icon is a blue square with rounded corners, featuring the white lowercase letter "f" in the center.', 'coordinates': [824, 847]}, {'text': 'icon: The icon is a simple, black house shape with a triangular roof on a white background.', 'coordinates': [65, 166]}, {'text': 'icon: The icon is a dark purple circle with a white camera outline inside it.', 'coordinates': [935, 560]}, {'text': 'icon: The icon is a yellow sun with rays extending outward, set against a white background.', 'coordinates': [109, 2265]}, {'text': 'icon: The icon depicts four orange plastic stools arranged in a square formation.', 'coordinates': [848, 2101]}, {'text': 'icon: The icon is a simple, rounded square with a white microphone symbol in the center, set against a dark gray background.', 'coordinates': [803, 560]}, {'text': 'icon: The icon is a square with rounded corners, colored black and white, featuring the number "1" in the center.', 'coordinates': [880, 166]}, {'text': 'icon: The icon is a black circle with a smaller white circle in the center, resembling an eye or a simplified face.', 'coordinates': [743, 165]}, {'text': "icon: The icon features a stylized, simplified shape of a tiger's head in black against a white background.", 'coordinates': [613, 848]}, {'text': 'icon: The icon is a circular shape with a dark background and features the red Chinese character "虎" (tiger) in the center.', 'coordinates': [612, 847]}, {'text': 'icon: The icon is a white, stylized word "Google" on a black background.', 'coordinates': [535, 337]}, {'text': 'icon: The icon is a black, X-shaped symbol on a white background.', 'coordinates': [913, 1327]}, {'text': 'icon: The icon is a black, circular shape with two smaller circles connected by lines, resembling a share symbol.', 'coordinates': [833, 2265]}, {'text': 'icon: The icon is a square with rounded corners, featuring a red play button on a white background, set against a dark circular backdrop.', 'coordinates': [399, 847]}, {'text': 'icon: The icon is an oval shape with a light purple background.', 'coordinates': [539, 1693]}, {'text': 'icon: The icon is a circular shape with a white background and a black outline, featuring a small vertical line in the center.', 'coordinates': [335, 62]}, {'text': 'icon: The icon depicts two orange, square-shaped stools stacked on top of each other.', 'coordinates': [777, 2090]}, {'text': 'icon: The icon depicts a simple, orange stool with a square seat and four legs.', 'coordinates': [823, 2128]}, {'text': 'icon: The icon features a simple, orange stool with a circular seat and four square legs.', 'coordinates': [935, 2120]}, {'text': 'icon: The icon is a simple, black Chinese character "探索" (exploration) on a white background.', 'coordinates': [125, 1185]}, {'text': 'icon: The icon features a square shape with rounded edges, colored in a bright orange hue.', 'coordinates': [884, 2052]}, {'text': 'icon: The icon is a simple, bold numeral "6" in black against a white background.', 'coordinates': [539, 1355]}, {'text': 'icon: The icon is a black circle with a diagonal line crossing through it, indicating prohibition or cancellation.', 'coordinates': [751, 61]}, {'text': 'icon: The icon is a white semicircle on a black background.', 'coordinates': [853, 60]}, {'text': 'icon: The icon is a square with rounded corners, featuring the letter "f" in white on a blue background.', 'coordinates': [824, 846]}, {'text': 'icon: The icon is a square with rounded corners, featuring a light blue paw print design and the letters "du" in a darker shade of blue.', 'coordinates': [186, 846]}, {'text': 'icon: The icon is a black square with white Chinese text that reads, "用了几十年才刚知道,塑料椅中间的小洞是这么用的,涨知识了."', 'coordinates': [361, 2106]}, {'text': 'icon: The icon is a circular shape with a dark gray color.', 'coordinates': [1017, 847]}, {'text': 'icon: The icon is a simple black circle with a horizontal line through the center, indicating a cancel or stop function.', 'coordinates': [800, 61]}]

### Action Reflector ... ###

Outcome: A
Progress Status: 1. Open the Chrome browser. 2. Navigate to yzddmr6.com.
Error Description: None

### NoteKeeper ... ###

Important Notes: - The Chrome browser is open and displaying the Google search page. - There is a search/URL input field labeled "搜索或输入网址" (Search or enter URL) at coordinates [283, 560].

=========================================================
sleeping for 5 before next iteration ...

运行的时候手机界面自动打开了谷歌

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749867595724-454da40f-4af4-4b76-91e2-622591862e69.png

访问论文链接

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749867619876-3c62f6c2-6d88-422a-8147-c4798e3ced33.png

每一步操作非常的慢,每步大概要等10-20s。

最终回到首页之后,模型似乎不知道干啥了,卡了很久,没有解决测试样例中的问题。

后来又换了一个任务尝试:通过浏览器访问yzddmr6.com这个博客,对前3篇文章进行查看,并生成一段话描述内容的总结。总结内容保存到文件管理器的blog.txt文件中。

结果还是没执行完就卡住了。。。没有再继续测下去了

在分析项目的过程中我会问自己:如果我是来设计,怎么样可以做的更好?

首先是页面元素识别的部分:出于通用性的考虑,MobileAgent采用的是截图的方式来获取页面数据,但是在使用的过程中发现非常的低效。

首先,adb截图->保存截图->base64截图发送给云端识别模型->本地OCR模型识别。链路太长,且增加了调用设备的负担。

其次,由于截屏的方式无法监听页面的变化,我们需要不断截屏来主动获取页面的信息。然而由于频繁截图会影响性能,且分析链路较长,所以截屏的频率不能过高。因此为了照顾到页面加载、APP启动这类需要等待的场景,项目中是写死了每次操作都要强制sleep,非常的低效。

这样会带来一个问题:不同的APP启动时间不一样,有些快有些慢。项目里只能统一取一个较长的等待时间,来保证大多数应用都能在这个时间内启动完毕,这样做会浪费了大量的时间。

并且代码里是把’Fandango’, ‘Walmart’, ‘Best Buy’这三个APP硬编码了等待的时间,可维护性很差。

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749878653692-cb86714f-143e-41a5-965f-ebbaa368a263.png

我注意到MobileAgent还准备推出PC版本,开发者想要一个通用的、大一统的方案的想法可以理解,然而现实常常是既要又要的方案,往往什么都做不好。

既然是安卓平台,应该优先考虑使用安卓的无障碍服务的接口,可以监听页面的变化,并且能够精确识别各个组件按钮的布局以及位置。然后只在某些极其特殊的情况下(例如某些APP的开屏广告,会把跳过按钮变成一个图片,来阻止对文本的识别),再考虑使用截图方案。

此外,是启动应用的部分,MobileAgent采用的是截图识别屏幕应用,然后点击的方式。

都有adb了,为什么不直接用adb shell命令列举出所有APP,然后adb+包名直接启动呢?

假如说我有10页APP,最差的情况下模型得先翻完10页才能找到需要调用的应用。另外,很多用户都会把APP进行分组,那模型是不是还需要把每个分组给点开看看?

在多Agent的实现方面,没有采用成熟的LangChain或者LangGraph等框架,而是选择手写。采用什么框架本来是个人喜好的问题,但是从当前的效果和后期维护成本来看,还是尽量使用成熟的框架比较好。

对我个人而言,安装和使用的体验是比较糟糕的。

同时,在浏览项目代码的过程中,还发现了很多不专业的地方。

在README里,提到要安装adbkeyboard这个apk,结果发现这个链接是挂掉的。

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749376664677-2e8a9f19-d9cb-4be2-a6fe-4ea2ac835168.png

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749376761655-4234c18b-182b-452e-bd80-250c276aef16.png

同样,首页的ModelScope链接也是挂的。

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749376828444-ca01b0d4-39f7-4863-92f5-07a206d40d65.png

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749376800989-be238479-d291-482e-a5c4-6fbb23eb572a.png

在代码里,甚至还能看到硬编码的开发者的服务器地址,推测是作者的Claude中转API。

https://github.com/X-PLUG/MobileAgent/blob/72a6fd9d245575e18f741141d92255de13c8c326/Mobile-Agent-E/MobileAgentE/api.py#L65

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749540003743-679904d2-9684-49d0-8190-a5fc1aea1bdc.png

一个充满了print的,600行的函数。

https://cdn.nlark.com/yuque/0/2025/png/1599908/1749540009024-224661db-1089-4dc0-9bf3-235fe5127a3d.png

除此之外,项目整体也缺乏设计模式,代码比较杂乱和冗余。

总结一下:本文测试的MobileAgent,还是有比较大的进步空间,离我设想中的安卓智能体还有很远的距离。

今后的交互方式,一定不是需要连接电脑然后配ADB这种“外挂式”的交互,而应该是内置于设备的,用户直接在设备上就可以通过自然语言完成各种复杂操作。从刚结束不久的WWDC25也能看到苹果的布局和思考。

在后续的调研中,我又发现了一个很有潜力的安卓智能体项目。简单看了一下文档,从设计理念上与我的想法高度符合。具体体验怎么样,等我下次分析分析再跟大家分享。