
先說結(jié)論我在一臺 4GB 顯存的舊筆記本上做 GUI Agent(讓 AI 看屏幕、點(diǎn)鼠標(biāo))。純視覺方案定位一個按鈕要 12.9 秒,換成無障礙樹優(yōu)先后只要 0.3 秒。這不是理論推導(dǎo),是我真跑出來的數(shù)據(jù),包括中間踩的三個坑。如果你也在做 computer use / GUI automation,這篇可能幫你省幾天。背景:我要解決什么問題我有個自用的 AI 助手(命令行工具),我給它加了視覺操控能力:截圖 → 云端視覺模型識別 → xdotool 點(diǎn)擊。核心痛點(diǎn)是慢。實(shí)測:定位屏幕上的關(guān)閉按鈕 12.5 秒 描述屏幕內(nèi)容 6.5 秒 一個 3 步的多步任務(wù) 25 秒12 秒什么概念?我讓它點(diǎn)個按鈕,得等十幾秒。這個延遲在真實(shí)使用里基本不可接受 —— 用戶盯著黑屏等,不知道它在干嘛。更糟的是不準(zhǔn):視覺模型給的是大概位置,偶爾會點(diǎn)偏。轉(zhuǎn)機(jī):一次框架考古我去翻了業(yè)界幾個成熟框架的源碼和文檔,重點(diǎn)看三家的設(shè)計(jì):UI-TARS(字節(jié))動作輸出用 Python 函數(shù)調(diào)用串而不是 JSON;解析失敗有多層修復(fù);prompt 要求先寫小步計(jì)劃,末句總結(jié)下一步動作。Anthropic Computer Use 最佳實(shí)踐截圖必須預(yù)縮放到 1280x720(別讓 API 自動縮,會糊);文本指令要放在圖片之前(先知道要找什么,再看圖);用rolling buffer只保留最近 3 張圖。UACC 和 screenpipe(這兩個是關(guān)鍵)UACC 提出結(jié)構(gòu)化 UI 文本地圖——純文本模型也能定位元素;screenpipe 的原則是Accessibility tree 優(yōu)先于截圖。第 3 條點(diǎn)醒了我:為什么非要看圖猜?操作系統(tǒng)本來就知道每個按鈕在哪啊。實(shí)測:Linux 的 AT-SPI 能直接讀出元素樹AT-SPI 是 Linux 的無障礙接口。它的設(shè)計(jì)初衷是給讀屏軟件用的,但它暴露的能力正好是 GUI Agent 需要的:元素名 角色 屏幕精確坐標(biāo)我實(shí)測了一下(一個終端窗口):frame | 終端 | 中心(993,556) push button | 最小化 | 中心(1817,55) push button | 還原 | 中心(1857,55) push button | 關(guān)閉 | 中心(1897,55) toggle button | 菜單 | 中心(1769,55) label | 終端 | 中心(993,54) push button | 新建標(biāo)簽頁 | 中心(90,55)注意:中文元素名原生保留,不需要 OCR,不需要翻譯,不需要猜。坐標(biāo)精確到什么程度?關(guān)閉按鈕的 bbox 是 (1880,40,34x30),中心點(diǎn) (1897,55) —— 這是窗口管理器自己報(bào)的位置,不是模型估的。關(guān)鍵實(shí)現(xiàn)(踩了坑)坑一:不能用 AT-SPI 自己的活動窗口狀態(tài)我第一版是這么寫的:遍歷所有應(yīng)用,找狀態(tài)是 ACTIVE 的窗口。結(jié)果它一直返回 gnome-shell —— 因?yàn)?gnome-shell 永遠(yuǎn)處于活動狀態(tài)。讀出來的元素是桌面圖標(biāo)和活動概覽,不是我要找的應(yīng)用。正確做法:用 X11 的焦點(diǎn)窗口 PID 去匹配 AT-SPI 的應(yīng)用進(jìn)程 ID。# 拿焦點(diǎn)窗口 PID wid subprocess.run([xdotool, getwindowfocus], ...).stdout focus_pid subprocess.run([xdotool, getwindowpid, wid], ...) # 在 AT-SPI 里找同 PID 的應(yīng)用 for app in atspi_apps: if app.get_process_id() focus_pid: return app這個 PID 匹配是關(guān)鍵。AT-SPI 的應(yīng)用對象有 get_process_id() 方法,和 X11 報(bào)的窗口 PID 是同一個??佣?有些應(yīng)用不給 AT-SPI 暴露內(nèi)容微信(WINE 程序)只返回一個空 frame,內(nèi)部元素讀不到。這類應(yīng)用必須回退到視覺方案。所以最終做成了三級定位:級別 1: AT-SPI 0.3 秒 精確,但需要應(yīng)用支持 a11y 級別 2: OCR 0.5-8 秒 文本類目標(biāo)可用(tesseract 中文包) 級別 3: 視覺模型 4-13 秒 萬能兜底實(shí)測結(jié)果(同一臺機(jī)器,同一個關(guān)閉按鈕):AT-SPI: 322ms / 343ms / 332ms (三次) 純視覺模型: 12920ms (一次)40 倍差距。第二個優(yōu)化:坐標(biāo)緩存微信這類 WINE 應(yīng)用用不了 AT-SPI,只能走視覺模型(5 秒一次)。但它的界面元素位置是穩(wěn)定的。我加了個坐標(biāo)緩存,key 是窗口標(biāo)題幾何目標(biāo)描述:冷啟動(第一次定位): 5060ms 緩存命中: 14ms ← 367 倍窗口移動/縮放時 key 會變,緩存自動失效,不會點(diǎn)錯地方。第三個優(yōu)化:元素地圖注入多步任務(wù)里,我把 AT-SPI 讀到的元素地圖直接喂給模型:界面元素地圖(精確定位可直接用, 坐標(biāo)已是屏幕真實(shí)坐標(biāo)): push button | 關(guān)閉 | 中心(1897,55) push button | 新建標(biāo)簽頁 | 中心(90,55) ...然后在 prompt 里寫一句:“如果界面元素地圖里有目標(biāo)元素的精確坐標(biāo),優(yōu)先用它而不是看圖猜”。實(shí)測效果很直接:一個讀取終端窗口標(biāo)題的任務(wù),之前(純看圖): 25 秒,3 步 現(xiàn)在(帶地圖): 3 秒,1 步模型的原話是:“地圖中 label「終端」位于 (993,54) 也印證了這一點(diǎn)。”它真的在用這個地圖。三個值得說的失敗教訓(xùn)教訓(xùn) 1:我的自動化工具把自己打了我在測按 Escape 鍵時,焦點(diǎn)正好在我自己運(yùn)行的終端上。而這個終端里跑的就是 AI 助手本身,它的 Escape 鍵是打斷當(dāng)前回合。結(jié)果:我按的 Escape 打進(jìn)了我自己,把自己正在跑的命令中斷了兩次。修法:發(fā)鍵前先檢查焦點(diǎn)窗口的 PID,如果在自己的進(jìn)程鏈里就拒絕。這是個 fail-closed 設(shè)計(jì) —— 拿不到信息就拒絕,而不是放行。教訓(xùn) 2:一個 60 秒的卡死,根因是管道繼承我寫了個粘貼中文功能(xdotool 對中文支持不好,用剪貼板更穩(wěn))。結(jié)果它卡死 60 秒不返回。根因很隱蔽:xclip 會 fork 一個 daemon 來保持剪貼板內(nèi)容,而這個 daemon 繼承了本進(jìn)程的 stdout —— 那個 stdout 是調(diào)用方捕獲輸出的管道。本進(jìn)程退出了,但 daemon 還攥著管道寫端,調(diào)用方讀端等 EOF 永遠(yuǎn)等不到。修法一行:subprocess 加 stdoutsubprocess.DEVNULL。有意思的是,我一開始以為用 xclip -loops 1能解決,實(shí)測那是錯的(內(nèi)容即失)。真正的修法是切斷管道繼承。教訓(xùn) 3:驗(yàn)證太早,把成功的操作當(dāng)成失敗我給微信發(fā)消息,輸入1后立即截圖驗(yàn)證,模型說輸入框是空的。我就又輸入了一次。結(jié)果查出來輸入框里是11 —— 第一次其實(shí)成功了,只是 WINE 應(yīng)用響應(yīng)有延遲,驗(yàn)證時還沒渲染出來。修法:不再用固定 sleep,改成連續(xù)兩次截圖指紋相同來判斷界面穩(wěn)定。寫在最后三個優(yōu)化加起來,我這個 GUI Agent 的定位從 12.9 秒降到 0.3 秒。而且這些優(yōu)化都不需要換硬件 —— 我用的還是一臺 4GB 顯存的舊筆記本。核心思路其實(shí)就一句話:AI 不需要看得更好,它需要問得更聰明。 操作系統(tǒng)知道的東西,就別讓模型去猜。代碼用的都是系統(tǒng)自帶組件(gi.repository.Atspi、xdotool、tesseract),沒有額外依賴。完整代碼已開源(MIT):https://github.com/asd369932/gui-agent-fast里面有個 benchmark.py,可以復(fù)現(xiàn)上面所有數(shù)據(jù)。如果你在做類似的事,建議先花半天時間看看 AT-SPI 能給你什么 ——可能比調(diào) prompt 有用得多。