目录
Claude 需要做的,只是将 iOS 时间线滚动 300 点后停下。它在模拟器上移动手指,在终点暂停片刻,然后松开。
时间线却移动了 1,407 点。
截图也带来了另一个问题。一位正在开发自用 Mastodon 客户端的开发者,在修改实现后,会让 Claude 在模拟器中进行检查。不断加载屏幕图像,似乎消耗了相当大一部分上下文。
对 99 个会话的调查促使他改变了工作流程:通过文本读取界面元素和应用状态,仅在检查外观时使用图像,同时为智能体提供行为更可预测的界面操作和拖动工具。
在 4 项基准任务中,图像加载次数从 27 次降至 1 次,上下文增量减少了 43%。不过,执行时间增加了 34%。
这个案例值得借鉴的,正是这种权衡。减少截图大幅节省了上下文,但并没有让所有操作都变快。
会话日志揭示了什么#
调查发现,99 个会话中共有 2,125 张模拟器图像被载入上下文,其中包括截图和从录屏帧生成的图像。
默认截图工具返回的是缩放至 920 × 2,000 像素的图像,每张消耗约 2,500 个 token。图像最多的会话包含 176 张图像。该会话的上下文达到约 77 万个 token,其中一半以上由图像占据。
日志还揭示了一些可以避免的操作浪费。
- 紧接着点击操作执行的 Bash 调用中,有 900 多次仅执行了 1~5 秒的
sleep。 - 在 892 次轻扫和拖动中,有 165 次在操作后没有等待就立即截图,占比 18%。
立即截取的图像有时会捕捉到界面切换的中间状态。Claude 根据该画面选择坐标,但等到实际点击时,界面已经发生了变化。
此次改进没有涉及录屏帧分析。图像对于研究界面切换仍然有用。改进针对的是查找按钮、打开设置、确认滚动停止位置等日常界面操作。
用文本读取坐标和状态#
主要新增功能是一个仅供调试使用的 URL,用于指示应用将界面转储写入文件。
转储内容包括无障碍元素的类型、标签和中心坐标,以及时间线的 contentOffset.y 等应用状态。Claude 无须再从图像中寻找标签页,而是可以读取如下描述。
画面: 静止済み(0.3秒)
ボタン "ホーム" 選択中 中心=(158,822)
ボタン "通知" 中心=(244,822)
状態: タイムライン
contentOffset.y: -116.0这样就能直接回答两个问题:“该点击哪里”,以及“操作后发生了什么”。
点击坐标采用各元素可见部分的中心。这对只露出一部分的行尤其重要,因为整行边框的中心可能位于屏幕外,或被栏遮挡。
在这套实现中,只有启用无障碍支持,UIKit 才会设置所需的标签。在模拟器的 com.apple.Accessibility 偏好设置中设置 ApplicationAccessibilityEnabled,并重启应用后,就能获取按钮和标签页的名称。
生成无障碍元素可能带来额外的处理开销,因此开发者在测量动画卡顿时会移除这项设置。
AXe、idb 等外部工具也能读取界面元素。这次开发者希望在不增加依赖的情况下获取内部状态,因此,将转储功能内置到应用中更为合适。
等界面停止移动后再检查#
仅仅改用文本,并不能解决坐标过时的问题。转储也需要一种等待界面停止移动的机制。
最初的方法通过比较截图来判断,每次转储约需 3 秒。后来改为检查应用内部的动画和滚动状态,等待相关运动连续停止 250 毫秒。这样,常规转储约 1 秒就能完成。
不过,有一点让判断变得复杂:iOS 26 标签栏中的 _UILiquidLensView 保持着一个跟随位置变化的动画。如果将这个动画纳入判断,界面就永远不会被视为静止。
因此,开发者将无限循环的动画,以及计算持续时间超过 60 秒的动画排除在判断之外,另外还设置了 5 秒的超时。
这是一种实用的近似判断,并不保证界面已加载完毕。图像可能在运动停止后才加载出来。智能体仍需确认预期元素是否存在,必要时重新获取转储。
时间线的实时更新需要单独控制。开发者通过调试 URL 暂缓显示新帖子,避免在检查与点击之间出现行位置偏移。解除暂缓后,等待中的新帖子才会插入时间线。
保留截图用于检查外观#
文本可以提供图标元素的信息,却无法判断图标是否正确绘制。
在新的工作流程中,检查外观时会通过 xcrun simctl io 截图,将其缩放至与设备以点为单位的宽度一致,再保存为 JPEG。测试设备对应的图像宽度为 402 像素。
在这一常用尺寸下,图像中的 1 像素对应点击坐标中的 1 点。整张屏幕图像约需 470 个 token,约为默认截图工具所报告消耗量的五分之一。如果只裁出一个小图标,则可降至约 50 个 token。
使用裁剪图像时需要注意坐标。坐标原点位于裁剪区域的左上角,因此点击前必须加上裁剪位置的偏移量。
实际使用时,分工很简单:通过文本确认操作对象的位置和状态,通过图像回答与外观有关的问题。
追求精度之前,先修好拖动#
导致时间线滚动 1,407 点的,是一次以约每秒 3,000 点的速度将手指移动 300 点的操作。在移动路径的末尾,工具重复指定了相同坐标,以表示松手前的短暂停留。
然而,这种暂停并不能可靠地消除惯性。
进一步实验揭示了更明确的规律。以 32 毫秒为间隔,每次移动 20 点,总共移动 200 点,即使在终点停留 300 毫秒,列表仍会滚动 416 点。
在终点加入微小移动后,结果发生了变化。让最后的位置每隔 16 毫秒交替偏移 0.5 点后,手指移动 200 点时,列表会在滚动约 180 点后停下。
当时的工作假设是:touch_path 不会将连续的相同坐标作为移动事件发送。UIKit 似乎保留了最后一次实际移动时的速度。在终点加入微小移动,可能就能发送速度低得多的新事件。
这一机制是根据观察到的行为推测的,并未通过检查工具实现来验证。
最终的拖动辅助工具每隔 32 毫秒移动约 20 点,随后在终点附近加入 16 次微小移动。在一次测量中,手指移动 320 点,界面滚动了 300 点。
滚动开始前被吸收的初始移动量,在 10~80 点之间波动。因此,要精确定位,仍需在操作后读取 contentOffset.y,并修正剩余距离。
基准测试实际测量了什么#
比较采用了以下 4 项任务。
- 打开一条测试图片帖子的第 3 张附件图,在查看器中轻扫一次,然后关闭。
- 修改时间显示设置,再恢复原状。
- 将时间线精确滚动 300 点后停下。
- 目视检查最上方通知的图标,并保存截图。
提供的报告称,测试使用的智能体是搭载 Opus 5.5 的 Claude Code。原计划是在一台 Mac 和一台模拟器设备上,对每种条件各运行 3 次。测量时间不包含子智能体的启动时间。
| 任务 | 原耗时 | 新耗时 | 原图像数 | 新图像数 | |---|---:|---:|---:|---:| | 图片查看器 | 18 秒 | 46 秒 | 5 | 0 | | 设置 | 36 秒 | 53 秒 | 13 | 0 | | 精确滚动 | 47 秒 | 28 秒 | 5 | 0 | | 通知检查 | 18 秒 | 32 秒 | 4 | 1 |
主要汇总值是各任务中位数之和,并非将整套操作连续执行后测得的数值。
| 指标 | 原工作流程 | 新工作流程 | |---|---:|---:| | 加载的图像数 | 27 | 1 | | 图像估算 token 数 | 约 66,000 | 约 50 | | 上下文增量 | 约 107,000 个 token | 约 61,000 个 token | | 操作时间 | 119 秒 | 159 秒 |
还有一个重要限制:使用原工作流程检查通知时,5 次尝试中有 4 次在加载通知截图后立即因 API 端错误而中止。由于原因不明,这项条件采用了唯一一次成功运行的结果。
这些结果支持了该项目中的选择,并不是适用于其他应用或机器的通用性能估算。
为什么上下文减少了,耗时却增加了#
新工作流程在图片查看器、设置和通知这三项任务上都变慢了。
基准测试中的转储处理需要等待运动停止,耗时为 1.6~5 秒,而截图约 0.4 秒即可返回。此外,观察到的模型判断时间也从约 1~2 秒增加到了 4~5 秒,原因不明。
原工作流程有时反而会因为检查得太早而更快完成。如果能通过另一张截图迅速修正过时的界面信息,那么即使图像数量增加、偶尔需要重试,整体仍可能更快结束。
在精确滚动任务中,这种权衡反了过来。新工作流程耗时 28 秒,低于原来的 47 秒,而且 3 次运行都只需拖动一次。原工作流程则需要拖动 2~3 次。
选择更小的模型之前,先改善接口#
开发者最初曾考虑将模拟器操作委派给 Haiku,以节省 Opus 的上下文。
使用原有工具的早期测试中,4 项任务有 3 项发生了重试。一次滚动只移动了 80 点就停下,另一次运行则修改了错误的设置。换用新工具后,Haiku 在全部 8 次预备测试中都取得了成功。
样本量很小,但仍说明,在选择模型之前,值得先重新审视工具设计。
几乎不再使用截图后,即使让 Opus 直接操作,每项任务新增的 token 数也能控制在约 14,000~16,000 个。委派任务则需要花费 3~18 秒启动并加载指令,还会增加汇报结果的步骤。开发者因此继续采用直接操作,并将工作流程记录在 AGENTS.md 中。
在自己的项目中,也可以先选一项经常重复执行的模拟器任务。添加文本转储,包含当前可见的操作元素和一个可测量的状态值,然后与使用截图的工作流程比较上下文增量、完成时间和准确性。将截图留给真正需要目视检查的环节。
来源:https://zenn.dev/eai/articles/dont-let-agents-see-the-ios-sim-screen
目录
