AI 輔助開發

一個有效的確定性 AI 配對程式設計流程

AI 程式設計師雖然強大,但若無閘門控管,便會飄移不定。以下是包裝它的「規劃、建置、驗證、交付」管線,具備狀態檔案與計畫凍結功能。

本文由 AI 模型從英文原文翻譯而來,用字可能與原文有所出入。 閱讀英文原文

將一個編碼任務交給 AI 助理,並附上「全部搞定」的指令,會以一種可預測的方式失敗。模型是有能力的,在它面前的狹窄領域中,其能力往往超出你的預期,但它不知道工作的終點在哪裡。它會在步驟之間失去上下文。它會偏離最初的目標,去修復它注意到的某些東西。它會寫出看起來正確的程式碼,然後在編寫和部署之間不做任何測試就直接交付。當出現問題時,你無法判斷是過去四十次編輯中的哪一次造成的,也無法乾淨地復原。

解決方案不是一個更好的提示,而是一個約束框架。你將 AI 的工作包裹在一個由人類設計和控制的確定性管線中:審查、實作、驗證、文件化、部署,每個階段都有一個明確的閘門,在失敗時會停止執行。模型在每個階段內部提供創造力。管線則在其周圍提供紀律。這篇文章描述了如何建構那個約束框架,為什麼每個階段都需要一個閘門,以及在何種特定情況下,完全跳過這一切才是正確的選擇。

為何無引導的 AI 會偏離

這些失敗模式相當一致,足以為其命名。理解這些模式,就能讓你明白該建立何種關卡。

首先是情境遺失。AI 助理在一個有限的近期對話視窗內運作。在一個長任務中,該視窗會被填滿,而最早的決策會被擠出視窗之外。當模型深入程式碼時,它已不再記得一開始同意的狹窄範圍。當它與先前的決策相矛盾時,它並不是在對你說謊。它是真的再也看不見那個決策了。

其次是範圍蔓延。要求修復一個錯誤,模型通常會注意到三個可以改進的相鄰事物,並加以改進,因為每一個改進在局部看來都很合理。每一個單獨的變更都是站得住腳的。結果是,你得到一個比你要求的大四倍的差異比對(diff),動到了你從未打算開啟的檔案,而真正的修復則被埋藏在其中。

第三是缺乏驗證,這點傷害最大。若置之不理,AI 會撰寫程式碼,並根據程式碼「看起來」正確,而非「執行起來」正確來回報成功。「看起來正確」和「實際上正確」的差異,恰好就是那些有趣的錯誤所在之處:差一錯誤(off-by-one)、錯誤的篩選器、一個在本地成立但在部署環境中失效的假設。若沒有一個能實際執行程式碼的關卡,模型的信心便與現實脫節。

可逆性不佳將前述幾點串連起來。當工作成果以一個龐大且未分化的變更形式呈現,且沒有記錄下決策內容或原因時,要回復(rolling back)就意味著要解開一個死結。你無法還原一個你從未寫下的決策。

這些都不是不信任模型原始能力的理由。它們是我們不該信任非結構化授權的理由。這股力量是真實的。指引和關卡,才能將其轉化為你能夠引以為傲、已交付的工作成果。

管線的五個階段

這個框架是一連串固定的階段。其順序並非僅為裝飾。每個階段都會產生下個階段所需的輸入,且每個階段都以一個閘門作結,必須通過該閘門才能繼續執行。

階段 AI 的工作 閘門的檢查項目 失敗時的處理
審查 (Review) 讀取上下文、撰寫計畫、指出風險 計畫具體、範圍有界、人類核准 停止,修訂計畫
實作 (Implement) 僅根據已定案的計畫撰寫程式碼 程式碼可編譯、類型檢查通過 停止,修復或復原
驗證 (Verify) 執行測試、檢查不變量 測試通過、不變量成立 停止,不繼續部署
文件記錄 (Document) 記錄變更內容與原因 變更日誌與實際差異 (diff) 相符 停止,進行核對
部署 (Deploy) 透過正常發布路徑交付 部署後的健康檢查通過 復原

「審查」是進行思考的環節,也是發現錯誤成本最低的地方。模型會讀取相關的程式碼,寫下它打算做什麼,並列出可能出錯的地方。人類會閱讀該計畫,並予以核准或退回。核准一份計畫遠比審查一份完成的差異 (diff) 來得便宜,因為計畫簡短而差異冗長,且在此處發現錯誤的計畫,其成本僅是幾分鐘,而非一次復原。

「實作」將核准的計畫轉化為程式碼,且僅限於已核准的計畫。「驗證」是將此管線與無引導的委派區分開來的閘門:它會執行程式碼,並在測試失敗時拒絕推進。「文件記錄」留下永久的紀錄,以便下一個人或下一個 AI 會話能夠重構其推理過程。「部署」會透過與人類相同的發布路徑進行交付,並進行相同的部署後檢查,在失敗時會進行復原,而不是留下半交付的狀態。

狀態儲存於檔案中,而非模型的記憶體內

AI 助理的工作記憶體是易失性的。關閉對話、超出上下文視窗,或遇到強制重啟的錯誤,模型所持有的所有內容都會消失。如果你的管線狀態只存在於該記憶體中,重置就意味著從頭開始,而在一個部署到一半的任務上從頭開始是危險的。

因此,管線會將其狀態寫入檔案。目前的階段、已做的決定、已完成的項目、剩餘的項目,所有這些都儲存在磁碟上,並隨著工作的進展而更新。模型在每一步開始時讀取此狀態,並在結束時寫入。易失性的上下文成為持久狀態的快取,而非事實的唯一來源。

pipeline-state:
  task: "add rate limiting to the public API"
  stage: verify              # review -> implement -> verify -> document -> deploy
  plan_frozen: true
  decisions:
    - "token bucket, 100 req/min per key, chosen over sliding window for simplicity"
    - "limit stored in existing cache layer, no new dependency"
  progress:
    - [x] middleware written
    - [x] unit tests for bucket refill
    - [ ] integration test under concurrent load
  failures:
    - none

回報會在最糟的時刻顯現。一個對話在任務中途終止。新的對話開始時,沒有任何先前的上下文。它讀取狀態檔案,並確切地知道工作進度:計畫已凍結,實作已完成,還剩下一個驗證步驟。它從那裡繼續,而不是去猜測,或更糟的是,重做已完成的工作並撤銷某些正確的東西。將狀態儲存於檔案中,才能讓 AI 驅動的任務在其中斷後存活下來,而這類任務又特別容易發生中斷。

計畫凍結可阻止範圍蔓延

在審核階段產生核准的計畫後,管線會將其凍結。凍結意味著範圍被鎖定:在實作期間,AI 不得在計畫中新增或刪除項目。它會建構已同意的內容,不多也不少。

這直接針對範圍蔓延的失敗情況。若沒有凍結,模型注意到並修復相鄰事物的習慣會不受控制地運行,且 diff 會膨脹。有了凍結,同樣的本能會碰壁。如果模型在實作中途決定需要另一項變更,它不會直接進行變更。該變更必須作為計畫的明確修正案被記錄下來,而人類可以查看並核准或拒絕。

規則並非範圍永遠不能改變。實際工作會揭示真正的意外,而拒絕適應本身就是一種失敗。規則是範圍變更必須是可見且刻意的,而非無聲無息地累積。一個帶有簡短記錄修正案清單的凍結計畫是可稽核的。一個因意外而增長的未凍結計畫,只是一個無人選擇的大型 diff。

plan (frozen at review):
  1. add token-bucket middleware
  2. wire it into the public router
  3. unit + integration tests

amendment (recorded during implement, approved):
  4. add a config flag to disable the limit per environment
     reason: staging load tests need it off

管線所觸及的每一件事物,都可以追溯到凍結的計畫或記錄的修正案。diff 中不會出現任何未經同意的內容。

停止規則可防止失敗惡化

AI 助理遇到錯誤時會重試。這通常是好事。但如果助理遇到相同的錯誤並嘗試相同的修復方法,它就會陷入迴圈,有時會持續很長時間,浪費精力,而且每次嘗試都可能讓情況變得更糟。管線需要有明確的規則來決定何時停止。

第一條規則是重複失敗的次數限制。如果同一步驟連續失敗兩次,管線就會停止,並將問題呈報給人類,而不是進行第三次嘗試。兩次相同的失敗意味著模型不理解問題,再多的自主嘗試也無法產生這種理解。它們只會空轉。在兩次失敗後停止,能將一個可能的無限迴圈轉變為一個有界限且有明確交接的迴圈。

第二條規則是為破壞性操作設立一個獨立的關卡。刪除資料庫、捨棄資料表、重寫歷史紀錄、force-pushing,任何難以或無法復原的操作,都不能像普通步驟一樣獲得相同的自動批准。它需要人類對該確切操作進行明確、具體的確認。其理由在於不對稱性:一個可逆的錯誤代價是回滾,而一個不可逆的錯誤可能讓你損失無法挽回的資料,因此其前的關卡必須更加嚴格。

on step failure:
  record failure in state
  if failures_of_this_step >= 2:
    HALT and surface to human      # no third attempt
  else:
    retry with adjusted approach

before any destructive action:
  require explicit human approval for THIS specific action
  # never covered by a blanket "yes, proceed"

這些規則共同限制了爆炸半徑。AI 可以在安全、可逆的工作中間環節自主行動,而管線會在自主性最危險的兩個時間點強制人類介入:當它卡住時,以及當它即將執行無法撤銷的操作時。

為何包裝器必須是確定性的

此流程核心的 AI 本質上是非確定性的。給它相同的請求兩次,你可能會得到兩種不同的答案、兩種不同的實作、兩種相同步驟但順序不同的排列。這種變異性並非缺陷。它正是使模型從一開始就變得有用的創造力的另一面。你會希望它探索解決方案空間。

圍繞它的流程則必須相反。各個階段以固定的順序運行。閘門每次都應用相同的檢查。狀態以相同的方式記錄在相同的位置。給定相同的狀態檔案,流程會從相同的點恢復並執行相同的事情。儘管其核心存在隨機性,但這正是使整個安排值得信賴的原因。

包裝器的確定性帶來了三項具體好處。可重現性:你可以重新運行流程,並讓流程的行為保持一致,即使模型在某個階段內的輸出有所不同。可稽核性:因為每個決策和每個階段轉換都被記錄下來,你可以在事後精確地重建發生了什麼以及為什麼發生。信任:你可以讓模型在一個階段內以真正的自主性行動,正是因為有一個可預測的閘門守在出口,在其工作成果被採納前進行檢查。

這種分工就是整個概念的核心。AI 掌握著創造力,也就是從變異和探索中受益的部分。流程則掌握著紀律,也就是絕不能改變的部分。兩者都不做對方的工作。一個有創造力的流程將會是一片混亂。一個有紀律的 AI 則不值得使用。

何時該跳過管線,直接提問

這套框架並非免費。它有實際的開銷:編寫計畫、維護狀態檔案、通過每個關卡、記錄文件。對於適合的任務,這些開銷是划算的。對於不適合的任務,它就成了扼殺一個兩分鐘工作的官僚程序。

當任務小、一次性且易於還原時,請跳過管線。在檔案中重新命名變數。草擬一個你會讀過即丟的拋棄式腳本。回答關於某段程式碼如何運作的問題。重新格式化一個區塊。對於任何這些情況,啟動一個包含凍結計畫和狀態檔案的五階段管線,純粹是浪費。直接問模型並閱讀結果即可。犯錯的成本不過是看一眼和一次復原。

當工作是可重複、有重大影響且難以還原時,請使用完整的管線,尤其是最終需要部署時。任何要上線到生產環境的東西。任何會動到資料庫結構或遷移資料的東西。任何隊友會在其基礎上繼續開發的東西。任何你日後需要解釋或稽核的東西。在這裡,開銷根本不是開銷。它是最便宜的保險,用以防範無引導的委派所引來的那些確切的失敗、情境遺失、範疇擴張、未經驗證的部署、不可逆的變更。

決定性的問題很簡單。一個錯誤的代價是什麼?如果答案是看一眼和一次復原,那就跳過框架,讓模型自由發揮。如果答案是生產環境事故、資料遺失,或是一個下午都在對無法追蹤的差異進行鑑識考古,那就把它包起來。讓流程的份量與後果的份量相匹配,並在風險允許的範圍內,讓 AI 盡可能地快速和寬鬆。