把同一個 Prompt 跑五次,只會得到五份相同的錯誤

N-version programming 的假設是:多樣性來自不同的作者。對 LLM 來說,這意味著不同的模型、不同的供應商,甚至不同的訓練版本。但這個假設是錯的。只要改變「怎麼問」,而不是「問什麼」,就能從同一個模型中獲得有意義的多樣性。

問題在於:把 temperature 調到 1.0 然後重複跑五次 prompt,這不是策略。你只會得到表面上的變化。變數名稱換了、註解調了順序,但結構一模一樣,bug 也一模一樣。

如果你想讓實作獨立地出錯,就必須在 prompt 中要求不同的思考模式,而不是不同的輸出。

N-Version Programming 對 LLM 的真正需求是什麼

N-version programming 是一種容錯技術:針對同一個規格,並行執行多個獨立實作,比對輸出後以多數決定正確結果。概念是:不同的開發者獨立作業時會引入不同的 bug,這些 bug 之間不會相關,因此多數決就能壓制錯誤。

這是個老想法,也很昂貴。你等於付錢請 N 個團隊做同一件事。

LLM 讓這件事便宜到可以嘗試。不用 N 個團隊,只要 N 次 API 呼叫。但問題是:對同一個模型用同一個 prompt 呼叫 N 次,會產生 N 份幾乎一模一樣的實作。bug 完美相關,你的多數決完全沒用。

解決方法是把 prompt 當成開發者,而不是模型。不同的 prompt 就是不同的開發者。

為什麼只靠 Temperature 只能產生化妝品等級的多樣性

Temperature 控制的是 token 的機率分佈。溫度越高,模型越傾向選擇機率較低的下一個 token。這會在措辭、變數命名和表面結構上產生變化。

但它不會在演算法思維上產生變化。如果你要求一個「尋找最長迴文子字串」的函式,temperature 頂多影響你用 left / right 還是 l / r,不會影響你選擇 expand-around-center 還是 dynamic programming。

對 N-version programming 來說,這完全沒用。你需要的是「用不同方法解決問題」的實作,而不是「看起來不同、但解法相同」的實作。

四種強制產生演算法多樣性的 Prompt 策略

以下是四種能改變模型思考問題方式的方法。

改變問題的框架

同樣的任務,framed 成「寫一個 parser」和「寫一個能辨識這個語法的 state machine」,產出的程式碼會完全不同。一個可能用 recursive descent,另一個可能用 table-driven 的做法。

你可以自動化這個過程,在解題前要求模型採用特定的框架:

import os
from openai import OpenAI

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

def generate_with_framing(task: str, framing: str) -> str:
    prompt = f"""{framing}

Task: {task}

Write a complete, correct implementation. Do not explain your approach."""
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.7,
    )
    return response.choices[0].message.content

task = "Parse a CSV string into a list of dictionaries, handling quoted fields and newlines within quotes."

framings = [
    "Approach this as a finite state machine with explicit state transitions.",
    "Approach this using recursive descent parsing with a lexer and parser.",
    "Approach this by splitting on delimiters and post-processing edge cases.",
]

for framing in framings:
    print(f"=== {framing} ===")
    print(generate_with_framing(task, framing))

實際對 GPT-4o 執行時,state machine 框架會穩定產出逐字元解析、帶有明確 state enum 的程式碼;recursive descent 框架會產出 lexer 與獨立的 parser 函式;split-and-fix 框架則會產出更精簡但較脆弱的解法。

切換角色(Persona)

不同的角色會啟動不同的知識。systems programmer 寫的程式碼,和 data scientist 或 competitive programmer 截然不同。

personas = [
    "You are a systems programmer who prioritizes memory efficiency and avoids unnecessary allocations.",
    "You are a Pythonic developer who prefers concise, idiomatic code using standard library features.",
    "You are an algorithms researcher who reaches for theoretically optimal solutions even if the code is longer.",
]

Persona prompting 對結構多樣性的效果出乎意料地好。systems programmer 會選擇 arrays 和 indices;Pythonic developer 會選 itertools 和 comprehensions;algorithms researcher 則可能引入函式庫或寫出更正式的解法。

限制可用的工具

限制或擴展可用的工具集,會迫使模型採用不同的做法。

constraints = [
    "You may only use the Python standard library. No external dependencies.",
    "You may use numpy and pandas. Optimize for vectorized operations.",
    "You must implement this without using regular expressions.",
]

當你知道某種做法有盲點時,這招特別有用。如果你的 regex-based parser 老是搞錯巢狀引號,就強制產出一個不用 regex 的版本。

搭配發散推理的 Chain-of-Thought

不要直接要求程式碼,而是請模型先列出多種解題策略,再選擇最不明顯的那個。

cot_prompt = f"""Task: {task}

First, list three different algorithms or approaches to solve this problem.
Then, pick the one that is most different from the others and implement it.
Do not pick the most obvious approach."""

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": cot_prompt}],
    temperature=0.7,
)

Chain-of-thought 會迫使模型浮現其推理過程。「最不同」的限制則會把它推離預設解法。實務上,這是單一技巧中產生結構多樣性最高的方法。

這招在哪裡會失效:多樣性的天花板

同模型多樣性有其極限,而且你一定會碰到。

基礎知識的盲點是共享的。如果訓練資料中對 floating-point comparison 有系統性的誤解,那麼無論換哪種框架、哪種角色,都會複製那個誤解。模型只有一組 weights,prompt 繞不過這一點。

還有邊際遞減的問題。前三種框架可能分別給你 state machine、recursive parser 和 split-based 做法;第四種可能只給你一個變數名稱不同的 state machine。三到五種真正不同的做法之後,就是在榨乾最後一滴汁。

有些技巧會降低品質。「最不同」的限制偶爾會產出「因為錯了所以不同」的解法。為了不同而不同沒有意義。你需要投票或測試機制來過濾掉爛點子。

今天就能部署的實務設定

如果你要把這個機制內建到系統中,不要隨機化。要設計你的多樣性。

從上面的清單中選三到五種技巧,每種技巧產生一份實作。對所有實作跑過你的 test suite 或 property-based tests,保留通過的。最後用簡單的多數決產出最終結果。

from collections import Counter

def majority_vote(outputs: list[str], test_fn) -> str:
    passing = [o for o in outputs if test_fn(o)]
    if not passing:
        raise RuntimeError("No implementation passed tests")

    # Exact match voting; swap for AST comparison if needed
    return Counter(passing).most_common(1)[0][0]

測試過濾這一步絕對不能省。沒有正確性的多樣性只是噪音。

常見問題

這對較小的模型也有效嗎?

有效,但多樣性的天花板較低。較小的模型在訓練資料中學到的不同解題策略較少。你可能只得到兩種真正不同的做法,而不是四種。技巧仍然管用,只是變化較少。

我究竟需要幾份實作?

多數決的實務最低門檻是三份。五份能提供更好的覆蓋率,但成本線性增加。超過五份之後,同模型多樣性會退化成表面變化。如果你需要超過五份,就該改用跨模型多樣性。

同模型多樣性和跨模型多樣性一樣好嗎?

不一樣。不同的模型有不同的訓練資料、架構和 fine-tuning。它們會以真正不同的方式失敗。同模型多樣性是成本與操作便利性之間的權衡。當你需要快速取得不錯的容錯能力時用它,而不是當你需要完美容錯時。

我可以混合使用這些技巧嗎?

當然可以。把 persona prompt、工具限制和 chain-of-thought 步驟結合起來,會比任何單一技巧產生更多的多樣性。代價是更長的 prompt 和每次生成消耗更多 tokens。對於關鍵的程式路徑,這些額外的 tokens 是值得的。

先試什麼

先從改變問題框架開始。這是最容易實作、且能產生最穩定結構多樣性的方法。如果需要更多變化,再加上 persona 切換。把跨模型多樣性留到同模型多樣性碰到天花板的時候。

在讓它們投票之前,先對所有實作跑過同一套 test suite。一個未經測試的多樣性實作,只是你還沒遇到的 buggy implementation。