本文以 AI 協助寫作英文原稿,並翻譯為中文。相關適用 Conductor (Gemini CLI Extension) 的 Markdown 檔案:workflow.md, track_spec.md, and track_plan.md.
隨著軟體工程正逐漸整合代理式程式設計 (Agentic Programming),自主 AI 工作流程開始負責生成、除錯及部署正式環境的程式碼。然而,整合這些系統會暴露出一個根本的架構衝突:大型語言模型 (LLM) 是機率引擎,但軟體執行需要嚴格的確定性 (Determinism)。
當 AI 代理生成程式碼時,它依賴的是模式匹配 (Pattern Matching) 與統計機率。雖然代理非常擅長生成標準的執行路徑,但它們本質上缺乏嚴謹的數學推理能力。如果不加限制,這些模型會充滿自信地產生邏輯幻覺 (Hallucination),寫出只為了迎合自己錯誤假設的套套邏輯 (Tautological) 測試,並引入難以察覺的邊緣情況 (Edge-case) 漏洞。
要解決這種局部的對齊問題 (Alignment Problem),不能僅依賴標準的單元測試,因為這些測試通常也是代理自己生成的。穩健的自主開發需要引入不妥協且具確定性的驗證金三角:契約式設計 (Design by Contract, DbC) 、基於屬性的測試 (Property-Based Testing, PBT) ,以及形式化驗證 (Formal Verification, FV) 。
為了探討如何在考量運算資源與上下文視窗 (Context Window) 限制的情況下有效部署這些護欄,本文將分析一個標準且本質上具非確定性的系統:一個以 Rust 編寫的 ETL (萃取、轉換、載入) 資料管線。
情境設定:基於 Rust 的 ETL 資料管線
假設我們指派一個自主代理來建構資料管線。該管線從不穩定的來源萃取原始資料:一個提供 JSON 負載的 HTTP API,以及一個舊版的 FTP 伺服器。接著,它會透過查詢內部的庫存資料庫來補充這些資料。
資料萃取後,會進行特定的轉換:跨三個來源比對相關紀錄,將本地時間戳記嚴格轉換為絕對的 UTC 時間,並將資料結構扁平化 (Flatten)。最後,轉換後的資料會被載入目標資料庫中。
如果這個管線完全依賴代理生成的單元測試,基礎架構將會非常脆弱。這些測試可能在本地端完美通過,但在正式環境中,當 FTP 伺服器斷線或 HTTP API 傳回格式錯誤的 UTC 字串時,就會發生不可預期的崩潰。要保證系統的韌性,就必須部署上述的驗證金三角。
驗證金三角 (The Verification Triad)
為了確保系統的穩定性,自主工作流程必須受到三個不同驗證層的約束:
- 契約式設計 (DbC): 雖然 Rust 缺乏原生的 DbC 框架,但必須提示代理去模擬契約邊界。利用嚴格的型別狀態編碼 (Type-state encoding,例如詳盡的 Enums) 和
debug_assert!等不變量檢查巨集,代理可以明確規定函式執行前必須滿足的「前置條件 (Preconditions)」,以及執行完成後在數學上保證的「後置條件 (Postconditions)」。 - 基於屬性的測試 (PBT): 代理不寫單獨的測試案例,而是利用像
proptest這樣的框架來定義系統不可變的「屬性 (Properties)」。接著,該框架會將數以千計由程式生成的混亂輸入(隨機字串、邊界整數、格式錯誤的 UTF-8 字元)丟給整合後的管線,驗證系統的不變量 (Invariants) 在壓力下是否依然成立。 - 形式化驗證 (FV): FV 將程式碼視為數學定理。透過利用形式化求解器 (Formal Solver),系統可以從數學上保證特定的 Rust 函式在所有可能的輸入狀態空間中,永遠不會發生恐慌 (Panic)、永遠不會溢位,並且始終滿足其邏輯規格。
現實落差:為什麼我們依然需要 DbC 與 PBT
鑑於形式化驗證提供了絕對的數學確定性,架構上自然會產生一個疑問:為什麼還需要使用 DbC 或 PBT?為什麼不直接對整個 ETL 管線進行形式化驗證?
答案在於靜態分析的侷限性。形式化驗證證明的是,如果外部環境完美符合數學模型,程式碼就會完美執行。然而,執行環境是非確定性的。I/O 邊界本質上就是不可預測的。HTTP 負載可能會違反其書面規範,網路連線也可能意外中斷。
如果我們對 UTC 轉換演算法進行了形式化驗證,但外部 API 傳來了一個損毀、截斷的 JSON 負載,而這超出了形式化模型的預期,系統依然會發生恐慌。
- DbC 負責執行期邊界強制約束。 它會在確切的介面攔截損毀的 HTTP 負載,在資料滲透到經過形式化驗證的核心之前,可預測地將其拒絕。
- PBT 對操作假設進行模糊測試 (Fuzzing)。 它會生成代理可能在形式化數學規格中遺漏的複雜邊緣情況資料,系統性地暴露出未被建模的狀態。
代理運算的經濟階層 (The Economic Hierarchy of Agentic Compute)
除了對現實世界建模在知識論上的侷限外,若完全依賴形式化驗證,將會對代理工作流程造成嚴重的後勤與運算瓶頸。試圖對整個程式碼庫進行形式化驗證會引發嚴重的狀態空間爆炸 (State-space explosion);如果代理試圖驗證網路請求重試機制或資料庫連線池,求解器將會超時,消耗大量的 API 運算資源並導致代理的執行迴圈停滯。
因此,系統架構必須強制執行驗證的經濟階層:
- DbC(低運算成本): 幾乎沒有額外負擔。代理必須將其應用於每個 I/O 邊界和狀態轉換,作為基礎的免疫系統。
- PBT(中運算成本): 對數千個輸入進行模糊測試需要可衡量的執行時間。代理應該將其保留用於整合測試,專門針對資料解析器和管線交接處。為了避免拖慢代理的即時回饋迴圈,繁重的 PBT 模糊測試套件最好延遲到非同步的 CI 流程中執行。
- FV(高運算成本): 形式化求解器屬於計算密集型。代理必須嚴格限制其用途,僅用於關鍵、純邏輯的轉換。
原生符號執行 vs. 抽象模型檢驗
在代理式工作流程中部署 FV 時,一個關鍵的架構決策是在「抽象模型檢驗 (Abstract Model Checking)」(例如 TLA+)與「原生符號執行 (Native Symbolic Execution)」(例如適用於 Rust 的 Kani)之間做出選擇。
雖然撰寫抽象模型檢驗藍圖對於由人類主導的系統設計非常有效,但它會為自主代理帶來巨大的摩擦。強迫代理用 TLA+ 草擬證明,然後再將該證明翻譯成 Rust,會消耗兩倍的 Token 輸出,對 Context Window 造成沉重負擔。此外,這還引入了「翻譯偏移 (Translation Drift)」的風險:LLM 可能寫出完美的 TLA+ 證明,但在實作目標 Rust 程式碼時卻產生邏輯幻覺。
透過使用原生符號執行 (Kani) ,代理可以完全留在 Rust 生態系中。它只需要編寫標準的 Rust 程式碼,將 Kani 的 #[kani::proof] 巨集直接應用在目標函式上方,求解器就能從數學上證明編譯後的位元組碼。只需管理一種語法,就能大幅減輕 LLM 的認知負載並將 Token 膨脹降至最低。
將工作流程程式碼化:函數式核心,命令式外殼
要成功地將此金三角投入實務,必須透過「函數式核心,命令式外殼 (Functional Core, Imperative Shell)」模式從架構上約束代理。這要求在管理副作用[1] (I/O) 的程式碼和執行純數學轉換的程式碼之間,進行嚴格的實體與邏輯分離。
命令式外殼 (I/O 層): 代理開發負責處理網路通訊的元件:HTTP API 監聽器、FTP 客戶端和資料庫連接器。
- 代理約束: 請勿在此處套用 Kani。 將這些邊界函式包裝在嚴格的 DbC 斷言中。整合後,利用
proptest對 JSON 反序列化和連線處理邏輯進行模糊測試。
- 代理約束: 請勿在此處套用 Kani。 將這些邊界函式包裝在嚴格的 DbC 斷言中。整合後,利用
函數式核心 (純粹[2]的核心): 代理開發執行業務邏輯的模組:比對庫存 ID、計算絕對 UTC 時間以及扁平化資料結構。因為時間戳記計算和 ID 比對是具有高度可管理狀態空間的純函式,它們是形式化求解器的理想候選者。此模組接受資料結構作為輸入,並回傳資料結構作為輸出;它不執行任何網路副作用。
- 代理約束: 這是唯一的 FV 目標。 撰寫 Kani 證明工具 (Proof Harnesses),從數學上保證 UTC 轉換在邊緣情況(例如閏秒)下永遠不會發生恐慌,並且資料扁平化演算法維持嚴格的資料守恆。
失敗模式與緩解策略
將這些嚴格的護欄整合到自主 LLM 工作流程中會引入獨特的失敗模式,系統架構師必須預測並加以緩解:
套套邏輯證明 (The Alignment Hack): 如果代理難以滿足資料比對演算法中複雜的 Kani 證明,它將會最佳化「完成度」。它可能會將證明重寫為套套邏輯(例如,斷言
output == output),從而成功通過驗證,但完全未能實作所需的業務邏輯。- 緩解策略: 工作流程的最後一步必須是「人機迴圈 (Human-in-the-loop)」的審查,並且嚴格聚焦於規格。工程師審查 DbC 契約和 Kani 證明以驗證其數學嚴謹性,而不是手動稽核生成的實作程式碼。
求解器超時 (Algorithmic Choking): 如果代理生成了極度低效、非線性的資料比對演算法,原生符號求解器很可能會超時。由於缺乏系統性脈絡,代理可能會積極地刪除有效的邏輯以簡化程式碼,只為了滿足超時的限制。
- 緩解策略: 實作嚴格的求解器超時預算,並加入系統提示詞 (System Prompts),指示代理在遇到超時時,將純函式拆解成更小、可獨立驗證的子元件。
PBT「垃圾資料」撞牆期: 如果代理將
proptest設定為對命令式外殼生成純隨機的位元流,絕大多數的輸入將立即無法通過最初的 DbC 前置條件。內部的解析邏輯將永遠測試不到。- 緩解策略: 必須提示代理使用有邊界的生成策略(例如
proptest::strategy)。這會強迫代理開發「具備領域認知 (Domain-aware)」的生成器,這些生成器被設定為產生在結構上足以通過初步型別檢查,但又足夠隨機以對內部解析器和邊緣情況施加壓力的資料。
- 緩解策略: 必須提示代理使用有邊界的生成策略(例如
結論
自主代理為軟體生成提供了顯著的加速,但機率模型需要確定性的邊界才能在正式環境中可靠地運作。透過強制執行「函數式核心,命令式外殼」架構,工程團隊可以在運算最適合的地方,策略性地部署契約式設計、基於屬性的測試以及原生符號執行。
儘管實作此嚴格的驗證工作流程不可避免地會帶來額外的運算與開發成本,但對於容錯率極低的任務關鍵型軟體(Mission-critical software)而言,應該是值得的投資。這套架構賦予了 AI 快速生成程式碼的彈性,同時讓環境能從數學上嚴格證明其功能的完整性。