移动端自动化测试这件事,真正上手做过的人都清楚,最难的从来不是写脚本,而是让脚本"看懂"界面。传统方案靠控件树、坐标、图像匹配,界面一改就集体失效,维护成本高得离谱。这两年大模型能力成熟之后,我一直在尝试把LLM塞进移动端自动化链路里,前面几篇聊过整体架构和基础接入,这一篇专门讲一个绕不开的核心问题:当你要让模型驱动一台手机完成复杂任务时,到底该用一个"全能大模型"包打天下,还是拆成多个各司其职的角色模型?DroidRun和Mobilerun这两个项目在社区里被讨论得比较多,它们的共同思路就是角色级模型拆分。我拿它们做过几轮实测,也踩了不少坑,这篇就把拆分逻辑、落地方式、参数取舍和实际效果完整讲一遍,适合已经了解LLM基础调用、想深入移动端自动化落地的同学参考。
1. 为什么"一个模型干所有事"在移动端会翻车
1.1 移动端任务的天然多阶段性
先想清楚移动端自动化任务长什么样。一个典型任务可能是"打开某购物应用,搜索一个关键词,筛选价格区间,把前三个商品加入购物车"。这件事拆开看,至少包含几个性质完全不同的子问题:理解当前屏幕上有哪些可交互元素、决定下一步点哪里、判断点击后界面是否如预期变化、在长列表里定位目标、处理弹窗和权限请求。这些子问题的输入输出形态差异极大——有的需要视觉理解,有的需要文本推理,有的需要时序判断。
如果你只用一个通用大模型从头到尾处理,会发生什么?我实测过,最直接的问题是上下文爆炸。每一步都要把当前截图、历史操作、任务目标全部塞进prompt,token消耗飞快,而且模型在长上下文里对早期指令的注意力会衰减,走到第十步的时候它可能已经"忘了"最初的任务约束。这不是模型不行,是任务结构本身不适合单模型串行处理。
1.2 单模型的三个具体失效场景
我把实测中单模型方案翻车的情况归了三类,都很典型。
第一类是视觉与推理互相干扰。通用模型既要描述屏幕内容又要做决策,结果描述得过于啰嗦,把真正关键的按钮信息淹没在无关细节里。比如屏幕上有个"立即购买"按钮,模型却花大段篇幅描述背景图里的商品图片,决策时反而抓不住重点。
第二类是错误累积无法隔离。单模型链路里,某一步判断错了,错误会顺着上下文一路传下去,后面每一步都建立在错误前提上。你很难定位到底是哪一步出的问题,因为所有决策都混在同一个模型的输出里。
第三类是成本与延迟不可控。移动端自动化往往要跑成百上千次回归,每次都用最强模型全流程推理,账单会非常难看。而且强模型响应慢,一个任务跑下来几分钟,根本没法做高频测试。
这里的关键认知是:移动端自动化的瓶颈不在"模型够不够聪明",而在"任务结构有没有被正确分解"。角色级拆分本质上是一种工程化的任务分解,把不同性质的子问题交给最合适的模型处理。
1.3 角色拆分的核心思想
DroidRun和Mobilerun给出的答案是一致的:把整个自动化流程拆成若干角色,每个角色是一个独立的模型调用单元,有明确的输入输出契约和专属的prompt策略。常见的角色划分包括:屏幕解析角色(负责把截图转成结构化元素列表)、决策角色(根据元素列表和任务目标决定动作)、验证角色(判断动作执行后状态是否符合预期)、纠错角色(在卡住时重新规划)。
这样拆的好处很直接:每个角色的prompt可以高度定制,上下文只保留该角色需要的信息,token消耗大幅下降;某个角色出错时影响范围被限制在局部,便于定位和重试;不同角色可以用不同规格的模型,视觉重的用多模态小模型,推理重的用文本强模型,成本可控。
2. DroidRun与Mobilerun的角色划分差异
2.1 DroidRun的三角色结构
DroidRun我实测下来,它的角色划分相对精简,核心是三个:Perceiver(感知)、Planner(规划)、Actor(执行)。感知角色负责处理屏幕截图,输出当前界面的可交互元素清单,每个元素带类型、文本、坐标范围。规划角色拿到元素清单和任务目标,输出下一步应该执行的动作序列。执行角色把动作翻译成具体的ADB指令或无障碍服务调用。
这个结构的好处是职责边界清晰。感知角色只关心"屏幕上有什么",规划角色只关心"该做什么",执行角色只关心"怎么落地成操作"。我在调试时发现,当任务失败时,只要看规划角色的输出就能快速判断是"没找到正确元素"还是"决策逻辑错了",排查效率比单模型高一个数量级。
2.2 Mobilerun的细粒度角色拆分
Mobilerun走得更细,它在感知和规划之间又插入了**Grounding(定位)**角色,专门负责把规划角色输出的抽象目标(比如"点击搜索框")映射到感知角色给出的具体元素ID上。这个设计解决了一个很实际的问题:规划角色用自然语言描述动作时,措辞和感知角色输出的元素描述往往对不上,中间需要一个"翻译层"。
另外Mobilerun还有一个**Reflector(反思)**角色,在动作执行后判断结果是否符合预期,不符合就触发重新规划。这个角色在长任务里价值很大,因为移动端界面变化多端,一次点击没生效、弹窗遮挡、加载延迟都是家常便饭,有个专门的反思环节能显著提升任务完成率。
2.3 两种划分的适用场景对比
| 维度 | DroidRun三角色 | Mobilerun五角色 |
|---|---|---|
| 结构复杂度 | 低,易上手 | 高,配置项多 |
| 单步延迟 | 较低(调用次数少) | 较高(多一层定位和反思) |
| 长任务完成率 | 中等 | 较高 |
| 调试难度 | 低 | 中等 |
| 适合场景 | 短流程、结构稳定的应用 | 长流程、界面多变的场景 |
| 模型成本 | 低 | 中高 |
我的建议是:如果你的测试场景是登录、下单这类步骤固定、界面稳定的流程,DroidRun的三角色足够用,跑得快、好维护。如果是跨多个页面、有大量动态内容的复杂任务,Mobilerun的细粒度拆分更稳,多出来的延迟换来的完成率提升是值得的。
3. 角色间通信契约的设计要点
3.1 为什么契约比模型本身更重要
角色拆分之后,角色之间怎么传递信息就成了成败关键。我踩过最大的坑就是:一开始没定义清楚角色间的数据格式,感知角色输出一段自然语言描述,规划角色拿到后理解偏差,导致决策完全跑偏。后来我把所有角色间的通信全部改成结构化JSON,问题立刻少了一大半。
核心原则是:每个角色的输出必须是下一个角色能直接解析的结构化数据,而不是自由文本。自然语言只允许出现在最终给用户看的报告里,角色内部流转一律用JSON。
3.2 感知角色的输出契约
感知角色的输出我固定成这样的结构:
{ "screen_id": "step_003", "elements": [ { "id": "e1", "type": "button", "text": "搜索", "bounds": [820, 120, 980, 180], "clickable": true, "enabled": true }, { "id": "e2", "type": "input", "text": "", "hint": "请输入关键词", "bounds": [40, 120, 800, 180], "clickable": true, "enabled": true } ], "screen_summary": "顶部搜索栏,下方商品列表" }这里每个元素都有稳定的id,规划角色后续引用元素时只用id,不用坐标也不用文本描述,彻底避免了"描述对不上"的问题。bounds用四元组表示,方便执行角色直接换算成点击坐标。
3.3 规划角色的输出契约
规划角色的输出我要求它必须给出动作类型、目标元素id和理由:
{ "action": "click", "target_id": "e2", "reason": "需要先点击搜索框才能输入关键词", "expected_state": "搜索框获得焦点,键盘弹出" }注意expected_state这个字段,它是给反思角色用的。规划角色在决策时顺便预测一下执行后的预期状态,反思角色拿到实际状态后一对比,就能判断这步是否成功。这个设计让反思变得有据可依,而不是让模型凭空判断"看起来对不对"。
3.4 契约设计的三个经验
第一,id必须由感知角色统一分配,且在同一屏幕内唯一。不要让规划角色自己编id,否则对不上。
第二,所有字段都要有明确的类型和取值范围。比如action只能是click、input、swipe、back、wait这几个枚举值,模型输出非法值时直接判为失败并重试,不要试图容错解析。
第三,契约要版本化。我在项目里给契约加了version字段,改结构时同步升版本,避免新旧角色混用导致诡异bug。这个习惯在多人协作时尤其重要。
4. 模型选型:每个角色该配什么模型
4.1 感知角色优先选多模态小模型
感知角色的任务是把截图转成元素列表,本质是视觉理解加结构化输出。这个任务不需要顶级推理能力,但需要稳定的多模态识别。我实测下来,中等规模的多模态模型就够用,响应快、成本低。用超大模型做感知是浪费,因为它的推理能力在这个环节根本用不上。
关键指标是元素召回率:屏幕上真实可点击的元素,感知角色能识别出多少。我一般要求召回率在95%以上,漏掉关键按钮会直接导致任务失败。测试方法是拿一批标注好的截图跑一遍,统计漏检和误检。
4.2 规划角色值得用强模型
规划角色是整个链路的大脑,它要根据元素列表和任务目标做决策,还要处理多步依赖、条件分支。这个环节我建议用推理能力强的模型,哪怕贵一点也值。因为规划错一步,后面全错,省这点钱不划算。
规划角色的prompt要包含:任务目标、当前元素列表、历史动作摘要(注意是摘要不是全量)、可用动作枚举。历史动作只保留最近几步加一个整体进度概述,避免上下文过长。
4.3 定位与反思角色的性价比选择
Mobilerun里的定位角色任务相对机械——把抽象目标映射到具体元素id,用中等模型即可。反思角色需要一定的判断力,但比规划简单,中等偏上的模型就够。
这里有个省钱技巧:反思角色可以只在规划角色"不确定"时才调用。让规划角色在输出时附带一个置信度,置信度高就直接执行,置信度低才触发反思。这样大部分简单步骤省掉了反思调用,整体成本能降三成左右。
4.4 各角色模型配置参考
| 角色 | 能力需求 | 模型规格建议 | 关键指标 |
|---|---|---|---|
| 感知 | 多模态识别、结构化输出 | 中等多模态模型 | 元素召回率≥95% |
| 规划 | 复杂推理、多步决策 | 强推理模型 | 决策准确率 |
| 定位 | 语义匹配 | 中等文本模型 | 映射准确率 |
| 反思 | 状态对比判断 | 中等偏上模型 | 误判率 |
| 执行 | 无需模型 | 规则引擎 | 指令成功率 |
5. 实测中的典型问题与排查链路
5.1 感知角色漏检导致规划"无路可走"
有一次跑一个登录流程,规划角色反复输出"找不到登录按钮",但截图里明明有。排查链路是这样的:先看感知角色的输出,发现元素列表里确实没有登录按钮;再看截图,按钮是有的,但它的文本是图片形式渲染的,不是标准控件文本。感知角色依赖控件树加OCR,这个按钮的OCR识别被背景干扰了。
解决办法是给感知角色加一路纯视觉检测兜底:当控件树里找不到可点击元素时,用视觉模型直接框选可疑区域。这个兜底逻辑加上之后,类似漏检问题基本消失。
5.2 规划角色陷入循环点击
另一个高频问题是规划角色反复点同一个元素。根因通常是反思角色没起作用,或者expected_state定义得太模糊。比如规划角色点击后预期"界面变化",但实际界面只是轻微刷新,反思角色判断"没变化"就触发重新规划,规划角色又选了同一个动作。
修复方法是让expected_state尽量具体,比如"搜索框内出现光标且键盘区域高度大于0",而不是笼统的"界面变化"。同时给规划角色加一个历史动作去重约束:最近三步内执行过的相同动作,除非有明确理由,否则不允许重复。
5.3 角色间契约字段缺失的连锁反应
还有一次是定位角色输出的元素id在感知角色的列表里不存在,导致执行角色拿到无效id直接报错。查下来是定位角色"幻觉"了一个id。修复分两层:一是在定位角色的prompt里明确要求只能从给定列表里选id,二是执行角色加一道校验,id不在列表里就拒绝执行并回退到重新定位。
经验总结:角色拆分的链路里,每一环都要有输入校验。不要假设上游角色一定输出合法数据,模型幻觉是常态,防御性编程是必须的。
5.4 排查链路的标准流程
我把排查流程固化成了四步:第一步,定位失败发生在哪个角色,看该角色的输入输出;第二步,判断是输入问题还是角色本身问题;第三步,如果是输入问题,往上游追;第四步,如果是角色问题,检查prompt和模型配置。这套流程走下来,大部分问题十分钟内能定位。
6. 提升拆分方案稳定性的几个实操技巧
6.1 给每个角色加超时和重试
模型调用不是百分百可靠的,网络抖动、服务限流都会导致失败。我给每个角色调用都设了超时和重试次数,重试时把温度参数调低,让输出更确定。重试仍失败就降级到备用模型,保证链路不中断。
6.2 用缓存减少重复感知
同一个界面如果连续多步没有变化,没必要每步都重新感知。我加了一层屏幕指纹缓存:对截图做哈希,哈希没变就复用上一次的感知结果。这在长列表滚动、等待加载的场景里能省下大量调用。
6.3 关键步骤加人工确认钩子
对于支付、删除这类高风险操作,我在执行角色前加了一个确认钩子,可以配置成自动通过或人工确认。测试环境自动通过,生产环境人工确认,兼顾效率和安全性。
6.4 记录完整链路日志
每个角色的输入输出、耗时、模型版本全部落盘。出问题时能完整回放整个决策链路,这是排查复杂问题的唯一可靠手段。日志格式我用JSON Lines,方便后续做统计分析和失败模式挖掘。
7. 角色拆分方案的扩展方向
角色拆分这套思路本身是可扩展的。我最近在尝试加一个学习角色,把每次成功任务的决策链路沉淀成few-shot示例,下次遇到相似任务时直接注入规划角色的prompt,提升首次决策准确率。另一个方向是多设备协同角色,当任务需要跨设备操作时,由一个协调角色统一调度各设备的角色实例。
还有一个我觉得很有价值的扩展是失败模式库。把历史失败案例按根因分类,每类对应一个修复策略,反思角色判断失败时先查库,命中已知模式就直接套用修复策略,不用每次都让模型重新想。这个库积累得越厚,整体稳定性越高。
角色级模型拆分的本质,是把一个模糊的"让AI操作手机"的大问题,拆成一串边界清晰、可独立优化、可独立测试的小问题。DroidRun和Mobilerun给了两套不同粒度的参考实现,但真正落地时,角色怎么分、契约怎么定、模型怎么配,还是要结合自己的测试场景来调。我个人的体会是,宁可一开始拆得细一点,跑通之后再合并冗余角色,也不要一上来就图省事用单模型,后面返工的成本会高得多。