Wenn du denselben Prompt fünfmal ausführst, entstehen fünf Kopien desselben Fehlers

N-Version-Programming geht davon aus, dass Vielfalt von unterschiedlichen Autoren kommt. Bei LLMs bedeutet das: verschiedene Modelle, verschiedene Anbieter, vielleicht verschiedene Training Runs. Aber diese Annahme ist falsch. Du kannst bedeutungsvolle Vielfalt aus dem exakt gleichen Modell erzeugen, indem du änderst, wie du fragst, nicht was du fragst.

Der Haken: die Temperature auf 1.0 zu drehen und den Prompt fünfmal auszuführen, ist keine Strategie. Du erhältst oberflächliche Variation. Die Variablennamen ändern sich. Die Kommentare verschieben sich. Die Struktur bleibt identisch, und die Bugs bleiben ebenfalls identisch.

Wenn du Implementierungen haben willst, die unabhängig voneinander failen, musst du nach unterschiedlichen Denkmustern prompten, nicht nach unterschiedlichen Outputs.

Was N-Version-Programming von LLMs wirklich braucht

N-Version-Programming ist eine Fault-Tolerance-Technik, bei der mehrere unabhängige Implementierungen derselben Spezifikation parallel ausgeführt werden. Die Outputs werden verglichen, und eine Mehrheitsabstimmung bestimmt das korrekte Ergebnis. Die Idee ist, dass verschiedene Entwickler, die unabhängig arbeiten, unterschiedliche Bugs einführen. Die Bugs korrelieren nicht, also unterdrückt die Mehrheitsabstimmung sie.

Es ist eine alte Idee. Sie ist auch teuer. Du zahlst N Teams, um dieselbe Sache zu bauen.

LLMs machen es billig genug, um es auszuprobieren. Statt N Teams hast du N API Calls. Das Problem ist, dass N API Calls an dasselbe Modell mit demselben Prompt N nahezu identische Implementierungen erzeugen. Die Bugs korrelieren perfekt. Deine Mehrheitsabstimmung ist nutzlos.

Die Lösung ist, den Prompt als Entwickler zu behandeln, nicht das Modell. Unterschiedliche Prompts erzeugen unterschiedliche Entwickler.

Warum Temperature allein nur kosmetische Vielfalt erzeugt

Temperature steuert die Wahrscheinlichkeitsverteilung über Tokens. Bei hoher Temperature wählt das Modell weniger wahrscheinliche nächste Tokens. Das erzeugt Variation in der Formulierung, der Variablenbenennung und der oberflächlichen Struktur.

Es erzeugt jedoch keine Variation im algorithmischen Ansatz. Wenn du nach einer Funktion fragst, die den längsten palindromischen Substring findet, ändert die Temperature, ob du left und right oder l und r verwendest. Sie ändert nicht, ob du zu Expand-Around-Center oder Dynamic Programming greifst.

Für N-Version-Programming ist das nutzlos. Du brauchst Implementierungen, die das Problem unterschiedlich lösen, nicht Implementierungen, die anders aussehen, während sie es auf dieselbe Weise lösen.

Vier Prompting-Strategien, die algorithmische Vielfalt erzwingen

Hier sind vier Ansätze, die ändern, wie das Modell über das Problem denkt.

Variiere das Problem-Framing

Dieselbe Aufgabe, geframed als “schreibe einen Parser” versus “schreibe eine State Machine, die diese Grammatik erkennt”, erzeugt unterschiedlichen Code. Einer könnte Recursive Descent verwenden. Der andere könnte einen table-driven Ansatz verwenden.

Du kannst dies automatisieren, indem du das Modell bittest, ein bestimmtes Framing zu übernehmen, bevor es löst:

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))

Wenn man das gegen GPT-4o ausführt, produziert das State-Machine-Framing konsistent einen character-by-character Parser mit einem expliziten State Enum. Das Recursive-Descent-Framing produziert einen Lexer und separate Parser-Funktionen. Das Split-and-Fix-Framing produziert eine kompaktere, aber brüchige Lösung.

Wechsle die Personas

Unterschiedliche Personas aktivieren unterschiedliches Wissen. Ein Systems Programmer schreibt anderen Code als ein Data Scientist oder ein 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 ist überraschend effektiv für strukturelle Vielfalt. Der Systems Programmer greift zu Arrays und Indizes. Der Pythonic Developer greift zu itertools und Comprehensions. Der Algorithms Researcher könnte eine Library einbinden oder eine formalere Lösung schreiben.

Schränke die verfügbaren Tools ein

Das Einschränken oder Erweitern des verfügbaren Toolkits zwingt zu unterschiedlichen Ansätzen.

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.",
]

Das ist besonders nützlich, wenn du weißt, dass ein Ansatz einen Blind Spot hat. Wenn deine regex-basierten Parser verschachtelte Quotes immer wieder falsch behandeln, zwinge eine Version, die keine regex verwendet.

Chain-of-Thought mit divergentem Reasoning

Statt direkt nach Code zu fragen, bitte das Modell, mehrere Lösungsstrategien zu generieren und die unauffälligste auszuwählen.

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,
)

Das Chain-of-Thought zwingt das Modell, sein Reasoning offenzulegen. Die “most different”-Constraint schiebt es vom Default-Lösung weg. In der Praxis produziert das die höchste strukturelle Vielfalt von allen einzelnen Techniken.

Wo das an seine Grenzen stößt: Die Diversity Ceiling

Die Vielfalt desselben Modells hat Grenzen, und du wirst sie erreichen.

Fundamentale Wissenslücken sind geteilt. Wenn die Trainingsdaten ein systematisches Missverständnis über Floating-Point-Vergleiche enthalten, reproduziert jedes Framing und jede Persona dieses Missverständnis. Das Modell hat einen Satz Weights. Darum kommst du nicht mit Prompts herum.

Es gibt auch abnehmende Returns. Die ersten drei Framings könnten dir eine State Machine, einen rekursiven Parser und einen Split-basierten Ansatz liefern. Das vierte Framing könnte dir eine State Machine mit anderen Variablennamen liefern. Nach drei bis fünf wirklich unterschiedlichen Ansätzen kratzt du am Boden des Barrels.

Manche Techniken degradieren die Qualität. Die “most different”-Constraint produziert gelegentlich Lösungen, die unterschiedlich sind, weil sie falsch sind. Divergenz um ihrer selbst willen ist nicht nützlich. Du brauchst einen Voting- oder Testing-Mechanismus, um die schlechten Ideen herauszufiltern.

Ein praktisches Setup, das du heute deployen kannst

Wenn du das in ein System baust, randomisiere nicht. Designe deine Vielfalt.

Wähle drei bis fünf Techniken aus der Liste oben. Generiere eine Implementierung pro Technik. Führe deine Test Suite oder Property-Based Tests gegen alle aus. Behalte diejenigen, die bestehen. Verwende eine einfache Mehrheitsabstimmung für den finalen Output.

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]

Der Test-Filtering-Schritt ist nicht verhandelbar. Vielfalt ohne Korrektheit ist nur Rauschen.

FAQ

Funktioniert das mit kleineren Modellen?

Ja, aber die Diversity Ceiling ist niedriger. Kleinere Modelle haben weniger unterschiedliche Lösungsstrategien in ihren Trainingsdaten. Du erhältst vielleicht zwei wirklich unterschiedliche Ansätze statt vier. Die Techniken funktionieren weiterhin; sie produzieren nur weniger Variation.

Wie viele Implementierungen brauche ich wirklich?

Drei ist das praktische Minimum für Majority Voting. Fünf bietet bessere Abdeckung, aber mit linear steigenden Kosten. Nach fünf degeneriert die Same-Model-Vielfalt zu kosmetischer Variation. Wenn du mehr als fünf brauchst, wechsle zu Cross-Model-Diversity.

Ist Same-Model-Diversity so gut wie Cross-Model-Diversity?

Nein. Unterschiedliche Modelle haben unterschiedliche Trainingsdaten, Architekturen und Fine-Tuning. Sie failen auf wirklich unterschiedliche Weise. Same-Model-Diversity ist ein Trade-off für Kosten und operative Bequemlichkeit. Verwende sie, wenn du schnell gute Fault Tolerance brauchst, nicht wenn du perfekte Fault Tolerance brauchst.

Kann ich diese Techniken kombinieren?

Absolut. Ein Persona-Prompt kombiniert mit einer Tool-Constraint und einem Chain-of-Thought-Schritt produziert mehr Vielfalt als jede einzelne Technik allein. Der Preis ist ein längerer Prompt und mehr Tokens pro Generation. Für kritische Code Paths sind die extra Tokens es wert.

Was du zuerst ausprobieren solltest

Beginne mit Framing-Variation. Es ist am einfachsten zu implementieren und produziert die konsistenteste strukturelle Vielfalt. Füge Persona-Switching hinzu, wenn du mehr brauchst. Spare Cross-Model-Diversity für die Fälle auf, in denen Same-Model-Diversity an ihre Grenze stößt.

Führe deine Implementierungen durch dieselbe Test Suite, bevor du sie abstimmen lässt. Eine ungetestete diverse Implementierung ist nur eine buggy Implementierung, die du noch nicht kennengelernt hast.