DeepEval: Unit Testing and Evaluation for LLM Applications
Building applications powered by Large Language Models (LLMs) such as RAG chatbots, AI assistants, or generative pipelines isn't just about making the model work. The real challenge is ensuring output quality consistently. How do you know your chatbot isn't hallucinating? How do you measure answer relevancy to user questions? How do you detect bias or toxic content in responses?
DeepEval is an open-source framework designed specifically to answer all these questions. DeepEval allows you to write unit tests for LLM applications just like you write unit tests for traditional software, complete with proven evaluation metrics and pytest integration.What Is DeepEval?
DeepEval is a Python framework for testing and evaluating LLM applications. The framework provides:
- Unit Testing for LLMs: Write test cases that validate LLM output with measurable metrics
- 14+ Built-in Metrics: Hallucination, Answer Relevancy, Faithfulness, Bias, Toxicity, and more
- Pytest Integration: Run LLM evaluations like regular test suites
- Custom Metrics: Create evaluation metrics tailored to your business needs
- Conversational Metrics: Evaluate multi-turn conversation quality
- Benchmarks: Test your model against standard benchmarks like MMLU and HellaSwag
- Confident AI Dashboard: Monitoring and analytics platform for evaluation results
- CI/CD Ready: Integrate evaluations into your deployment pipeline
Installation
Basic Installation
pip install deepeval
Installation with Additional Dependencies
# For all features including benchmarks
pip install deepeval[benchmark]
For Confident AI integration
pip install deepeval[dashboard]
Initial Setup
After installation, login to Confident AI (optional but recommended for tracking):
deepeval login
Or set the API key manually:
export DEEPEVALAPIKEY="your-api-key-here"
Make sure you also have an API key for the LLM used as evaluator:
export OPENAIAPIKEY="your-openai-api-key"
Core Concept: LLMTestCase
LLMTestCase is the fundamental unit in DeepEval. Each test case represents a single interaction with an LLM that you want to evaluate.
from deepeval.testcase import LLMTestCase
test
case = LLMTestCase(
input="What is the capital of Indonesia?",
actualoutput="The capital of Indonesia is Jakarta. However, the Indonesian government is building a new capital called Nusantara (IKN) in East Kalimantan.",
expectedoutput="Jakarta (with note about IKN Nusantara as the new capital)",
retrievalcontext=[
"Jakarta has been the capital of Indonesia since independence in 1945.",
"Nusantara (IKN) is Indonesia's capital relocation project to East Kalimantan, inaugurated in 2024."
]
)
Key LLMTestCase parameters:
| Parameter | Description | Required |
|-----------|-------------|----------|
| input | The question or prompt given to the LLM | Yes |
| actualoutput | The actual response from the LLM | Yes |
| expectedoutput | The expected response (ground truth) | Depends on metric |
| retrievalcontext | Context retrieved from knowledge base | Depends on metric |
| context | Ground truth context (ideal) | Depends on metric |
Built-in Evaluation Metrics
DeepEval provides various ready-to-use evaluation metrics. Here are the key metrics explained:
1. Hallucination Metric
Measures whether the LLM output contains information not supported by the given context.
from deepeval.metrics import HallucinationMetric
from deepeval.testcase import LLMTestCase
metric = HallucinationMetric(threshold=0.5)
testcase = LLMTestCase(
input="Tell me about PT ABC company",
actualoutput="PT ABC was founded in 1990 in Jakarta. The company has 5000 employees and operates in the technology sector.",
context=[
"PT ABC was founded in 1990 in Jakarta.",
"PT ABC operates in the information technology sector.",
"PT ABC has approximately 2000 employees."
]
)
metric.measure(testcase)
print(f"Score: {metric.score}") # 0.0 - 1.0 (lower is better)
print(f"Reason: {metric.reason}") # Explanation of why the score was given
In the example above, the LLM stated "5000 employees" while the context mentions "2000 employees", resulting in a hallucination.
2. Answer Relevancy Metric
Measures how relevant the LLM's answer is to the given question.
from deepeval.metrics import AnswerRelevancyMetric
metric = AnswerRelevancyMetric(threshold=0.7)
testcase = LLMTestCase(
input="How do I install Python on Ubuntu?",
actualoutput="To install Python on Ubuntu, run: sudo apt update && sudo apt install python3. You can also use pyenv to manage multiple Python versions.",
retrievalcontext=[
"Python can be installed on Ubuntu using the apt package manager.",
"Pyenv is a tool for managing multiple Python versions."
]
)
metric.measure(testcase)
print(f"Score: {metric.score}")
print(f"Reason: {metric.reason}")
3. Faithfulness Metric
Measures whether the LLM output is faithful to the retrieved context. Unlike the hallucination metric, faithfulness focuses more on whether claims in the output are supported by retrieval context.
from deepeval.metrics import FaithfulnessMetric
metric = FaithfulnessMetric(threshold=0.7)
testcase = LLMTestCase(
input="What are the benefits of regular exercise?",
actualoutput="Regular exercise can improve heart health, reduce diabetes risk, and boost mood.",
retrievalcontext=[
"Research shows regular exercise improves cardiovascular health.",
"Physical activity helps regulate blood sugar levels and reduces risk of type 2 diabetes.",
"Exercise releases endorphins that can improve mood."
]
)
metric.measure(testcase)
print(f"Score: {metric.score}")
4. Bias Metric
Detects whether the LLM output contains bias related to gender, race, religion, or other groups.
from deepeval.metrics import BiasMetric
metric = BiasMetric(threshold=0.5)
testcase = LLMTestCase(
input="Who is suitable to become a software engineer?",
actualoutput="Anyone can become a software engineer, regardless of gender, age, or background. What matters most is willingness to learn, problem-solving ability, and dedication to continuous growth."
)
metric.measure(testcase)
print(f"Score: {metric.score}") # Low score = not biased (good)
5. Toxicity Metric
Detects toxic, offensive, or harmful content in LLM output.
from deepeval.metrics import ToxicityMetric
metric = ToxicityMetric(threshold=0.5)
testcase = LLMTestCase(
input="Give feedback on this code",
actualoutput="Your code is fairly good, but there are some areas for improvement: 1) Use consistent naming conventions, 2) Add error handling, 3) Separate business logic from controllers."
)
metric.measure(testcase)
print(f"Score: {metric.score}") # Low score = not toxic (good)
Running Tests with Pytest
One of DeepEval's key strengths is its integration with pytest. You can run LLM evaluations as a test suite.
File Structure
project/
├── tests/
│ ├── testchatbot.py
│ ├── testragpipeline.py
│ └── conftest.py
├── src/
│ └── chatbot.py
└── pyproject.toml
Writing Test Files
# tests/testchatbot.py
import pytest
from deepeval import asserttest
from deepeval.testcase import LLMTestCase
from deepeval.metrics import (
AnswerRelevancyMetric,
FaithfulnessMetric,
HallucinationMetric,
)
Import your chatbot function
from src.chatbot import getchatbotresponse
def generatetestcase(question: str) -> LLMTestCase:
"""Helper to create test case from chatbot."""
response = getchatbotresponse(question)
return LLMTestCase(
input=question,
actualoutput=response["answer"],
retrievalcontext=response["sources"],
context=response.get("groundtruthcontext", [])
)
def testanswerrelevancy():
testcase = generatetestcase("What is the company refund policy?")
metric = AnswerRelevancyMetric(threshold=0.7)
asserttest(testcase, [metric])
def testfaithfulness():
testcase = generatetestcase("How do I apply for leave?")
metric = FaithfulnessMetric(threshold=0.7)
asserttest(testcase, [metric])
def testnohallucination():
testcase = generatetestcase("What is the minimum employee salary?")
metric = HallucinationMetric(threshold=0.5)
asserttest(testcase, [metric])
def testmultiplemetrics():
"""Test with multiple metrics at once."""
testcase = generatetestcase("What are the employee health benefits?")
metrics = [
AnswerRelevancyMetric(threshold=0.7),
FaithfulnessMetric(threshold=0.7),
HallucinationMetric(threshold=0.5),
]
asserttest(testcase, metrics)
Running Tests
# Run all tests
deepeval test run tests/
Run specific test file
deepeval test run tests/testchatbot.py
Run with verbose output
deepeval test run tests/testchatbot.py -v
Run and send results to Confident AI
deepeval test run tests/testchatbot.py --confident
You can also use pytest directly:
pytest tests/testchatbot.py -v
Custom Metrics
When built-in metrics aren't enough, you can create custom metrics tailored to your business needs.
Custom Metric with LLM as Judge
from deepeval.metrics import GEval
from deepeval.testcase import LLMTestCase, LLMTestCaseParams
Create custom metric to measure answer completeness
completenessmetric = GEval(
name="Completeness",
criteria="Evaluate whether the response comprehensively addresses all aspects of the user's question. A complete response should cover all key points without leaving important information out.",
evaluationparams=[
LLMTestCaseParams.INPUT,
LLMTestCaseParams.ACTUALOUTPUT,
],
threshold=0.7,
)
testcase = LLMTestCase(
input="Explain the steps to create a REST API with FastAPI",
actualoutput="To create a REST API with FastAPI: 1) Install FastAPI and uvicorn, 2) Create main.py file, 3) Define routes with decorators, 4) Run with uvicorn."
)
completenessmetric.measure(testcase)
print(f"Completeness Score: {completenessmetric.score}")
print(f"Reason: {completenessmetric.reason}")
Custom Metric with Custom Logic
from deepeval.metrics import BaseMetric
from deepeval.testcase import LLMTestCase
class ResponseLengthMetric(BaseMetric):
"""Metric to ensure response has appropriate length."""
def init(self, minwords: int = 50, maxwords: int = 500, threshold: float = 0.5):
self.threshold = threshold
self.minwords = minwords
self.maxwords = maxwords
def measure(self, testcase: LLMTestCase) -> float:
wordcount = len(testcase.actualoutput.split())
if self.minwords <= wordcount <= self.maxwords:
self.score = 1.0
self.reason = f"Response length ({wordcount} words) is within acceptable range."
elif wordcount < self.minwords:
self.score = wordcount / self.minwords
self.reason = f"Response too short ({wordcount} words). Minimum: {self.minwords}."
else:
self.score = self.maxwords / wordcount
self.reason = f"Response too long ({wordcount} words). Maximum: {self.maxwords}."
self.success = self.score >= self.threshold
return self.score
def issuccessful(self) -> bool:
return self.success
@property
def name(self):
return "Response Length"
Conversational Metrics
For chatbot applications, DeepEval provides specialized metrics that evaluate multi-turn conversation quality.
from deepeval.testcase import ConversationalTestCase, LLMTestCase
from deepeval.metrics import ConversationRelevancyMetric
Simulate multi-turn conversation
conversation = ConversationalTestCase(
turns=[
LLMTestCase(
input="Hello, I'd like to ask about your products",
actualoutput="Hello! Of course, I'd be happy to help. Which product would you like to know about?"
),
LLMTestCase(
input="How much is the Enterprise plan?",
actualoutput="Our Enterprise plan starts at $3,000 per month. This package includes unlimited user access, 24/7 dedicated support, custom integration, and 99.9% SLA."
),
LLMTestCase(
input="Is there a free trial?",
actualoutput="Yes, we offer a 14-day free trial for the Enterprise plan. You can sign up on our website without a credit card."
),
]
)
metric = ConversationRelevancyMetric(threshold=0.7)
metric.measure(conversation)
print(f"Conversation Relevancy: {metric.score}")
Benchmarks: MMLU and HellaSwag
DeepEval allows you to test models against standard benchmarks to measure general capabilities.
MMLU (Massive Multitask Language Understanding)
MMLU tests model understanding across 57 subjects, from STEM to humanities.
from deepeval.benchmarks import MMLU
from deepeval.benchmarks.tasks import MMLUTask
Evaluate on specific subjects
benchmark = MMLU(
tasks=[
MMLUTask.COMPUTERSCIENCE,
MMLUTask.MATHEMATICS,
MMLUTask.PHYSICS,
],
nshots=5 # few-shot examples
)
Run benchmark on your model
benchmark.evaluate(model=yourmodel)
HellaSwag
HellaSwag tests model ability in commonsense reasoning by completing scenarios.
from deepeval.benchmarks import HellaSwag
benchmark = HellaSwag(
nshots=10
)
Run benchmark
benchmark.evaluate(model=yourmodel)
Confident AI Dashboard
Confident AI is the companion platform for DeepEval that provides a visual dashboard for monitoring and analytics.Setting Up Confident AI
# Login to Confident AI
deepeval login
Run tests and send results to dashboard
deepeval test run tests/ --confident
Key Dashboard Features
- Test Run History: View all test run results with time trends
- Metric Breakdown: Detailed per-metric analysis with score distributions
- Failure Analysis: Identify failed test cases and their reasons
- Comparison: Compare performance across models or prompt versions
- Dataset Management: Manage golden datasets for consistent evaluation
- Collaboration: Share results with team through shared workspaces
Sending Results Programmatically
import deepeval
from deepeval.testcase import LLMTestCase
from deepeval.metrics import AnswerRelevancyMetric
from deepeval.dataset import EvaluationDataset
Create evaluation dataset
dataset = EvaluationDataset(
testcases=[
LLMTestCase(
input="What is machine learning?",
actualoutput="Machine learning is a branch of AI that enables computers to learn from data without being explicitly programmed.",
retrievalcontext=["Machine learning is a subset of artificial intelligence."]
),
# ... more test cases
]
)
Evaluate and send to Confident AI
results = deepeval.evaluate(
testcases=dataset.testcases,
metrics=[AnswerRelevancyMetric(threshold=0.7)],
)
CI/CD Integration
Integrate DeepEval into your CI/CD pipeline to ensure LLM quality is maintained with every change.
GitHub Actions
# .github/workflows/llm-tests.yml
name: LLM Evaluation
on:
push:
branches: [main]
pullrequest:
branches: [main]
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install deepeval
- name: Run LLM evaluations
env:
OPENAIAPIKEY: ${{ secrets.OPENAIAPIKEY }}
DEEPEVALAPIKEY: ${{ secrets.DEEPEVALAPIKEY }}
run: |
deepeval test run tests/ --confident
- name: Check evaluation results
if: failure()
run: echo "LLM evaluation failed. Check Confident AI dashboard for details."
GitLab CI
# .gitlab-ci.yml
llm-evaluation:
stage: test
image: python:3.11
script:
- pip install -r requirements.txt
- pip install deepeval
- deepeval test run tests/ --confident
variables:
OPENAIAPIKEY: $OPENAIAPIKEY
DEEPEVALAPIKEY: $DEEPEVALAPIKEY
only:
- main
- mergerequests
Practical Example: Test Suite for a RAG Chatbot
Let's build a complete test suite for a RAG chatbot that answers questions about company policies.
Project Structure
rag-chatbot/
├── src/
│ ├── chatbot.py
│ ├── retriever.py
│ └── config.py
├── tests/
│ ├── conftest.py
│ ├── testhallucination.py
│ ├── testrelevancy.py
│ └── testcomprehensive.py
├── data/
│ └── testgoldendataset.json
└── requirements.txt
Golden Dataset
[
{
"input": "How long is the probation period for new employees?",
"expectedoutput": "The probation period for new employees is 3 months.",
"context": [
"Every new employee undergoes a 3-month probation period.",
"During probation, employees receive weekly evaluations from their supervisor."
]
},
{
"input": "What is the procedure for annual leave?",
"expectedoutput": "Annual leave is submitted through the HR system at least 3 days in advance and must be approved by the direct supervisor.",
"context": [
"Annual leave requests are made through the online HR system.",
"Employees must submit leave requests at least 3 working days in advance.",
"Leave approval is given by the employee's direct supervisor."
]
},
{
"input": "Does the company provide health insurance?",
"expectedoutput": "Yes, the company provides health insurance for permanent employees and their immediate family.",
"context": [
"The company provides health insurance for all permanent employees.",
"Insurance covers the employee, spouse, and up to 3 children.",
"Probationary employees do not yet receive insurance benefits."
]
}
]
Conftest for Shared Fixtures
# tests/conftest.py
import json
import pytest
from deepeval.testcase import LLMTestCase
def loadgoldendataset():
with open("data/testgoldendataset.json", "r") as f:
return json.load(f)
def simulatechatbotresponse(question: str, context: list[str]) -> str:
"""
Simulate RAG chatbot response.
In real implementation, this would call your chatbot.
"""
from src.chatbot import getresponse
return getresponse(question)
@pytest.fixture
def goldendataset():
return loadgoldendataset()
@pytest.fixture
def testcases(goldendataset):
cases = []
for item in goldendataset:
response = simulatechatbotresponse(item["input"], item["context"])
cases.append(
LLMTestCase(
input=item["input"],
actualoutput=response,
expectedoutput=item["expectedoutput"],
context=item["context"],
retrievalcontext=item["context"],
)
)
return cases
Hallucination Tests
# tests/testhallucination.py
import pytest
from deepeval import asserttest
from deepeval.metrics import HallucinationMetric
class TestHallucination:
"""Test suite for detecting chatbot hallucinations."""
@pytest.fixture
def metric(self):
return HallucinationMetric(
threshold=0.5,
model="gpt-4o",
)
def testprobationnohallucination(self, testcases, metric):
"""Ensure probation answer doesn't hallucinate."""
testcase = testcases[0]
asserttest(testcase, [metric])
def testleavenohallucination(self, testcases, metric):
"""Ensure leave policy answer doesn't hallucinate."""
testcase = testcases[1]
asserttest(testcase, [metric])
def testinsurancenohallucination(self, testcases, metric):
"""Ensure insurance answer doesn't hallucinate."""
testcase = testcases[2]
asserttest(testcase, [metric])
Relevancy Tests
# tests/testrelevancy.py
import pytest
from deepeval import asserttest
from deepeval.metrics import AnswerRelevancyMetric, FaithfulnessMetric
class TestRelevancy:
"""Test suite for measuring chatbot answer relevancy."""
@pytest.fixture
def relevancymetric(self):
return AnswerRelevancyMetric(threshold=0.7)
@pytest.fixture
def faithfulnessmetric(self):
return FaithfulnessMetric(threshold=0.7)
def testanswersarerelevant(self, testcases, relevancymetric):
"""All answers must be relevant to the question."""
for testcase in testcases:
asserttest(testcase, [relevancymetric])
def testanswersarefaithful(self, testcases, faithfulnessmetric):
"""All answers must be faithful to the context."""
for testcase in testcases:
asserttest(testcase, [faithfulnessmetric])
Comprehensive Tests
# tests/testcomprehensive.py
import pytest
from deepeval import asserttest
from deepeval.metrics import (
AnswerRelevancyMetric,
FaithfulnessMetric,
HallucinationMetric,
BiasMetric,
ToxicityMetric,
)
from deepeval.metrics import GEval
from deepeval.testcase import LLMTestCaseParams
class TestComprehensive:
"""Comprehensive test suite with all metrics."""
@pytest.fixture
def allmetrics(self):
return [
AnswerRelevancyMetric(threshold=0.7),
FaithfulnessMetric(threshold=0.7),
HallucinationMetric(threshold=0.5),
BiasMetric(threshold=0.5),
ToxicityMetric(threshold=0.5),
GEval(
name="Helpfulness",
criteria="Evaluate whether the response is helpful and actionable for the user.",
evaluationparams=[
LLMTestCaseParams.INPUT,
LLMTestCaseParams.ACTUALOUTPUT,
],
threshold=0.7,
),
]
def testfullevaluation(self, testcases, allmetrics):
"""Full evaluation of each test case with all metrics."""
for testcase in testcases:
asserttest(testcase, allmetrics)
Running the Test Suite
# Run all tests
deepeval test run tests/ -v
Run only hallucination tests
deepeval test run tests/test_hallucination.py -v
Run and send to Confident AI
deepeval test run tests/ --confident
Run with pytest
pytest tests/ -v --tb=short
Tips and Best Practices
Conclusion
DeepEval brings the software testing paradigm to the LLM world, enabling development teams to ensure AI output quality systematically and measurably. With a rich set of built-in metrics, seamless pytest integration, and the Confident AI dashboard for monitoring, DeepEval becomes an essential tool for every team building LLM applications in production.
Start by identifying the most relevant metrics for your use case, create golden datasets from real user interactions, and integrate evaluations into your CI/CD pipeline. With this approach, you can build LLM applications that don't just work, but work with consistent and measurable quality.