Spaces:
Running
Running
| """ | |
| critic.py — Critic Model | |
| Secondo passaggio: verifica output, trova errori, suggerisce miglioramenti. | |
| Usa AIClient generico (non OllamaClient) per compatibilità HF Space. | |
| """ | |
| import json | |
| import re | |
| from typing import Any | |
| import logging | |
| _logger = logging.getLogger("agents.critic") | |
| CRITIC_SYSTEM = """Sei un critico AI. Valuta l'output dato e rispondi SOLO con JSON valido: | |
| { | |
| "quality": 0-10, | |
| "issues": ["lista problemi trovati — solo se GRAVI, non dettagli stilistici"], | |
| "suggestions": ["lista miglioramenti concreti"], | |
| "is_complete": true/false, | |
| "needs_retry": true/false, | |
| "confidence": 0.0-1.0 | |
| } | |
| Criteri: | |
| - quality 8-10: risposta corretta, completa, con codice/calcoli se richiesti | |
| - quality 5-7: risposta parziale ma utile, mancano dettagli non essenziali | |
| - quality 0-4: risposta sbagliata, vuota, o fuori tema → needs_retry: true | |
| - needs_retry: true SOLO se quality <= 3 (non per risposte corrette ma incomplete) | |
| - Se la risposta ha codice funzionante, calcoli corretti o dati reali → quality >= 7 | |
| Nessun testo prima o dopo il JSON.""" | |
| def _extract_json_balanced(raw: str) -> str | None: | |
| """P16-B3: depth-counting bilanciato — sostituisce regex greedy r'{[\s\S]+}'. | |
| Gestisce oggetti JSON annidati correttamente (es. patch con sub-oggetti). | |
| """ | |
| depth = 0 | |
| start = -1 | |
| for i, ch in enumerate(raw): | |
| if ch == '{': | |
| if depth == 0: | |
| start = i | |
| depth += 1 | |
| elif ch == '}': | |
| depth -= 1 | |
| if depth == 0 and start != -1: | |
| return raw[start:i + 1] | |
| return None | |
| class Critic: | |
| def __init__(self, llm_client: Any): | |
| self.llm = llm_client | |
| async def evaluate(self, task: str, output: str, model: str | None = None) -> dict: | |
| messages = [ | |
| {"role": "system", "content": CRITIC_SYSTEM}, | |
| { | |
| "role": "user", | |
| "content": ( | |
| f"Task originale: {task}\n\n" | |
| f"Output da valutare:\n{output[:2000]}" | |
| ), | |
| }, | |
| ] | |
| try: | |
| raw = await self.llm.chat(messages, temperature=0.2, max_tokens=512) | |
| json_match = _extract_json_balanced(raw) | |
| if json_match: | |
| result = json.loads(json_match) | |
| result["_evaluated"] = True | |
| return result | |
| except Exception as _exc: | |
| _logger.debug("[critic] silenced %s", type(_exc).__name__) # noqa: BLE001 | |
| # Fallback euristico (nessuna chiamata LLM) | |
| quality = 5 | |
| issues: list[str] = [] | |
| if len(output) < 50: | |
| quality -= 3 | |
| issues.append("Output troppo breve") | |
| if "errore" in output.lower() or "error" in output.lower(): | |
| quality -= 2 | |
| issues.append("Potenziali errori nell'output") | |
| if len(output) > 100: | |
| quality += 2 | |
| # Penalizza description leak dei tool | |
| if "usa il tool" in output.lower() or "esegui il comando" in output.lower(): | |
| quality -= 3 | |
| issues.append("Agente descrive tool invece di usarli") | |
| return { | |
| "quality": max(0, min(10, quality)), | |
| "issues": issues, | |
| "suggestions": ["Verifica la completezza della risposta"], | |
| "is_complete": len(output) > 100, | |
| "needs_retry": quality < 3, # S192: alzato soglia 4→3 — meno falsi negativi | |
| "confidence": 0.5, | |
| "_fallback": True, | |
| } | |