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