Spaces:
Sleeping
Sleeping
| # evaluator.py — LLM-as-Judge: Relevance Grader + Self-Critique | |
| """ | |
| Implementa dois avaliadores LLM clássicos de produção: | |
| 1. Relevance Grader — chunks recuperados são relevantes para a pergunta? | |
| 2. Self-Critique — a resposta gerada é boa? (score 0-10 + feedback) | |
| """ | |
| import re | |
| import json | |
| from openai import OpenAI | |
| # ── RELEVANCE GRADER ────────────────────────────────────────── | |
| GRADER_PROMPT = """Você é um avaliador especialista em sistemas RAG. | |
| Avalie se o contexto recuperado é RELEVANTE para responder a pergunta. | |
| Pergunta: {question} | |
| Contexto recuperado: | |
| {context} | |
| Responda APENAS com JSON válido, sem texto adicional: | |
| {{"relevant": true/false, "reason": "justificativa em 1 frase", "confidence": 0.0-1.0}} | |
| Critério: relevant=true se o contexto contém informação útil para responder.""" | |
| def grade_relevance(client: OpenAI, question: str, chunks: list, model: str = "gpt-4o-mini") -> dict: | |
| """ | |
| Avalia se os chunks recuperados são relevantes para a pergunta. | |
| Retorna: {relevant, reason, confidence, raw_chunks_used} | |
| """ | |
| context = "\n\n---\n\n".join( | |
| f"[Chunk {i+1}: {c['title']}]\n{c['text'][:400]}" | |
| for i, c in enumerate(chunks[:4]) | |
| ) | |
| prompt = GRADER_PROMPT.format(question=question, context=context) | |
| try: | |
| resp = client.chat.completions.create( | |
| model=model, | |
| messages=[{"role": "user", "content": prompt}], | |
| temperature=0.0, | |
| max_tokens=150, | |
| ) | |
| raw = resp.choices[0].message.content.strip() | |
| # Remove markdown se presente | |
| raw = re.sub(r'```json|```', '', raw).strip() | |
| result = json.loads(raw) | |
| result["raw_chunks_used"] = len(chunks) | |
| return result | |
| except Exception as e: | |
| return {"relevant": True, "reason": f"Erro na avaliação: {e}", "confidence": 0.5, "raw_chunks_used": len(chunks)} | |
| # ── SELF-CRITIQUE JUDGE ─────────────────────────────────────── | |
| CRITIQUE_PROMPT = """Você é um juiz especialista avaliando respostas de sistemas RAG. | |
| Pergunta original: {question} | |
| Contexto usado (base de conhecimento): | |
| {context} | |
| Resposta gerada: | |
| {answer} | |
| Avalie a resposta nos seguintes critérios e responda APENAS com JSON válido: | |
| {{ | |
| "score": <0-10>, | |
| "faithfulness": <0-10>, | |
| "relevance": <0-10>, | |
| "completeness": <0-10>, | |
| "issues": ["lista de problemas encontrados"], | |
| "improvements": "sugestão concreta de melhoria em 1-2 frases", | |
| "verdict": "APPROVE" ou "REFINE" | |
| }} | |
| Critérios: | |
| - faithfulness (0-10): resposta é fiel ao contexto? Sem alucinações? | |
| - relevance (0-10): resposta responde a pergunta? | |
| - completeness (0-10): resposta é completa e informativa? | |
| - score: média ponderada (faithfulness*0.4 + relevance*0.35 + completeness*0.25) | |
| - verdict: APPROVE se score >= 7, REFINE se score < 7""" | |
| def self_critique(client: OpenAI, question: str, context: str, answer: str, model: str = "gpt-4o-mini") -> dict: | |
| """ | |
| Avalia a qualidade da resposta gerada. | |
| Retorna: {score, faithfulness, relevance, completeness, issues, improvements, verdict} | |
| """ | |
| prompt = CRITIQUE_PROMPT.format( | |
| question=question, | |
| context=context[:1500], | |
| answer=answer | |
| ) | |
| try: | |
| resp = client.chat.completions.create( | |
| model=model, | |
| messages=[{"role": "user", "content": prompt}], | |
| temperature=0.0, | |
| max_tokens=300, | |
| ) | |
| raw = resp.choices[0].message.content.strip() | |
| raw = re.sub(r'```json|```', '', raw).strip() | |
| return json.loads(raw) | |
| except Exception as e: | |
| return { | |
| "score": 7, | |
| "faithfulness": 7, | |
| "relevance": 7, | |
| "completeness": 7, | |
| "issues": [], | |
| "improvements": f"Erro na auto-avaliação: {e}", | |
| "verdict": "APPROVE" | |
| } |