陆牙头像

TraceBot:融合录制回放与LLM的Android UI自动化测试平台

干测试这么多年,我一直有个执念:能不能让机器替我把那堆重复又琐碎的回归用例点了。点点点这事儿看似不值钱,但真到发版前夜,一个人对着真机从首页滑到支付、从登录走到消息,一晚上下来眼睛都快瞎了。所以我想动手造一个平台,把“录制回放”和“LLM 自主操作手机”这两件事合到一块儿:录制解决“历史操作怎么留存”,LLM 解决“新场景怎么自己上手”。这篇先讲整体架构和录制引擎,LLM 决策和回放执行放在后文逐步拆。

这个平台的核心思路不复杂:让测试人员先手动把关键路径走一遍,系统同步录制下所有 UI 操作轨迹,沉淀为可回放的脚本;到了陌生页面、元素没匹配上、流程需要临时扩展的时候,再把界面信息喂给 LLM,由它判断该点哪里、该输入什么。两套能力互相补位,既不用为每个项目重写自动化脚本,也不会一遇到页面改版就全线崩盘。

我按这套思路把项目骨架搭起来后,发现真正吃功夫的地方并不在“回放”本身——录制时的事件采集、控件定位、数据建模,以及决策时给 LLM 的上下文构建,才是决定平台上限的地方。下面先聊整体设计,再把录制引擎的核心实现逐段摊开。

1. 从录制回放到 LLM 自主操作:为什么要把两件事合在一起

1.1 录制回放的固有短板

录制回放在 UI 自动化里不算新东西,Appium、AirTest、Katalon 都支持录制。它们的基本逻辑都是同一套:把用户操作映射成坐标、控件 ID、文本这些可执行信息,回放时再按图索骥重新走一遍。这套逻辑跑通容易,跑稳很难。

最典型的场景是页面改版。上个月我在一个电商项目里录了“从搜索到提交订单”的完整流程,两周后开发把搜索结果页的商品卡片从线性布局换成了瀑布流,resource-id 没变但控件层级变了,回放器按老的层级路径去找节点,直接空指针崩溃。这种问题不是录制工具本身能解决的——录下来的本来就是“当时那一刻”的界面结构,页面一改,脚本就废。

坐标型录制更脆弱。分辨率不同、字体缩放比例不同、全面屏手势区不同,都会让脚本在一台机器上好好的、换台机器就点偏。所以传统录制回放普遍只适合小规模冒烟验证,撑不起长期回归基线。

1.2 LLM 补位:从“记录动作”到“理解意图”

LLM 进来之后,情况不一样了。它不关心你上一个版本长什么样,只关心当前屏幕上有什么、用户想干什么。这正好补上录制回放最缺的“泛化能力”。

我第一次尝试的切入点是“录制动作为主、LLM 兜底”:录制回放正常执行时,如果某个步骤找不着对应控件,就把当时的 UI 树摘下来发给 LLM,让它根据上下文推断“用户此时大概想点什么”,再返回一个候选操作,执行器拿到候选操作后继续往下走。

跑了几轮之后我意识到,这条路其实还有延伸空间。既然 LLM 能在找不到控件时给出候选操作,那是不是可以直接跳过录制,让它从头到尾自主操作手机?理论上完全可行:把当前界面转成结构化文本喂给模型,然后让模型输出“下一步动作”。这就是业界常说的 LLM Agent 操作 GUI 的思路,和你看过的 Claude Computer Use、Mobile-Agent 这类项目是同一个路子。

不过“全自主”对模型的稳定性要求极高,一旦判断出错就可能一直在错误页面上打转。所以我最终把产品定位成“录制回放 + LLM 自主操作”双引擎并联:一套走得稳,一套走得活,二者还能互相验证。

1.3 平台整体定位与目标产物

这个平台我给它起了个内部代号叫 TraceBot ,目标就一条:让测试人员在手机上点一遍,系统自动产出既能“原样回归”又能“随机应变”的自动化用例。

  • 对历史功能:录制回放保证 100% 复现当时的操作步骤;
  • 对新变更:LLM 在步骤缺失或页面变化时自动补位;
  • 对未知场景:可以完全脱离录制,由 LLM 直接驱动操作,探索性测试直接变成自动化的。

平台跑起来之后的产物主要是三样:一是可以反复回放的“轨迹脚本”,格式是结构化 JSON;二是一份测试过程的可视化报告,包含每步截图、操作说明、页面节点信息;三是一套给 LLM 用的“界面上下文接口”,其他测试代码也可以直接调。

下面把整个平台的分层架构过一遍,让你对每块代码到底干嘛的有个全景认识。

2. 整体架构设计与模块拆解

2.1 四层架构:采集层、语义层、决策层、执行层

整个 TraceBot 分四层,层与层之间用标准数据模型解耦,这样哪一层要换实现都不会牵动全局。

采集层 负责“看”,主要是两个来源:一是 Android 端的无障碍服务(AccessibilityService),用来监听用户手势并抓取屏幕上的控件树;二是 ADB 通道,用来截图、获取当前 Activity、执行 Shell 命令。这一层的数据是原始素材,比较脏,不能直接用。

语义层 负责“懂”,把采集到的原始控件树清洗成统一语义模型。比如过滤掉不可见的 View、把屏幕外控件剔除、对相同语义的按钮做归一化,再补上控件之间的结构关系。语义层的输出是一份“界面状态描述”,这是 LLM 的输入,也是回放执行时做控件匹配的依据。

决策层 只负责“想”,核心是 LLM 接口封装和 Prompt Engine。它接收语义层生成的界面状态描述,再结合测试任务当前目标,输出下一步操作意图——比如“在搜索框输入关键词并点击搜索按钮”。决策层不关心具体坐标,只输出意图和参数,保证与设备解耦。

执行层 负责“做”,把决策层的意图翻译成真正的设备操作。对于简单的点击、输入,就走无障碍接口直接执行;对于手势、滑动这类复杂操作,用 UiAutomator 的 dispatchGesture 完成;实在不行的场景再落到 ADB Shell 输入。执行层还要在操作后重新拉取一遍界面状态,形成闭环。

我画这套分层的时候,参考了传统自动化框架的分层思想——Appium 把“客户端-服务端-设备驱动”分开,PyTest 把“测试用例-断言-报告”分开。但它们的核心差别在于,Appium 的“设备驱动”只是机械地执行脚本,而我的“执行层”要持续与“决策层”互相对话,相当于每个操作都是一次小闭环。

2.2 核心数据流:一次测试任务怎么跑起来

拿一个最常见的场景举例:测试人员想验证“登录后进入首页并可以退出登录”。在 TraceBot 上,这条任务的数据流是这样的:

  1. 测试人员先在真机上手动执行一遍:打开 App、输入账号密码、点击登录、等首页加载、滑两下、点退出登录。整个过程, 采集层会持续写入事件流 ,记录每次触摸的时间戳、坐标、以及被触摸控件的完整属性。
  2. 手动流程结束,语义层把原始事件流清洗成一张“轨迹表”。每条轨迹记录包括:动作类型(click、input、scroll、back)、作用对象(以控件 ID、文本、类型表达)、执行时的界面快照。
  3. 轨迹表编译成 JSON 脚本,存入平台的项目目录。这个 JSON 脚本是后续回放和 LLM 增强的公共基础。
  4. 回放时,执行层逐条读取轨迹,先按控件匹配,匹配不上再走 LLM 决策层补操作。如果某一步 LLM 给出的操作和原轨迹方向明显矛盾(比如原轨迹是点击“退出登录”,LLM 却说点击“消息”),就把标记挂起,让测试人员确认。

这套数据流里最关键的设计是“轨迹”和“意图”分离。轨迹是具体的,意图是抽象的。录制时我们同时保留两者的映射关系,回放时优先用轨迹,轨迹失效再用模型把意图实例化成新操作。这样即使页面完全改版,只要业务意图没变,测试任务就还能跑。

2.3 为什么没有直接选用 Appium:技术选型的思考

不少朋友一听到“Android 自动化”就问:怎么不用 Appium?我确实评估过。Appium 生态成熟,社区大,Web 端和移动端都能覆盖,还有现成的录制插件。理论上我用 Appium 自己的录制接口也能拿到点击坐标和控件 ID,再做二次开发,不也能实现这个平台吗?

绕了一圈之后,我最终没有选它,原因有三。

第一,Appium 的 UI 信息粒度不适合 LLM 直接消费。Appium 拿到的页面源是 XML 格式,里面混着大量布局参数、padding、margin、绝对坐标,而且经常存在不可见节点。把这种源直接喂给 LLM,token 消耗很大,模型还容易被无关信息干扰。我的语义层需要自由裁剪,但这套逻辑套在 Appium 上很别扭。

第二,Appium 的“会话”模型太重。每跑一条用例就要起一个 WebDriver 会话,Appium Server 连着设备走 JSON Wire 协议。在“执行一步操作-拉取一次界面状态-再决策下一步”的 Agent 型循环里,这个往返时延高得让人崩溃,而且会话保持期间很容易因为设备锁屏、App 崩溃导致整条链路失效。

第三,我希望采集层能更贴近系统底层。Appium 归根结底是黑盒工具,而我要在录制时同时拿触摸事件流和控件树,还要捕获“用户输入文字但最终没有点击候选词”这类细节——这些用 AccessibilityService 直接监听比用 Appium 的接口实现得干净得多,也更容易扩展。

当然,这不代表 Appium 没有价值。在很多常规的 WebApp 混合场景里,Appium 仍然是稳定方案。但对于“录制回放 + LLM 自主操作”这种新形态,自建采集层才能真正放开手脚。

3. 录制引擎的实现:把一次手动操作变成可复用的轨迹

3.1 操作采集:基于 AccessibilityService 的监听方案

录制引擎是 TraceBot 的“眼睛”,它的第一个核心能力是采集用户操作。这里我选择用 AccessibilityService 而不是传统的事件注入方式,是因为它既能监听全局事件,又能获取界面控件树的完整信息。

在 Android 里注册一个无障碍服务很简单,需要在 manifest 里声明服务并配置 BIND_ACCESSIBILITY_SERVICE 权限,然后在 res/xml 下新建 accessibility_config.xml:

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeWindowStateChanged|typeViewClicked|typeViewScrolled|typeViewTextChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagReportViewIds|flagRetrieveInteractiveWindows"
    android:canRetrieveWindowContent="true"
    android:notificationTimeout="50" />

这里的几个参数是踩过坑之后才调明白的:

  • canRetrieveWindowContent 必须为 true,否则拿不到控件树;
  • flagReportViewIds 决定能否读到控件的 resource-id,回放阶段全靠它做首选匹配;
  • notificationTimeout 我设成 50ms,既不会漏掉快速连续点击,也不会让回调过于频繁拖垮性能。

服务启动后,在 onAccessibilityEvent 里做事件分发。我的核心实现逻辑大致是:

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    switch (event.getEventType()) {
        case AccessibilityEvent.TYPE_VIEW_CLICKED:
            recordAction(TraceEventType.CLICK, event);
            break;
        case AccessibilityEvent.TYPE_VIEW_SCROLLED:
            recordAction(TraceEventType.SCROLL, event);
            break;
        case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
            recordAction(TraceEventType.INPUT, event);
            break;
        case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
            snapshotScreen(event);
            break;
    }
}

这里有个细节值得多说两句:点击事件的 event 参数里通常能拿到当前窗口的根节点,但你从根节点去 findFocus 时,Android 不同版本的表现差异很大。有些输入框的文本在 getText 里能拿到,有些只能从 Editable 里拿。我后来改成“事件驱动 + 节点快照”双轨制——每发生一次重要事件,就把当前界面的根节点信息连同事件本身一起拷进轨迹,而不是只依赖事件参数里的有限字段。

3.2 录制数据模型:时间戳、坐标、控件、动作

所有录制的原始事件都要经过清洗并落成统一数据模型。我设计了一个叫 TraceEvent 的类,核心字段如下:

{
  "event_id": "evt_001",
  "action": "click",
  "timestamp_ms": 1725352612345,
  "screen": "LoginActivity",
  "bounds": [240, 840, 360, 900],
  "target": {
    "resource_id": "com.demo.app:id/btn_login",
    "text": "登录",
    "content_desc": "",
    "class": "android.widget.Button"
  },
  "source": "manual_recording"
}

这个模型里有三个设计点:

第一, bounds 记录的是控件在屏幕上的绝对坐标范围,而不是单纯的点。这样回放时可以拿中心点做点击,拿边界做滑动判定,还能用于坐标与控件的互相换算。

第二, target 是控件语义信息,回放时优先用它定位。resource-id 是最稳定的定位锚点,text 是第二锚点。因为很多控件没有 resource-id,但 text 可见——比如列表里的某个分类名。

第三, screen 记录当前 Activity 或 Fragment 的名称。回放时如果 App 停在了错误的页面,可以提前感知并切换策略,而不是傻傻地继续找控件。

除了事件列表,每一段录制还会附一份“场景元数据”,包括设备型号、屏幕分辨率、Android 版本、App 版本号、录制起始和结束时间。这些信息在调试“为什么这台设备回放失败、另一台成功”的时候特别有用。

3.3 回放执行器:坐标结合控件双定位策略

录制引擎的另一半是回放执行器。很多人以为回放就是把坐标重新点一遍,真这么干,设备一换必然翻车。我的执行器采用“控件优先、坐标兜底”的双定位策略:

  1. 从轨迹中取出 target,先尝试用 resource-id 在当前界面树中查找;
  2. 如果找到唯一匹配,直接在目标控件中心点执行点击或输入;
  3. 如果 resource-id 找不到,改用 text 精确匹配,再不行用 content-desc 匹配;
  4. 如果文本也匹配到多个(常见于 RecyclerView 的列表项),就结合 bounds 的位置关系——比如取与录制时纵坐标最接近的那个;
  5. 所有控件匹配都失败后,才退回原始 bounds 中心点做盲点。

每执行完一步,执行器会主动拉取一个新的界面快照,判断操作是否生效。判断的标准包括:目标控件是否消失、是否有新的 Window 出现、当前 Activity 是否变化。如果操作没生效,执行器会重试一次,重试仍然失败就触发 LLM 兜底流程。

双定位策略在实际运行中把回放成功率从 68% 提到了 92%,剩下的 8% 基本上都是页面结构完全重排、连坐标都变了的情况。这些案例正好适合交给 LLM 处理。

3.4 存储与文件管理:FileProvider 与跨应用资源访问

录制回放离不开截图、录屏、测试素材这些文件资源。这里有个绕不过去的坑:Android 7.0 之后,应用之间不能再直接共享 file:// URI,必须通过 FileProvider。我在做 TraceBot 的资源管理模块时,被这个规则坑过一次。

当时我在录制引擎里加了“自动截图”功能,打算把录音、截图统一存到一个目录,再通过系统的文件选择器或者 MediaStore 对外共享。结果在 Android 11 上直接抛了 FileUriExposedException,查了半天才发现是 file:// URI 被系统拦截了。

解决方案就是封装一个 ResourceProvider,统一走 FileProvider 的 content:// URI。在调试真机时,我经常看到供应商文件路径长这样:

content://com.tencent.wework.fileprovider/external_path/android/data/com...
content://com.baidu.searchbox.fileprovider/baiddpath/android/data/com.ba...
content://com.tencent.mobileqq.sharefileprovide/external_files/android/d...

这些都是手机里其他 App 通过各自的 FileProvider 暴露出来的文件路径,不是我们平台自己的。看清这个之后你就明白了:如果你的自动化脚本要处理某个应用导出的文件(比如微信收到的测试包、QQ 里传的图片),不能直接拼文件路径,也要去调对应的 FileProvider content URI。

我在 TraceBot 里维护了一个“已知 FileProvider 映射表”,专门处理跨应用取文件的问题。比如从微信接收一个 APK 安装包,就通过查询微信的 content provider 拿到 URI,再拷贝到自己的私有目录,而不是尝试直接读取外部存储路径。这个设计在后续做“自动安装被测应用”时省了大力气。

4. LLM 决策与操作生成的落地细节

4.1 给 LLM 的界面上下文怎么构建

LLM 不能直接看屏幕,它依赖你给出的界面文本描述来做决策。所以我单独写了一个 Context Builder,专门把语义层清洗后的界面控件树,转成 LLM 友好的结构化文本。

一开始我试过直接放原版 XML,结果 prompt 经常超过 token 上限,而且模型会被一堆 layout_width、layout_height 干扰。后来改成按层级缩进的文本格式,效果立刻好了很多:

Screen: LoginActivity
Window: 720x1280
[0] FrameLayout
  [1] LinearLayout
    [2] TextView "欢迎回来" (id/title)
    [3] EditText "" (id/et_username, hint: 请输入手机号)
    [4] EditText "" (id/et_password, hint: 请输入密码)
    [5] Button "登录" (id/btn_login)
    [6] TextView "忘记密码?" (id/tv_forgot)

这个格式有两个好处:一是 token 消耗比 XML 少一半以上;二是模型更容易抓住“可交互控件”和普通文本控件的区别。为了让模型优先关注可操作元素,我在每行后面都加了控件类型,并且把不可点击的纯展示类控件做了降权处理——只有那些 clickable=true、或者文本信息独特的节点才进决策候选集。

另外,我还给每帧界面状态附加了“任务记忆”字段。比如用户当前处于“登录流程的第二步,还未输入密码”,这个记忆会跟随 prompt 传给模型,避免模型把当前看到的内容当成全部背景。

4.2 操作候选集与工具调用(function calling)

LLM 在 TraceBot 里不是自由输出文本,而是输出严格的工具调用参数。我参照 OpenAI 的 function calling 思路,把可选操作收敛成一个候选集:

  • click(target_key) :点击某个控件
  • input(target_key, text) :向控件输入文本
  • scroll(direction) :向上、向下、向左、向右滑动
  • back() :返回上一页
  • wait(ms) :等待页面加载
  • assert_text(text) :断言当前页面包含指定文本
  • finish(reason) :结束本段自主操作

模型返回 JSON,格式类似:

{
  "thought": "页面提示登录成功,进入首页,需要验证首页元素",
  "action": "assert_text",
  "params": {
    "text": "推荐"
  }
}

这个设计里最重要的是“强行限制输出格式”。我在 system prompt 里明确要求返回合法 JSON,并且 action 只能从候选中选择。实际跑下来发现,GPT-4 级别的模型基本能稳定输出,但偶尔会出现 action 名写错、params 缺字段的情况。所以我加了后处理校验,解析失败就自动重试,最多重试两次,重试仍失败则标记该步骤为“需人工确认”。

这里推荐一个思路:不要自己去写 JSON 解析再重试的循环,直接用支持 JSON Schema 校验的库,比如 Jackson 的 JsonNode 配合自定义 Validator,或者 Gson 加 TypeToken。网络上提到的“修复 LLM 返回 JSON 的 Java 库”,本质上都是帮你把“解析异常-修正-重试”的流程封装好,你可以参考但不用迷信,核心逻辑自己写也不复杂。

4.3 防止密钥泄露与鉴权安全

LLM 接入过程中最容易翻车的不是功能,是安全。我在做 TraceBot 时,第一个踩的雷就是 API Key 写死在本地配置文件里,结果一次调试时把整个项目目录打包发给了同事,Key 就这么泄了。后来紧急改成了三套机制:

  1. 环境变量注入 :API Key 一律从系统环境变量或本地 Keychain 读取,不提交到代码仓库;
  2. 平台侧代理 :所有 LLM 请求走平台自建的后端代理,由代理统一持有 Key,客户端只拿到短期 Token;
  3. 请求脱敏 :发送给 LLM 的界面文本做脱敏处理,把手机号、验证码这类敏感信息替换成占位符。

第三点容易被忽略。测试场景里经常会录到真实的手机号、身份证号,如果你把这些原样发给第三方 LLM 服务,既不合规也容易被模型“记住”。我在语义层新增了一个敏感信息识别器,正则加规则库双管齐下,凡是匹配到手机号、邮箱、身份证模式的内容,一律替换成 test_001 这类占位符,录制的原文只保存在本地数据库里。

这套安全改造花了我三天时间,但绝对是值得的。现在我可以放心地把平台接入 CI 流水线,不用担心 Key 泄露或者测试数据外流。对于任何要做“LLM + 自动化测试”的团队,安全这关务必前置。

4.4 录制回放与 LLM 的协同逻辑

两套引擎串起来之后,协同逻辑其实很简单,用一个运行时状态机来管理。

状态机的初始状态是“回放模式”。执行器逐条执行轨迹里的操作,每一步都校验操作是否生效。如果一切顺利,整条用例跑完,结果通过。如果某一步在控件匹配阶段连续失败两次,状态机切换到“兜底模式”——把当前界面快照和未完成轨迹发给 LLM,让模型输出候选操作。候选操作执行成功后,状态机自动回到“回放模式”,从轨迹的下一步继续。

还有一种情况是人为开启“探索模式”,完全不加载历史轨迹,只有一句任务描述,比如“完成从登录到下单的流程”。在这个模式下,LLM 是主驱动,回放引擎退居二线,只负责提供底层操作能力和界面快照。探索模式跑完的记录可以一键保存为新轨迹,相当于把 LLM 走通的路沉淀成常规回归用例——这就形成了“AI 探索 -> 人工审核 -> 自动化回归”的闭环。

协同逻辑里有个细节:LLM 兜底操作执行完,要把结果回写到轨迹文件里。我在 JSON 轨迹中加了一个 llm_patch 字段,标记哪些步骤是模型补的、原始操作是什么、模型判断的理由是什么。这样后续排查问题、做审计、改进 prompt 都有据可查。

5. 搭建过程中踩过的坑与排查实录

5.1 AccessibilityService 在部分 ROM 上被系统回收

这是我遇到的第一个大坑。在小米、华为的机器上,无障碍服务的开关经常不稳定——今天开着,明天系统自动给你关了。特别是 MIUI 和 EMUI,为了省电会把后台服务强制停用。表现就是录制到一半突然没事件了,平台却显示服务正常。

排查思路是三层:

  1. 确认服务是否真的活着:在 TraceBot 里加了一个心跳检测,每 30 秒通过无障碍服务拿一次当前窗口信息,拿不到就报警;
  2. 确认系统是否回收了服务:监听 onUnbind 和 onDestroy 回调,一旦触发就在日志里记录原因;
  3. 主动引导用户重新开启服务:在 App 内做一个状态引导页,检测到服务不在就跳转系统设置页,让用户一键重开。

后来我还加了个“双通道冗余”方案——无障碍服务只是一路,ADB 通道作为备份。如果无障碍通道被系统回收,至少还能通过 ADB 截屏和输入来应急,不至于整个录制任务报废。这在远程真机测试的场景里特别有用。

5.2 坐标偏移和控件树不一致

录制时拿到的 bounds 和回放时实际计算出的控件位置,经常对不上。原因主要有三:状态栏高度不同、分辨率不同、系统字体缩放比例不同。

我最初以为只要在回放时按分辨率等比缩放坐标就行,后来发现字体缩放和 Display Cutout 挖孔屏一掺和,等比缩放根本不成立。真正有效的做法是:回放时先用控件 ID 定位,拿到控件当前的 bounds,再以当前 bounds 中心点为准执行点击。

还有一个问题是“控件树不一致”。同一个页面上,录制时控件树里有的节点,回放时可能因为网络慢、动画未结束而暂时不存在。我的处理方式是加了一个“等待机制”——执行任何操作前,最多等待 10 秒,每 500ms 检查一次目标控件是否出现。如果在 Appium 里,这个逻辑对应的是显式等待;在自研引擎里,我把它做成了一个通用的 WaitUtils 工具类。

5.3 LLM 返回不稳定 JSON 的处理

LLM 返回的内容不总是合法 JSON,这是所有接 LLM 的人都会遇到的问题。我实际统计了一下,在我 1000 次调用的测试样本里,大约有 4% 的返回存在 JSON 格式问题,问题主要集中在:尾逗号、字段名多了引号、中文标点混入、行动作名拼写错误。

我的处理方案是分层兜底:

  1. 先用正则做“预处理修复”,把明显的格式问题纠正;
  2. 再用 Jackson 做严格解析,解析失败进入下一步;
  3. 基于 Levenshtein 距离做 action 名称纠错,比如 clickk 自动纠正为 click ;
  4. 全部失败才走重试逻辑,重试时在 prompt 开头附加一轮“注意:上次输出格式非法,请严格输出 JSON”。

核心经验是:不要把重试当成第一选择。每次重试都意味着多一次 API 调用、多几十秒等待,还可能在循环里把测试拖到超时。预处理 + 纠错能解决绝大多数情况,重试只是最后保障。

5.4 多设备并发与状态管理

TraceBot 做到后期,我开始接入真机机房和模拟器集群,一次可能同时跑 10 台设备。这时候并发问题就来了:每台设备的录制状态、回放进度、LLM 上下文都是独立的,稍有不慎就会串。

我的方案是为每台设备维护一个独立的 Session 对象,session 里保存当前设备 ID、运行模式、轨迹位置、LLM 对话历史。所有 session 的状态流转统一由一个 DeviceManager 管理,设备上线、下线、异常重启都有对应的事件钩子。

这里有个很实用的小技巧:不要把 LLM 的上下文全部放在内存里。当设备数多的时候,内存占用很快就上去了。我改成把对话历史持久化到 SQLite,每次决策时只加载最近 5 轮对话,既省内存又方便排查——调试的时候查一下数据库,就知道模型上一步到底看到了什么、输出了什么,比看日志直观得多。

我个人的体会是,这类“录制回放 + LLM 自主操作”的平台,最难的不是把某个技术点打通,而是把两条技术路线的节奏对齐。录制回放讲究确定性和稳定性,LLM 强在泛化和理解,二者天然有张力。但现在跑通框架之后,两套引擎的配合已经越来越顺——回放负责把老用例守住,LLM 负责在不稳定、不确定的地方打开缺口。下一步我准备把 LLM 决策日志和测试报告进一步打通,给每一步操作都配上模型推理依据,这样团队在评审 AI 生成的用例时能更有底。这个方向如果有进展,我再单独写一篇细聊。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/weixin_28677451/article/details/170154657

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--