Spaces:
Runtime error
Runtime error
| import time | |
| import logging | |
| import os | |
| import evaluate | |
| from huggingface_hub import InferenceClient | |
| logger = logging.getLogger(__name__) | |
| # YOUR EXACT PROMPT | |
| JUDGE_PROMPT = """Grade the system's answer. | |
| Question: {q} | |
| Correct answer: {correct} | |
| System answer: {answer} | |
| Reply with only PASS or FAIL. | |
| PASS = the system answer correctly addresses the question with no major errors. | |
| FAIL = the answer is wrong, missing, or contradicts the correct answer.""" | |
| class MetricsService: | |
| def __init__(self, model_name="gemini-1.5-flash"): | |
| self.model_name = model_name.lower() | |
| self.hf_token = os.environ.get("HF_TOKEN") | |
| # Setup as per your implementation | |
| self.client = InferenceClient( | |
| model="meta-llama/Llama-3.1-8B-Instruct", | |
| token=self.hf_token | |
| ) | |
| self.bertscore = evaluate.load("bertscore") | |
| self.tiers = { | |
| "flash": {"input": 0.075, "output": 0.30}, | |
| "pro": {"input": 3.50, "output": 10.50}, | |
| "groq_large": {"input": 0.59, "output": 0.79}, | |
| "groq_small": {"input": 0.05, "output": 0.08}, | |
| } | |
| def warmup(self): | |
| """Proactively load BERTScore and check Judge connectivity""" | |
| logger.info("PRE-LOADING METRICS MODELS (BERTScore)...") | |
| try: | |
| # Dummy compute to force model download/load into memory/MPS | |
| self.bertscore.compute( | |
| predictions=["warmup"], | |
| references=["warmup"], | |
| lang="en" | |
| ) | |
| logger.info("METRICS MODELS LOADED SUCCESSFULLY.") | |
| except Exception as e: | |
| logger.warning(f"Metrics warmup failed: {e}") | |
| def calculate_cost(self, input_tokens, output_tokens, model_override=None): | |
| m_name = (model_override or self.model_name).lower() | |
| if "70b" in m_name or "versatile" in m_name: | |
| prices = self.tiers["groq_large"] | |
| elif "8b" in m_name or "instant" in m_name: | |
| prices = self.tiers["groq_small"] | |
| elif "pro" in m_name: | |
| prices = self.tiers["pro"] | |
| else: | |
| prices = self.tiers["flash"] | |
| return ((input_tokens / 1_000_000) * prices["input"]) + ((output_tokens / 1_000_000) * prices["output"]) | |
| def process_metrics(self, client, query, answer, context, usage_metadata, start_time, abstracts_list=None, ground_truth=None, model_name=None): | |
| """EXACT implementation with NO truncation.""" | |
| # 1. Prepare "Correct Answer" | |
| # If ground_truth is provided (from benchmark), use it. Otherwise fallback to abstracts/context. | |
| final_ground_truth = ground_truth or ("\n\n".join(abstracts_list) if abstracts_list else context) | |
| # 2. LLM-as-a-Judge β strip citation markers and disclaimer sentences before judging | |
| import re as _re | |
| clean_for_judge = _re.sub(r'\[Paper \d+\]', '', answer).strip() | |
| # Strip sentences where the model hedges about missing context β these cause false judge FAILs | |
| _DISCLAIMER = _re.compile( | |
| r'[^.!?]*\b(not explicitly (mentioned|stated|provided)|' | |
| r'not (mentioned|provided|specified) in (the )?(provided |given )?context|' | |
| r'is not clear from|cannot be (determined|found) from|' | |
| r'no (explicit |direct )?mention)\b[^.!?]*[.!?]?', | |
| _re.IGNORECASE | |
| ) | |
| clean_for_judge = _DISCLAIMER.sub('', clean_for_judge).strip() | |
| prompt = JUDGE_PROMPT.format( | |
| q=query, | |
| correct=final_ground_truth, | |
| answer=clean_for_judge | |
| ) | |
| try: | |
| verdict = self.client.chat_completion( | |
| [{"role": "user", "content": prompt}], | |
| max_tokens=20, | |
| temperature=0.0 | |
| ) | |
| judge_pass = "PASS" in verdict.choices[0].message.content.upper() | |
| except Exception as e: | |
| logger.error(f"Judge error: {e}") | |
| judge_pass = False | |
| # 3. BERTScore β reuse the already-cleaned answer from above | |
| clean_answer = clean_for_judge | |
| try: | |
| bert_results = self.bertscore.compute( | |
| predictions=[clean_answer], | |
| references=[final_ground_truth], | |
| lang="en", | |
| rescale_with_baseline=True | |
| ) | |
| f1_score = float(bert_results["f1"][0]) | |
| except Exception as e: | |
| logger.error(f"BERTScore error: {e}") | |
| f1_score = 0.0 | |
| # 4. Latency and Cost | |
| end_time = time.time() | |
| input_tokens = usage_metadata.prompt_token_count if usage_metadata else 0 | |
| output_tokens = usage_metadata.candidates_token_count if usage_metadata else 0 | |
| return { | |
| "tokens": {"input": input_tokens, "output": output_tokens, "total": input_tokens + output_tokens}, | |
| "latency_seconds": round(end_time - start_time, 2), | |
| "cost_usd": float(self.calculate_cost(input_tokens, output_tokens, model_override=model_name)), | |
| "accuracy": { | |
| "llm_judge": "PASS" if judge_pass else "FAIL", | |
| "bert_score": f1_score | |
| } | |
| } | |