Aditya
Deploy RAG benchmark dashboard
af383cf
Raw
History Blame Contribute Delete
4.51 kB
"""Base evaluator class for all metrics."""
from abc import ABC, abstractmethod
from typing import Optional
import logging
from datetime import datetime
from ..types import (
QAPair,
SystemOutput,
EvaluationResult,
EvaluationMetric,
)
from ..utils.llm_client import LLMClient
logger = logging.getLogger(__name__)
class BaseEvaluator(ABC):
"""Abstract base class for all evaluators.
Each evaluator measures a specific dimension of LLM output quality.
They all follow the same pattern:
1. Receive question, reference answer, and system output
2. Use an LLM judge to score the output
3. Return EvaluationResult with score and reasoning
"""
def __init__(self, judge_client: LLMClient, model_name: Optional[str] = None):
"""Initialize evaluator with an LLM client.
Args:
judge_client: LLMClient instance (OpenAI or Anthropic)
model_name: Name of the judge model (for logging/display)
"""
self.judge_client = judge_client
self.model_name = model_name or "unknown-model"
@property
@abstractmethod
def metric(self) -> EvaluationMetric:
"""Return the metric this evaluator measures."""
pass
@property
@abstractmethod
def system_prompt(self) -> str:
"""Judge system prompt - defines the role and task."""
pass
@abstractmethod
def format_prompt(
self,
qa_pair: QAPair,
system_output: SystemOutput,
) -> str:
"""Format the evaluation prompt for the judge.
Args:
qa_pair: The question and reference answer
system_output: The system's output to evaluate
Returns:
Formatted prompt for the judge
"""
pass
@abstractmethod
async def parse_judge_response(self, response: str) -> tuple[float, str]:
"""Parse judge's response into score and reasoning.
Args:
response: Raw response from the judge LLM
Returns:
Tuple of (score_0_to_1, reasoning)
"""
pass
async def evaluate(
self,
qa_pair: QAPair,
system_output: SystemOutput,
) -> EvaluationResult:
"""Evaluate system output for this metric.
This is the main entry point. It orchestrates:
1. Formatting the evaluation prompt
2. Calling the judge
3. Parsing the response
4. Returning a structured result
Args:
qa_pair: Question and reference answer
system_output: Output from system under test
Returns:
EvaluationResult with score, reasoning, confidence
"""
try:
# Format prompt for the judge
user_prompt = self.format_prompt(qa_pair, system_output)
# Get judge's evaluation
logger.debug(f"Calling judge for {self.metric.value}")
judge_response = await self.judge_client.generate(
system_prompt=self.system_prompt,
user_message=user_prompt,
temperature=0.1, # Low temp for consistency
max_tokens=500,
)
# Parse the response
score, reasoning = await self.parse_judge_response(judge_response)
# Validate score is 0-1
if not (0 <= score <= 1):
logger.warning(f"Score {score} out of range, clamping to [0, 1]")
score = max(0, min(1, score))
# Return structured result
result = EvaluationResult(
metric=self.metric,
score=score,
raw_score=score, # Could store original if different
reasoning=reasoning,
judge_model=self.model_name,
confidence=0.8, # Could estimate from judge's confidence
timestamp=datetime.utcnow(),
)
logger.info(f"Evaluation complete: {self.metric.value}={score:.2f}")
return result
except Exception as e:
logger.error(f"Evaluation failed for {self.metric.value}: {e}")
raise
def __repr__(self) -> str:
"""String representation."""
return f"{self.__class__.__name__}(metric={self.metric.value}, judge={self.model_name})"