尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
用Harness打造Agent:Android模拟器自动登录实战
如果你最近也在折腾 Harness 这类 agent 开发框架多半遇到过同一个问题环境搭起来了模型也连上了但 agent 除了聊天什么也干不了——因为它没有手。我这次实战做了一件很具体的事用 Harness 作为工具编排层把 Android 模拟器上的一个 App 登录模块变成 agent 能独立操作的完整任务跑通了一条从环境搭建到自动登录的可靠链路。整个过程涉及 Android Studio、ADB、UI 控件识别和 agent 提示词设计适合正在做 UI 自动化、或想让大模型真正“动手”干活的开发者参考。先说我的背景。之前团队做 App 回归测试我一直被两件事折磨录制回放脚本太脆页面一改就重录纯坐标脚本更是灾难换个分辨率全废。后来我把目光转到 agent 方案上想法很简单——让模型自己看屏幕、自己决定下一步点哪里而不是把每一步都写死。但真做起来才发现裸调模型 API 根本不够用模型输出的是“我想点登录按钮”这种意图文本我还得自己写意图解析器等于把 agent 框架又造了一遍。所以这次我选了 Harness把精力放在业务逻辑上而不是底层工具调用上。1. 整体设计为什么是 Harness 加 Android 登录模块1.1 Harness 在 agent 开发环境里到底解决什么问题裸调 API 的核心痛点在于模型只负责“想”不负责“做”。你调用一次大模型它返回一段自然语言说它想打开应用、想点击某个按钮但接下来执行、验证、纠错全部要你自己写代码。更棘手的是模型输出经常不稳定上次返回 JSON这次就变成一段散文你写的解析器就被打穿了。Harness 这类工具编排层解决的就是这个断层它允许你把真实可用的工具提前注册进去比如终端命令、文件读写、adb 操作、浏览器控制模型在任务循环里按协议发起工具调用Harness 负责真正把命令跑起来再把结果返回给模型。用类比来理解模型是大脑Harness 是手脚。大脑负责下指令手脚负责执行两边通过一套标准协议通信。我这次把“手脚”集中封装成三个工具观察屏幕、点击控件、输入文本。Harness 只需要知道这三个工具叫什么、怎么调用剩下的决策全部交给模型。这种分层的好处是以后换模型、换任务、甚至换操作系统工具层不用重写只要保证协议不变就行。1.2 为什么拿“登录模块”当第一个被 agent 攻克的场景在 App 自动化领域有个共识登录是最适合做自动化验证的地方。它界面结构稳定输入框、按钮、校验逻辑都是固定套路恰好可以拆成观察、决策、操作三个步骤这正是 agent 能力的最小闭环。而且登录模块虽然控件少却包含了密码掩码、错误提示、toast 反馈、页面跳转这些典型细节能暴露真实场景里的大部分问题。我见过不少团队一上来就让 agent 操作复杂的电商首页结果模型被满屏商品卡片干扰连“搜索框在哪”都找不到最后把问题归咎于模型不聪明其实只是场景选错了。反过来登录模块失败成本极低。测试账号可以随便造模拟器可以随时重置跑坏了拍个快照十秒恢复。这种“可反复折腾”的环境恰恰是 agent 开发最需要的土壤。你让 agent 在一个不可复现的环境里试错出了问题根本说不清是环境问题还是模型问题环境稳定了agent 每一次失败都值得分析因为大概率是任务设计或工具定义的问题。1.3 整体架构开发机、模拟器、模型服务三层这次实战的硬件和软件组合很常规开发机是一台 Windows 11装了 Android Studio 和对应 SDK Platform ToolsAndroid 模拟器用的 Pixel 6 镜像API 33分辨率 1080x2400Harness 跑在开发机的命令行里通过 adb 与模拟器通信。模型层我用的是 DeepSeek 的对话模型通过 OpenAI 兼容接口接入只需要在环境变量里配置服务地址和 API key。整体拓扑用一个简单的文字图就能说明白----------------------- | 开发机 (Windows) | | Harness(插件编排) |------ HTTP API ----- 模型服务(DeepSeek) | 自定义工具(adb脚本) | -----------|----------- | adb (5037 端口) -----------v----------- | Android 模拟器 | | Pixel 6 / API 33 | | 登录 Demo App | -----------------------把模型服务独立出来的好处是开发和模型解耦。以后你想换别的模型、或者把模型部署到内网只需要改环境变量工具脚本和 Harness 配置完全不用动。很多团队从 demo 走向生产时都卡在“换模型就得重构”上根源就是没有在一开始做这层抽象。工具层只认模型输入输出的格式不认具体是哪个模型这是 agent 开发环境设计的核心原则之一。1.4 关键取舍为什么坚持用 resource-id 定位而不是视觉识别技术圈里有人喜欢让 agent 直接看截图如果模型带视觉能力这确实可行但我这次坚持用 uiautomator dump 出来的 UI 树来定位控件。原因很简单UI 树里有 text、resource-id、bounds、clickable 这些结构化字段模型从 JSON 里选控件比从像素里猜控件可靠得多而且不依赖视觉模型模型选型面更宽。更重要的是坐标只是结果不是原因。我让 agent 按“找一个 id 是 et_username 的输入框”这样的逻辑行事而不是“点 540, 800”因为前者在界面微调后依然成立后者一次分辨率变化就废了。这个原则也直接影响了我写 Demo App 的方式。我在布局里给用户名框、密码框、登录按钮都加了稳定的 resource-id相当于给 agent 准备了路标。真实 App 里很多控件没有 id那就退一步用 text 匹配实在不行才用坐标。后面 5.3 节我会专门展开讲控件的兜底策略这里先记住优先级resource-id 大于 text 大于 bounds。这个优先级应该写死在 agent 的提示词里模型就不会在最简单的事情上乱发挥。2. 环境准备从 Android Studio 到 Harness 一条链路全打通2.1 先把 Android 模拟器调整到“适合被折腾”的状态创建模拟器有几个细节直接决定了后续的省心程度。第一镜像选 x86_64 而不是 arm 的x86 在 Windows、Linux 上跑得快冷启动 30 秒内arm 镜像虽然兼容性好但性能差距肉眼可见。第二分辨率固定 1080x2400密度 420dpi不要开自动缩放这样 uiautomator 里 bounds 的坐标和 adb 输入的坐标是同一套坐标系省去换算的麻烦。第三创建时勾选启用 ADB 调试第一次启动后模拟器会弹窗问是否允许调试要勾选“始终允许”。我见过很多人在这一步没勾选后面 adb devices 一直显示 unauthorized白白浪费一下午。还有一个新手经常卡住的地方Android Studio 默认英文界面。想切中文的话打开 Settings进入 Plugins搜索“中文语言包”安装后重启就是中文了。虽然不是必备项但对不熟悉英文菜单的人来说能省不少查字典时间。另外建议顺手把 SDK Platform 和 Platform Tools 更新到最新版本老版本 adb 有时会出一些莫名其妙的兼容问题升级往往就消失了。环境准备阶段的每一次升级都是在给后面的调试减少变量。2.2 安装 Harness一条命令启动插件系统是核心Harness 的安装并不复杂社区最常见的做法是 npm 全局安装然后在工作目录里初始化并启动。因为项目的包名和发布源可能会有更新这里我以官方仓库给的安装方式为准安装完先确认命令行能正常执行# 以官方仓库的安装指引为准社区里常见的全局安装方式如下 npm install -g dsh-harness harness --version harness init my-agent-env cd my-agent-env harness启动之后第一件事不是急着让它干活而是先把模型配置好。Harness 本身不绑定具体模型它通过 OpenAI 兼容接口接模型服务。我用的是 DeepSeek 的对话模型配置方式就是设置几个环境变量export LLM_BASE_URLhttps://api.deepseek.com/v1 export LLM_API_KEYsk-xxxxxxxx export LLM_MODELdeepseek-chat设置完在 Harness 会话里随便发一条消息测试连通性。这一步很多人忽略后面 agent 调工具全部失败查了半天发现是模型根本没接上。我自己的习惯是配完环境先问一句“你是什么模型”能答上来再继续几十秒的验证能省几小时的排查。Harness 的核心是插件机制通过 web boot 方式加载。你可以把它理解成浏览器的插件市场有现成的工具插件可以直接用也可以把自己写的脚本注册成自定义工具。我这次实战不依赖重量级插件自己写了一个叫 android-bridge 的工具包里面就三个 Python 脚本后面第三章专门讲。2.3 打通开发机和模拟器的 ADB 链路ADB 是整个链路的地基先确认它认得出模拟器。打开终端执行adb kill-server adb start-server adb devices输出里有emulator-5554 device就算打通了。注意看状态如果是unauthorized说明模拟器弹窗没勾选允许如果是空列表十有八九是 5037 端口被占用。我遇到过被 scrcpy 抢占端口的情况处理办法是找到占用进程杀掉然后重启 adb server。这类问题其实都有固定套路卡住时先看端口、再看授权基本能解决八成的连接问题。再确认模拟器真的启动完了。直接adb shell input有时会报错因为系统还在 boot 阶段。稳妥做法是先执行adb shell getprop sys.boot_completed输出为 1 再继续。这一步在自动化和 CI 脚本里尤其重要很多人都是在这栽跟头——脚本启动模拟器后立刻去操作结果界面还没渲染完命令全打在了空气上。确认完成之后把登录 Demo 装进去并启动adb install -r app-debug.apk adb shell am start -n com.example.login/.MainActivityDemo App 是一个一百行左右的登录页用户名输入框、密码输入框、登录按钮。按钮点击后校验账号密码是否匹配成功跳欢迎页失败弹出错误提示。所有控件都加上 resource-id这就是给 agent 留的路标。布局里的关键部分大概是这样的EditText android:idid/et_username android:hint用户名/邮箱 android:inputTypetextEmailAddress android:layout_widthmatch_parent android:layout_heightwrap_content / EditText android:idid/et_password android:hint密码 android:inputTypetextPassword android:layout_widthmatch_parent android:layout_heightwrap_content / Button android:idid/btn_login android:text登录 android:layout_widthmatch_parent android:layout_heightwrap_content /3. 给 Agent 一双能操作安卓的手自定义工具集3.1 工具设计原则工具做薄提示词做厚写这套工具集之前我给自己定了一条纪律每个工具只做一件确定的事输出必须是结构化 JSON并且包含足够多的“可决策信息”。agent 不需要知道 adb 怎么调用它只需要知道屏幕上有什么控件、点哪个、往哪个控件输入什么文本。逻辑和上下文交给模型执行和采集交给工具两边职责干净出了问题也容易定位——工具出错就去查工具模型跑偏就去调提示词不会互相甩锅。对应地我给 agent 只准备了三个工具。get_screen 负责观察tap_on 负责按 resource-id 点击input_into 负责向指定控件输入文本。我没有给“滑动”“长按”“返回”这些工具因为登录流程用不到。这里有个反直觉的经验你以为工具越全越强其实在 agent 开发里工具越收敛任务越不容易跑偏。工具少了模型没有乱发挥的空间每一步只能走你铺好的路这对环境稳定性的价值远大于多几个炫技功能。3.2 get_screen让模型“看见”当前界面get_screen 的实现思路是调用系统的 uiautomator dump拿到当前页面的 UI 树 XML再解析成紧凑的 JSON 返回给模型。为什么要解析因为原始 XML 动辄几百上千行全塞给模型反而干扰决策。我过滤掉大量无效节点只保留有文本、有 id、或者可点击的节点同时把 bounds 坐标保留下来备用。核心代码大概长这样# scripts/get_screen.py import json import subprocess import time import xml.etree.ElementTree as ET def dump_ui(): time.sleep(0.3) # 等界面稳定避免 dump 到半渲染状态 subprocess.run( [adb, shell, uiautomator, dump, /sdcard/ui.xml], checkTrue, capture_outputTrue ) subprocess.run( [adb, pull, /sdcard/ui.xml, ui.xml], checkTrue, capture_outputTrue ) tree ET.parse(ui.xml) nodes [] for node in tree.iter(node): text node.attrib.get(text, ).strip() rid node.attrib.get(resource-id, ) clazz node.attrib.get(class, ) bounds node.attrib.get(bounds, ) clickable node.attrib.get(clickable, false) true # 只保留值得模型关注的节点 if text or rid or clickable: nodes.append({ text: text, id: rid, class: clazz, bounds: bounds, clickable: clickable, }) print(json.dumps(nodes, ensure_asciiFalse, indent2)) if __name__ __main__: dump_ui()这里有个不同版本间的坑值得单独说。新版 Android 的 uiautomator dump 很可能不是在 /sdcard/ui.xml 生成普通 XML而是生成一个带随机后缀的 zip 文件直接 pull 下来解析会报错找不到节点。我的处理办法是 dump 完成后再看一眼实际生成的文件名如果是 zip 先解压再解析。为了代码简洁上面这段默认了普通 XML 场景真要上生产环境记得补一层文件类型判断。这种版本差异在 Android 生态里太常见了跑在不同 API Level 的模拟器上行为可能完全不一样。3.3 tap_on 和 input_into让模型“动起来”点击工具不能直接接收坐标那样模型容易乱猜。我的设计是让模型传 resource-id比如 et_username工具自己到 UI 树里找到这个节点算中心点再执行 adb tap。这样模型的操作是语义化的即使界面发生小改动只要 id 不变脚本就能稳住。这和上一章说的“能不用坐标就不用坐标”是一脉相承的思路。tap_on 的简单实现长这样# scripts/tap_on.py resource_id import json, re, subprocess, sys def bounds_center(bounds): m re.match(r\[(\d),(\d)\]\[(\d),(\d)\], bounds) x1, y1, x2, y2 map(int, m.groups()) if m else (0, 0, 0, 0) return (x1 x2) // 2, (y1 y2) // 2 def get_nodes(): subprocess.run([adb, shell, uiautomator, dump, /sdcard/ui2.xml], capture_outputTrue) subprocess.run([adb, pull, /sdcard/ui2.xml, ui2.xml], capture_outputTrue) import xml.etree.ElementTree as ET root ET.parse(ui2.xml).getroot() return [n.attrib for n in root.iter(node)] rid sys.argv[1] for node in get_nodes(): if node.get(resource-id, ).endswith(rid): cx, cy bounds_center(node.get(bounds, )) subprocess.run([adb, shell, input, tap, str(cx), str(cy)], checkTrue) print(json.dumps({ok: True, id: rid, x: cx, y: cy})) break else: print(json.dumps({ok: False, error: fresource-id {rid} not found}))输入文本比点击麻烦得多因为adb shell input text对空格、、! 这类特殊字符经常出错。我第一次跑的时候给测试账号用纯字母数字顺顺利利一换成带 的邮箱就翻车输入到一半字符被 shell 吃掉。稳妥做法是安装 ADBKeyboard 这个开源输入法 APK通过广播把文本发给输入框任何字符都不会被 shell 处理掉。它其实是自动化团队的标配不只是 agent 场景录制回放脚本里同样适用。配置方式adb install -r ADBKeyboard.apk adb shell ime enable com.android.adbkeyboard/.AdbIME adb shell ime set com.android.adbkeyboard/.AdbIME adb shell am broadcast -a ADB_INPUT_TEXT --es msg testerexample.com如果你不想引入额外 APK也可以用转义法空格替换成 %s在 mksh 下把 和 ! 用反斜杠转义后传给 input text。但转义规则在不同 shell 版本上表现不一致我的经验是别在这种细节上硬刚直接上广播输入法省心也稳定。低级工具层越稳定agent 后面才越不容易被带偏。3.4 把工具注册进 Harness最小插件配置这三个 Python 脚本要被 agent 调用需要告诉 Harness 它们存在。Harness 的插件机制定义了一个很简单的协议注册时声明工具名、执行命令、输出格式Harness 就会在模型请求工具时把命令跑起来把 stdout 返回给模型。用一个简化的配置举例逻辑大致如下# android-bridge/tools.yaml name: android-bridge description: 操作 Android 模拟器上的 UI用于登录等界面流程。 tools: - name: get_screen command: python3 scripts/get_screen.py description: 获取当前屏幕上的控件列表返回 JSON。 - name: tap_on command: python3 scripts/tap_on.py {resource_id} description: 按 resource-id 点击一个控件。 - name: input_into command: python3 scripts/input_into.py {resource_id} {text} description: 向指定控件输入文本。注册完以后我在 Harness 会话里发一条消息让 agent 自报家门“你有哪些工具”它要是能列出 get_screen、tap_on、input_into配置就算通了。这一步每次都值得做因为插件路径写错、脚本权限没给足、依赖缺失都会让工具静默失败。到时候你看到的效果就是 agent 在那里一本正经地“假装点击”实际上什么都没有发生你还以为是模型不行其实是工具层早就断了。4. 实战跑通让 Agent 自动完成一次登录全流程4.1 任务提示词给 agent 划清边界agent 的任务提示词不需要写得像论文但一定要把边界划清楚。我第一次跑的时候给了大段自由发挥空间结果它一会儿想 dump 全部控件一会儿想打开开发者选项完全跑偏。后来收敛成下面这个版本把目标、步骤顺序、工具上限、终止条件全部说死当前任务在 Android 模拟器中完成登录 Demo 的登录操作。 账号testerexample.com 密码Passw0rd! 规则 1. 每一步先调用 get_screen确认当前界面有哪些控件。 2. 只使用 get_screen / tap_on / input_into 三个工具。 3. 用户名输入到 et_username密码输入到 et_password然后点击 btn_login。 4. 如果界面出现错误提示停止操作并汇报提示内容。 5. 当 get_screen 返回的界面中出现“欢迎回来”文本时宣布任务完成。值得注意的细节是第 4 条和第 5 条。刚上手时我不写终止条件agent 登录成功后还在反复截图白白浪费 token。加了“欢迎回来”这个界面信号之后它每次截图都会自己判断是否结束。错误提示那条也很重要它告诉 agent“遇到意外不要硬来把现场交还给人”这其实是在为后面的失败分支做铺垫。agent 提示词的哲学就是这样不是教它怎么思考而是给它铺一条不会掉进去的轨道。4.2 一次完整执行的现场记录我在模拟器上把测试账号密码清空然后让 Harness 开始执行。agent 的第一步永远是观察它调用 get_screen返回的 JSON 大致长这样[ {text: 登录 Demo, id: tv_title, class: android.widget.TextView}, {text: , id: et_username, class: android.widget.EditText, bounds: [180,720][900,880]}, {text: , id: et_password, class: android.widget.EditText, bounds: [180,900][900,1060]}, {text: 登录, id: btn_login, class: android.widget.Button, bounds: [180,1100][900,1240]} ]agent 看到这些控件后先调用 tap_on参数是 et_username再调用 input_into 把账号输进去。输入完它没有急着点密码框而是又 get_screen 了一次确认密码框还躺在原来的位置然后才 tap 密码框、输入密码、点登录。这一步“多回头看一眼”是 agent 和传统脚本最大的区别脚本按部就班执行agent 会基于观察结果动态调整。它不会因为第一眼看到密码框在旁边就默认它一直在旁边每一步都把界面状态重新读一遍这是稳定性的关键。最后的 get_screen 返回里出现了“欢迎回来”文本agent 判断任务完成整个流程跑了 3 分 40 秒。拆开来看耗时大头在 uiautomator dump每次 2 到 3 秒以及模型推理时间真正点击和输入几乎一瞬间。如果你觉得慢可以考虑给 get_screen 加缓存或者降低 dump 频率但稳定性优先时我还是选择每步都观察反正 4 分钟跑完一个登录流程完全可接受。对更复杂的任务这个观察频率肯定要调整但在登录模块这个粒度上慢就是稳。4.3 失败分支agent 的试错与自纠错能力光看顺利路径说明不了问题我更想知道 agent 面对失败会不会自救。于是我把测试账号的密码故意改成错的重新让 agent 执行。它输入完点击登录后界面上弹出了“密码错误”的提示。agent 的 get_screen 结果里出现了这段文本它的下一步是读取错误提示决定不清除密码框tap 密码框重新输入正确密码再点登录。全程没有要我干预。这个过程在日志里看起来相当丝滑但背后的逻辑其实很值得琢磨。这就是我选登录模块做第一个场景的深层原因登录有天然的错误反馈闭环能逼 agent 面对不确定性。录制回放脚本遇到密码错误只能停下来等人处理而 agent 可以从错误提示里判断下一步动作等于把传统自动化里需要写死的一堆 if-else 交给了模型推理。不过这里有个前提错误提示必须暴露在 UI 树里。我一开始用的是 Toast但 Toast 在 uiautomator dump 里经常抓不到后来改成屏幕中央的一个 TextView 提示dump 必现。这是给 agent 做 UI 的一个反常识建议反馈信息要走标准控件别只走 Toast否则再聪明的模型也看不见。4.4 提高可靠性的三个小改动第一次完整跑通后我又连续跑了 10 次发现偶发失败主要有三类模拟器窗口失焦导致输入丢失、agent 连续点击产生状态错乱、以及界面停在动画过渡期。针对这三类我做了三个改动。第一每次点按钮前先用adb shell wm dismiss-keyguard确保锁屏和焦点不影响操作第二在提示词里明令“每一次工具调用后必须 get_screen 才能进行下一次调用”禁止连点第三给整个流程包一层 60 秒超时超时自动截图留档。第三点的截图留档特别实用。它把 agent 的“最后一屏”保存下来排查问题时可以直接看现场不用靠猜。我还在模拟器里建了快照机制登录页初始状态拍一张快照每次跑新的验证前先恢复到快照这样不管上一轮把界面搞成什么样10 秒内就能回到干净状态。快照相当于 agent 的沙盒是自动化里的保底手段。有了它你才敢让 agent 放手折腾折腾坏了也能瞬间重来这个安全网的存在本身就能提升 agent 的试错意愿。5. 常见问题与排查技巧实录5.1 插件加载失败harness failed to load plugins web boot这个报错我在搭环境第二天就撞上了。完整的报错类似 “harness failed to load plugins web boot: 2 entries did not activate”意思是插件引导阶段有两个插件条目没有激活。第一反应不要去看模型配置问题基本在插件层。我那次是因为 Node 版本不对一个插件要求大于等于 18系统默认还是 16另一部分则是因为插件目录里有多个入口文件Harness 不知道该激活哪个。排查路径我建议按顺序来先node -v确认版本满足要求再看插件目录里有没有重复的入口文件最后把自定义工具独立成子目录避免和官方插件混在一起。还有一个很隐蔽的原因插件依赖没装全。有些插件是 git 直接拉的拉下来没执行依赖安装也会出现“条目未激活”。解决后建议跑一次harness doctor之类的自检命令让它逐个报告插件状态能省很多时间。下面这个表格是我后来整理的排查速查现象常见原因处理方式报错里有 did not activateNode 版本不满足升级 Node 到 18 或更高插件重复激活入口文件重复只保留一个入口其他移出目录插件静默失败依赖未安装在插件目录执行依赖安装所有插件都不加载配置目录权限检查工作目录可写清理缓存5.2 ADB 链路不稳unauthorized、端口占用、设备掉线ADB 既是一切的基石也是最容易出幺蛾子的一层。我碰到过三种典型问题。第一种是设备显示 unauthorized多半是模拟器弹窗被忽略重启后勾选“始终允许调试”即可。第二种是 adb devices 列表为空常见原因是 5037 端口被占用执行netstat -ano | grep 5037找到占用进程杀掉然后adb kill-server adb start-server。第三种是跑着跑着设备掉线特别是长时间执行后我养成了一个习惯大任务前先执行adb reconnect offline清理状态。还有一个容易被忽视的细节多台设备同时在线时adb shell 命令会因为设备不唯一而报错。自动化脚本里最好固定用-s emulator-5554指定设备不要依赖默认设备。测试账号、密码这类输入内容也建议写进配置文件而不是堆在命令行里既方便替换也避免 shell 转义连环坑。总之ADB 层出现问题先稳定链路再排查 agent否则很容易把环境问题误判成模型问题。环境问题误判成模型问题是 agent 开发里最常见的返工原因没有之一。5.3 控件定位失败id 找不到、dump 空 XML、界面有弹窗我调试最多的问题就是 get_screen 返回的列表里找不到目标控件。常见原因有三个。第一界面还没渲染完就 dump 了返回空 XML解决方法是 dump 前 sleep 300ms 以上宁可多等也别抢跑。第二目标控件的 id 是动态生成的或者干脆是自定义 View 没有 id这种就退回 text 匹配比如按钮文字是“登录”就用 text 去匹配。第三界面上盖了弹窗弹窗把目标控件遮住dump 出来的却是弹窗的层级先adb shell input keyevent 4关掉弹窗再重试。关于坐标兜底我的经验是把它当成最后手段而不是常规手段。坐标只在 id 和 text 都失效时启用而且启用前最好让模型说明原因。因为坐标天然脆分辨率一变、系统字体一变、屏幕旋转一下坐标全部作废。定位策略的优先级应当是 resource-id 大于 text 大于 boundsagent 的任务提示词里也按这个顺序写模型就不会在最简单的事情上乱发挥。如果你发现 agent 频繁输出坐标参数而不是 id 参数说明提示词约束不够强应该强化约束而不是去优化坐标算法。5.4 模型行为异常反复点同一个按钮、幻觉出控件、假装成功这类问题最让人抓狂因为报错不在工具层而在模型的决策层。比如 agent 连续三次点击登录按钮每次都汇报“已点击”但界面上毫无变化。排查后发现它确实在调用工具但点击坐标算错了因为 dump 出来的 bounds 是物理像素坐标而我给模型看的截图经过了缩放两边对不上。结论是登录流程所有坐标都必须以 uiautomator 的 bounds 为准不要让它结合截图猜坐标。任何涉及坐标的地方只认 UI 树里的数字。还有“幻觉控件”问题模型会杜撰一个 id 叫 login_btn 的按钮而 UI 树里根本没有。这通常是因为它没好好看最新的 get_screen 结果凭经验在猜。我的对策是在提示词里加一句话“每次工具调用前必须引用上一次 get_screen 结果中真实存在的 resource-id 或 text如果目标不在列表里停止操作并汇报。”这句话把模型的注意力逼回现场数据。包括很多人遇到的 agent 框架里“更新沙盒卡住、无法发送消息”这类问题本质也是工具状态和模型认知不一致清掉会话缓存重新同步后绝大多数都能恢复。5.5 进阶场景当登录页出现系统文件选择器这类脏场景登录模块不总是井井有条的输入框加按钮很多 App 有第三方授权登录会唤起系统文件选择器或 WebView。这类场景有个典型特征uiautomator dump 出来的控件里会出现一堆 content:// URI 形式的路径长串的 fileprovider 路径让模型完全懵掉它根本不知道这些字符串是什么、该怎么办。我的处理方式是单独给 agent 准备一个 file_picker 工具专门解析最近的文件列表按文件名过滤后返回可选文件让模型按文件名决策而不是去理解那串 URI。这也是把“登录模块”做透的乐趣所在从一个最简单的输入框场景起步逐步把弹窗、错误反馈、文件选择器、多步校验这些真实世界的问题一个个接进来。agent 的能力是被这些边界场景撑起来的而不是被几张顺利路径的截图撑起来的。我每次改完工具脚本都会先跑一条固定的冒烟测试确认 get_screen 能正常出结果再让 agent 上场。这个小习惯帮我把绝大部分无谓排查挡在了开始之前也是这整套环境里我觉得最值得分享的一条个人心得。
RELATED

相关推荐

大模型写Three.js 3D网页游戏:Step 5、DeepSeek、GLM三强实测

大模型写Three.js 3D网页游戏:Step 5、DeepSeek、GLM三强实测

1. 评测基准:为什么把“3D网页游戏”当成三个模型的试金石先交代一下背景。我最近在琢磨一件事:到底该怎么衡量一个大模型写代码的真实水平?LeetCode题、CRUD接口、工具函数这些常规题,模型的分数已经非常接近,拉不开差…

📅 2026/11/2 9:18:31
雷达原始数据在多模态融合中的创新应用与优化

雷达原始数据在多模态融合中的创新应用与优化

1. 项目概述:雷达原始数据在多模态融合中的突破性应用这篇论文《Echoes Beyond Points: Unleashing the Power of Raw Radar Data in Multi-modality Fusion》提出了一个颠覆性的观点:在自动驾驶和环境感知领域,我们应该超越传统的点云处理方…

📅 2026/11/2 9:13:31
SPA应用中window.location.href跳转不刷新的解决方案

SPA应用中window.location.href跳转不刷新的解决方案

1. 问题现象与背景分析最近在开发一个SPA应用时遇到了一个典型问题:使用window.location.href进行页面跳转后,目标页面内容没有自动刷新。这个问题在传统多页应用中很少出现,但在现代前端框架(Vue/React/Angular)构建的单页应用中却相当普遍。…

📅 2026/11/2 9:13:31
MORE NEWS

更多资讯

📰

React Server Components RCE漏洞分析与防御实践

1. React Server Components RCE漏洞深度解析CVE-2025-55182是React生态中近年来最严重的安全漏洞之一,它影响了React Server Components(RSC)的核心实现机制。这个漏洞本质上源于服务端组件序列化过程中的代码注入缺陷,攻击者可以…

📰

Flink实时处理淘客订单架构设计与实践

1. 淘客订单实时处理的业务挑战在传统淘客系统中,订单处理通常采用批处理模式,这种架构存在几个致命缺陷。以我们团队运营的微赚淘客系统为例,日均处理80万笔淘宝联盟订单回调时,批处理模式会导致用户从下单到看到返利状态更新平均…

📰

Shapefile文件族解析与shp地图显示实操指南

前阵子有个做区域规划的朋友丢过来一个压缩包,说里面是某片区的边界数据,让我帮忙“打开看看长什么样”。我解压出来一看,十几个同名不同后缀的文件摊在文件夹里,他以为只需要双击那个扩展名是 shp 的就能出图,结果双击…

📰

ICLR 2026预讲会:DAMO开发者矩阵与AI前沿技术

1. 项目概述ICLR 2026预讲会|DAMO开发者矩阵专场是一个面向人工智能研究者和开发者的前沿技术分享活动。作为机器学习领域的顶级会议ICLR(International Conference on Learning Representations)的预热活动,这个专场聚焦于DAMO开…

📰

Hindsight:工程师的技术复盘方法论与工程化实践

1. “Hindsight”不是工具名,而是开发者对技术决策的反思视角“Hindsight”这个词在当前技术社区里被高频提及,但它不是某个开源库、CLI 工具或 npm 包的正式名称——至少在 PyPI、npm registry 和 Docker Hub 的官方索引中,不存在一个叫hind…

📰

图论中的偏序关系与DAG拓扑排序实战解析

1. 问题背景与核心概念解析Codeforces 2195F这道题目出现在编程竞赛平台上,考察的是图论中偏序关系与DAG拓扑排序的综合应用。这类问题在实际工程中有着广泛的应用场景,比如任务调度、依赖管理、编译顺序确定等。偏序关系(Partial Order&…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬