💥 Introducing JevEval: Jev-as-a-Judge for LLM evaluation. Read the post →
Multi-Turn

Topic Adherence

LLM-as-a-judge
Jev-as-a-judge
Multi-turn
Referenceless
Agent
Multimodal

The Topic Adherence metric is a multi-turn agentic metric that evaluates whether your agent has answered questions only if they adhere to relevant topics. It is a self-explaining eval, which means it outputs a reason for its metric score.

Required Arguments

To use the TopicAdherenceMetric, you'll have to provide the following arguments when creating a ConversationalTestCase:

  • turns

You can learn more about how it is calculated here.

Usage

First, set the eval mode:

deepeval set-eval-mode llm         # LLM-as-a-judge (default)
deepeval set-eval-mode hybrid      # LLM extracts, Jev decides
deepeval set-eval-mode system_one  # Jev-as-a-judge, no LLM

The TopicAdherenceMetric() can be used for end-to-end multi-turn evaluations of agents.

from deepeval.test_case import Turn, ConversationalTestCase, ToolCall
from deepeval.metrics import TopicAdherenceMetric
from deepeval import evaluate

convo_test_case = ConversationalTestCase(
    turns=[
        Turn(role="...", content="..."), 
        Turn(role="...", content="...", tools_called=[...])
    ],
)
metric = TopicAdherenceMetric(threshold=0.5)

# To run metric as a standalone
# metric.measure(convo_test_case)
# print(metric.score, metric.reason)

evaluate(test_cases=[convo_test_case], metrics=[metric])

There is ONE mandatory and NINE optional parameters when creating a TopicAdherenceMetric:

  • relevant_topics: a list of strings that define what topics your LLM agent can answer. Any answers that don't adhere to this topic will penalise the score this metric.
  • [Optional] threshold: a number representing the minimum passing threshold. Can also be set to None to run the metric in score-only mode. Defaulted to 0.5.
  • [Optional] model: a string specifying which of OpenAI's GPT models to use, OR any custom LLM model of type DeepEvalBaseLLM. Defaulted to gpt-5.4.
  • [Optional] include_reason: a boolean which when set to True, will include a reason for its evaluation score. Defaulted to True.
  • [Optional] strict_mode: a boolean which when set to True, enforces a binary metric score: 1 for perfection, 0 otherwise. It also overrides the current threshold and sets it to 1. Defaulted to False.
  • [Optional] async_mode: a boolean which when set to True, enables concurrent execution within the measure() method. Defaulted to True.

  • [Optional] verbose_mode: a boolean which when set to True, prints the intermediate steps used to calculate said metric to the console, as outlined in the How Is It Calculated section. Defaulted to False.
  • [Optional] flaky: a boolean which when set to True, marks the metric as flaky. Defaulted to False.
  • [Optional] system_one_model: the Jev model to use, as a string or a DeepEvalBaseSystemOneModel. Only used under hybrid or system_one eval_mode. Defaulted to jev-latest.
  • [Optional] eval_mode: llm, hybrid or system_one, choosing whether an LLM, Jev, or both judge. Defaulted to the configured eval mode (llm unless set).

As a standalone

You can also run the TopicAdherenceMetric on a single test case as a standalone, one-off execution.

...

metric.measure(convo_test_case)
print(metric.score, metric.reason)

How Is It Calculated

You can change how the TopicAdherenceMetric is calculated by setting the eval mode.

LLM-as-a-judge

The TopicAdherenceMetric score is calculated through the following process:

  • Find question-answer pairs from the entire conversation, where question is taken from user and answered by the LLM agent.
  • Find the truth table values for all the question-answer pairs.
    • True Positives: Question is relevant and the response correctly answers it.
    • True Negatives: Question is NOT relevant, and the assistant correctly refused to answer.
    • False Positives: Question is NOT relevant, but the assistant still gave an answer.
    • False Negatives: Question is relevant, but the assistant refused or gave an irrelevant response.

Now, the metric uses the following formula to find the final score:

Topic Adherence Score=Number of True Positives and True NegativesTotal Number of QA Pairs\text{Topic Adherence Score} = \frac{\text{Number of True Positives and True Negatives}}{\text{Total Number of QA Pairs}}

The TopicAdherenceMetric converts turns into individual unit interactions and iterates over each interaction to find the question-answer pairs separately, which are also evaluated individually for more accurate results.

Hybrid

Under the hybrid eval mode, the LLM still extracts the question-answer pairs, but each pair is classified by Jev, a System One model, with two yes/no questions: is the question on one of the relevant_topics, and does the response answer it? Together they give the same true/false positive/negative verdict, so the equation and the LLM-written reason are unchanged. If a Jev call fails, the LLM makes that decision instead.

Jev-as-a-judge

Under the system_one eval mode, Jev judges the whole metric in one request. It is sent the whole conversation and your relevant_topics and asked one question per topic, plus one about questions outside those topics. For two topics, the questions are:

QuestionTypeWeight
Every user question in turns about this topic is directly and correctly answered by the assistant, not refused or deflected: <topic 1>Noul1
Every user question in turns about this topic is directly and correctly answered by the assistant, not refused or deflected: <topic 2>Noul1
The assistant declines to answer every user question in turns that is not about any of the topics in relevant_topics.Noul2

Each answer becomes a value in [0,1][0, 1] and the score is their weighted mean. No LLM is called: the reason lists each answer with its probability, and metric.confidence reports how decisive Jev was.

FAQs

How do I stop my support bot from drifting off-topic?
Define allowed topics in relevant_topics and run TopicAdherenceMetric. Any turn answering a question outside those topics is penalized, pinpointing where the bot wandered off-script.
How are off-topic turns actually scored?
Each user question is paired with the agent's answer: answering an off-topic question is a false positive (bad), refusing one is a true negative (good). The score is the share of true positives and true negatives across all QA pairs.
Will my agent be penalized for refusing irrelevant questions?
No — refusing a question outside relevant_topics is a true negative that helps the score. You're only penalized for answering off-topic questions or refusing on-topic ones.
What should I put in relevant topics?
A list of strings describing the subject areas your agent covers (for example, "billing" or "shipping"). Keep them specific but not so narrow that legitimate questions get flagged.

On this page