RainVisitor Blog

RainVisitor

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://flutter.dev/blog/building-multi-agent-dev-teams

架構師、測試人員和程式設計師:建立多代理開發團隊

如何讓在 Antigravity 中運行的多代理開發團隊使用測試驅動開發將 Python 函式庫移植到慣用的 Dart 套件。

當我第一次開始嘗試 AI 編碼助手時,我只使用一個代理來處理所有事情:架構程式碼、編寫單元測試和偵錯堆疊追蹤。雖然這種方法對於小任務運作良好,但對於複雜的軟體工程問題,它很快就會退化。隨著我的對話歷史記錄增長,代理的上下文視窗會被日誌和搜尋結果飽和,導致幻覺、遺漏連結、馬虎的程式碼和錯誤。

幾週前,我偶然發現了一篇關於多代理、TDD 編碼工作流程的部落格文章。我以前聽說過這種方法,但從未嘗試過。由於 Antigravity 的 Agent Hub 包含了多代理設定的工具,我決定對這種技術進行測試。

為了在真正的挑戰中測試它,我著手將流行的 python-statemachine 函式庫移植到靜態類型、無反射的 Dart 套件。儘管 pub.dev 已經有穩定的狀態機選項,但 python-statemachine 是一個絕佳的基準。它嚴重依賴於動態 Python 功能(例如元類和執行時回呼),這些功能必須針對 Dart 強大的靜態類型和 Flutter 對不使用 dart:mirrors 的要求進行徹底重新架構。

這比看起來要複雜,我發誓!

我學到的第一件事是,有效代理團隊的基礎是一組明確定義的角色,這些角色對代理可以做的事情有特定的限制。為了實施這些,我建立了一個總體工作流程技能(tdd-dart-workflow)來建立共用的 TDD 規則和權限,以及四個定義每個代理的角色特定技能:

  • **架構師 (tdd-dart-architect)**:分析原始碼,生成前端的 architecture_blueprint.md (階段 0),並在 specs/ 下編寫有針對性的模組規範 (階段 1)。架構師不能寫入 lib/、test/ 或 example/。
  • **測試人員 (tdd-dart-tester)**:根據架構師的規範 (階段 2a) 在 test/ 下編寫全面的失敗單元測試。測試人員不能查看或寫入 lib/ 或 specs/。
  • **程式設計師 (tdd-dart-coder)**:在 lib/src/ 下建立編譯骨架並實作函式庫程式碼以使失敗的測試通過 (階段 2b 和 3)。程式設計師不能編輯 test/ 或 specs/。
  • **協調者 (tdd-dart-coordinator)**:協調者父代理。協調者管理 Git 分支提交,執行 dart analyze && dart test 驗證,並將任務委派給子代理。

透過強制執行嚴格的角色分離,沒有任何單一代理可以同時修改測試斷言和底層程式碼。協調者充當守門員,僅在子代理完成任務時收到簡短的完成報告。這建立了一個「認知防火牆」。除了在工作面試中是一個有趣的詞語之外,這意味著每個子代理的試錯標記歷史記錄會在任務完成後被丟棄。因此,父協調者的上下文視窗保持乾淨、專注,並且沒有標記飽和。

需要澄清的是,我在此處省略了一些潛在問題,例如「如果子代理陷入循環會發生什麼?」和「是否應該設定一個時間截止,在此之後子代理會被終止並重新創建或通知人類?」不過,這是我首次涉足多代理團隊,這些問題我將留到後續的部落格文章中再討論!

為了防止競爭條件和未經證實的程式碼提交,檔案系統寫入權限在工具層級的 tdd-dart-workflow 中受到限制,具體取決於代理的指定角色:

代理角色 允許讀取路徑 允許寫入路徑 禁止操作
協調者 任何地方 任何地方 直接程式碼修改 (委派給程式設計師/測試人員)
架構師 任何地方 specs/, skills/ 不能寫入 lib/、test/ 或 example/
測試人員 任何地方 test/, example/ 不能寫入 lib/ 或 specs/
程式設計師 任何地方 lib/, example/ 不能寫入 test/ 或 specs/

在我的版本中,這些限制包含在代理的說明中,因此它們技術上可能會被違反。然而,隨著多代理系統的演進,我預計我們將開始看到以更安全的方式實施限制的方法。

一個挑戰範例

在像 Dart 這樣的編譯語言中操作多代理團隊帶來了一個有趣的挑戰:靜態編譯和為尚不存在的程式碼編寫測試之間的緊張關係。在像 Python 這樣的動態語言中,TDD 以一個拋出運行時錯誤的失敗測試(紅)開始。然而,在靜態語言中,為尚不存在的方法編寫單元測試會導致編譯時失敗。編譯器會中止,阻止測試執行器執行並證明測試本身是有效的。

我的代理團隊在 紅色階段 循環中解決了這個問題。首先,測試人員根據架構師的規範編寫測試。接下來,程式設計師在 lib/src/ 下建立一個編譯骨架,其中包含返回虛擬值或拋出 UnimplementedError 的類和方法存根。一旦骨架滿足編譯器,協調者會運行 dart analyze && dart test 以驗證測試因正確的原因(未實現的邏輯而不是語法或匯入錯誤)而失敗。註釋邊界確保臨時骨架存根與永久測試工具隔離,允許協調者在程式設計師完成綠色階段後自動清除它們:

1
2
3
4
5
6
7
8
9
// TEST UTILITIES - KEEP PERMANENTLY
class MockListener extends Mock implements StateMachineListener {}

// SKELETON STUBS FOR COMPILATION - DELETE ONCE SKELETON IS IMPLEMENTED
class StateMachine<T extends StateModel> {
dynamic get currentState => throw UnimplementedError();
dynamic get currentStateValue => throw UnimplementedError();
void send(String eventId) => throw UnimplementedError();
}

出了什麼問題:摩擦點和飛行中的技能更新

使用代理確實加快了移植函式庫的過程,但它也帶來了自己的挑戰。然而,工作流程中斷的時刻提供了最有價值的工程教訓。在移植過程中,我不得不暫停並更新我的技能檔案(SKILL.md)以解決三個問題:

  • 意外刪除測試工具:一旦程式設計師在 lib/src/ 中實作了真實的函式庫類別,協調器就必須清理測試檔案中留下的臨時編譯存根。它總是將存根與真實的東西混淆,因此需要清晰的註釋標頭才能使真實的實作清晰。
  • 展開運算子 (...) 類型不匹配:Python 和 Dart 處理類型的方式不同,Python 中預設可迭代的東西在 Dart 中不一定如此。我不得不針對需要實作 Iterable 的東西新增一些明確的說明。
  • 「帶有外來口音」的動態類型:再次地,不同的類型系統造成了問題。原始的 Python 函式庫在幾個地方使用了 hasattr 檢查。我不得不改進 tdd-dart-architect/SKILL.md 以強制架構師使用慣用的 Dart 和明確的回呼委派,而不是動態屬性查找。

這些技能改進直接塑造了最終的設計。產生的 Dart 套件 (state_machine) 保留了 python-statemachine 的完整功能集,程式設計師代理實作了流暢的建構器以支援 Dart 的原生展開運算子 (...)。以下是原始 Python 類別定義與代理團隊產生的最終 Dart 狀態機之間的比較:

舊方式:

1
2
3
4
5
6
7
8
9
10
11
12
# python-statemachine syntax (declarative via metaclasses)
from statemachine import StateMachine, State

class TrafficLightMachine(StateMachine):
green = State("Green", initial=True)
yellow = State("Yellow")
red = State("Red")

cycle = green.to(yellow) | yellow.to(red) | red.to(green)

def on_enter_green(self):
print("Entered Green state")

新方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// state_machine Dart syntax (declarative constructor DSL)
import 'package:state_machine/state_machine.dart';

class TrafficLightMachine extends StateMachine<TrafficLightModel> {
final green = State('green', initial: true);
final yellow = State('yellow');
final red = State('red');

late final Event cycle;

TrafficLightMachine(TrafficLightModel model) : super(model: model) {
cycle = event('cycle');

initialize(
states: [green, yellow, red],
transitions: [
...green.to(yellow).on(cycle).transitions,
...yellow.to(red).on(cycle).transitions,
...red.to(green).on(cycle).transitions,
],
);

green.onEnter((EventData data) {
print('Entered Green state');
});
}
}

所有這些的重點更多是為我自己產生一套可重複使用的技能,而不是建立和發布一個新的 Dart 套件。我目前無法監測報告的問題並長期維護這個套件。我無法保證所有建立的程式碼都沒問題(儘管在我測試時它確實有效),而且生態系統中已經有幾個維護良好的套件。然而,希望這能讓您了解在 Antigravity 中運行的多代理系統能為 Dart 和 Flutter 開發人員做些什麼。

如果您想在自己的專案中探索多代理工作流程,請嘗試一下!

  • 下載 Antigravity。
  • 造訪 AGY 入門文件,了解如何定義自己的代理和自訂開發人員技能。
  • 查看我在這個專案中建立的實際技能檔案,根據您的需求修改它們,並讓我們知道進展如何!

Flutter 的更多內容

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://flutter.dev/blog/speeding-up-generative-ui-with-async-a2ui

使用非同步 A2UI 加速生成式 UI

生成式 UI (GenUI) 正在改變我建構使用者介面的方式。GenUI 允許 AI 代理程式在執行時使用基於 JSON 的訊息動態組合和更新使用者介面,而不是向每個使用者呈現一組靜態畫面。在 Flutter 中,genui 套件讓我可以即時渲染這些 AI 驅動的組件。

閱讀全文 »

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://flutter.dev/blog/whats-new-in-flutter-3-47

Flutter 3.47 的新功能

依模組設計:獨立 UI 套件和桌上型 Impeller

What's new in Flutter 3.47
Flutter 3.47 的新功能

Flutter 3.47 登場,帶來了一些令人興奮的更新。

今天,我們很高興地發佈獨立 material_ui 和 cupertino_ui 套件的 1.0 版本。這是一個重要的里程碑,它將設計系統從核心 SDK 中分離出來。

閱讀全文 »

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://flutter.dev/blog/flutter-q2-2026-survey

Flutter 2026 年第二季度使用者調查 — 信任、透明度和不斷發展的社群

Flutter 2026 年第二季度使用者調查結果出爐!探索開發者滿意度、信任度、AI 編碼工具採用情況以及 Cupertino widgets 的更新。

閱讀全文 »

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://medium.com/dartlang/weve-moved-the-official-dart-blog-has-a-new-home-2b9ea40d2a72

官方 Dart 部落格已遷移至 dart.dev/blog!

歡迎來到我們的新家!官方 Dart 部落格已從 Medium 遷移到 dart.dev/blog。未來,您將在此處找到所有語言發佈公告、深入技術文章以及來自 Dart 團隊的工程更新。

閱讀全文 »

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

與 Rody Davis 共同撰寫

程式碼代理程式及其使用方式在短短幾個月內就已經發生了巨大的演變。最初,重點主要放在觀察上——逐行審查每個輸出。但隨著模型能力迅速提升,業界轉向了真正的代理工程。如今,開發人員、PM 和設計師身兼多職,專注於崇高的概念目標,讓代理程式處理各個組件。

為了探索這個新領域,我們的團隊希望建立一個能展示這種確切工作流程的體驗。我們想建立一個遊戲,產生其資產,編寫行銷頁面,並部署整個項目,所有這些都使用 Google 首屈一指的 AI 原生平台:Antigravity。

Antigravity 將 Google 的最佳功能匯集於一處,採用規劃、執行和驗證的緊密回饋迴圈。它會建立 Artifact、撰寫程式碼、執行測試,甚至點擊 UI 中的按鈕,以確保它實際正確完成了任務。

我們代理程式冒險的成果是 DashLander——一款設定在程序生成的小行星上的月球著陸器風格遊戲。以下是我們如何建立它的故事。

在 AI 時代為何選擇 Flutter?

很多人可能會想:如果代理程式可以撰寫原生程式碼,為什麼不讓它們完全分開撰寫 Android、iOS 和 Web 應用程式呢?

這是一個合理的問題;這也是最初激發 Flutter 作為「Vibe once, run anywhere」UI 工具包概念的原因。

擁有單一真實來源對 AI 和人類都同樣重要。透過讓代理程式編寫單一的跨平台應用程式,團隊可以消除不同語言或平台特定範式之間不可避免地出現的細微錯誤。此外,Dart 強型別為 LLM 提供了出色的回饋。具有較鬆散型別系統的語言要求 LLM 進行更多的分析,才能知道給定程式碼在所有情況下是否正確。另一方面,Flutter 和 Dart 使用其分析伺服器向您的代理程式發送有關函數簽名或類別形狀不匹配的強烈訊號。搭配有狀態熱重載,Flutter 對代理程式的加速作用與對人類開發人員的加速作用一樣。

而且,儘管代理程式開發大幅降低了生產新軟體的成本,但它仍遠非免費。對於代理程式開發來說,時間就是金錢,這既是比喻意義上的,也是字面意義上的,因為查詢時間越長,意味著更多的令牌;這意味著更多的 AI 開支。有了 Flutter,成功完成編碼任務的代理程式無需重新開始支援另一個平台,從而為您節省了資金。

快速失敗,才能建造更好

我們知道我們想建造一個遊戲,您在其中駕駛月球著陸器,配備動態元素、自訂著色器和粒子效果。(我們不忍心將 Dash 本身置於冰冷的真空空間,所以,您將駕駛一艘船。) 當然,使用 AI 大大加速了這些概念的整合,但這並不妨礙稍後放慢速度並認真編寫高品質程式碼。如果有的話,透過 AI 進行的快速探索階段有助於您更快地達到該階段,因為您的許多失敗實驗可以在幾個提示的生命週期內來去,而不是花費數小時或數天的編程時間,只是為了了解某個特定的想法是否真的很有趣。如果這輩子有一件事無關緊要,那就是從未見天日的實驗性功能的程式碼品質。

最重要的是,我們也知道我們沒有在建造什麼:一款高性能、即時的多人遊戲。即時多人遊戲功能會引入延遲限制並呈指數級擴大基礎設施複雜性。相反,我們意識到可以透過在挑戰模式中重播過去高分的回影來提供 90% 的引人入勝的競爭體驗。這使我們可以依賴靜態儲存和簡單的後端,並巧妙地免費重複使用完美編寫的「AI 對手邏輯」,只需重播最佳使用者已經做過的事情即可。這就是所有程式碼中最好的:您既不編寫也不維護的程式碼。

生成一個資產的宇宙

為了讓小行星著陸器遊戲變得有趣,我們需要出色的資產,我們在很大程度上依賴代理生態系統來獲取它們:

  • **音訊:** 我們使用 Google 的 Lyria 生成背景音樂。(不過,Rody 戴上他老音訊工程師的帽子,確實用麥克風、水管和噴霧罐在他家後院錄製了自訂的推進器噪音。多麼有趣的下午!)
  • **視覺效果:** 我們在 Stitch 和 Google Canvas 中生成了 UI 設計。Gemini 直接在程式碼中編寫了我們的粒子效果,我們使用 Nano Banana 生成了應用程式圖示。
  • **物理:** 因為這個遊戲發生在真空環境中,我們想要精確的反應控制系統 (RCS)。我們釋放了 Gemini Deep Research 來尋找我們需要的精確零大氣物理公式,它將這些公式編譯成 Google Doc。然後我們將該研究納入我們的語境中,Gemini 將方程式直接翻譯成 Dart。這些真的是正確的物理方程式嗎?可笑的是,我們不知道,因為我們不是物理學家!但是當您玩遊戲時,控制感肯定感覺正確。

**疊代原型**

我們從 AI Studio 的沙盒開始建立,以在提交架構之前生成快速、一次性的程式碼。這些豐富的原型可以作為模型以後的上下文壓縮,及早鎖定微觀決策。我們的一些原型具有更有趣的遊戲玩法,有些看起來更具吸引力,還有一些最清晰地捕捉了我們的零重力物理。對於我們的最終遊戲,我們將每個原型中最好的想法融合在一起。

一旦我們將專案載入 Antigravity,我們就為我們的代理程式配備了 Firebase、Flutter、Flame 遊戲引擎和 Gemini API 的 MCP 伺服器。我們啟動了一個新的提示,Antigravity 將任務分配給後端、UI 和遊戲邏輯的專門子代理程式。在五分鐘內,我們就有了一個用 Flutter 和 Flame 編寫的遊戲,這個遊戲通常需要幾天才能編寫。但實際上,它遠未完成。只有三個硬編碼的關卡,攝影機沒有跟隨著陸器,而且數學意味著我們的飛船大約有帝國大廈那麼大。

大約又用了 100 個提示才讓遊戲達到最終狀態。其中很大一部分提示只是關於程式碼組織和增加測試。每個人對閱讀 AI 生成的程式碼都有自己的看法,但 Rody 和我非常重視我所稱的「認知所有權」,並且仍然想了解我們應用程式的深層內部運作方式。我們閱讀了 Gemini 編寫的許多內容,並推動 LLM 進行重構以提高清晰度和可重用性。

我還必須面對一個嚴酷的現實:我不太擅長三角函數,而 DashLander 中確實有很多三角函數。當代理程式告訴我他們完美地實現了一個新的系統,例如著陸計算和得分時,我經常玩遊戲並立即發現差異。不幸的是,無論我在我的程式碼庫中閱讀 Gemini 密集的三角函數多少次,我都無法找出斷開點隱藏在哪裡。為了擺脫這個惡性循環,我要求 Gemini 在鍵盤快捷鍵後面建立一個絕密調試模式。它呈現了疊加層,顯示了確切的地形資料、表面的相對傾斜度和碰撞碰撞框,讓我能夠向自己證明代理程式的計算在哪裡是錯誤的。掌握了確鑿的證據,我能夠與 Gemini 合作解決了密集數學文件中的問題。

**時空旅行的棘手問題**

當我們加入挑戰模式時,Antigravity 在連接資料模型並將它們連接到 Cloud Firestore 方面做得非常出色。但在測試過程中,我發現了一個隱蔽的錯誤。我會與之前高分的鬼魂比賽,視覺上一切看起來都很棒,因為鬼魂飛船順利地漂移。但隨著重播的進行,事實證明鬼魂飛船與過去的自己不知不覺地脫節了。在比賽結束時,儘管原始玩家完美著陸,但鬼魂飛船有時會劇烈撞上小行星。

為了解決這個問題,我必須像 2023 年一樣,開動我的大腦。可怕,我知道。

我意識到核心問題是現代電腦架構的一個基本面向。您看,在現代多處理器 CPU 上,您可以告訴程式在未來精確的毫秒運行一段程式碼,但由於行程排程的性質,CPU 永遠無法保證完美精確度。而且,對我來說不幸的是,在重播期間啟動推進器的一毫秒延遲會從根本上改變飛船的模擬飛行路徑。我意識到,我不能僅僅記錄推進器時間戳,而是必須在每個推進器事件的精確時刻儲存著陸器物理狀態的完整表示,以校正重播期間的不準確性。Gemini 隨後整合了邏輯,以不斷將重播的即時模擬與這些物理檢查點進行比較,自動校正任何微小偏差。結果是完美無瑕、無崩潰的重播。

**從遊戲到發布**

您可能已經感受到在日常工作中身兼多職的壓力,無論您的團隊規模大小。例如,您可能擅長編寫遊戲,但不想推廣它們。或者,您是一位傑出的使用者體驗工程師,但更喜歡不處理圖形設計。好消息是,您不必獨自完成所有事情。

我們使用 Stitch 從單一提示生成了我們的行銷登陸頁面,並將我們正在運行的遊戲截圖作為上下文傳入。(誠然,如果生成優化的登陸頁面是我們的實際目標,我們會對此進行更多疊代。)使用一鍵匯出到 Antigravity,我們隨後下載了 HTML 並開始工作。因為我們是用 Dart 構建的,所以我們希望保持堆疊統一。我們使用 [Jaspr](https://pub.dev/packages/jaspr)(一個將 HTML 直接映射到 Dart 組件的 Web 框架)來無縫整合生成的 HTML。Gemini 完美處理了這種轉換,使我們能夠在遊戲和行銷網站之間共用程式碼,同時保留行業標準的 SEO 索引。

最後,我們使用 Antigravity CLI 來建構我們的 Flutter 網頁應用程式,將建構目錄複製到 Jaspr,執行建構腳本,然後直接部署到 Firebase Hosting——所有這些都在一個不間斷的終端工作流程中完成。

雖然 DashLander 目前以網路優先,以便輕鬆驗證機制,但由於它使用 Flutter 建構,我們只需一個指令即可部署到 iOS、Android、macOS、Windows、Linux,甚至豐田 RAV4 的資訊娛樂面板。截至撰寫本文時,該遊戲目前可在 [dashlander.com](http://dashlander.com) 上玩;但如果您在足夠遙遠的未來發現這篇文章,且該連結已失效,歡迎您直接從 [GitHub](https://github.com/craiglabenz/dashlander) 下載程式碼。

**實現您的願景**

若要開始建構您夢想中的應用程式或遊戲,請前往 [antigravity.google](https://antigravity.google) 獲取 Google 的 Agent Manager 和 IDE;並前往 [flutter.dev](https://flutter.dev) 開始使用 Flutter SDK。

祝您好運,我們迫不及待地想看看您會建立什麼!

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

Dash 坐在咖啡店的收銀檯上喝著拿鐵,上面印有 Flutter 標誌

如果你開始贈送咖啡,人們就會出現。Flutter 團隊曾嘗試過一段時間,想辦法將這個原則應用到一個從 Flutter 應用程式開始,以一杯熱騰騰美味飲料結束的演示中。問題是如何讓它更有趣、更個人化,而不僅僅是點餐和送餐。咖啡很酷,但它不像「你絕對會記得 Google I/O 的一件事」那麼酷。

閱讀全文 »
0%