Skip to main content

Adding New Evaluators

This guide explains how to add new evaluation methods to PatientHub.

Overview​

Evaluators assess the quality of simulations and generated artifacts using an LLM as a judge. Every evaluator subclasses LLMJudge, is keyed by an agent_name, and reads its scoring schema (the dimensions) from a prompt YAML. PatientHub currently ships two evaluators:

  • conv_judge: evaluates a therapy session (conversation).
  • profile_judge: evaluates a generated client profile.

Adding a new evaluator means writing a small subclass plus a config dataclass, registering both, and providing a prompt YAML.

Architecture​

patienthub/evaluators/
├── __init__.py # Registries and get_evaluator()
├── base.py # LLMJudge, LLMJudgeConfig
├── conv.py # ConvJudge, ConvJudgeConfig
├── profile.py # ProfileJudge, ProfileJudgeConfig
└── your_evaluator.py # Your new evaluator

base.py does the heavy lifting: it loads the prompt from prompt_path, builds Pydantic response models from the dimensions in that YAML, calls the chat model with response_format=<dimension model>, and returns one structured result per dimension. Each evaluator subclass typically just prepares the data payload and calls self.evaluate_dimensions(data).

The base config LLMJudgeConfig subclasses APIModelConfig, so every evaluator config inherits these fields:

FieldDefaultNotes
model_typeOPENAIChat model backend
model_namegpt-4oModel identifier
temperature0.7
max_tokens8192
max_retries3
langenPrompt language
use_reasoningFalseAdds a reasoning field per judgment

Step 1: Create Evaluator File​

Create a new file in patienthub/evaluators/. Define a config dataclass (subclassing LLMJudgeConfig) and an evaluator class (subclassing LLMJudge) that implements evaluate:

# patienthub/evaluators/myEvaluator.py

from typing import Any, Dict
from omegaconf import DictConfig
from dataclasses import dataclass

from .base import LLMJudge, LLMJudgeConfig


@dataclass
class MyEvaluatorConfig(LLMJudgeConfig):
agent_name: str = "my_evaluator"
prompt_path: str = "data/prompts/evaluator/my_evaluator.yaml"


class MyEvaluator(LLMJudge):
"""Your custom evaluation method.

Describe what this evaluator measures and what input it expects.
"""

def __init__(self, configs: DictConfig):
super().__init__(configs)

def evaluate(self, data: Dict[str, Any], *args) -> Dict[str, Any]:
# Validate/prepare the payload your prompt expects.
if not data:
print("No data provided for evaluation.")
return {}

# The base class renders the prompt with this `data` (as `{{data.*}}`),
# calls the model per dimension, and returns structured results.
return self.evaluate_dimensions(data)

The base class references data inside the prompt's Jinja2 template (e.g. {{data.profile}} or {{data.conv_history}}), so shape the data dict to match the fields your prompt uses.

Step 2: Register the Evaluator​

Add your evaluator and its config to the registries in patienthub/evaluators/__init__.py:

# patienthub/evaluators/__init__.py
from .myEvaluator import MyEvaluator, MyEvaluatorConfig

EVALUATOR_REGISTRY = {
"conv_judge": ConvJudge,
"profile_judge": ProfileJudge,
"my_evaluator": MyEvaluator,
}

EVALUATOR_CONFIG_REGISTRY = {
"conv_judge": ConvJudgeConfig,
"profile_judge": ProfileJudgeConfig,
"my_evaluator": MyEvaluatorConfig,
}

get_evaluator(agent_name, configs=None, lang="en") looks the evaluator up by agent_name. Registering the config lets get_evaluator construct sensible defaults when configs is not passed, and lets the config be selected as a Hydra group (evaluator=my_evaluator).

Step 3: Define Evaluation Dimensions​

Dimensions are not Python objects — they live in the prompt YAML referenced by prompt_path. Create it with a sys_prompt (Jinja2 template) and a dimensions list. Each dimension has a name, a description, and either a scoring paradigm or a list of aspects that inherit the dimension's paradigm and range:

# data/prompts/evaluator/my_evaluator.yaml
sys_prompt: |
You are an expert evaluator assessing therapy conversations.

## Conversation History:
{{data.conv_history}}

Return the judgment in the required structured format.

dimensions:
- name: empathy
description: Whether the therapist demonstrates understanding of the client's emotional experience.
paradigm: scalar
range: [1, 5]
- name: authenticity
description: How realistic and consistent the simulated client's responses are.
paradigm: scalar
range: [1, 5]
aspects:
- name: profile_match
description: Responses match the character profile
- name: consistency
description: Maintains a consistent personality

Supported paradigms and their returned fields:

ParadigmReturned FieldExample
binarylabel: bool{"label": true}
scalarscore: int{"score": 4}
categoricallabel: Literal[...]{"label": "very consistent"}
extractionsnippets: List[str]{"snippets": ["Client: ..."]}

If use_reasoning=True, each judged item also gets a reasoning field. Dimensions whose paradigm is unsupported are skipped when the schema is built.

Step 4: Create Configuration​

You can select and override the evaluator from Hydra using its real config fields. There is a config group per registered evaluator:

patienthub evaluate \
evaluator=my_evaluator \
evaluator.prompt_path=data/prompts/evaluator/my_evaluator.yaml \
evaluator.use_reasoning=true \
evaluator.model_name=gpt-4o

Overrides must use fields that actually exist on the config (the inherited APIModelConfig/LLMJudgeConfig fields plus any you add). For conv_judge, evaluator.granularity=turn selects the granularity.

Step 5: Test Your Evaluator​

Load the evaluator with get_evaluator (note that agent_name is a required positional argument) and run it on a sample payload:

from omegaconf import OmegaConf
from patienthub.evaluators import get_evaluator

configs = OmegaConf.create({
"agent_name": "my_evaluator",
"prompt_path": "data/prompts/evaluator/my_evaluator.yaml",
"use_reasoning": False,
"model_type": "OPENAI",
"model_name": "gpt-4o",
})

evaluator = get_evaluator(agent_name="my_evaluator", configs=configs, lang="en")

sample = {
"messages": [
{"role": "therapist", "content": "How are you feeling today?"},
{"role": "client", "content": "I've been really anxious lately."},
],
}

results = evaluator.evaluate(sample)
print(results)

To use the registered defaults instead, call get_evaluator(agent_name="my_evaluator", lang="en") without a configs argument.

Checklist​

Before submitting your new evaluator:

  • Evaluator class subclassing LLMJudge in patienthub/evaluators/
  • Config dataclass subclassing LLMJudgeConfig with an agent_name and prompt_path
  • Both registered in EVALUATOR_REGISTRY and EVALUATOR_CONFIG_REGISTRY
  • Prompt YAML with sys_prompt and dimensions
  • Verified end-to-end: patienthub evaluate evaluator=my_evaluator
  • Documentation updated

See Also​