Custom Classifier
The Classifier in deepeval is a categorical LLM-as-a-judge that assigns one label from a set you define to any single-turn or multi-turn test case. It is the class every built-in classifier is built on, and the one to reach for when the labels are specific to your application: topic, intent, product area, escalation category, or anything else you can name.
The whole classifier is the label set. Writing good labels is most of the work, and the rest of this page is about that.
Usage
from deepeval.classifiers import Classifier, Label
from deepeval.test_case import LLMTestCase
from deepeval import evaluate
classifier = Classifier(
name="topic",
labels=[
Label(name="billing", description="Questions about invoices, charges, or payment methods."),
Label(name="refund", description="Requests to get money back for a purchase that has already been made."),
Label(name="shipping", description="Questions about delivery status, times, or addresses."),
],
)
test_case = LLMTestCase(
input="I was charged twice this month.",
actual_output="Sorry about that. I can see the duplicate charge and will reverse it.",
expected_labels={classifier.name: "billing"},
)
evaluate(test_cases=[test_case], classifiers=[classifier])There are TWO mandatory and FIVE optional parameters when creating a Classifier:
name: a string that identifies the classifier. It is the key a test case uses inexpected_labels, so it must be unique within a run.labels: a list ofLabels or plain strings. At least one is required and names must be unique.- [Optional]
model: a string specifying which of OpenAI's GPT models to use, OR any custom LLM model of typeDeepEvalBaseLLM. Defaulted togpt-5.4. - [Optional]
include_reason: a boolean which when set toTrue, includes a reason for the chosen label. Defaulted toTrue. - [Optional]
allow_none: a boolean which when set toTrue, lets the classifier return no label when none of them fit (surfaced aslabel=None, with a reason). WhenFalse, the closest label is always chosen. Defaulted toFalse. - [Optional]
async_mode: a boolean which when set toTrue, enables concurrent execution within theclassify()method. Defaulted toTrue. - [Optional]
classification_template: a subclass ofClassifierTemplateused to override the default prompts. Defaulted toClassifierTemplate.
The same Classifier also accepts a ConversationalTestCase, in which case it reads every turn along with the scenario and expected outcome.
As a standalone
You can also run a Classifier on a single test case as a standalone, one-off execution. classify() returns the label and stores the result on the instance:
...
label = classifier.classify(test_case)
print(classifier.label, classifier.reason)Writing Good Labels
This is the data model of a Label:
from pydantic import BaseModel
class Label(BaseModel):
name: str
description: Optional[str] = NoneWhich can be used in a classifier as such:
from deepeval.classifiers import Classifier, Label
urgency = Classifier(
name="urgency",
labels=[
Label(name="urgent", description="The user needs a response within the hour or is blocked from working."),
Label(name="routine", description="The user can wait; no deadline or blocker is mentioned."),
],
)A label's description is the boundary the judge uses to decide, in the same way that G-Eval's criteria are the boundary for a score. Three things make a label set work well:
- Make labels mutually exclusive. The judge picks exactly one, so overlapping labels give inconsistent results between runs.
- Describe the edge, not the centre. "Questions about billing" adds nothing to the name; "invoices, charges, or payment methods, but not requests for money back" tells the judge where billing stops and refund starts.
- Decide what happens when nothing fits. The judge picks the closest label by default. If some responses genuinely match none, set
allow_none=Truerather than adding an "other" label.
Plain strings are fine when the name is unambiguous on its own:
from deepeval.classifers import Classifier
sentiment = Classifier(name="sentiment", labels=["positive", "neutral", "negative"])How Is It Calculated?
A Classifier is a one-shot LLM-as-a-judge. A single call to your evaluation model does all the work:
- The prompt contains the populated fields of the test case (or every turn of a conversation) and your labels with their descriptions.
- The judge returns one label and a reason.
- The label is matched against your declared labels. Anything else is an error, not a guess at the closest one. With
allow_none=True, the judge may also return no label. - If the test case has an
expected_labelsentry for this classifier, the result passes when they match and fails otherwise.
Results on an LLMTestCase are cached like metrics, keyed on name, labels, model, include_reason, and allow_none.
Customize Your Template
You can override the default prompts by subclassing ClassifierTemplate and passing it as classification_template. There are two methods, one per test case type:
classify_single_turn(labels: str, test_case_content: str)classify_multi_turn(labels: str, test_case_content: str, turns: list)
labels is the rendered list of label names and descriptions, test_case_content is the rendered test case fields, and turns is a list of dicts with the populated fields of each turn. An override only needs to declare the variables it uses, and must keep asking for JSON with label and reason fields.
from deepeval.classifiers import Classifier, ClassifierTemplate
import textwrap
class StrictClassifierTemplate(ClassifierTemplate):
@staticmethod
def classify_single_turn(labels: str, test_case_content: str) -> str:
return textwrap.dedent(
f"""
Pick exactly one label for the test case below. Prefer the most
specific label.
Labels:
{labels}
Test case:
{test_case_content}
Return JSON only: {{"label": "...", "reason": "..."}}
JSON:
"""
)
classifier = Classifier(
name="topic",
labels=["billing", "refund", "shipping"],
classification_template=StrictClassifierTemplate,
)FAQs
Can a classifier return more than one label?
Classifier assigns exactly one label per test case. If your problem is genuinely multi-label, create one classifier per label with a binary label set such as ["present", "absent"].What happens when none of my labels fit?
allow_none=True if you would rather it return no label in that situation; the classification then has label=None, no error, and fails only if the test case expected a specific label.Why does my classifier error instead of picking the closest label?
deepeval records an error rather than guessing which of your labels it meant. This usually means two labels overlap or a description is vague; tighten the descriptions before reaching for a custom template.