Die falsche Antwort sieht exakt aus wie die richtige
Du schickst einen Prompt an GPT-4o. Es liefert einen JSON-Blob mit einer Confidence von 0,97 zurück. Du schickst denselben Prompt an Claude 3.5 Sonnet. Es liefert einen anderen JSON-Blob, ebenfalls mit einer Confidence von 0,97. Beide Modelle klingen absolut sicher. Beide liegen auf unterschiedliche Weise falsch.
Das ist keine Hypothese. Wenn du auch nur eine nicht-triviale LLM-Workload über einige tausend Requests hinweg laufen lässt, wirst du auf Fälle stoßen, bei denen zwei fähige Modelle bei Fakten, Klassifikationen oder strukturierten Outputs uneins sind. Die Meinungsverschiedenheit selbst ist ein Signal. Die meisten Teams ignorieren es.
N-Version Programming, die alte Idee, mehrere Implementierungen parallel laufen zu lassen und über das Ergebnis abzustimmen, galt für die meiste Software als zu teuer. Bei LLMs sind die “Implementierungen” lediglich unterschiedliche API-Calls. Die Kosten sind real, aber die Kosten für das Ausliefern einer falschen Antwort sind oft schlimmer.
Wie N-Version Programming für LLMs aussieht
Die klassische Variante stammt aus sicherheitskritischen Systemen. Drei unabhängig geschriebene Programme verarbeiten denselben Input. Ein Voter vergleicht die Outputs. Wenn zwei übereinstimmen und eines nicht, gewinnt die Mehrheit und die Minderheit wird zur Inspektion markiert.
Bei LLMs brauchst du keine drei Engineering-Teams. Du brauchst drei API-Keys. Die Unabhängigkeit ist schwächer – die Modelle teilen Trainingsdaten –, aber die Divergenz ist immer noch messbar und nützlich. Zwei Modelle, die auf überlappenden Corpora trainiert wurden, können trotzdem unterschiedliche Fakten halluzinieren, Ambiguitäten anders interpretieren oder an entgegengesetzten Edge Cases scheitern.
Die Frage ist nicht, ob sie uneins sind. Das werden sie. Die Frage ist, was du tust, wenn sie es sind.
Ein einfacher Disagreement-Detector, der tatsächlich funktioniert
Hier ist ein konkretes System in Python. Es schickt denselben Prompt an mehrere Modelle, extrahiert strukturierte Outputs und vergleicht sie mithilfe domänenspezifischer Regeln statt naiver String-Gleichheit.
import asyncio
from dataclasses import dataclass
from typing import Callable, List, Any
import json
@dataclass(frozen=True)
class VariantResult:
model: str
output: dict
raw: str
latency_ms: float
class DisagreementResolver:
def __init__(
self,
comparators: List[Callable[[Any, Any], bool]],
tiebreaker: Callable[[List[VariantResult]], VariantResult],
):
# Each comparator returns True if two outputs are "the same"
self.comparators = comparators
# Tiebreaker picks a winner when no clear majority exists
self.tiebreaker = tiebreaker
def cluster(self, results: List[VariantResult]) -> List[List[VariantResult]]:
"""Group results into equivalence classes."""
clusters: List[List[VariantResult]] = []
for r in results:
placed = False
for cluster in clusters:
# A result joins a cluster if it matches the cluster's first member
if all(c(r.output, cluster[0].output) for c in self.comparators):
cluster.append(r)
placed = True
break
if not placed:
clusters.append([r])
return clusters
def resolve(self, results: List[VariantResult]) -> tuple[VariantResult, List[VariantResult]]:
"""Returns (winner, dissenters)."""
clusters = self.cluster(results)
clusters.sort(key=len, reverse=True)
if len(clusters) == 1:
# Unanimous agreement. Pick the fastest as winner.
winner = min(clusters[0], key=lambda r: r.latency_ms)
return winner, []
if len(clusters[0]) > len(clusters[1]):
# Clear majority.
winner = min(clusters[0], key=lambda r: r.latency_ms)
dissenters = [r for c in clusters[1:] for r in c]
return winner, dissenters
# No clear majority. Invoke tiebreaker.
winner = self.tiebreaker([c[0] for c in clusters])
dissenters = [r for c in clusters for r in c if r.model != winner.model]
return winner, dissenters
Die zentrale Designentscheidung ist der Comparator. String-Gleichheit ist für LLM-Output nutzlos. Zwei Modelle könnten {"category": "refund"} und {"category": "Refund"} zurückliefern und in nichts Relevantem uneins sein.
def category_comparator(a: dict, b: dict) -> bool:
return a.get("category", "").lower() == b.get("category", "").lower()
def amount_comparator(a: dict, b: dict) -> bool:
# Floats from different models can differ slightly
return abs(a.get("amount", 0) - b.get("amount", 0)) < 0.01
def strict_json_comparator(a: dict, b: dict) -> bool:
return json.dumps(a, sort_keys=True) == json.dumps(b, sort_keys=True)
# Use them in order of domain importance
resolver = DisagreementResolver(
comparators=[category_comparator, amount_comparator],
tiebreaker=lambda candidates: min(candidates, key=lambda r: r.latency_ms),
)
Wo das tatsächlich Bugs aufdeckt
Disagreement-Detection geht nicht darum, offensichtliche Fehler zu finden. Offensichtliche Fehler werden bereits durch einfachere Mittel gefangen.
Es geht darum, subtile Fehler aufzudecken, die für jedes einzelne Modell korrekt aussehen. Hier sind die drei Muster, die wir am häufigsten sehen:
Confident Hallucination Divergence. Ein Modell erfindet eine Produkt-SKU. Ein anderes erfindet eine andere SKU. Beide Outputs validieren gegen das Schema. Die Meinungsverschiedenheit ist das einzige Signal, dass etwas erfunden wurde.
Boundary Condition Splits. Ein Modell klassifiziert eine transaction von 999,99 $ als “high value”. Ein anderes klassifiziert sie als “standard”. Der Threshold ist im Prompt unklar formuliert. Die Meinungsverschiedenheit sagt dir, dass der Prompt unter-spezifiziert ist.
Temporal Drift. Ein Modell liefert ein Datum in ISO 8601. Ein anderes liefert dasselbe Datum in einer anderen Zeitzone, weil im Prompt “today” stand und die Modelle unterschiedliche Annahmen zur Inference-Time-Uhr haben.
In jedem Fall ist keiner der Outputs offensichtlich kaputt. Die Meinungsverschiedenheit ist der Bug-Report.
Das Kostenmodell ist erstaunlich vertretbar
Drei API-Calls kosten dreimal so viel wie einer. Das ist die oberflächliche Rechnung. Die wahre Rechnung hängt davon ab, was du mit den Ergebnissen machst.
Wenn du auf jedem Request drei Modelle laufen lässt und den Mehrheits-Output nimmst, verbrennst du Geld. Das sollten die meisten Teams nicht tun.
Das intelligentere Pattern ist gestaffelt:
- Fast Path: Ein günstiges Modell bearbeitet den Request.
- Audit Path: Eine Stichprobe von Requests, oder alle Requests oberhalb eines Confidence-Thresholds, wird asynchron an ein zweites Modell geschickt.
- Escalation Path: Wenn der Audit Path eine Meinungsverschiedenheit feststellt, entscheidet ein drittes Modell den Streit. Die Meinungsverschiedenheit wird für ein Prompt-Engineering-Review geloggt.
Das bedeutet, du zahlst das Dreifache nur für den Teil der Requests, der tatsächlich Prüfung braucht. Für viele Workloads sind das unter 5 % des Traffics.
async def tiered_resolve(prompt: str, primary_model: str, audit_model: str) -> dict:
primary = await call_model(primary_model, prompt)
# Async audit: do not block the fast path
audit_task = asyncio.create_task(call_model(audit_model, prompt))
try:
audit = await asyncio.wait_for(audit_task, timeout=2.0)
except asyncio.TimeoutError:
# Audit lagged. Ship the primary result.
return primary.output
if outputs_agree(primary.output, audit.output):
return primary.output
# Disagreement detected. Escalate.
tiebreaker = await call_model("gpt-4o", prompt)
log_disagreement(primary, audit, tiebreaker)
return tiebreaker.output
Der log_disagreement-Call ist der Ort, an dem der Langzeitwert entsteht. Jede Meinungsverschiedenheit ist ein Datenpunkt, der dir sagt, wo dein Prompt mehrdeutig ist, wo dein Schema unter-constrained ist oder wo deine Training-Examples widersprüchlich sind.
Was das nicht behebt
N-Version Programming mit LLMs hat echte Grenzen, und etwas anderes zu behaupten, macht es gefährlich.
Shared Failure Modes. Wenn beide Modelle scheitern, weil der Prompt einen Tippfehler enthält, werden beide auf dieselbe Weise scheitern. Die Modelle sind nicht unabhängig wie drei handgeschriebene Programme. Sie teilen Architektur, Trainingsziele und große Teile des Internets.
Systematischer Bias. Wenn dein Prompt implizit einen Bias kodiert, können mehrere Modelle ihn konsistent reproduzieren. Disagreement-Detection fängt nur Divergenz, nicht geteiltes Unrecht ab.
Kosten im großen Maßstab. Selbst eine Audit-Rate von 5 % wird teuer, wenn dein Volumen Milliarden von Requests pro Monat beträgt. Die Ökonomie funktioniert für hochriskante Entscheidungen, nicht für jedes Autocomplete.
Latency auf dem Escalation Path. Wenn zwei Modelle uneins sind und du ein drittes brauchst, hast du zwei Roundtrips zum Critical Path hinzugefügt. Für Echtzeit-Anwendungsfälle musst du möglicherweise das Primary-Ergebnis ausliefern und die Meinungsverschiedenheit asynchron auflösen, wobei du ein kurzes Fenster potenzieller Fehlerhaftigkeit akzeptierst.
Wie man ohne Over-Engineering anfängt
Du brauchst am Tag eins kein vollständiges Voting-Framework. Beginne mit einem Paarvergleich auf deinem höchstrisikoreichsten Prompt.
Wähle einen strukturierten Output-Call, bei dem eine falsche Antwort tatsächlich Geld oder Vertrauen kostet. Füge in einem Background-Job einen zweiten Model-Call hinzu. Vergleiche die Outputs mit einem domänenspezifischen Comparator. Logge Meinungsverschiedenheiten. Reviewe die Logs wöchentlich.
Nach zwei Wochen weißt du, ob dein Prompt solide ist oder eine Katastrophe. Die meisten Prompts sind fragiler, als ihre Autoren denken. Das Disagreement-Log ist ein Ehrlichkeitscheck.
Sobald du Daten hast, kannst du entscheiden, ob du einen Resolver baust, deinen Prompt anpasst oder akzeptierst, dass die Aufgabe zu mehrdeutig für vollautomatisierte Verarbeitung ist.
Der echte Gewinn ist der Feedback Loop
Das Ziel von LLM N-Version Programming ist nicht, einen perfekten Voter zu bauen. Das Ziel ist, den Loop zwischen Production-Disagreement und Prompt-Improvement zu schließen.
Jedes Mal, wenn zwei Modelle uneins sind, hast du eine Stelle gefunden, an der deine Spezifikation unvollständig ist. Fixe die Spec, nicht nur den Output. Mit der Zeit sinkt die Disagreement-Rate. Wenn sie weit genug sinkt, kannst du die Audit-Stichprobe reduzieren oder das Tiebreaker-Model downgraden.
Das System wird billiger, je besser es wird. Das ist das Gegenteil der meisten Safety-Mechanismen, die mit zunehmendem Scale teurer werden.
Beginne mit einem Prompt, zwei Modellen und einer Log-Zeile. Die Meinungsverschiedenheiten werden dir genau sagen, wo du hinschauen musst.