16.2 為什麼 LLM 會寫出「看起來完美卻無法通過編譯」的代碼?(概率預測與符號邏輯的天然衝突)#
現象:流暢與正確的分離#
任何使用過 LLM 寫代碼的人都遇過:生成的代碼讀起來毫無破綻——命名得體、註解工整、結構清晰——卻編譯不過:函數簽名對不上、導入了不存在的模組、參數順序顛倒。這不是偶然的 bug,而是範式本身的必然症狀。
根因一:LLM 沒有符號表#
回憶編譯器的工作流(6.3):詞法 → 語法 → 語義(符號表、型別檢查)→ 代碼生成。語義階段需要維護精確的符號表。
LLM 的工作流只有一件事:對 token 序列做條件機率預測。它沒有符號表、沒有型別環境、沒有作用域棧。當模型寫出 user.get_profile() 時,它不是「查表確認 user 有 get_profile 成員」,而是「在訓練語料中,這個上下文之後最常出現的 token 是什麼」。
結果:只要某個 API 呼叫在訓練資料中看起來合理,模型就可能生成它——即使它不存在。這就是「API 幻覺」:自信地呼叫不存在的函數、import 不存在的模組。訓練資料中 API 的過時版本混雜更加劇此症。
根因二:機率沒有「違反即報錯」的機制#
規則系統的偉大之處在違反的可見性:型別不匹配 → 精確報錯。統計系統則是連續的:
模型可以以 0.97 的機率生成「正確」的下一步,以 0.03 的機率生成「幾乎正確」的步驟——幻覺往往就藏在這 3% 裡。且「看起來像正確代碼」的 token 模式統計上極度靠近正確代碼——LLM 的錯誤不是隨機噪聲,而是「語料中存在但當前情境不適用」的模式。這比隨機錯誤更難被肉眼發現。
根因三:局部最優 vs 全局契約#
編譯器的型別檢查是全域契約。LLM 是局部生成器:每次預測只看上下文窗口。結果:模型可以完美地寫好函數 A 的調用,卻與它自己三十行前寫的定義不一致——窗口越長、項目越大,一致性崩壞越快。這正是 Agentic Coding(17.1)必須配備編譯器回饋迴圈的理論原因。
停機問題的幽靈#
回憶 3.2:程式行為不可靜態判定(Rice 定理)。這對 LLM 有雙重含義:
- 無法設計完美的靜態檢查器判斷生成代碼的語義正確性——只能靠執行(測試)或受限驗證(型別)
- 也無法靜態證明「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 多輪、上下文工程 |