AI 模型可在幾分鐘內生成完整功能,但程式碼是否正確?我們分析了數千項生成的程式碼變更,並建立 AI 評估基準 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%。
我們知道標準太低,而且這個資料集已飽和。
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 全體團隊感謝您的關注。
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)
}// AuthFlowScreenshotTests.kt
@get:Rule
val dropshots = Dropshots(
resultValidator =
ThresholdValidator(
- 0.05f
+ 0.80f
)
)