Evaluation & Testing

Bewertung und Qualitätssicherung von KI-Agenten


Inhaltsverzeichnis

  1. Kurzüberblick: Warum Evaluation?
  2. Evaluierungsebenen
    1. Komponenten-Evaluation
    2. Workflow-Evaluation
    3. Nutzer-Evaluation
  3. Metriken für Agenten
    1. Quantitative Metriken
    2. Qualitative Metriken
  4. Datasets erstellen
    1. Grundstruktur
    2. Dataset-Erstellung mit LangSmith
    3. Best Practices für Datasets
    4. Kategorien von Testfällen
  5. Evaluierungsmethoden
    1. Exakte Übereinstimmung
    2. Teilübereinstimmung
    3. Semantische Ähnlichkeit
    4. LLM-as-Judge
  6. Automatisierte Evaluation mit LangSmith
    1. Evaluator definieren
    2. Evaluation ausführen
    3. Mehrere Evaluatoren kombinieren
  7. Regression Testing
    1. Workflow
    2. Praktische Umsetzung
    3. CI/CD-Integration
  8. Feedback-Schleifen
    1. Production-Feedback sammeln
    2. Feedback → Dataset → Verbesserung
    3. Automatische Anomalie-Erkennung
  9. A/B-Testing
    1. Versuchsaufbau
    2. Auswertung
  10. Evaluation von RAG-Systemen
    1. Retrieval-Evaluation
    2. Generation-Evaluation
    3. End-to-End RAG-Evaluation
  11. Best Practices
    1. Evaluations-Strategie
    2. Häufige Fehler vermeiden
    3. Dokumentation
  12. Zusammenfassung
    1. Kernkonzepte
    2. Evaluierungs-Workflow
    3. Quick Reference
  13. Abgrenzung zu verwandten Dokumenten

Kurzüberblick: Warum Evaluation?

KI-Agenten unterscheiden sich fundamental von klassischer Software: Ihre Ausgaben sind nicht deterministisch, ihre Entscheidungswege oft überraschend, und kleine Prompt-Änderungen können große Auswirkungen haben. Ohne systematische Evaluation entstehen kritische Probleme:

  • Keine Baseline: Ist Version 2.0 wirklich besser als 1.0?
  • Versteckte Regressionen: Ein “Fix” verbessert einen Fall, verschlechtert zehn andere
  • Subjektive Bewertung: “Fühlt sich besser an” ist keine Metrik
  • Production-Überraschungen: Agenten verhalten sich mit echten Nutzern anders als im Test

Systematische Evaluation löst diese Probleme durch:

Aspekt Ohne Evaluation Mit Evaluation
Qualitätsmessung Bauchgefühl Objektive Metriken
Vergleichbarkeit Nicht möglich A/B-Tests, Experimente
Regression Zufällig entdeckt Automatisch erkannt
Dokumentation Anekdoten Reproduzierbare Ergebnisse

Kernprinzip: Was nicht gemessen wird, kann nicht verbessert werden — und bei KI-Agenten ist Messen schwieriger als bei klassischer Software.


Evaluierungsebenen

Komponenten-Evaluation

Einzelne Bausteine werden isoliert getestet:

Komponente Was wird geprüft? Beispiel-Metrik
LLM-Ausgabe Qualität der Antwort Relevanz, Kohärenz
Tool-Auswahl Richtiges Tool gewählt? Accuracy
Tool-Parameter Korrekte Argumente? Validierungsrate
Retriever Relevante Dokumente gefunden? Precision@k, Recall
Strukturierte Ausgabe Schema eingehalten? Validierungsrate

Workflow-Evaluation

Der gesamte Ablauf wird End-to-End bewertet:

  • Erreicht der Agent das gewünschte Ziel?
  • Wie viele Schritte werden benötigt?
  • Welche Fehler treten auf?
  • Wie lange dauert die Ausführung?

Nutzer-Evaluation

Die tatsächliche Nutzererfahrung steht im Fokus:

  • Zufriedenheit mit der Antwort
  • War die Interaktion hilfreich?
  • Würde der Nutzer den Agenten erneut verwenden?

[!WARNING] Ohne Baseline keine belastbare Aussage
Eine einzelne Messung sagt nichts — erst der Vergleich (vorher/nachher, Version A/B) zeigt, ob eine Änderung wirklich hilft oder nur an einem Ort verbessert und anderswo verschlechtert. Regression-Tests sind kein Optional-Feature.

Metriken für Agenten

Quantitative Metriken

Accuracy-basiert:

# Beispiel: Tool-Auswahl-Accuracy
expected_tool = "search_database"
actual_tool = agent_response.tool_calls[0].name

accuracy = 1.0 if expected_tool == actual_tool else 0.0
Metrik Beschreibung Berechnung
Accuracy Anteil korrekter Antworten Richtig / Gesamt
Precision Anteil relevanter unter gefundenen TP / (TP + FP)
Recall Anteil gefundener unter relevanten TP / (TP + FN)
F1-Score Harmonisches Mittel 2 × (P × R) / (P + R)

Performance-basiert:

Metrik Beschreibung Zielwert
Latenz (p50) Median der Antwortzeit < 2 Sekunden
Latenz (p95) 95. Perzentil < 5 Sekunden
Token-Verbrauch Kosten pro Request Minimieren
Schritte bis Lösung Anzahl Tool-Calls Minimieren

Qualitative Metriken

Nicht alles lässt sich in Zahlen fassen. Qualitative Bewertungen erfassen:

  • Kohärenz: Ist die Antwort in sich schlüssig?
  • Relevanz: Beantwortet die Antwort die Frage?
  • Vollständigkeit: Fehlen wichtige Aspekte?
  • Ton: Ist die Antwort angemessen formuliert?
  • Sicherheit: Enthält die Antwort problematische Inhalte?

Datasets erstellen

Gute Evaluation beginnt mit guten Testdaten. Ein Dataset besteht aus Input-Output-Paaren, die das erwartete Verhalten definieren.

Grundstruktur

examples = [
    {
        "inputs": {"question": "Was ist die Hauptstadt von Frankreich?"},
        "outputs": {"answer": "Paris"}
    },
    {
        "inputs": {"question": "Berechne 15 * 7"},
        "outputs": {"answer": "105", "tool_used": "calculator"}
    },
]

Dataset-Erstellung mit LangSmith

from langsmith import Client

client = Client()

# Dataset anlegen
dataset = client.create_dataset(
    dataset_name="Agent-Evaluation-v1",
    description="Testfälle für den Support-Agenten"
)

# Beispiele hinzufügen
for example in examples:
    client.create_example(
        dataset_id=dataset.id,
        inputs=example["inputs"],
        outputs=example["outputs"],
    )

Best Practices für Datasets

Aspekt Empfehlung Begründung
Größe 20–50 Beispiele Balance: Aussagekraft vs. Laufzeit
Diversität Edge Cases abdecken Nicht nur “Happy Path”
Versionierung Namen mit Version v1, v2 für Vergleiche
Dokumentation Beschreibung pro Beispiel Nachvollziehbarkeit
Aktualisierung Bei neuen Fehlern erweitern Kontinuierliche Verbesserung

Kategorien von Testfällen

Ein ausgewogenes Dataset enthält verschiedene Kategorien:

Dataset-Struktur:
├── Happy Path (60%)
│   └── Typische, erwartbare Anfragen
├── Edge Cases (25%)
│   ├── Grenzwerte
│   ├── Ungewöhnliche Formulierungen
│   └── Mehrdeutige Anfragen
└── Negative Tests (15%)
    ├── Ungültige Eingaben
    ├── Out-of-Scope Fragen
    └── Adversariale Inputs

Evaluierungsmethoden

Exakte Übereinstimmung

Die einfachste Form: Stimmt die Ausgabe exakt mit dem Erwartungswert überein?

def exact_match(predicted: str, expected: str) -> float:
    return 1.0 if predicted.strip().lower() == expected.strip().lower() else 0.0

Vorteile: Einfach, deterministisch, schnell
Nachteile: Zu streng für natürlichsprachliche Ausgaben

Teilübereinstimmung

Prüft, ob erwartete Elemente in der Antwort enthalten sind:

def contains_answer(predicted: str, expected_keywords: list) -> float:
    predicted_lower = predicted.lower()
    matches = sum(1 for kw in expected_keywords if kw.lower() in predicted_lower)
    return matches / len(expected_keywords)

Semantische Ähnlichkeit

Nutzt Embeddings, um die Bedeutungsähnlichkeit zu messen:

from langchain_openai import OpenAIEmbeddings
import numpy as np

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

def semantic_similarity(text1: str, text2: str) -> float:
    vec1 = embeddings.embed_query(text1)
    vec2 = embeddings.embed_query(text2)
    # Cosine Similarity
    return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))

LLM-as-Judge

Ein LLM bewertet die Qualität einer Antwort — der mächtigste, aber auch teuerste Ansatz.

from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate

judge_llm = init_chat_model("openai:gpt-4o-mini", temperature=0.0)

judge_prompt = ChatPromptTemplate.from_template("""
Bewerte die folgende Antwort auf einer Skala von 1 bis 5.

Frage: {question}
Erwartete Antwort: {expected}
Tatsächliche Antwort: {actual}

Bewertungskriterien:
- 5: Perfekt, vollständig korrekt
- 4: Korrekt mit minimalen Abweichungen
- 3: Teilweise korrekt
- 2: Überwiegend falsch
- 1: Komplett falsch oder irrelevant

Antworte NUR mit einer Zahl von 1 bis 5.
""")

def llm_judge(question: str, expected: str, actual: str) -> float:
    response = judge_llm.invoke(
        judge_prompt.format(question=question, expected=expected, actual=actual)
    )
    score = int(response.content.strip())
    return score / 5.0  # Normalisiert auf 0-1

Vorteile:

  • Flexibel bei natürlichsprachlichen Ausgaben
  • Kann komplexe Kriterien bewerten
  • Skaliert besser als menschliche Bewertung

Nachteile:

  • Zusätzliche LLM-Kosten
  • Nicht deterministisch
  • Kann eigene Fehler haben (Bias)

Automatisierte Evaluation mit LangSmith

LangSmith bietet integrierte Tools für systematische Evaluation.

Evaluator definieren

from langsmith.evaluation import evaluate

def my_agent(inputs: dict) -> dict:
    """Wrapper für den zu testenden Agenten."""
    response = agent.invoke({
        "messages": [{"role": "user", "content": inputs["question"]}]
    })
    return {"answer": response["messages"][-1].content}

def accuracy_evaluator(inputs: dict, outputs: dict, reference_outputs: dict) -> dict:
    """Custom Evaluator: Vergleicht mit Referenz."""
    predicted = outputs["answer"].lower()
    expected = reference_outputs["answer"].lower()
    
    score = 1.0 if expected in predicted else 0.0
    
    return {
        "key": "contains_answer",
        "score": score,
        "comment": f"Expected '{expected}' in output"
    }

Evaluation ausführen

results = evaluate(
    my_agent,
    data="Agent-Evaluation-v1",
    evaluators=[accuracy_evaluator],
    experiment_prefix="v2.0-release"
)

print(f"Durchschnittliche Accuracy: {results['contains_answer']:.1%}")

Mehrere Evaluatoren kombinieren

def latency_evaluator(inputs: dict, outputs: dict, reference_outputs: dict) -> dict:
    """Bewertet die Antwortzeit."""
    latency = outputs.get("latency_ms", 0)
    score = 1.0 if latency < 2000 else 0.5 if latency < 5000 else 0.0
    return {"key": "latency_ok", "score": score}

def length_evaluator(inputs: dict, outputs: dict, reference_outputs: dict) -> dict:
    """Prüft, ob Antwort nicht zu lang ist."""
    word_count = len(outputs["answer"].split())
    score = 1.0 if word_count <= 100 else 0.5 if word_count <= 200 else 0.0
    return {"key": "concise", "score": score}

# Alle Evaluatoren zusammen
results = evaluate(
    my_agent,
    data="Agent-Evaluation-v1",
    evaluators=[
        accuracy_evaluator,
        latency_evaluator,
        length_evaluator,
    ],
    experiment_prefix="v2.0-full-eval"
)

Regression Testing

Regression Testing stellt sicher, dass Änderungen keine bestehende Funktionalität brechen.

Workflow

1. Baseline etablieren
   └── Evaluation gegen Dataset → Metriken speichern

2. Änderung durchführen
   └── Prompt, Tool, Modell anpassen

3. Re-Evaluation
   └── Gleiche Tests, gleiche Metriken

4. Vergleich
   └── Ist die neue Version besser oder schlechter?

5. Entscheidung
   └── Deployment oder Rollback

Praktische Umsetzung

# Baseline etablieren (einmalig)
baseline_results = evaluate(
    my_agent_v1,
    data="Agent-Evaluation-v1",
    evaluators=[accuracy_evaluator],
    experiment_prefix="baseline-v1"
)

# Nach Änderungen: Re-Evaluation
new_results = evaluate(
    my_agent_v2,
    data="Agent-Evaluation-v1",
    evaluators=[accuracy_evaluator],
    experiment_prefix="candidate-v2"
)

# Automatischer Vergleich
if new_results["contains_answer"] >= baseline_results["contains_answer"]:
    print("✅ Keine Regression — Deployment möglich")
else:
    print("❌ Regression erkannt — Änderungen überprüfen")

CI/CD-Integration

# In pytest-Test integrieren
def test_agent_regression():
    """Regression-Test für CI/CD-Pipeline."""
    results = evaluate(
        my_agent,
        data="Agent-Evaluation-v1",
        evaluators=[accuracy_evaluator],
        experiment_prefix="ci-test"
    )
    
    # Mindest-Schwellenwert
    assert results["contains_answer"] >= 0.8, \
        f"Accuracy zu niedrig: {results['contains_answer']:.1%}"

Feedback-Schleifen

Evaluation ist kein einmaliger Vorgang, sondern ein kontinuierlicher Prozess.

Production-Feedback sammeln

from langsmith import Client

client = Client()

# Nach jeder Agent-Antwort in Production
def collect_user_feedback(run_id: str, rating: int, comment: str = ""):
    """Sammelt Nutzer-Feedback für kontinuierliche Verbesserung."""
    client.create_feedback(
        run_id=run_id,
        key="user_satisfaction",
        score=rating / 5.0,  # Normalisiert auf 0-1
        comment=comment
    )

Feedback → Dataset → Verbesserung

Production-Feedback
       │
       ▼
┌──────────────────┐
│ Schlechte Fälle  │
│ identifizieren   │
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│ Zu Dataset       │
│ hinzufügen       │
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│ Agent            │
│ verbessern       │
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│ Re-Evaluation    │
│ durchführen      │
└──────────────────┘

Automatische Anomalie-Erkennung

def monitor_agent_quality():
    """Überwacht Agent-Qualität in Production."""
    # Letzte 100 Runs abrufen
    runs = client.list_runs(
        project_name="Production-Agent",
        limit=100
    )
    
    # Feedback-Scores aggregieren
    scores = [r.feedback_stats.get("user_satisfaction", {}).get("avg", 0) 
              for r in runs if r.feedback_stats]
    
    avg_score = sum(scores) / len(scores) if scores else 0
    
    # Alert bei Qualitätsabfall
    if avg_score < 0.7:
        send_alert(f"⚠️ Agent-Qualität gesunken: {avg_score:.1%}")

A/B-Testing

Systematischer Vergleich verschiedener Agent-Varianten unter realen Bedingungen.

Versuchsaufbau

import random

def get_agent_variant(user_id: str) -> str:
    """Deterministische Zuweisung zu Variante."""
    # Gleicher User → immer gleiche Variante
    random.seed(hash(user_id))
    return random.choice(["control", "treatment"])

def invoke_with_variant(user_id: str, question: str):
    """Ruft die zugewiesene Agent-Variante auf."""
    variant = get_agent_variant(user_id)
    
    if variant == "control":
        agent = agent_v1  # Bisherige Version
    else:
        agent = agent_v2  # Neue Version
    
    response = agent.invoke({
        "messages": [{"role": "user", "content": question}]
    }, config={
        "metadata": {"variant": variant, "user_id": user_id}
    })
    
    return response

Auswertung

Nach ausreichend Datenpunkten (typischerweise > 100 pro Variante):

def analyze_ab_test():
    """Analysiert A/B-Test-Ergebnisse."""
    control_runs = client.list_runs(
        project_name="Production-Agent",
        filter="eq(metadata.variant, 'control')"
    )
    
    treatment_runs = client.list_runs(
        project_name="Production-Agent",
        filter="eq(metadata.variant, 'treatment')"
    )
    
    control_satisfaction = calculate_avg_satisfaction(control_runs)
    treatment_satisfaction = calculate_avg_satisfaction(treatment_runs)
    
    print(f"Control (v1): {control_satisfaction:.1%}")
    print(f"Treatment (v2): {treatment_satisfaction:.1%}")
    
    if treatment_satisfaction > control_satisfaction + 0.05:
        print("✅ Treatment signifikant besser — Rollout empfohlen")
    elif treatment_satisfaction < control_satisfaction - 0.05:
        print("❌ Treatment schlechter — Rollback empfohlen")
    else:
        print("⚖️ Kein signifikanter Unterschied — mehr Daten sammeln")

Evaluation von RAG-Systemen

RAG-Systeme erfordern spezialisierte Evaluation auf mehreren Ebenen.

Retrieval-Evaluation

Findet der Retriever die richtigen Dokumente?

def evaluate_retrieval(query: str, relevant_doc_ids: list, k: int = 5):
    """Bewertet Retriever-Qualität."""
    results = retriever.invoke(query)
    retrieved_ids = [doc.metadata.get("id") for doc in results[:k]]
    
    # Precision@k
    relevant_retrieved = len(set(retrieved_ids) & set(relevant_doc_ids))
    precision = relevant_retrieved / k
    
    # Recall@k
    recall = relevant_retrieved / len(relevant_doc_ids)
    
    return {"precision": precision, "recall": recall}

Generation-Evaluation

Nutzt das LLM den Kontext korrekt?

Metrik Beschreibung Prüfung
Faithfulness Basiert Antwort auf Kontext? Keine Halluzination
Relevanz Beantwortet Frage? Nicht off-topic
Groundedness Quellenangaben korrekt? Zitate prüfbar
def faithfulness_evaluator(inputs: dict, outputs: dict, reference_outputs: dict) -> dict:
    """Prüft, ob Antwort auf dem Kontext basiert."""
    context = inputs.get("context", "")
    answer = outputs["answer"]
    
    # LLM-as-Judge für Faithfulness
    prompt = f"""
    Kontext: {context}
    
    Antwort: {answer}
    
    Basiert die Antwort ausschließlich auf dem gegebenen Kontext?
    Antworte mit JA oder NEIN.
    """
    
    response = judge_llm.invoke(prompt)
    score = 1.0 if "JA" in response.content.upper() else 0.0
    
    return {"key": "faithfulness", "score": score}

End-to-End RAG-Evaluation

rag_evaluators = [
    retrieval_precision_evaluator,
    retrieval_recall_evaluator,
    faithfulness_evaluator,
    relevance_evaluator,
    latency_evaluator,
]

results = evaluate(
    my_rag_chain,
    data="RAG-Evaluation-v1",
    evaluators=rag_evaluators,
    experiment_prefix="rag-v2"
)

Best Practices

Evaluations-Strategie

Phase Fokus Methode
Entwicklung Schnelles Feedback Kleine Datasets, einfache Metriken
Pre-Release Umfassende Prüfung Volle Datasets, alle Evaluatoren
Production Kontinuierlich Sampling, Feedback-Loops

Häufige Fehler vermeiden

Fehler 1: Zu kleine Datasets

❌ 5 Beispiele → Keine statistische Aussagekraft
✅ 30+ Beispiele → Belastbare Ergebnisse

Fehler 2: Nur Happy Path testen

❌ Nur Standardfälle → Produktions-Überraschungen
✅ Edge Cases einbeziehen → Robustere Agenten

Fehler 3: Evaluation ignorieren nach Launch

❌ Einmalige Evaluation → Schleichende Qualitätsverluste
✅ Kontinuierliches Monitoring → Frühe Problemerkennung

Fehler 4: Falsche Metriken

❌ Nur Accuracy → Langsame, teure Antworten unerkannt
✅ Mehrere Dimensionen → Vollständiges Bild

Dokumentation

Jede Evaluation sollte dokumentiert werden:

## Evaluation Report: Agent v2.1

**Datum:** 2025-01-15
**Dataset:** Agent-Evaluation-v1 (45 Beispiele)
**Experiment:** v2.1-pre-release

### Ergebnisse

| Metrik | v2.0 | v2.1 | Δ |
|--------|------|------|---|
| Accuracy | 82% | 87% | +5% |
| Latenz (p50) | 1.8s | 1.5s | -17% |
| Kosten/Request | $0.02 | $0.018 | -10% |

### Fazit
Version 2.1 zeigt Verbesserungen in allen Metriken.
Empfehlung: Deployment freigeben.

Zusammenfassung

Kernkonzepte

Konzept Beschreibung
Dataset Sammlung von Input-Output-Paaren für reproduzierbare Tests
Evaluator Funktion, die Qualität einer Antwort bewertet
Experiment Ein Durchlauf der Evaluation mit eindeutigem Identifier
Metrik Quantifizierbare Messgröße (Accuracy, Latenz, etc.)
Baseline Referenzwert für Vergleiche
Regression Qualitätsverschlechterung nach Änderungen

Evaluierungs-Workflow

1. Dataset erstellen
   └── Inputs + erwartete Outputs definieren

2. Evaluatoren wählen
   └── Accuracy, LLM-as-Judge, Custom-Metriken

3. Baseline etablieren
   └── Aktuelle Version evaluieren

4. Änderungen testen
   └── Neue Version gegen gleiches Dataset

5. Vergleichen & Entscheiden
   └── Deployment oder Iteration

6. Kontinuierlich überwachen
   └── Production-Feedback → Dataset erweitern

Quick Reference

# Dataset erstellen
from langsmith import Client
client = Client()
dataset = client.create_dataset(dataset_name="My-Tests")

# Evaluator definieren
def my_evaluator(inputs, outputs, reference_outputs):
    score = 1.0 if expected in outputs["answer"] else 0.0
    return {"key": "accuracy", "score": score}

# Evaluation ausführen
from langsmith.evaluation import evaluate
results = evaluate(my_agent, data="My-Tests", evaluators=[my_evaluator])

# Feedback sammeln
client.create_feedback(run_id=run_id, key="user_rating", score=0.8)

Abgrenzung zu verwandten Dokumenten

Dokument Inhalt
Lohnt es sich überhaupt? Machbarkeitsbewertung vor dem Bau — Evaluation bewertet nach dem Bau
RAG-Konzepte RAG-Architektur — dieses Dokument beschreibt deren Evaluierung
Agent Security Sicherheitstests als eigene Evaluierungsebene

Version: 1.0
Stand: November 2025
Kurs: KI-Agenten. Verstehen. Anwenden. Gestalten.