
1. 為什么手機 GUI Agent 需要一個“記憶層”做過手機端 GUI Agent 的人大概都有過這種體驗模型第一次幫你點開某個 App 完成一個任務第二次再讓它做同樣的事它還是像失憶一樣從頭摸索。每一步都要重新截圖、重新理解界面、重新推理下一步點哪里。單次任務跑下來token 消耗驚人延遲也高得讓人抓狂。這個問題的本質是 Agent 沒有記憶。它每次都在“從零開始理解這個世界”。我在給自己的 Android GUI Agent 加記憶層之前專門統(tǒng)計過一組數(shù)據(jù)一個典型的“打開外賣 App 搜索某家店并下單”的任務平均需要 18 到 25 步操作每步都要把當前屏幕截圖喂給視覺模型做理解單次任務消耗的 token 在 15000 以上。而如果 Agent 能記住“這個 App 的搜索框在頂部”“下單按鈕在右下角”這類信息至少能省掉三分之一的探索步驟。但記憶層不是簡單地把所有歷史操作都塞進上下文就完事了。我踩過的最大坑就是一開始把所有歷史記錄無腦拼接進 prompt結果上下文迅速膨脹到幾萬 token模型反而因為信息過載變得遲鈍該記住的沒記住不該記的全塞進去了。所以后來我重新設計了整個記憶架構核心思路就一句話事實常駐、技能按需用一條統(tǒng)一的寫入鏈把兩者串起來。事實類信息比如某個 App 的界面布局特征常駐在系統(tǒng)提示詞里技能類信息比如“如何完成一次搜索操作”按需檢索注入。兩者通過同一條寫入鏈從 Agent 的執(zhí)行軌跡中自動提取和沉淀。這套方案跑下來同樣的下單任務token 消耗降到了 6000 左右步驟數(shù)從平均 21 步降到了 13 步而且隨著使用次數(shù)增加Agent 對常用 App 的操作越來越熟練。下面我把整個設計和實現(xiàn)過程拆開講。2. 記憶層的整體架構設計2.1 兩類記憶的劃分邏輯在動手寫代碼之前我先想清楚了一個問題Agent 到底需要記住什么從實際執(zhí)行軌跡來看信息可以分成兩大類。第一類是事實也就是那些相對穩(wěn)定、不隨任務變化的描述性知識。比如“微信的搜索入口在首頁頂部的放大鏡圖標”“設置 App 的返回按鈕在左上角”“這個 App 啟動后有 3 秒開屏廣告”。這類信息的特點是跨任務復用率高變化頻率低適合常駐在上下文里。第二類是技能也就是完成某類任務的操作流程。比如“如何在購物 App 里完成一次搜索”“如何關閉一個彈窗廣告”“如何從列表中找到目標項并點擊”。這類信息的特點是數(shù)量多、按場景觸發(fā)、不需要一直占著上下文。這個劃分直接決定了記憶的存儲方式和注入策略。事實常駐意味著它要放在 system prompt 里每次請求都帶著技能按需意味著它要存在外部存儲里通過檢索機制在需要時才注入到當前輪次的上下文中。這里有個容易混淆的點有些信息既像事實又像技能。比如“這個 App 的搜索框在頂部”是事實“點擊搜索框然后輸入關鍵詞”是技能。我的處理原則是靜態(tài)描述歸事實動態(tài)操作歸技能。如果一條信息里既有靜態(tài)描述又有操作步驟就拆成兩條分別存儲。2.2 為什么不用向量數(shù)據(jù)庫做全量檢索很多人一提到記憶層第一反應就是上向量數(shù)據(jù)庫把所有記憶做 embedding 然后語義檢索。我一開始也是這么想的但實際跑下來發(fā)現(xiàn)兩個問題。第一個問題是延遲。手機端 Agent 的每一步操作都需要快速響應如果每次注入記憶都要做一次向量檢索哪怕只增加 200 毫秒累積到 20 步的操作里就是 4 秒的額外延遲。對于交互式任務來說這個延遲是能感知到的。第二個問題是精度。向量檢索擅長語義相似但 GUI 操作場景下很多記憶的觸發(fā)條件是高度結構化的。比如“當前前臺 App 是某購物 App 且當前頁面包含搜索框”這種條件用向量檢索反而容易召回不相關的記憶。所以我最終采用的方案是事實直接常駐技能用結構化索引加關鍵詞匹配做檢索。技能庫用 JSON 存儲每條技能帶有明確的觸發(fā)條件App 包名、頁面特征、任務類型檢索時先按結構化條件過濾再用關鍵詞做二次排序。這樣既保證了速度又保證了精度。2.3 一條寫入鏈的設計記憶層的難點不在于讀而在于寫。怎么從 Agent 的執(zhí)行軌跡中自動提取出值得記住的事實和技能這是整個系統(tǒng)能不能持續(xù)進化的關鍵。我的寫入鏈設計是這樣的每次任務執(zhí)行完成后把完整的操作軌跡包括每步的截圖描述、操作類型、操作目標、執(zhí)行結果送入一個蒸餾模塊。這個蒸餾模塊本身也是用大模型做的通過精心設計的提示詞讓它從軌跡中提取出兩類輸出新發(fā)現(xiàn)的事實和可復用的技能。這里的關鍵是提示詞的設計。我試過很多版本最后穩(wěn)定下來的提示詞核心邏輯是讓模型先判斷這次任務是否成功成功的任務才值得提取技能然后讓模型區(qū)分哪些信息是跨任務穩(wěn)定的事實哪些是本次任務特有的操作序列。提取出來的事實和技能分別寫入對應的存儲事實更新到常駐區(qū)技能追加到技能庫。3. 事實常駐層的實現(xiàn)細節(jié)3.1 事實的存儲結構事實層我用的是一棵簡單的樹形結構按 App 包名做一級索引下面掛這個 App 的各種事實條目。每條事實包含幾個字段事實內容、置信度、最后驗證時間、來源任務 ID。{ com.tencent.mm: { facts: [ { id: fact_001, content: 搜索入口位于首頁頂部右側的放大鏡圖標, confidence: 0.95, last_verified: 2025-01-15T10:30:00, source_task: task_20250115_001 }, { id: fact_002, content: 啟動后有約2秒的開屏廣告需等待或點擊跳過, confidence: 0.88, last_verified: 2025-01-14T20:15:00, source_task: task_20250114_003 } ] } }置信度這個字段很重要。不是所有提取出來的事實都靠譜有些可能是模型誤判有些可能因為 App 版本更新而過時。我給每條事實設了初始置信度 0.7每次被成功驗證即 Agent 依據(jù)這條事實操作成功就加 0.05上限 0.99如果依據(jù)這條事實操作失敗就減 0.15。置信度低于 0.5 的事實會被標記為待驗證低于 0.3 的直接淘汰。3.2 事實如何注入 system prompt事實常駐不等于把所有事實都塞進去。我的做法是按當前任務涉及的 App 做過濾只注入相關 App 的事實。具體來說Agent 在接到任務后先做一次任務解析識別出可能涉及的 App 列表然后從事實庫里撈出這些 App 的事實按置信度排序取前 N 條N 根據(jù) token 預算動態(tài)調整一般不超過 15 條注入到 system prompt 的一個專門區(qū)域。注入格式我試過幾種最后發(fā)現(xiàn)用簡潔的自然語言描述效果最好不要用 JSON 或表格模型對自然語言的理解更準確。比如你已知的關于當前App的信息 - 搜索入口位于首頁頂部右側的放大鏡圖標 - 啟動后有約2秒開屏廣告 - 底部導航欄有首頁、發(fā)現(xiàn)、通訊錄、我四個標簽實操心得事實描述要盡量具體避免模糊表述?!八阉骺蛟陧敳俊辈蝗纭八阉骺蛟谑醉擁敳坑覀仁且粋€放大鏡圖標”來得有用。前者模型還要猜后者直接就能定位。3.3 事實的更新與淘汰機制事實不是寫完就不管了。App 會更新界面會變化舊的事實會失效。我的更新機制分兩種主動更新和被動更新。主動更新是在每次任務執(zhí)行前如果任務涉及的 App 有超過 7 天未驗證的事實就先跑一個輕量的驗證流程——讓 Agent 打開這個 App截圖然后讓模型判斷已有事實是否仍然成立。這個流程消耗不大但能有效防止用過時事實指導操作。被動更新就是前面說的置信度機制。每次操作成功或失敗都會反饋到相關事實的置信度上形成自動的優(yōu)勝劣汰。4. 技能按需層的實現(xiàn)細節(jié)4.1 技能的粒度控制技能粒度是設計中最難拿捏的部分。粒度太粗比如“完成一次購物”作為一個技能那它跟直接執(zhí)行任務沒區(qū)別沒有復用價值粒度太細比如“點擊坐標 (100, 200)”作為一個技能那它跟具體屏幕分辨率綁定換個設備就廢了。我最后確定的粒度是原子操作序列一個技能對應一個語義完整的操作單元通常包含 2 到 5 步操作。比如“在搜索框中輸入關鍵詞并提交”是一個技能“從彈窗中關閉廣告”是一個技能“在列表中滑動找到目標項并點擊”是一個技能。這個粒度下技能既有復用價值又不會跟具體任務綁定太死。一個復雜任務可以被拆解成多個技能的串聯(lián)Agent 在執(zhí)行時按需調用。4.2 技能的觸發(fā)條件設計每條技能都帶有明確的觸發(fā)條件這是實現(xiàn)精準檢索的關鍵。觸發(fā)條件包含三個維度App 維度這條技能適用于哪個或哪些 App。有些技能是通用的比如“關閉彈窗”有些是 App 特有的。頁面維度這條技能適用的頁面特征。比如“頁面包含搜索框元素”“頁面標題為‘訂單確認’”。任務維度這條技能適用的任務類型。比如“搜索類任務”“支付類任務”。檢索時Agent 根據(jù)當前的前臺 App、當前頁面截圖描述、當前任務類型三個維度同時匹配召回符合條件的技能。匹配度用加權分數(shù)計算App 維度權重最高頁面維度次之任務維度最低。4.3 技能庫的存儲與檢索技能庫我用的是 JSON 文件加內存索引的方式。JSON 文件持久化存儲Agent 啟動時加載到內存構建倒排索引加速檢索。{ skill_id: skill_search_001, name: 在搜索框中輸入并提交, trigger: { apps: [com.tencent.mm, com.taobao.taobao], page_features: [包含搜索框, 包含輸入框], task_types: [search, query] }, steps: [ 定位頁面中的搜索框元素, 點擊搜索框使其獲得焦點, 輸入目標關鍵詞, 點擊提交按鈕或按回車鍵 ], success_count: 23, fail_count: 2, avg_steps: 4 }檢索時先按 App 維度過濾再按頁面特征做關鍵詞匹配最后按任務類型排序。召回 top 3 的技能注入到當前輪次的上下文中。注入時只注入技能名稱和步驟概要不注入完整細節(jié)避免占用過多 token。5. 寫入鏈的完整實現(xiàn)5.1 軌跡采集與預處理寫入鏈的輸入是 Agent 執(zhí)行任務的完整軌跡。軌跡采集從 Agent 啟動任務時開始每一步操作都記錄以下信息時間戳、操作類型點擊/輸入/滑動/返回等、操作目標元素描述或坐標、操作前的頁面截圖描述、操作結果成功/失敗/未知。軌跡采集的關鍵是頁面截圖描述的質量。我用的是一個視覺模型對截圖做結構化描述輸出包括當前 App 包名、頁面標題、可見的交互元素列表、頁面布局特征。這個描述質量直接決定了后續(xù)蒸餾的效果。預處理階段會把原始軌跡整理成結構化的 JSON去掉冗余信息比如連續(xù)多次滑動只保留一次標注出關鍵節(jié)點任務開始、任務成功、任務失敗、遇到異常。5.2 蒸餾提示詞的設計蒸餾模塊是整個寫入鏈的核心它決定了什么信息會被記住。我用的蒸餾提示詞經過多次迭代核心結構如下你是一個GUI操作軌跡分析專家。請分析以下Agent執(zhí)行軌跡提取出兩類信息 1. 事實跨任務穩(wěn)定的描述性知識。判斷標準這條信息在下次執(zhí)行類似任務時仍然成立且不依賴于具體任務目標。 2. 技能可復用的操作序列。判斷標準這個操作序列在類似場景下可以被重復使用且不綁定具體任務參數(shù)。 輸出格式要求 - 事實用一句話描述具體到可操作的程度 - 技能包含名稱、觸發(fā)條件、步驟列表 - 只提取本次任務中成功驗證過的信息 - 如果任務失敗只提取失敗原因相關的教訓不提取技能 軌跡數(shù)據(jù) {trajectory_json}這個提示詞的關鍵在于“判斷標準”部分。我試過不給判斷標準讓模型自由發(fā)揮結果提取出來的東西要么太泛“這個 App 可以用”要么太具體“點擊了坐標 350, 800”。給了明確的判斷標準后提取質量明顯提升。5.3 寫入沖突的處理寫入鏈會遇到沖突新提取的事實和已有事實矛盾或者新技能和已有技能重復。我的處理策略是事實沖突時比較置信度和時間。新事實置信度更高且時間更新就替換舊事實否則保留舊事實把新事實標記為待驗證。如果兩條事實置信度接近但內容矛盾就都保留在注入時都帶上讓 Agent 在實際操作中驗證哪條正確。技能重復時比較步驟相似度。如果新技能和已有技能的步驟相似度超過 80%就合并——更新已有技能的觸發(fā)條件取并集增加成功計數(shù)。如果相似度在 50% 到 80% 之間就作為獨立技能保留但在檢索時做去重。6. 常見問題與排查技巧6.1 記憶注入導致上下文超限這是最常見的問題。事實和技能都注入后system prompt 可能膨脹到幾千 token加上對話歷史和截圖描述很容易超過模型的上下文窗口。我的解決方案是動態(tài)預算分配。給記憶注入設一個 token 上限比如 1500 token事實和技能各分一半。如果事實超過預算按置信度排序截斷如果技能超過預算按匹配分數(shù)排序截斷。同時監(jiān)控實際使用量如果連續(xù)多次任務都觸發(fā)了截斷就說明記憶庫需要做一次清理和壓縮。6.2 技能檢索召回不相關有時候 Agent 會召回一些看起來相關但實際上用不上的技能浪費上下文空間。排查這類問題我一般按以下順序檢查排查項檢查方法常見原因觸發(fā)條件是否過寬查看召回技能的 trigger 字段apps 列表包含太多 App頁面特征是否模糊對比當前頁面描述和技能頁面特征特征詞太泛如“包含按鈕”匹配權重是否合理檢查 App/頁面/任務三維度權重任務維度權重過高導致誤召回技能本身是否過時查看技能的 fail_countApp 更新導致技能失效6.3 蒸餾提取質量不穩(wěn)定蒸餾模塊的輸出質量直接決定記憶庫的質量。我遇到過的典型問題包括提取的事實太泛沒有操作價值、提取的技能步驟不完整、把失敗的操作也提取成技能。解決這些問題的核心是提示詞的約束要足夠強。我后來在提示詞里加了幾個硬性約束事實必須包含具體的位置或特征描述技能必須包含完整的起止步驟只提取成功軌跡中的技能。另外我加了一個后處理校驗步驟用規(guī)則過濾掉明顯不合格的輸出比如步驟數(shù)少于 2 的技能、不包含任何位置信息的事實。避坑技巧蒸餾模塊本身也會消耗 token如果每個任務都跑一次全量蒸餾成本不低。我的做法是只對成功任務做蒸餾失敗任務只提取教訓類事實。另外對于高度重復的任務類型可以設置蒸餾頻率限制比如同一 App 同一任務類型每天最多蒸餾 3 次。6.4 記憶庫膨脹導致檢索變慢隨著使用時間增長記憶庫會越來越大檢索速度會下降。我的經驗是當技能庫超過 500 條時就需要做一次壓縮。壓縮策略是合并相似技能、淘汰低成功率技能、歸檔長期未使用的技能。具體操作上我寫了一個定時任務每周跑一次記憶庫維護。維護內容包括重新計算所有技能的相似度矩陣合并相似度超過閾值的技能淘汰 fail_count 超過 success_count 且最近 30 天未使用的技能把 90 天未使用的事實標記為歸檔不再注入但保留在存儲中。7. 實際效果與后續(xù)優(yōu)化方向這套記憶層跑在我的 Android GUI Agent 上已經有一段時間了。最直觀的變化是常用 App 的任務執(zhí)行步驟數(shù)平均下降了 35% 到 40%token 消耗下降了約 55%。更重要的是Agent 的行為開始有了“熟練感”——第一次做某個任務可能磕磕絆絆第三次第四次就明顯流暢了。不過也有做得不夠好的地方。目前事實和技能的提取還是依賴大模型蒸餾偶爾會有誤提取。我下一步想嘗試的是引入一個輕量的驗證機制讓 Agent 在執(zhí)行任務時順便驗證一兩條已有記憶形成一個持續(xù)的自我校驗循環(huán)。另外跨 App 的通用技能提取目前效果一般很多技能還是綁定在具體 App 上這塊的泛化能力還有提升空間。如果你也在做類似的手機端 Agent我的建議是先把寫入鏈跑通哪怕提取質量一開始不高也沒關系重要的是讓記憶流動起來。有了流動才有優(yōu)化的基礎。事實常駐和技能按需這個框架本身不復雜難的是讓它在真實使用中持續(xù)沉淀出有價值的記憶。