InferRoute / benchmarks /evaluate_quality.py
Ypeng12's picture
feat: implement learned router and reproducible benchmark harness
41910df
Raw
History Blame Contribute Delete
4.22 kB
import json
import re
import ast
from typing import List, Optional
def evaluate_response_quality(
category: str,
output: str,
requires_json: bool = False,
expected_keys: Optional[List[str]] = None,
reference_keywords: Optional[List[str]] = None
) -> float:
"""
Evaluates the quality of an LLM response string on a scale from 0.0 to 1.0.
Uses structural, syntactic, and semantic-keyword indicators.
"""
if not output or not isinstance(output, str):
return 0.0
output_clean = output.strip()
if len(output_clean) < 2:
return 0.0
# Check for excessive repetition (indicates loop failure / garbage)
words = output_clean.lower().split()
if len(words) > 10:
# Check if a single word constitutes more than 40% of the output
word_counts = {}
for w in words:
word_counts[w] = word_counts.get(w, 0) + 1
max_freq = max(word_counts.values())
if max_freq / len(words) > 0.40:
return 0.05 # Loop penalty
# 1. JSON Extraction Task Evaluation
if requires_json:
# Attempt to extract JSON if wrapped in markdown blocks
json_str = output_clean
if "```json" in json_str:
match = re.search(r"```json\s*(.*?)\s*```", json_str, re.DOTALL)
if match:
json_str = match.group(1)
elif "```" in json_str:
match = re.search(r"```\s*(.*?)\s*```", json_str, re.DOTALL)
if match:
json_str = match.group(1)
# Clean up outer brackets
json_str = json_str.strip()
try:
parsed = json.loads(json_str)
if not expected_keys:
return 1.0
# Check presence of expected keys
found_keys = 0
for key in expected_keys:
if key in parsed:
# Check that value is not empty or null
val = parsed[key]
if val is not None and str(val).strip() != "":
found_keys += 1
key_score = found_keys / len(expected_keys)
return max(0.1, key_score)
except Exception:
return 0.0 # Invalid JSON format
# 2. Code Generation Task Evaluation
if category == "code":
# Check for Python syntax compile
if "def " in output_clean or "import " in output_clean:
try:
# Strip markdown blocks to compile raw python code
py_code = output_clean
if "```python" in py_code:
py_code = re.search(r"```python\s*(.*?)\s*```", py_code, re.DOTALL).group(1)
elif "```" in py_code:
py_code = re.search(r"```\s*(.*?)\s*```", py_code, re.DOTALL).group(1)
ast.parse(py_code.strip())
syntax_score = 0.5
except Exception:
syntax_score = 0.1 # Syntax error
else:
# Non-python code syntax (e.g. JS), give base score
syntax_score = 0.3
# Keyword checks
keyword_score = 0.0
if reference_keywords:
matched = sum(1 for kw in reference_keywords if kw.lower() in output_clean.lower())
keyword_score = (matched / len(reference_keywords)) * 0.5
return syntax_score + keyword_score
# 3. Math and General QA Evaluation
if category == "math":
# Extract last numbers or single numbers from response to match numeric answer
numbers = re.findall(r"\d+", output_clean)
if reference_keywords and numbers:
expected_num = reference_keywords[0]
if expected_num in numbers:
# Direct match gets 1.0, otherwise 0.0
return 1.0
return 0.0
# General / Long Context Keyword Evaluation
if reference_keywords:
matched = sum(1 for kw in reference_keywords if kw.lower() in output_clean.lower())
return matched / len(reference_keywords)
return 0.8 # Default positive score for general coherent answers