LLM-evaluering med DeepEval: Byg en pålidelig eval-pipeline i Python (2026)

DeepEval er blevet det mest brugte open source-framework til LLM-evaluering i Python. Denne guide dækker G-Eval, RAG-metrikker, hallucinationsdetektion og CI-integration med praktiske kodeeksempler fra tre produktions-implementeringer.

DeepEval LLM-evaluering Guide (2026)

Opdateret: 3. august 2026

LLM-evaluering er processen med systematisk at måle, hvor godt en sprogmodel eller LLM-applikation løser en defineret opgave, typisk ved at kombinere referencebaserede metrikker (BLEU, ROUGE), model-baserede vurderinger (LLM-as-judge, G-Eval) og opgavespecifikke domænemetrikker (faithfulness, answer relevancy, contextual precision). I 2026 er DeepEval blevet det mest udbredte open source-framework til denne opgave i Python-økosystemet, fordi det behandler LLM-tests som pytest-tests og integrerer direkte i CI/CD. Jeg har brugt DeepEval på tre produktions-RAG-systemer det seneste år, og denne guide destillerer det, jeg ville have ønsket at vide fra dag ét.

  • DeepEval kører LLM-tests som pytest-cases, hvilket gør regression testing af prompts trivielt i CI.
  • G-Eval er DeepEvals fleksible LLM-as-judge-metrik, der lader dig definere kriterier i naturligt sprog.
  • For RAG bør du altid måle mindst tre dimensioner: faithfulness, answer relevancy og contextual precision.
  • Deterministiske metrikker (BLEU, exact match) er billige, men fanger sjældent semantiske fejl. Kombinér dem med LLM-as-judge.
  • Confident AI-platformen giver et dashboard oven på DeepEval, men frameworket kan køre helt lokalt uden konto.
  • En god baseline for et produktions-eval-sæt er 50–200 kuraterede testcases per use case, ikke tusinder af syntetiske eksempler.

Hvorfor LLM-evaluering ikke er valgfrit længere

Der findes en påstand, jeg møder ofte: "Vi kigger bare manuelt på outputtet og ser om det ser rigtigt ud." Det holder præcis indtil den dag, hvor din prompt ændres, en model opdateres, eller en ny retriever-strategi rulles ud, og pludselig producerer systemet subtilt værre svar for 8% af trafikken uden at nogen opdager det. Uden en automatiseret eval-pipeline er du blind for regressions.

Det er også blevet en compliance-nødvendighed. EU AI Act, der trådte i fuld kraft i august 2026, kræver dokumenteret systematisk testning for højrisiko-AI-systemer, herunder LLM-baserede beslutningsstøttesystemer. En eval-suite er ikke længere "nice to have". Det er dokumentation, du skal kunne fremvise ved audit.

Endelig er der økonomien. Uden evals ender teams typisk med at re-teste manuelt hver gang de rører en prompt, hvilket er den dyreste form for QA, der findes. En eval-pipeline med 100 testcases, der kører på 3 minutter, er billigere at eje end fem manuelle QA-runder om måneden.

DeepEval vs. RAGAS: hvad er forskellen?

Både DeepEval og RAGAS er open source-frameworks til LLM-evaluering, men de har forskellige styrker. Kort sagt: RAGAS er specialiseret til RAG-pipelines og har en meget kompakt API. DeepEval er bredere. Det dækker RAG, agenter, chatbots, red teaming og lader dig definere brugerdefinerede metrikker via G-Eval.

EgenskabDeepEvalRAGAS
Primært fokusBred LLM-evalueringRAG-pipelines
Test-integrationNative pytest-integrationKræver wrapper-kode
Brugerdefinerede metrikkerJa, via G-Eval (naturligt sprog)Begrænset
Agent-evalueringTask Completion, Tool CorrectnessIkke understøttet
Red teamingDeepTeam (integreret)Nej
DashboardConfident AI (SaaS eller self-hosted)Ingen officiel
Modeller understøttetAlle via LiteLLMAlle via LiteLLM

I min erfaring vælger jeg DeepEval, når jeg bygger noget nyt fra bunden, fordi pytest-integrationen sparer så meget lim-kode. Hvis jeg overtager et eksisterende RAG-projekt, der allerede bruger RAGAS, bevarer jeg det. Begge frameworks er velvedligeholdte, og skiftet er sjældent umagen værd.

Installation og første testcase

DeepEval installeres via pip. Frameworket understøtter Python 3.9+ og kræver en OpenAI- eller Anthropic-nøgle for de LLM-baserede metrikker (du kan også pege det på en lokal Ollama-model).

pip install -U deepeval
export OPENAI_API_KEY="sk-..."
# Alternativt for Claude:
# export ANTHROPIC_API_KEY="sk-ant-..."
# deepeval set-anthropic --model "claude-sonnet-5"

Her er den enkleste tænkelige test. Vi tjekker, om et modelsvar er relevant for et input:

from deepeval import assert_test
from deepeval.metrics import AnswerRelevancyMetric
from deepeval.test_case import LLMTestCase

def test_answer_relevancy():
    test_case = LLMTestCase(
        input="Hvad er hovedstaden i Danmark?",
        actual_output="København er hovedstaden i Danmark og har cirka 660.000 indbyggere.",
    )
    metric = AnswerRelevancyMetric(threshold=0.7, model="gpt-4o-mini")
    assert_test(test_case, [metric])

Kør testen præcis som en almindelig pytest:

deepeval test run test_relevancy.py

Bag kulisserne kalder AnswerRelevancyMetric en dommer-LLM (som standard gpt-4o-mini), som deler outputtet op i statements, vurderer hver enkelts relevans for spørgsmålet, og returnerer en score mellem 0 og 1. Threshold på 0.7 er en rimelig start. Jeg strammer den ofte til 0.8 for produktionskritiske use cases.

G-Eval: LLM-as-judge med brugerdefinerede kriterier

De indbyggede metrikker dækker de mest almindelige tilfælde, men før eller siden vil du evaluere noget domænespecifikt: "Er tonen professionel?", "Nævnes alle produkter i faktura-summeringen?", "Er kodesvaret idiomatisk Python?". Her kommer G-Eval ind. G-Eval er DeepEvals implementation af den G-Eval-paper-metode, hvor du beskriver et evalueringskriterium i naturligt sprog, og LLM-dommeren genererer chain-of-thought-trin og en score.

from deepeval.metrics import GEval
from deepeval.test_case import LLMTestCaseParams, LLMTestCase

professionel_tone = GEval(
    name="Professionel tone",
    criteria=(
        "Vurder om svaret bruger en professionel, respektfuld tone "
        "uden slang, sarkasme eller kolloquialismer. Formelt dansk "
        "forretningssprog forventes."
    ),
    evaluation_params=[
        LLMTestCaseParams.INPUT,
        LLMTestCaseParams.ACTUAL_OUTPUT,
    ],
    threshold=0.8,
    model="gpt-4o",
)

test_case = LLMTestCase(
    input="Kan I refundere min ordre?",
    actual_output=(
        "Selvfølgelig, vi behandler din refundering inden for "
        "5 hverdage. Send venligst ordrenummeret til support@..."
    ),
)
professionel_tone.measure(test_case)
print(professionel_tone.score, professionel_tone.reason)

Det, jeg synes gør G-Eval brugbar i praksis, er reason-feltet. Når en test fejler, får du en forklaring på dansk, du kan læse, ikke bare et tal. Det gør det trivielt at diskutere fejl med domæneeksperter, der ikke kender til embeddings eller cosine similarity.

RAG-metrikker: faithfulness, relevancy og precision

Hvis du bygger en RAG-pipeline (og du har fulgt vores RAG-pipeline med ChromaDB-guide), vil du bruge mindst tre af DeepEvals indbyggede RAG-metrikker:

  • Faithfulness: Er svaret understøttet af den hentede kontekst, eller hallucinerer modellen fakta udenfor konteksten?
  • Answer Relevancy: Adresserer svaret faktisk brugerens spørgsmål?
  • Contextual Precision: Rangeres de mest relevante chunks højest af din retriever?
  • Contextual Recall: Henter din retriever al nødvendig information for at besvare spørgsmålet?
from deepeval.metrics import (
    FaithfulnessMetric,
    AnswerRelevancyMetric,
    ContextualPrecisionMetric,
    ContextualRecallMetric,
)
from deepeval.test_case import LLMTestCase

test_case = LLMTestCase(
    input="Hvornår trådte EU AI Act i fuld kraft?",
    actual_output="EU AI Act trådte i fuld kraft i august 2026.",
    expected_output="EU AI Act blev fuldt anvendelig 2. august 2026.",
    retrieval_context=[
        "EU AI Act blev vedtaget i 2024 og blev fuldt "
        "anvendelig den 2. august 2026 for højrisikosystemer.",
        "Forordningen dækker biometrisk identifikation, "
        "kritisk infrastruktur og LLM-baserede beslutningsstøttesystemer.",
    ],
)

metrics = [
    FaithfulnessMetric(threshold=0.8),
    AnswerRelevancyMetric(threshold=0.7),
    ContextualPrecisionMetric(threshold=0.7),
    ContextualRecallMetric(threshold=0.7),
]
for m in metrics:
    m.measure(test_case)
    print(f"{m.__class__.__name__}: {m.score:.2f} — {m.reason[:100]}...")

Læg mærke til, at Faithfulness og Answer Relevancy ikke behøver expected_output; de er reference-fri. Dette er nyttigt i produktion, hvor du ofte har logs uden ground truth. ContextualPrecision og ContextualRecall kræver derimod en forventet output for at sammenligne mod.

Hallucinationsdetektion i praksis

Hallucinationer er den enkeltstående største grund til, at LLM-applikationer mister brugertillid. DeepEvals HallucinationMetric sammenligner modellens output mod en liste af kontekstpassager og markerer alle påstande, der ikke er understøttet:

from deepeval.metrics import HallucinationMetric
from deepeval.test_case import LLMTestCase

test_case = LLMTestCase(
    input="Beskriv Claude Sonnet 5's context window.",
    actual_output=(
        "Claude Sonnet 5 har et 500K token context window "
        "og understøtter 40 sprog."
    ),
    context=[
        "Claude Sonnet 5 har et 200K token context window "
        "og understøtter over 100 sprog."
    ],
)

metric = HallucinationMetric(threshold=0.3)
metric.measure(test_case)
print(f"Hallucination score: {metric.score}")
print(f"Reason: {metric.reason}")

I dette eksempel vil scoren være høj (dårlig), fordi outputtet påstår "500K token context window" og "40 sprog", mens konteksten siger "200K" og "over 100 sprog". Reason-feltet vil nævne præcis hvilke påstande, der er ubelagte.

Et almindeligt setup, jeg bruger, er at køre HallucinationMetric som en gate i CI: hvis nogen ændrer en prompt og hallucination-score stiger over baseline med mere end 0.05, fejler builden. Dette har fanget adskillige subtile prompt-regressioner for mig, som manuel review ville have misset.

Evaluering af AI-agenter og tool use

Evaluering af agenter er svært, fordi de involverer flerhandlings-trajektorier, ikke bare et enkelt input/output-par. DeepEval 2.x introducerede TaskCompletionMetric og ToolCorrectnessMetric specifikt til dette. Hvis du bygger agenter, anbefaler jeg også vores guide til function calling og tool use med LLM'er i Python, som dækker de underliggende primitiver.

from deepeval.metrics import ToolCorrectnessMetric
from deepeval.test_case import LLMTestCase, ToolCall

test_case = LLMTestCase(
    input="Bestil en Uber til Nørreport Station kl. 18:00",
    actual_output="Din Uber er bestilt til kl. 18:00.",
    tools_called=[
        ToolCall(name="get_current_location"),
        ToolCall(
            name="book_uber",
            input_parameters={
                "destination": "Nørreport Station",
                "pickup_time": "18:00",
            },
        ),
    ],
    expected_tools=[
        ToolCall(name="book_uber"),
    ],
)

metric = ToolCorrectnessMetric(threshold=0.9)
metric.measure(test_case)
print(metric.score, metric.reason)

Metrikken tjekker, at agenten kaldte de forventede værktøjer (og kun dem), med de rigtige argumenter. For mere komplekse agent-workflows (som dem, du kunne bygge med LangGraph) kan du kombinere TaskCompletionMetric (blev slutmålet nået?) med custom G-Eval-metrikker for delmål.

Integration i CI/CD med GitHub Actions

Den virkelige gevinst kommer, når evals kører automatisk på hver pull request. Her er en minimal GitHub Actions-workflow:

name: LLM Evals
on: [pull_request]

jobs:
  eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - name: Run DeepEval
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: |
          deepeval test run tests/evals/ \
            --parallel 4 \
            --cache
      - name: Upload results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: eval-results
          path: .deepeval-cache/

--cache-flaget er kritisk her: det genbruger metric-resultater for uændrede testcases, hvilket reducerer omkostninger og runtime dramatisk. En suite med 200 testcases går fra ~15 minutter til ~90 sekunder på en typisk PR, hvor kun en håndfuld tests er berørt.

Dataset-strategi: hvor mange testcases?

Et almindeligt spørgsmål er, hvor mange testcases du har brug for. Mit svar, baseret på tre produktionsimplementationer: 50–200 kuraterede cases per use case slår 5.000 syntetisk genererede cases hver eneste gang. Kvalitet trumfer volumen.

En balanceret suite indeholder typisk:

  • Golden cases (20–40%): Kanoniske eksempler, hvor du kender det korrekte svar præcist.
  • Edge cases (30–40%): Uklare, tvetydige eller ondsindede inputs. Prompt injections, kodeblokke i normale spørgsmål, meget lange inputs.
  • Regression cases (20–30%): Konkrete bugs, du har rettet, for at sikre, at de ikke vender tilbage.
  • Domænespecifikke cases (10–20%): Fra faktiske produktionslogs, anonymiseret.

DeepEval kan generere syntetiske cases via Synthesizer-klassen, hvilket er nyttigt til at bootstrappe et dataset. Men behandl syntetiske cases som et udgangspunkt, aldrig som endelig sandhed. De tenderer til at teste de mest åbenlyse ting og misser den slags subtile fejl, brugere faktisk møder.

Ofte stillede spørgsmål

Hvad er forskellen på DeepEval og LangSmith?

LangSmith er en observability-platform (traces, logs, dashboards) primært designet omkring LangChain-økosystemet. DeepEval er et evalueringsframework, der fokuserer på at score outputs mod metrikker og køre som tests. De er komplementære: mange teams bruger LangSmith til produktions-traces og DeepEval til CI-tests.

Kan DeepEval køre uden en OpenAI-nøgle?

Ja. Du kan konfigurere en lokal model via deepeval set-local-model (fx en Ollama- eller vLLM-endpoint), eller bruge Anthropic Claude med deepeval set-anthropic. Referencebaserede metrikker som BLEU og ROUGE kræver slet ingen LLM.

Hvor meget koster det at køre en eval-suite?

Med gpt-4o-mini som dommer og 100 testcases på 4 metrikker koster en fuld kørsel typisk 0,10–0,40 USD. Med caching aktiveret betaler du kun for berørte testcases på efterfølgende kørsler, hvilket gør daglig CI-kørsel praktisk økonomisk overkommelig.

Hvordan tester jeg AI-agenter, der har flere trin?

Brug TaskCompletionMetric til at vurdere om slutmålet blev nået, og ToolCorrectnessMetric til at verificere, at de rigtige værktøjer blev kaldt. For komplekse agenter (fx LangGraph-baserede) kan du desuden logge hvert delskridt som en separat LLMTestCase og evaluere trajektorien som en helhed.

Er LLM-as-judge pålideligt?

Pålideligt nok til de fleste use cases, men ikke perfekt. Studier viser 80–85% enighed mellem GPT-4-baserede dommere og menneskelige annotatorer på tekstkvalitet. For høj-stakes beslutninger bør du kalibrere dommer-modellen mod et menneskeligt-mærket referencesæt og supplere med deterministiske checks.

Nikhil Verma
Om Forfatteren Nikhil Verma

AI automation engineer chaining LLMs into workflows that actually work. Bullish on tool use; bearish on prompt theatre.