# DeepEval LLM Evaluation

> Test LLM applications with DeepEval, pytest-style unit tests for LLM outputs using G-Eval, answer relevancy, faithfulness, hallucination and custom metrics, with CI quality gates and dataset-driven regression runs.

- Skill: `pramoddutta/deepeval-llm-evaluation` (Agent Skill)
- Install (CLI): `npx skillmds@latest add pramoddutta/deepeval-llm-evaluation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/pramoddutta/deepeval-llm-evaluation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: MIT
- Author: PramodDutta (https://skillmd.com/u/pramoddutta)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/pramoddutta/deepeval-llm-evaluation

---


# DeepEval LLM Evaluation Skill

You are an expert AI quality engineer specializing in DeepEval. When the user asks you to test, evaluate, or gate LLM application outputs, follow these instructions.

## Core Principles

1. **Evals are unit tests.** Write them pytest-style, run them in CI, fail builds on regressions. No dashboard-only quality.
2. **Metric per failure mode.** Pick metrics for the failures that matter (hallucination, irrelevance, unfaithfulness to context), not every metric available.
3. **Thresholds are contracts.** Every metric gets an explicit threshold agreed with the team; a metric without a threshold is a vibe.
4. **Datasets over ad-hoc prompts.** Evaluate against a versioned golden dataset, grow it from production failures.
5. **LLM-as-judge needs spot checks.** Periodically hand-verify judge scores; recalibrate criteria when the judge drifts from human judgment.

## Setup

```bash
pip install deepeval
# judge model key (defaults to OpenAI; other providers configurable)
export OPENAI_API_KEY=sk-...
deepeval login   # optional: Confident AI dashboard for run history
```

## Project Structure

```
llm-app/
├── evals/
│   ├── conftest.py            # fixtures: app client, dataset loader
│   ├── datasets/
│   │   └── golden_v3.jsonl    # versioned eval cases
│   ├── test_correctness.py    # G-Eval correctness suite
│   ├── test_rag_quality.py    # faithfulness + relevancy for RAG
│   └── test_safety.py         # hallucination, bias, toxicity
└── .github/workflows/evals.yml
```

## Writing Eval Tests

```python
import pytest
from deepeval import assert_test
from deepeval.test_case import LLMTestCase
from deepeval.metrics import (
    AnswerRelevancyMetric,
    FaithfulnessMetric,
    HallucinationMetric,
    GEval,
)
from deepeval.test_case import LLMTestCaseParams

def make_case(query: str) -> LLMTestCase:
    response = my_app.answer(query)          # your application under test
    return LLMTestCase(
        input=query,
        actual_output=response.text,
        retrieval_context=response.chunks,   # required for faithfulness
    )

def test_answer_relevancy():
    case = make_case("What is your refund policy for annual plans?")
    assert_test(case, [AnswerRelevancyMetric(threshold=0.8)])

def test_faithfulness_to_context():
    case = make_case("How long does shipping take to Germany?")
    assert_test(case, [FaithfulnessMetric(threshold=0.9)])

# G-Eval: custom criteria in natural language, scored by a judge model
correctness = GEval(
    name="Correctness",
    criteria="Determine whether the actual output states the same policy facts as the expected output. Penalize invented numbers or dates.",
    evaluation_params=[LLMTestCaseParams.ACTUAL_OUTPUT, LLMTestCaseParams.EXPECTED_OUTPUT],
    threshold=0.7,
)

def test_policy_correctness():
    case = LLMTestCase(
        input="Can I cancel within 30 days?",
        actual_output=my_app.answer("Can I cancel within 30 days?").text,
        expected_output="Yes, full refund within 30 days of purchase.",
    )
    assert_test(case, [correctness])
```

Run: `deepeval test run evals/ -n 8` (parallel) or plain `pytest evals/`.

## Dataset-Driven Regression

```python
from deepeval.dataset import EvaluationDataset

dataset = EvaluationDataset()
dataset.add_test_cases_from_json_file(
    file_path="evals/datasets/golden_v3.jsonl",
    input_key_name="input",
    expected_output_key_name="expected",
)

@pytest.mark.parametrize("case", dataset.test_cases)
def test_golden(case):
    case.actual_output = my_app.answer(case.input).text
    assert_test(case, [AnswerRelevancyMetric(threshold=0.8), correctness])
```

Rules for the golden set: 30 to 200 cases per feature; every production incident adds a case; version the file and reference the version in eval reports; never edit expected outputs to make a failing run pass without review.

## Metric Selection Guide

| Failure mode | Metric | Typical threshold |
|---|---|---|
| Answer ignores the question | AnswerRelevancyMetric | 0.7 to 0.85 |
| Answer contradicts retrieved docs | FaithfulnessMetric | 0.85 to 0.95 |
| Invented facts vs provided context | HallucinationMetric | <= 0.1 (lower is better) |
| Domain-specific correctness | GEval with written criteria | 0.7 start, calibrate |
| Retrieval brought wrong chunks | ContextualRelevancyMetric | 0.7 |
| Tone, format, policy compliance | GEval per rule | per rule |

## CI Quality Gate

```yaml
# .github/workflows/evals.yml
- name: Run LLM evals
  env:
    OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
  run: deepeval test run evals/ --display-mode failing
```

Gate policy: block merge on any metric below threshold for P0 flows; run the full golden set nightly (judge calls cost money, so PR runs can use a 20-case smoke slice); alert on aggregate score drops week over week even when above threshold.

## Common Mistakes

- Testing with one hand-written prompt instead of a dataset; you are testing the demo, not the product
- FaithfulnessMetric without passing retrieval_context; the metric needs the chunks
- Thresholds copied from docs instead of calibrated against 20 human-labeled examples
- Letting judge model version float; pin it, bump deliberately, re-baseline
- Ignoring cost: cache app responses so re-runs only re-judge, and slice suites for PR vs nightly

## Checklist

- [ ] Metrics mapped to real failure modes, each with a threshold
- [ ] Golden dataset versioned, grown from production failures
- [ ] PR smoke slice + nightly full run wired in CI
- [ ] Judge model pinned; monthly human spot-check of 10 judge scores
- [ ] Eval results visible to the team (CI output or dashboard), regressions block release

