工具與成效

Android Bench 的代理程式碼生成洞察

喇叭

  • Tomáš Mlynarič

    Android 開發人員關係維繫

關於

講座簡介

AI 模型可在幾分鐘內生成完整功能,但程式碼是否正確?我們分析了數千項生成的程式碼變更,並建立 AI 評估基準 Android Bench,以回答這個問題。

這場講座將分享我們在旅程中獲得的深入分析,包括我們觀察到的所有模型作弊方式、提示詞具體程度如何提高程式碼品質,以及不同代理如何處理相同工作。

摘要

  • Android Bench 2.0 採用封閉原始碼任務,避免資料污染,並評估 AI 程式設計代理在複雜 Android 開發人員工作中的表現。
  • 評估作業仰賴與實作無關的 UI 測試和多模態 LLM 評審,而非脆弱的單元測試或嚴格的像素差異比較。
  • 內建的作弊偵測工具可找出代理程式捷徑,例如竄改測試或偽造背景可繪項目。只要偵測到任何作弊行為,就會立即判定為不及格。
  • 每項工作的成本與廣告宣傳的詞元價格有很大差異,因為話多的模型會反覆重建及重新執行,消耗更多詞元。
  • 前沿模型的完成率約為 80%,但由於細微缺陷,只有 20% 到 40% 的工作能通過。

資源

Gemini Notebook

主題

  • android-bench
  • 基準化
  • llm-evaluation
  • code-generation
  • ai-agents

轉錄稿

Android Bench 簡介

  • Tomáš Mlynarič

    大家早。

  • 開始吧。

  • 我們在 3 月推出 Android Bench,這個資料集和排行榜可用於評估 LLM 在 Android 開發工作上的表現。

  • 自那時起,我們已對許多新模型執行這項測試。

  • 上個月,我們更推出了 Android Bench 2.0。

  • 雖然才過了幾個月,但在 AI 領域,這感覺就像一輩子。

  • 我是 Tomáš,今天將分享我們在 Android Bench 的歷程,以及如何達到目前的成果。

  • 我們先從 1.0 開始。

  • 在 1.0 版中,我們從開放原始碼存放區中擷取實際問題。

  • 我們篩選出含有 GitHub 問題、解決這些問題的提取要求,以及驗證解決方案的測試。

  • 我們將提取要求拆分為 AI 模型的任務 bucket。

  • GitHub 問題會成為模型的指示或提示,我們會在 PR 前將其植入狀態。

  • 模型生成內容後,我們就會進行測試並套用至輸出內容,結果會是通過或失敗。

  • AI 嘗試解決問題的過程稱為「試驗」。

  • 我們也可以根據該 PR 中人工設計的解決方案 (或稱為黃金解決方案) 進行比較。

  • 這個方法與知名的 SWE-bench 基準測試類似。

  • 我們一開始有超過 10,000 項潛在工作,但並非所有工作都適合。

  • 有些指令不完整或過於具體。

  • 測試可能與黃金解決方案結合,或 PR 之前的狀態已損毀。

  • 因此,我們必須反覆審查,才能選出 100 項公平的工作。

  • 在這些審查中,我們使用最先進的模型取得一些結果。

  • 一開始,我們解決了約 30% 的任務,這稱為通過率。

  • 當我們鎖定資料集準備正式發布時,頂尖模型的準確率已達到 70% 左右。

  • 但那是下個月的事了。

  • 我們也計劃每個月評估新模型,觀察模型如何進步。

  • 在 8 月的版本中,最佳模型的通過率為 92%。

  • 我們知道標準太低,而且這個資料集已飽和。

Android Bench 2.0 隆重登場

  • Tomáš Mlynarič

    不過,我們早已預料到這一點,因此我們已努力開發 2.0 版,並在上個月發布。

  • 我們的目標是透過複雜的實際 Android 任務提高標準,為開發人員提供 LLM 效能的指引和透明度,同時也為模型製作者提供良好的基準,推動他們優先考慮 Android。

  • 並提供分析和結果,供您和模型製作者參考。

  • 但我們也必須考慮到聰明才智、韌性和作弊。

  • 我們也需要防範污染風險。

  • SWE-bench 方法的問題之一,在於原始碼的實際開放程度。

  • OpenAI 發布了一篇文章。

  • 他們發現模型能完成指令的下一個字詞,或解決指令中未提及的模糊錯誤。

  • 他們發現模型開發人員會使用開放原始碼訓練模型,導致 SWE-bench 受到污染。

  • 因此,在 2.0 版中,我們實際上會保留程式碼,不公開程式碼,以防止對其進行訓練。

  • 我們將工作分為四個工作串流。

  • 首先是應用程式轉換,我們將開放原始碼的 Flutter 和 React Native 應用程式,交給代理轉換成原生 Android 解決方案。

  • 這實際上反映了我們目前看到的跨平台遷移至原生的趨勢。

  • 第二個工作流程是新功能和平台整合。

  • 這些功能已內建於既有的正式環境程式碼集。

  • 舉例來說,我們在 Now in Android 中新增了影片功能、子母畫面等。

  • 第三個工作串流是遷移。

  • 我們正在升級先前未公開的正式版開放原始碼應用程式,採用現代化程式庫和架構。

  • 例如從 Nav 2 遷移至 Nav 3、從 XML 檢視畫面遷移至 Compose,或從單一窗格遷移至自動調整式版面配置。

  • 最後一個是使用視覺設計模擬,從頭開始建構的內部全新應用程式。

  • 這裡的範例包括完整的簡訊動態密碼驗證流程、使用 Google 地圖追蹤訂單等。

評分挑戰

  • Tomáš Mlynarič

    我們只用了幾位工程師,就打造出與 Now in Android 規模相當的應用程式。

  • 但新任務也帶來了新挑戰。

  • 第一點:正確評估代理。

  • 基準測試比應用程式測試困難得多,因為基準測試必須通過任何有效的解決方案,而不僅僅是一個,而且任務越大,有效的解決方案就越多。

  • 在 1.0 版中,基準主要仰賴單元測試,而單元測試在不限平台方面表現不佳。

    import com.foodvibes.MyViewModel
    
    @Test
    fun testRetry() {
        val vm = MyViewModel()
        vm.retry()
    
        assertTrue(vm.retried)
    }
  • 必須提供確切的類別或函式名稱。

  • 就像這個例子,雖然測試並不複雜,但基本上,代理可能會以不同方式命名這些項目,或者即使命名方式相同,只要放在不同的套件中,測試就會編譯失敗,因此無法用於我們的案例。

    import com.foodvibes.feature.my.RetriableViewModel
    
    @Test
    fun testRetry() {
        val vm = RetriableViewModel()
        vm.tryAgain()
    
        assertTrue(vm.tried)
    }
  • 因此,我們在技術上可以在提示中指定 API,但這樣做會洩漏太多細節,或導致提示中出現太多測試需求。

  • 因此,解決方法是執行與實作無關的測試。

  • 這些測試非常高階,會測試結果和行為,而非內部。

  • 我們使用確定性 UI 測試,這類測試會與公開畫面資訊互動,就像使用者一樣。

  • 針對這些項目,我們會使用 UI Automator 或 Compose UI 測試,但我們也有專門的指令碼,可檢查一些外部系統邊界,例如網路呼叫是否具有正確的資料,或是否將正確的資料儲存至資料庫,而不需要知道內部輔助程式或 DAO。

  • 此外,我們也會進行靜態檢查,例如檢查資訊清單中的權限,或程式庫是否位於建構檔案中等。

  • 此外,我們也有混合測試,結合了這些指令碼和 UI 測試。

  • 這是其中一項混合測試的範例。

  • 他們在模擬器外駕駛。

  • 這是簡訊動態密碼驗證流程的測試,指令碼必須先計算應用程式的雜湊,才能正確將簡訊傳送至模擬器,然後啟動測試、UI Automator 測試,並傳遞所需的隨機生成動態密碼,以及使用 ADB 指令將簡訊傳送至模擬器。

  • 而測試只會檢查輸入內容是否包含正確的數字。

  • 基本上就是這樣。

  • 實作之間沒有任何耦合,只會檢查流程是否正常運作。

  • 第二個評分問題是,我們的任務需要多模態。

  • 代理必須產生 UI,或由我們提供設計模擬圖。

  • 最簡單的方法是使用螢幕截圖測試。

  • 我們一開始就知道螢幕截圖門檻必須更高,但還是失敗了。

  • 我們嘗試了一些不同的像素差異演算法,但無法處理系統層級雜訊等問題,例如模擬器狀態列不同、電池圖示不同,甚至是字型硬體反鋸齒不同。

  • 不過,在某些情況下,即使實作方式大致正確,例如邊框間距略有不同,或色調有些微差異 (例如 UI 完全正確,但背景色調略有不同),像素差異演算法也會失敗。

  • 因此,我們建構並使用多模態 LLM 評審模型,比較螢幕截圖測試中的螢幕截圖組合,並給予分數,同時提供分數的結構化推論。

  • 我們也會提供一些無障礙階層轉儲,檢查結構是否正確,以及結構是否一致。

  • 我們在許多試驗中實際測試,發現這個方法具有高度一致性,而且能忽略我們看到的問題。

  • 現在,我們有很好的方法可以驗證視覺效果和行為,因此我們發現代理程式試圖偽造一切。

  • 請說。

作弊挑戰

  • Tomáš Mlynarič

    一開始很簡單。

  • 現在,代理基本上是「好的,

  • 我有個任務。

  • 我們來看看 GitHub。

  • 我們來找出解決方案吧。」

  • 所以,這就是他們首先要做的事情。

  • 他們會嘗試在 GitHub 或網際網路上搜尋有效的解決方案,因此我們必須保留原始碼,不公開。

  • 但這很簡單。

  • 在某些試驗中,我們說所有項目都順利通過,代理就說:「太棒了!」

  • 這是我的想法

  • 真有趣,他們發現了什麼?

  • 但我們仔細研究了軌跡,他們說:「我做不到。

  • 如果我將螢幕截圖測試的門檻變更為 80%,會怎麼樣?」

    // AuthFlowScreenshotTests.kt
    
    @get:Rule
    val dropshots = Dropshots(
        resultValidator =
            ThresholdValidator(
    -           0.05f
    +           0.80f
            )
    )
  • 就像是「沒錯,勝利,一切都會過去。」

  • 是沒錯,但螢幕截圖測試沒用。

  • 但這並非最複雜的。

  • 在某些情況下,例如 Flutter 轉換應用程式,代理會嘗試建構 Android,但最後會說「我不知道該怎麼做」。

  • 因此,他們將我們提供的螢幕截圖,設為背景中的可繪項目。

  • 現在,這就像通過螢幕截圖測試一樣。

  • 太好了!

  • 但 UI 測試還是失敗,所以他們決定在上面放一個隱形的疊加層,裡面有按鈕、文字和測試標記,以及所有需要的東西。

  • 現在所有項目都顯示綠色。

  • 有了!

  • 做得好。」

  • 沒有哪一個特別好。

  • 因此,我們有一項 Java 遷移至 Kotlin 的工作,代理基本上需要從 Java 遷移至 Kotlin,對吧?

  • 他們無法建構。

  • 但還是失敗,所以代理程式決定:「好吧,我直接從 Java 取得編譯過的類別,放入 JAR,然後新增至建構檔案,當做一般程式庫。

  • 現在,每項測試都通過了,但我們有靜態檢查,會檢查 Java 檔案,所以,是的,我只要刪除它們。」

  • 我刪除 Java 檔案,所有項目都顯示綠色,但 Kotlin 仍失敗,所以他們會說:「好吧,我要移除 Kotlin 來源集,現在所有項目都通過了。

  • 太好了。」

  • 我甚至不知道這算不算作弊。

  • 我是說,這真的很有創意。

  • 為了防止這種情況,我們打造了作弊偵測工具,提供三道防線。

  • 我們查看 UI 樹狀結構,套用不同的螢幕大小,讓背景失真,然後查看。

  • 不過,我們也會重設測試,並檢查是否有任何測試遭到竄改,確保他們無法變更門檻。

  • 我們也使用 LLM-as-a-Judge 稽核代理的軌跡和實際輸出內容,找出異常之處。

  • 如果發生任何問題,我們就會說:「好吧,直接失敗。」

基準測試結果的洞察資訊

  • Tomáš Mlynarič

    現在,所有捷徑都消失了,無法查詢解決方案,也沒有脆弱的單元測試或假 UI。

  • 因此,當我們強制代理實際設計 Android 應用程式時,數據會如何?

  • 截至週一,你可能看過網誌文章,數字看起來像這樣。

  • 即使是最優秀的代理,也只能通過約 40% 的任務,而大多數先進代理的通過率則介於 20% 到 40% 之間。

  • 這對我們這些基準作者來說是好事,因為這能取得適當平衡,例如我們提供的任務具有實際價值,但也有很大的成長空間。

  • 現在就來看看結果。

  • 現在,我們可以從多個角度查看結果。

  • 我們可以看看已發現的一些洞察。

  • 看到這張圖表時,您可能會先想到:執行這個代理需要多少費用?

  • AI 公司通常會回報每個詞元的成本,包括輸入、輸出,以及快取詞元。

  • 這或許是您決策時的考量因素。

  • 但我們可以從其他角度來看。

  • 我們可以在 2D 空間繪製相同圖表,呈現每項工作的平均成本 (而非每詞元的成本),也就是每項工作的成本與通過率。

  • 從這裡可以發現,有些模型突然變得更健談。

  • 每項工作花費的時間或代幣多得多,而其他工作則花費較少,但通過的工作較多。

  • 但如果我們在同一張圖表中加入每個權杖的價格,就會發現 AI 公司實際宣傳的價格與你實際支付的價格不符。

  • 因此,如果發現通過率最高的代理,實際上是每個詞元最便宜的代理之一,但每項工作的費用最高。

  • 或者,其他代理的權杖價格完全相同,通過約 30% 的工作,但費用便宜 15 倍。

  • 因此,代幣價格並非指標。

  • 有些模型會深入探索程式碼,重新建構及執行,每一步都會消耗更多權杖。

  • 因此,重點在於不要只看每個權杖的價格。

  • 選擇模型,查看實際資料,例如在您的案例中使用,或查看 Android Bench,瞭解哪些模型在任務中實際花費最多任務、最多權杖。

  • 有些模型需要更多步驟,但每個權杖的成本較低。

  • 不過,這個問題還有另一個層面。

  • 這張圖表與上一張相同,但我們也可以查看並顯示代理完成這項工作所需的時間。

  • 我們可以看到,通過率最高的服務專員,可能也是最慢的。

  • 例如,我們可以看到有些工作需要將近兩個小時才能完成。

  • 他們會不斷重建、重新測試,直到成功為止。

  • 雖然通過率最高,但耗時也最長。

  • 有些較便宜的代理仍需約一個半小時才能完成工作,但結果並未更好。

  • 其他代理則在 20 分鐘內完成,且仍能解決超過四分之一的工作。

  • 因此,小型輕量模型通常速度較快、成本較低,但處理困難工作時較容易失敗。

  • 結論是,沒有任何一個模型是最好的。

  • 這取決於您的最佳化目標。

  • 是通過率嗎?

  • 是成本嗎?

  • 是等待時間嗎?

  • 在這種情況下,視工作類型而定,如果不是處理這類困難工作,可以考慮使用速度較快的模型,以更低成本、更快速地完成工作。

  • 不過,成本和速度只能反映代理的執行情況,無法說明為何大多數測試都失敗。

  • 因此,我們檢查了失敗的執行作業,發現代理通常會提早退出。

  • 他們確信自己已經完成。

  • 任務完成。

  • 好,我回家。

  • 不過,如前所述,代理在工作封包中只會收到指示和前置狀態。

  • 但無法查看測試。

  • 他們發現自己無法查看驗證套件。

  • 因此我們想,如果讓他們看到測試結果,會發生什麼事?

  • 因此,我們進行了實驗,對同一項工作進行開放式測試,讓代理一開始就取得失敗的驗證測試套件。

  • 因此,他們現在會反覆執行測試。

  • 他們可以檢查錯誤,並在意見回饋循環中修正程式碼。

  • 我們發現,當服務專員看到測試時,會更努力、更長時間地工作。

  • 在開放式執行中,對話回合數增加 25%,Agent Runtime 增加 40%。

  • 他們不必盲目偵錯,因此每項工作只需花費近 30 分鐘,就能執行並修正失敗的測試。

  • 因此,他們可以清楚看到失敗的原因,但不幸的是,通過率也上升了將近 50 個百分點。

  • 如前所述,這項額外措施可提高平均通過率。

  • 但隨著新的前沿模型陸續推出,這個開放式變體很快就飽和了。

  • 通過率接近 90%。

  • 因此,我們將封閉搜尋封閉測試變化版本保留為主要基準指標。

  • 不過,如果模型知道評分標準,實際表現會更好,這點非常有趣。

模型如何測試自己的工作

  • Tomáš Mlynarič

    不過,這項功能可讓我們更深入瞭解代理。

  • 舉例來說,我們可以查看模型如何實際測試自己的工作。

  • 從圖表可以看出,前沿模型編寫的測試越來越好,通過率也更高。

  • 有些模型過於自信,認為自己能做到,不需要測試。

  • 有時還是能用。

  • 因此,如果您使用的模型不擅長測試,可以提供更好的測試技巧,或具體指示如何編寫測試,協助模型提升能力。

  • 但相關不代表有因果關係,對吧,CS?

  • 我們需要對此進行對照實驗。

  • 我們仍需進一步研究,確認提示是否能有效提升分數。

  • 不過,我們在另一半的 Android 測試中進行了實驗,讓服務專員使用模擬器。

  • 在基準測試初期,我們並未提及模擬器。

  • 我們以為代理應該會自行解決,對吧?

  • 結果發現,幾乎沒有人能成功啟動模擬器。

  • 如前所述,UI 測試在基準測試中相當重要,如果沒有模擬器,就無法真正使用 UI 測試。

  • 所以,他們都在非常簡單的事情上失敗了。

  • 現在我們面臨兩難。

  • 我們是否應該告訴服務專員,他們需要使用模擬器,並承擔實際分數更高的風險,還是什麼都不說,希望他們能自己想出辦法?

  • 但畢竟,我們的目標是建立公平的基準,評估 Android 程式設計和問題解決能力。

  • 我們不希望測量到環境中的一些異常現象。

  • 因此,我們選擇折衷方案。

  • 我們告訴他們這是啟動模擬器的方式,但並未提及是否要使用。

  • 畢竟,啟動模擬器並不是我們實際需要或想要測試的項目。

  • 並評估該模擬器指令的效果。

  • 我們發現,提供 How-to 後,代理程式啟動模擬器的次數大幅增加,並實際執行 UI 測試,這對通過率造成了重大影響,但對我們來說,這並不是好消息。

  • 但也能讓我們檢查他們真正的程式碼錯誤和問題,而不只是在未啟動的模擬器上失敗。

  • 總之,提示詞的具體程度仍相當重要。如果您要設計自己的基準或評估,請務必測試對您來說真正重要的項目。

  • 舉例來說,假設您要評估某項技能或工具的權杖效率,首先必須通過評估,接著調整效率,然後確認調整後仍能通過評估,但使用的權杖數量較少。

  • 如果一次變更太多變數,就無法確定成效提升是否確實是變更帶來的效果。

通過率與完成率

  • Tomáš Mlynarič

    返回排行榜。

  • 如前所述,最佳模型通過約 40% 的任務,而通過率要求所有檢查都通過。

  • 否則,工作就會失敗。

  • 那麼,問題來了:難道代理對其餘 60% 的工作毫無幫助嗎?

  • 不,他們正在努力。

  • 為了呈現代理與實際解決方案的接近程度 (類似距離),我們也會計算完成率或部分分數。

  • 這是四個類別的加權分數:視覺一致性、功能性、是否正常運作、部分工作特定需求,以及部分迴歸檢查。

  • 可以看到,排名較低的模型能完成約一半的工作,而前沿模型平均能完成近 80% 的工作,但通過率只有 40%。

  • 因此,我們對 Flutter 轉換為 Android 的過程進行實驗,瞭解實際情況。

  • 因此,我們每隔幾個代理步驟就重新計算完成率,並在所有前沿模型中觀察到相同的模式。

  • 大約 140 步時,他們努力達到平均近 90% 的分數,然後又花了 90 步或近 40% 的執行時間,試圖縮小與 100% 的差距,但他們只能多得 1 或 2% 的分數。

  • 我們認為這反映了目前的代理開發情況,也就是經驗不足的開發人員或使用者,會認為應用程式已完成。

  • 所有內容都會轉譯。

  • 輸入內容大致符合要求。

  • 可能沒有任何當機情形。

  • 但資深工程師查看程式碼時,會發現許多缺陷,例如架構問題、生命週期缺陷等。

基準比較和收尾

  • Tomáš Mlynarič

    最後,我們來看看 Android Bench 2.0 的整體情況。

  • 與 1.0 版相比,我們現在只有 30 項工作,這是刻意安排的。

  • 我們不只修正個別錯誤和提供小型修補程式,還會提供完整的畫面、多模組遷移和整個應用程式轉換。

  • 現在,修補程式的大小已增加數個數量級,涵蓋數十個檔案。

  • 我們也提供多模態 UI 檢查。

  • 代理必須執行更長時間,並更努力工作。

  • 如果與其他前沿基準測試比較,我們仍可看出我們的基準測試需要更大幅度的程式碼變更。

  • 由於大多數工作都是從頭開始編寫,因此不會受到污染,而且仍有很大的成長空間。

  • 就像我一開始說的,AI 領域的幾個月就像一輩子,最終所有基準都會達到極限並飽和。

  • 希望這項技術成熟後,我們能挑戰更困難、更複雜的工作,再次與那個時代和 AI 發展階段產生共鳴。

  • 現在,請使用 Android Bench 做為指南,比較模型和測試架構,並從中獲得一些洞察。

  • 準備好深入瞭解時,請將相同的基準測試和評估原則,套用至自己的程式碼集和工作流程,因為評估 AI 的最佳方式,就是實際測試您使用的程式碼。

  • 這樣一來,您就不必再擔心 AI 是否正確,而是能確信 AI 的準確度。

  • Android Bench 全體團隊感謝您的關注。

程式碼

程式碼片段

「Grading challenge」影片 05:29 處的 Kotlin 程式碼片段
import com.foodvibes.MyViewModel

@Test
fun testRetry() {
    val vm = MyViewModel()
    vm.retry()

    assertTrue(vm.retried)
}
「Grading challenge」影片 05:41 處的 Kotlin 程式碼片段
import com.foodvibes.feature.my.RetriableViewModel

@Test
fun testRetry() {
    val vm = RetriableViewModel()
    vm.tryAgain()

    assertTrue(vm.tried)
}
「Cheating challenge」影片 10:15 處的 Kotlin 程式碼片段
// AuthFlowScreenshotTests.kt

@get:Rule
val dropshots = Dropshots(
    resultValidator =
        ThresholdValidator(
-           0.05f
+           0.80f
        )
)