跳轉到

16.2 為什麼 LLM 會寫出「看起來完美卻無法通過編譯」的代碼?(概率預測與符號邏輯的天然衝突)#

現象:流暢與正確的分離#

任何使用過 LLM 寫代碼的人都遇過:生成的代碼讀起來毫無破綻——命名得體、註解工整、結構清晰——卻編譯不過:函數簽名對不上、導入了不存在的模組、參數順序顛倒。這不是偶然的 bug,而是範式本身的必然症狀。

根因一:LLM 沒有符號表#

回憶編譯器的工作流(6.3):詞法 → 語法 → 語義(符號表、型別檢查)→ 代碼生成。語義階段需要維護精確的符號表。

LLM 的工作流只有一件事:對 token 序列做條件機率預測。它沒有符號表、沒有型別環境、沒有作用域棧。當模型寫出 user.get_profile() 時,它不是「查表確認 user 有 get_profile 成員」,而是「在訓練語料中,這個上下文之後最常出現的 token 是什麼」。

結果:只要某個 API 呼叫在訓練資料中看起來合理,模型就可能生成它——即使它不存在。這就是「API 幻覺」:自信地呼叫不存在的函數、import 不存在的模組。訓練資料中 API 的過時版本混雜更加劇此症。

根因二:機率沒有「違反即報錯」的機制#

規則系統的偉大之處在違反的可見性:型別不匹配 → 精確報錯。統計系統則是連續的:

\[\text{「型別錯誤的代碼」與「正確的代碼」在 token 機率上只差一點點}\]

模型可以以 0.97 的機率生成「正確」的下一步,以 0.03 的機率生成「幾乎正確」的步驟——幻覺往往就藏在這 3% 裡。且「看起來像正確代碼」的 token 模式統計上極度靠近正確代碼——LLM 的錯誤不是隨機噪聲,而是「語料中存在但當前情境不適用」的模式。這比隨機錯誤更難被肉眼發現。

根因三:局部最優 vs 全局契約#

編譯器的型別檢查是全域契約。LLM 是局部生成器:每次預測只看上下文窗口。結果:模型可以完美地寫好函數 A 的調用,卻與它自己三十行前寫的定義不一致——窗口越長、項目越大,一致性崩壞越快。這正是 Agentic Coding(17.1)必須配備編譯器回饋迴圈的理論原因。

停機問題的幽靈#

回憶 3.2:程式行為不可靜態判定(Rice 定理)。這對 LLM 有雙重含義:

  1. 無法設計完美的靜態檢查器判斷生成代碼的語義正確性——只能靠執行(測試)或受限驗證(型別)
  2. 也無法靜態證明「LLM 不會生成有害代碼」——安全過濾本質上是統計對抗

工程對策:把符號邏輯當作外部約束#

對策 機制 對應的傳統技術
工具呼叫(function calling) 讓 LLM 呼叫真實 API 而非憑空生成 符號表/介面契約
檢索增強(RAG) 生成前檢索真實代碼與文檔 符號表的全局一致性
編譯/測試迴圈 生成→編譯→錯誤回饋→修正 編譯器的語義階段
型別友好的目標語言 選 TypeScript/Rust 作為輸出 羅素類型論(1.3)的復仇
AST 級後處理 tree-sitter 解析、linter 自動修 BNF/AST(6 章)
形式化規格 從 TLA+/契約生成測試 Hoare 邏輯(5.2)

有趣的歷史回響:這份清單幾乎是本書前十五章的目錄——七十年積累的符號邏輯基礎設施,一夜之間成為 AI 代碼生成的驗證骨架。

更深的哲學:幻覺是特性而非 Bug?#

一個反直覺的觀點:LLM 的機率性在「生成」上是優勢——它可以提出人類不會想到的組合(創造性的來源恰在於偏離語料模式)。編譯器永遠寫不出新演算法;LLM 偶爾可以。

所以正確的框架不是「修好 LLM 的幻覺」,而是「接受機率生成,包圍以確定驗證」:

LLM 負責探索廣闊的候選空間(機率、創造、模糊意圖);符號系統負責裁決候選的合法性(確定、嚴格、明確報錯)。

這個分工與人類工程同構:建築師提出方案(模糊、創造),結構計算與法規審查裁決可行性(精確、嚴格)。AI Coding 的本質,是把這個人類流程壓縮到秒級迴圈。

小結#

根因 症狀 解法
無符號表 API 幻覺 工具呼叫、RAG
無全域契約 跨檔案不一致 編譯器回饋迴圈、型別友好語言
機率無違反機制 錯誤難以肉眼識別 自動化測試、靜態分析
局部注意力 長程依存崩壞 Agent 多輪、上下文工程

下一節:16.3 雙劍合璧