Metriken und Benchmarks
Auf dieser Seite
Lektion aktualisiert am 3. November 2026 basierend auf der OpenAI-Dokumentation:
temperatureundtop_pwerden nur mit einem Reasoning-Effort vonnoneakzeptiert, während GPT-5.6-Modelle standardmäßig mitmediumreasonieren. Aufrufe, die diese Parameter setzen, müssen daherreasoning={"effort": "none"}(API Responses) oderreasoning_effort="none"(Chat Completions) angeben. Quellen: Using GPT-6 · GPT-5.6 Terra.
Qualität mit Metriken messen
Neben Scores von LLM-as-Judge existieren quantitative Metriken, um verschiedene Aspekte von LLM-Ausgaben zu bewerten. Ihr Wert liegt nicht darin, dass sie genauer wären – ein gut konstruierter LLM-Judge sieht Dinge, die eine Formel niemals sehen wird –, sondern darin, dass sie stabil und kostengünstig sind. Die kosinäre Ähnlichkeit liefert bei jedem Lauf exakt dieselbe Zahl, kostet fast nichts und kann daher bei jedem Commit auf Tausenden von Fällen ausgeführt werden. Sie reservieren den LLM-Judge dann für Dimensionen, die dies wirklich erfordern.
Wir werden die wesentlichen Metriken durchgehen, bevor wir sie zu reproduzierbaren Benchmarks zusammenfügen.
Bedeutung statt Wörter vergleichen
Der Vergleich zweier Texte Zeichen für Zeichen wird unbrauchbar, sobald die Formulierung frei ist. Die semantische Ähnlichkeit umgeht dieses Problem: Jeder Text wird durch ein Embedding-Modell in einen Vektor umgewandelt, und wir messen den Winkel zwischen den beiden Vektoren. Zwei Sätze, die dasselbe mit unterschiedlichen Worten sagen, zeigen in dieselbe Richtung.
import openai
import numpy as np
client = openai.OpenAI()
def cosine_similarity(vec_a: list[float], vec_b: list[float]) -> float:
"""Berechnet die kosinäre Ähnlichkeit zwischen zwei Vektoren."""
a = np.array(vec_a)
b = np.array(vec_b)
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
def semantic_similarity(text_a: str, text_b: str) -> float:
"""Misst die semantische Ähnlichkeit zwischen zwei Texten."""
response = client.embeddings.create(
model="text-embedding-3-large",
input=[text_a, text_b],
)
vec_a = response.data[0].embedding
vec_b = response.data[1].embedding
return cosine_similarity(vec_a, vec_b)
# Beispiel
score = semantic_similarity(
"The cat is on the rug.",
"A feline is resting on the carpet.",
)
print(f"Similarity: {score:.3f}") # ~0.85+
Das Beispiel ist lehrreich: Zwei Sätze ohne gemeinsame Wörter erzielen einen Score über 0,85. Dies ist die Stärke der Methode, aber auch ihre Falle. Die semantische Ähnlichkeit ignoriert Negationen und Nuancen des Grades – “die Zahlung wurde akzeptiert” und “die Zahlung wurde abgelehnt” sehen sich vektoriell sehr ähnlich. Verwenden Sie sie daher niemals allein, um eine Antwort zu validieren, bei der faktische Genauigkeit eine Rolle spielt.
Wenn die Frage lautet, ob die Antwort die gestellte Frage tatsächlich adressiert, ersetzt keine Formel das Urteil. Der Relevanz-Score unten zerlegt dieses Urteil in drei getrennte Fragen: Adressiert die Antwort die Frage direkt? Bleibt sie fokussiert? Ist sie vollständig? Diese Aufschlüsselung ist besser als ein globaler Score, da sie Ihnen sagt, in welche Richtung Sie den Prompt korrigieren müssen – ein wortwüchtiges Modell und ein unvollständiges Modell erfordern gegenteilige Korrekturen.
def relevance_score(
question: str,
answer: str,
context: str | None = None,
) -> dict:
"""Bewertet, ob die Antwort relevant für die Frage ist."""
prompt = (
"Bewerten Sie die Relevanz dieser Antwort.\n\n"
f"Frage: {question}\n"
)
if context:
prompt += f"Bereitgestellter Kontext: {context[:1000]}\n"
prompt += (
f"Antwort: {answer}\n\n"
"Kriterien:\n"
"1. Adressiert die Antwort die Frage direkt? (0-5)\n"
"2. Enthält die Antwort nicht angeforderte Informationen? (0-5, 5=keine)\n"
"3. Ist die Antwort vollständig? (0-5)\n\n"
"Antworten Sie in JSON mit den Schlüsseln: direct, focus, completeness."
)
response = client.responses.create(
model="gpt-5.6-terra",
reasoning={"effort": "none"}, # temperature erfordert effort none
input=prompt,
temperature=0.0,
)
import json
try:
scores = json.loads(response.output_text)
average = sum(scores.values()) / (len(scores) * 5)
return {"score": average, "details": scores}
except (json.JSONDecodeError, TypeError):
return {"score": 0, "error": "Parsing fehlgeschlagen"}
Beachten Sie die umgekehrte Formulierung des zweiten Kriteriums, bei dem 5 “keine nicht angefordeten Informationen” bedeutet. Dies ermöglicht es, die drei Scores in dieselbe Richtung zu addieren, aber es ist kontraintuitiv zu lesen: Wenn Sie eigene Kriterien hinzufügen, halten Sie sich an diese Konvention, sonst erhalten Sie Durchschnittswerte, die den Fehler belohnen, den Sie bestrafen wollten.
Prüfen, ob das Modell halluziniert
In einer anwendungsbezogenen Dokumenten-Q&A-Lösung ist die entscheidende Metrik nicht die Schreibqualität, sondern die Treue zur Quelle. Das Steuerungsprinzip ist einfach: Stellen Sie dem Richter den ursprünglichen Kontext und die generierte Antwort vor und bitten Sie ihn, jede Aussage als durch den Kontext gestützt, im Kontext nicht vorhanden oder widersprüchlich zum Kontext zu klassifizieren. Die Unterscheidung zwischen diesen beiden letzten Kategorien ist wichtig, da eine Aussage, die lediglich fehlt, möglicherweise korrektes Allgemeinwissen sein kann, während eine Aussage, die der Quelle widerspricht, immer ein Fehler ist.
def detect_hallucinations(
context: str,
answer: str,
) -> dict:
"""Erkennt Aussagen, die nicht durch den Kontext gestützt werden."""
prompt = (
"Analysieren Sie diese Antwort und identifizieren Sie jede Aussage, "
"die NICHT durch den bereitgestellten Kontext gestützt wird.\n\n"
f"Kontext: {context[:3000]}\n\n"
f"Antwort: {answer}\n\n"
"Für jede Aussage in der Antwort angeben:\n"
"- gestützt: die Aussage ist im Kontext\n"
"- nicht gestützt: die Aussage ist nicht im Kontext\n"
"- erfunden: die Aussage widerspricht dem Kontext\n\n"
"Antworten Sie in JSON mit dem Schlüssel statements (Liste) und "
"fidelity_score (0 bis 1)."
)
response = client.responses.create(
model="gpt-5.6-terra",
reasoning={"effort": "none"}, # temperature erfordert effort none
input=prompt,
temperature=0.0,
)
import json
try:
return json.loads(response.output_text)
except json.JSONDecodeError:
return {"fidelity_score": 0, "error": "Parsing fehlgeschlagen"}
Das Kürzen des Kontexts auf 3.000 Zeichen ist eine Grenze, der Sie sich bewusst sein müssen: Wenn die Quelle diese Größe überschreitet, werden perfekt genaue Aussagen einfach als nicht gestützt gezählt, weil der Abschnitt, der sie rechtfertigt, abgeschnitten wurde. Bei langen Dokumenten teilen Sie und bewerten Sie nach Abschnitten auf, anstatt systematische Falschpositive zu akzeptieren.
Zusammenstellen eines reproduzierbaren Benchmarks
Ein Benchmark ist nicht nur eine weitere Metrik: Es ist eine Einrichtung, die einen Datensatz und ein Protokoll einfriert, sodass zwei Läufe vergleichbar sind. Die Klasse unten misst Relevanz, Treue (wenn Kontext verfügbar ist), Latenz und Kosten und speichert jedes Ergebnis mit Datum und Kennzeichnung der Prompt-Version.
from dataclasses import dataclass, field
import time
@dataclass
class BenchmarkResult:
model: str
prompt_version: str
scores: dict
avg_latency: float
total_cost: float
date: str = field(default_factory=lambda: time.strftime("%Y-%m-%d"))
class Benchmark:
"""Wiederverwendbarer Benchmark zum Vergleichen von Modellen und Prompts."""
def __init__(self, name: str, dataset: list[dict]):
self.name = name
self.dataset = dataset
self.results: list[BenchmarkResult] = []
def run(
self,
model: str,
system_prompt: str,
prompt_version: str,
) -> BenchmarkResult:
"""Führt den Benchmark mit einem gegebenen Modell und Prompt aus."""
relevance_scores = []
hallucination_scores = []
latencies = []
cost = 0
for case in self.dataset:
start = time.perf_counter()
response = client.responses.create(
model=model,
instructions=system_prompt,
input=case["input"],
)
latency = time.perf_counter() - start
latencies.append(latency)
# Bewerten
relevance = relevance_score(
case["input"], response.output_text
)
relevance_scores.append(relevance["score"])
if case.get("context"):
halluc = detect_hallucinations(
case["context"], response.output_text
)
hallucination_scores.append(
halluc.get("fidelity_score", 0)
)
result = BenchmarkResult(
model=model,
prompt_version=prompt_version,
scores={
"relevance": sum(relevance_scores) / len(relevance_scores),
"fidelity": (
sum(hallucination_scores) / len(hallucination_scores)
if hallucination_scores else None
),
},
avg_latency=sum(latencies) / len(latencies),
total_cost=cost,
)
self.results.append(result)
return result
def compare(self) -> str:
"""Vergleicht alle Benchmark-Ergebnisse."""
lines = [f"Benchmark: {self.name}\n"]
for r in self.results:
lines.append(
f" {r.model} (Prompt {r.prompt_version}) | "
f"Relevanz: {r.scores['relevance']:.2f} | "
f"Latenz: {r.avg_latency:.2f}s"
)
return "\n".join(lines)
Die Ausgabe von compare platziert absichtlich Relevanz und Latenz auf derselben Zeile, da dies ist, wie die Entscheidung tatsächlich getroffen wird. Ein Modell, das zwei Hundertstelpunkte an Relevanz gewinnt, während die Antwortzeit verdoppelt wird, ist für eine interaktive Anwendung keine bessere Wahl; es könnte für die Verarbeitung über Nacht sein. Die Tabelle entscheidet nicht für Sie; sie stellt die Abwägung direkt vor Ihre Augen.
Schließlich bleibt total_cost in dieser Implementierung null. Füllen Sie es aus den Nutzungsfeldern aus, die die API zurückgibt: Ein Modellvergleich, der Kosten weglässt, lässt das Kriterium weg, das die Diskussion mit Ihrem Finanzteam beenden wird.
Kernaussagen
- Semantische Ähnlichkeit misst Bedeutung, bleibt aber gegenüber Negationen blind
- Zerlegen Sie Relevanz in getrennte Kriterien statt eines globalen Scores
- Erkennen Sie Halluzinationen, indem Sie die Antwort mit dem Quellkontext konfrontieren
- Erstellen Sie reproduzierbare Benchmarks, die Qualität, Latenz und Kosten gemeinsam messen
- Kombinieren Sie automatische Metriken und LLM-as-Judge für eine vollständige Bewertung