agentic-model-selector / src /agents /evaluation_agent.py
honourjesus's picture
Agentic Model Selector
af2c3f6
Raw
History Blame Contribute Delete
11.5 kB
"""
Evaluation Agent - Scores speech and translation models
"""
import numpy as np
from datetime import datetime, timezone
from typing import List, Dict, Optional
from src.models.schemas import (
ModelMetadata, UserRequirements, HardwareConstraint,
ModelScore, TaskType
)
class EvaluationAgent:
"""
Evaluates and scores translation, TTS, and STT models.
"""
def __init__(self, weights: Dict[str, float] = None):
self.weights = weights or {
'downloads': 0.20,
'recency': 0.15,
'license': 0.10,
'size': 0.15,
'performance': 0.25,
'language_match': 0.15
}
def score_models(self,
models: List[ModelMetadata],
requirements: UserRequirements) -> List[ModelScore]:
"""Score models based on multiple criteria"""
scored_models = []
for model in models:
scores = {}
# Common scores
scores['downloads'] = self._score_downloads(model.downloads)
scores['recency'] = self._score_recency(model.last_modified)
scores['license'] = self._score_license(model.license)
scores['size'] = self._score_size(model.model_size, requirements)
scores['performance'] = self._score_performance(model, requirements)
scores['language_match'] = self._score_language_match(model, requirements)
# Task-specific scores
if model.task_type in [TaskType.TEXT_GENERATION, TaskType.CHAT,
TaskType.INSTRUCTION_FOLLOWING, TaskType.CODE_GENERATION,
TaskType.QUESTION_ANSWERING, TaskType.SUMMARIZATION]:
scores['llm_capabilities'] = self._score_llm_capabilities(model, requirements)
elif model.task_type in [TaskType.OCR, TaskType.DOCUMENT_UNDERSTANDING]:
scores['ocr_capabilities'] = self._score_ocr_capabilities(model, requirements)
# Calculate weighted total
total_score = 0.0
weight_sum = 0.0
all_weights = {
'downloads': 0.15,
'recency': 0.10,
'license': 0.05,
'size': 0.10,
'performance': 0.20,
'language_match': 0.15,
'llm_capabilities': 0.25,
'ocr_capabilities': 0.25
}
for metric, score in scores.items():
if metric in all_weights:
total_score += score * all_weights[metric]
weight_sum += all_weights[metric]
if weight_sum > 0:
total_score /= weight_sum
# Apply hardware penalty
hardware_penalty = self._check_hardware_constraints(model, requirements)
total_score *= hardware_penalty
scored_models.append(ModelScore(
model_id=model.model_id,
task_type=model.task_type,
total_score=float(total_score),
component_scores=scores,
metadata=model
))
return sorted(scored_models, key=lambda x: x.total_score, reverse=True)
def _score_downloads(self, downloads: int) -> float:
"""Score based on downloads (log scale)"""
if downloads <= 0:
return 0.0
log_downloads = np.log10(downloads + 1)
return min(log_downloads / 6.0, 1.0)
def _score_recency(self, last_modified) -> float:
"""Score based on recency - FIXED timezone issue"""
if not last_modified:
return 0.5
try:
# Make last_modified timezone-naive for comparison
if hasattr(last_modified, 'tzinfo') and last_modified.tzinfo is not None:
# Convert to timezone-naive by removing timezone info
last_modified = last_modified.replace(tzinfo=None)
# Get current time as timezone-naive
now = datetime.now()
# Calculate days difference
days_since_update = (now - last_modified).days
if days_since_update < 30:
return 1.0
elif days_since_update < 90:
return 0.8
elif days_since_update < 180:
return 0.6
elif days_since_update < 365:
return 0.4
else:
return 0.2
except Exception as e:
print(f" Warning: Error calculating recency: {e}")
return 0.5
def _score_license(self, license: str) -> float:
"""Score based on license"""
license_lower = license.lower()
open_licenses = ['mit', 'apache', 'bsd', 'cc', 'gpl', 'lgpl']
if any(open_license in license_lower for open_license in open_licenses):
return 0.9
elif 'commercial' in license_lower:
return 0.5
else:
return 0.7
def _score_size(self, model_size: Optional[float], requirements: UserRequirements) -> float:
"""Score based on size - smaller is better"""
if not model_size:
return 0.5
if requirements.max_model_size_gb:
if model_size > requirements.max_model_size_gb:
return 0.0
size_ratio = 1.0 - (model_size / requirements.max_model_size_gb)
return 0.5 + (size_ratio * 0.5)
# No constraint - smaller is better
if model_size < 0.5:
return 1.0
elif model_size < 1.0:
return 0.9
elif model_size < 2.0:
return 0.7
elif model_size < 5.0:
return 0.5
else:
return 0.3
def _score_performance(self, model: ModelMetadata, requirements: UserRequirements) -> float:
"""Score based on performance metrics"""
metrics = model.performance_metrics
if not metrics:
return 0.5
if model.task_type == TaskType.TRANSLATION:
# Prefer BLEU scores
if 'bleu' in metrics:
return min(metrics['bleu'] / 50, 1.0) # BLEU up to 50
return 0.5
elif model.task_type == TaskType.SPEECH_TO_TEXT:
# Prefer low WER
if 'wer' in metrics:
return max(0, 1.0 - (metrics['wer'] / 100))
return 0.5
elif model.task_type == TaskType.TEXT_TO_SPEECH:
# Prefer more voices and higher sample rate
score = 0.5
if model.voice_count > 0:
score += min(model.voice_count / 10, 0.3)
if model.sample_rate and model.sample_rate >= 16000:
score += 0.2
return min(score, 1.0)
return 0.5
def _score_language_match(self, model: ModelMetadata, requirements: UserRequirements) -> float:
"""Score based on language support"""
if requirements.task_type == TaskType.TRANSLATION and requirements.translation_reqs:
req = requirements.translation_reqs
source = req.source_language.value
target = req.target_language.value
score = 0.5
if model.source_languages and source in model.source_languages:
score += 0.25
if model.target_languages and target in model.target_languages:
score += 0.25
return score
elif requirements.task_type == TaskType.TEXT_TO_SPEECH and requirements.tts_reqs:
req = requirements.tts_reqs
lang = req.language.value
if model.languages and lang in model.languages:
return 1.0
return 0.5
elif requirements.task_type == TaskType.SPEECH_TO_TEXT and requirements.stt_reqs:
req = requirements.stt_reqs
lang = req.language.value
if model.languages and lang in model.languages:
return 1.0
return 0.5
return 0.5
def _score_llm_capabilities(self, model: ModelMetadata, requirements: UserRequirements) -> float:
"""Score LLM based on capabilities"""
if not requirements.llm_reqs:
return 0.5
req = requirements.llm_reqs
score = 0.5
# Check context length
if model.context_length and req.context_length:
if model.context_length >= req.context_length:
score += 0.2
else:
score -= 0.1
# Check chat template
if req.wants_chat_template and model.has_chat_template:
score += 0.2
# Check function calling
if req.wants_function_calling and model.supports_function_calling:
score += 0.2
# Check code generation
if req.wants_code_generation and model.supports_code:
score += 0.2
# Check instruction following
if req.wants_instruction_following and model.supports_instruction:
score += 0.2
# Check quantization support
if req.quantization and req.quantization in model.quantization_supported:
score += 0.1
return min(score, 1.0)
def _score_ocr_capabilities(self, model: ModelMetadata, requirements: UserRequirements) -> float:
"""Score OCR model based on capabilities"""
if not requirements.ocr_reqs:
return 0.5
req = requirements.ocr_reqs
score = 0.5
# Check handwriting support
if req.handwritten and model.supports_handwriting:
score += 0.3
elif req.handwritten and not model.supports_handwriting:
score -= 0.2
# Check layout analysis
if req.wants_layout_analysis and model.supports_layout:
score += 0.2
# Check table extraction
if req.wants_table_extraction and model.supports_tables:
score += 0.2
# Check formula recognition
if req.wants_formula_recognition and model.supports_formulas:
score += 0.2
return min(score, 1.0)
def _check_hardware_constraints(self, model: ModelMetadata,
requirements: UserRequirements) -> float:
"""Apply penalty if hardware constraints not met"""
if not requirements.hardware_constraints:
return 1.0
hardware = model.hardware_requirements
for constraint in requirements.hardware_constraints:
if constraint == HardwareConstraint.CPU:
if hardware.get('cpu_compatible', True):
return 1.0
elif constraint in [HardwareConstraint.GPU_4GB, HardwareConstraint.GPU_8GB,
HardwareConstraint.GPU_16GB, HardwareConstraint.GPU_24GB,
HardwareConstraint.GPU_40GB, HardwareConstraint.GPU_80GB]:
if hardware.get('gpu_required', False):
return 0.9 # Small penalty for requiring GPU
return 1.0
return 0.8