Aditya
Deploy RAG benchmark dashboard
af383cf
Raw
History Blame Contribute Delete
6.64 kB
"""Simple metric evaluators - Latency and Cost"""
from typing import Optional
from ..types import (
QAPair,
SystemOutput,
EvaluationMetric,
EvaluationResult,
)
from ..utils.llm_client import LLMClient
from .base import BaseEvaluator
from datetime import datetime
class LatencyEvaluator(BaseEvaluator):
"""Evaluates response latency without using an LLM judge.
This is a non-LLM evaluator that directly measures:
- Response time in milliseconds
- Compares against acceptable threshold
- Scores based on latency bucket
Scoring:
- 1.0 = <500ms (very fast)
- 0.8 = 500-1000ms (fast)
- 0.6 = 1-2 seconds (acceptable)
- 0.4 = 2-5 seconds (slow)
- 0.2 = 5-10 seconds (very slow)
- 0.0 = >10 seconds (unacceptable)
"""
def __init__(
self,
judge_client: Optional[LLMClient] = None,
model_name: Optional[str] = None,
latency_threshold_ms: float = 2000.0,
):
"""Initialize latency evaluator.
Args:
judge_client: Not used (included for interface compatibility)
model_name: Name for logging
latency_threshold_ms: Target latency in milliseconds
"""
# Latency evaluator doesn't need LLM judge
super().__init__(judge_client, model_name)
self.latency_threshold_ms = latency_threshold_ms
@property
def metric(self) -> EvaluationMetric:
return EvaluationMetric.LATENCY
@property
def system_prompt(self) -> str:
return "Not used - latency is directly measured"
def format_prompt(
self,
qa_pair: QAPair,
system_output: SystemOutput,
) -> str:
return f"Latency evaluation for output with {system_output.latency_ms}ms response time"
async def parse_judge_response(self, response: str) -> tuple[float, str]:
"""Not used - we compute score directly."""
return 0.5, "Not used"
async def evaluate(
self,
qa_pair: QAPair,
system_output: SystemOutput,
) -> EvaluationResult:
"""Evaluate latency based on response time.
Uses thresholds:
- <500ms (1.0), <1s (0.8), <2s (0.6), <5s (0.4), <10s (0.2), else (0.0)
"""
latency_ms = system_output.latency_ms
# Score based on latency buckets
if latency_ms < 500:
score = 1.0
reasoning = f"Excellent response time: {latency_ms:.0f}ms"
elif latency_ms < 1000:
score = 0.8
reasoning = f"Good response time: {latency_ms:.0f}ms"
elif latency_ms < 2000:
score = 0.6
reasoning = f"Acceptable response time: {latency_ms:.0f}ms"
elif latency_ms < 5000:
score = 0.4
reasoning = f"Slow response time: {latency_ms:.0f}ms"
elif latency_ms < 10000:
score = 0.2
reasoning = f"Very slow response time: {latency_ms:.0f}ms"
else:
score = 0.0
reasoning = f"Unacceptable response time: {latency_ms:.0f}ms"
result = EvaluationResult(
metric=self.metric,
score=score,
raw_score=latency_ms, # Store raw latency
reasoning=reasoning,
judge_model=self.model_name,
confidence=1.0, # Perfect confidence (not LLM-based)
timestamp=datetime.utcnow(),
)
return result
class CostEvaluator(BaseEvaluator):
"""Evaluates API cost per query without using an LLM judge.
This is a non-LLM evaluator that directly measures:
- Cost in USD per API call
- Compares against budget threshold
- Scores based on cost efficiency
Scoring based on cost efficiency:
- 1.0 = <$0.001 per query (very cheap)
- 0.8 = $0.001-0.005 (cheap)
- 0.6 = $0.005-0.01 (acceptable)
- 0.4 = $0.01-0.05 (expensive)
- 0.2 = $0.05-0.10 (very expensive)
- 0.0 = >$0.10 (prohibitively expensive)
"""
def __init__(
self,
judge_client: Optional[LLMClient] = None,
model_name: Optional[str] = None,
cost_threshold_usd: float = 0.01,
):
"""Initialize cost evaluator.
Args:
judge_client: Not used (included for interface compatibility)
model_name: Name for logging
cost_threshold_usd: Target cost per query in USD
"""
super().__init__(judge_client, model_name)
self.cost_threshold_usd = cost_threshold_usd
@property
def metric(self) -> EvaluationMetric:
return EvaluationMetric.COST
@property
def system_prompt(self) -> str:
return "Not used - cost is directly measured"
def format_prompt(
self,
qa_pair: QAPair,
system_output: SystemOutput,
) -> str:
cost = system_output.cost_usd or 0
return f"Cost evaluation for API call costing ${cost:.6f}"
async def parse_judge_response(self, response: str) -> tuple[float, str]:
"""Not used - we compute score directly."""
return 0.5, "Not used"
async def evaluate(
self,
qa_pair: QAPair,
system_output: SystemOutput,
) -> EvaluationResult:
"""Evaluate cost efficiency based on API cost.
Uses thresholds for cost per query in USD.
"""
cost_usd = system_output.cost_usd or 0.0
# Score based on cost buckets
if cost_usd < 0.001:
score = 1.0
reasoning = f"Very cost-effective: ${cost_usd:.6f} per query"
elif cost_usd < 0.005:
score = 0.8
reasoning = f"Cost-effective: ${cost_usd:.6f} per query"
elif cost_usd < 0.01:
score = 0.6
reasoning = f"Reasonable cost: ${cost_usd:.6f} per query"
elif cost_usd < 0.05:
score = 0.4
reasoning = f"Expensive: ${cost_usd:.6f} per query"
elif cost_usd < 0.10:
score = 0.2
reasoning = f"Very expensive: ${cost_usd:.6f} per query"
else:
score = 0.0
reasoning = f"Prohibitively expensive: ${cost_usd:.6f} per query"
result = EvaluationResult(
metric=self.metric,
score=score,
raw_score=cost_usd, # Store raw cost
reasoning=reasoning,
judge_model=self.model_name,
confidence=1.0, # Perfect confidence
timestamp=datetime.utcnow(),
)
return result