# Fast-Path Picker:端侧本地决策的 UI 自动化加速层 > 技术沉澱文档 · 2026-10-10 · 本文件为「将来独立成项目」的种子资料 > 原始实现:`dsh-android`(DeepSeek Harness Android 移植)的 `scr pick` ## 一句话 **把「该点屏幕上的哪个元素」这个判断从云端大模型(秒级)搬到设备本地(毫秒~百毫秒级), 同时让本地模型永远不需要看图、不需要输出坐标。** ## 问题:为什么现有 GUI Agent 慢 调研过的开源方案(GitHub / arXiv,2026-10-10): | 项目 | 星 | 做法 | |---|---|---| | opendroid | 1124 | 无障碍 + 12 个云端 LLM / Ollama,**整屏交一个模型** | | zafiro | 237 | 无障碍 + Shizuku + MCP,同上 | | OnScreen-Agent | — | 上下文压缩(8KB/120 节点上限),**仍走大模型单层** | arXiv 综述《LLM-Powered GUI Agents in Phone Automation》(2504.19838) 把 **on-device deployment efficiency 列为 open challenge**。 共同瓶颈:每一步都要「截屏或 UI 树 → 编码 → 上传 → 大模型推理 → 解析坐标 → 执行」, 单步 2~5 秒,而**其中大部分步骤只是在回答一个很窄的问题**: > 「屏幕上这几个文本里,哪个是用户要点的那个?」 ## 核心设计:三层递进,越快的越先命中 ``` 目标描述(如「打开 WLAN」) │ ▼ ┌─ ① 精确/子串匹配 ─────────────────────────── 1-2 ms ─┐ │ 节点文本 == 目标 / 包含 / 被包含 │ 无需模型 └───────────────────────────────────────────────────────┘ │ 未命中 ▼ ┌─ ② 本地语义匹配 ───────────────────────────── ~93 ms ─┐ │ bge-small-zh(15MB, 24M 参数, 512 维) │ 无网络往返 │ 目标向量 vs 各节点文本向量 → 余弦相似度取最高 │ │ 阈值 0.62;低于阈值宁可降级也不瞎点 │ └───────────────────────────────────────────────────────┘ │ 仍不可判定 ▼ ┌─ ③ 云端兜底 ──────────────────────────────── 2-5 s ──┐ │ 把候选列表(含文本与坐标)一并返回, │ │ 大模型在**一轮内**做出选择(不必反复 dump) │ └───────────────────────────────────────────────────────┘ ``` ### 为什么本地模型可以「极小」 三条约束的撤销,让 15MB 的模型就够用: 1. **不看图** —— 输入是屏幕文本节点,不是截屏 → 不需要多模态模型 2. **不输出坐标** —— 坐标由无障碍层从节点直接取(点文本节点的中心, 事件会冒泡到可点击祖先,与 `tap-text` 同一原理)→ 不需要生成式解码 3. **只做排序** —— 「哪个最相关」本质是检索问题 → **嵌入模型**比生成模型快一个量级 (一次前向 vs 逐 token 解码) 生成式模型解码一次要几百 ms 到秒级;嵌入一次前向在手机上是**几毫秒到百毫秒**。 ## 实测数据(emulator-5554,llama-server 0.6.0,bge-small-zh-v1.5-q4_k_m) | 层 | 延迟 | 命中样例 | |---|---|---| | ① 精确 | **1-2 ms** | `网络和互联网`→该节点 @(369,712);`WLAN`→`移动网络、WLAN、热点` | | ② 语义 | **85-102 ms(均 92.7)** | `打开 WLAN`→`移动网络、WLAN、热点`,cos **0.670** | | ② 对照 | — | `铃声和振动` cos **0.333**(区分度足够) | | 首次(预热) | 247 ms | 仅一次 | | 模型体积 | **15.4 MB** | gguf q4_k_m,512 维 | 安装侧:`llama-cpp` 扩展安装 **15 秒**(Termux 源直连),提供 `llama-server`。 ## 关键实现要点(踩过的坑) 1. **紧凑 dump 里"可点击的容器文本是 `-`"** —— 真正文本在子节点上而无 `c` 标记。 若按 clickable 过滤,候选全是 `-`,永远选不中。**候选 = 有文本的节点, clickable 只用于同分排序。** 2. **模型只能是加速器,不能是依赖** —— 没装扩展 / 没下模型 / 服务没起时, 第 ② 层返回 null,第 ③ 层接住。任何情况下用户不被阻塞。 3. **下载写到 `.part` 再改名** —— 半截文件不能当成完整模型。 4. **`llama-server` 的 `-t 2`** —— 别把引擎和 UI 的核全占了;`-c 512` 足够放 「目标 + ~10 个屏幕文本」。 5. **WebUI 的桥 token 与 engine.log 里的 WebUI token 不是同一个** —— 调桥要用 `DSH_BRIDGE_TOKEN`(引擎子进程环境里)。 ## 代码位置(dsh-android) | 文件 | 职责 | |---|---| | `engine/FastPicker.kt` | 三层选择逻辑 + 紧凑 dump 解析 | | `engine/LocalEmbedder.kt` | 嵌入服务客户端(/v1/embeddings + 余弦) | | `engine/LocalModel.kt` | 模型下载(多镜像 failover)+ llama-server 生命周期 | | `engine/AgentBridge.kt` | `POST /pick`、`GET /local/status`、`POST /local/ensure` | | `assets/gates/scr` | `scr pick <目标> [--tap] [--all]` | | `assets/extensions/catalog.json` | `llama-cpp` 扩展条目 | | `scripts/probes/test_pick.py` | pick 三层命中测试(UTF-8 正确) | | `scripts/probes/test_embed.mjs` | 嵌入延迟与命中率测量 | ## 尚未验证 / 下一步 - **真机(arm64)数据** —— 模拟器是 x86_64,真机延迟预计更优(Nemilus/大核), 但须实测。 - **经 `/pick` 的语义层全链路** —— 已分别验证嵌入服务与 FastPicker 精确层, `via=semantic` 的那一跳尚未在设备上跑过。 - **决策缓存** —— 「同屏幕指纹 + 同目标」直接复用结果,可把重复操作压到 ~0 ms。 - **用户接管后的缓存失效** —— 屏幕指纹须包含变化摘要(已有 `scr changed` 可复用)。 - **多语言** —— 现在用中文语义模型;英文界面需换 `bge-small-en`(同样体积)。 ## 独立成项目时的定位建议 把「快速路径选择器」抽成一个**可插拔的 Android 库**: - 输入:屏幕文本列表(或 ReadableNodeInfo 树)+ 目标描述 - 输出:选中节点索引 + 置信度 + 命中路径 - 不绑定任何具体自动化框架(无障碍 / ADB / adb-tools 均可作为前端) - 依赖注入「模型提供者」,支持「无模型 → 纯规则」到「本地嵌入」到「云端」多档