Spaces:
Configuration error
Configuration error
File size: 9,455 Bytes
7d580fd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | """
error_classifier.py β S404: Classificazione errori nel repair loop
Documento strategico: "Non basta 'vedere un errore'. Devi distinguere almeno:
selettore sbagliato, pagina diversa dal previsto, login fallito, errore runtime,
rete lenta, frame non caricato, stato UI incoerente."
Implementa la classificazione per consentire repair MIRATI invece di analisi generiche.
Integrazione:
_reflective_debug β classify_error(goal, errors) β ErrorResult
β inject repair_strategy nel context prima del retry
Pipeline classificazione:
1. Pattern matching regex (deterministico, zero latenza)
2. Fallback: UNKNOWN β generic strategic recovery
Non usa LLM per classificare β Γ¨ sincrono e costa zero latency.
"""
import re
from dataclasses import dataclass
from enum import Enum
class ErrorCategory(str, Enum):
SELECTOR = "selector" # CSS/DOM selector non trovato
NAVIGATION = "navigation" # URL sbagliato, pagina non trovata, redirect
AUTH = "auth" # Login fallito, token scaduto, 401/403
RUNTIME = "runtime" # TypeError, ReferenceError, is not defined
NETWORK = "network" # Timeout, ECONNREFUSED, offline, 503
FRAME = "frame" # iframe non caricato, frame non trovato
SYNTAX = "syntax" # SyntaxError, IndentationError, parse error
LOGIC = "logic" # AssertionError, valore atteso β ottenuto
LIMIT = "limit" # Rate limit, quota, 429, OOM
DB_ERROR = "db_error" # Sprint 3b: IntegrityError, FK, connection pool, deadlock
UNKNOWN = "unknown" # Non classificato
# ββ Strategie di repair per categoria βββββββββββββββββββββββββββββββββββββββββ
_REPAIR_STRATEGIES: dict[ErrorCategory, str] = {
ErrorCategory.SELECTOR: (
"SELETTORE DOM: usa ID espliciti (`#id`), aria-label (`[aria-label='...']`) "
"o ruoli ARIA (`getByRole('button', {name:'...'})`) β evita classi dinamiche. "
"Prima leggi il DOM attuale per trovare il selettore corretto."
),
ErrorCategory.NAVIGATION: (
"NAVIGAZIONE: verifica l'URL esatto prima di navigare. "
"Attendi `domcontentloaded` o un elemento specifico prima di operare sulla pagina. "
"Gestisci redirect e pagine di errore (404/500) esplicitamente."
),
ErrorCategory.AUTH: (
"AUTENTICAZIONE: gestisci il login PRIMA di qualsiasi azione protetta. "
"Verifica il token, gestisci la scadenza della sessione e i redirect post-login. "
"Non assumere che la sessione sia giΓ attiva."
),
ErrorCategory.RUNTIME: (
"ERRORE RUNTIME: controlla undefined/null con optional chaining (`?.`) "
"o guardie esplicite (`if (x == null) return`). "
"Verifica i tipi degli argomenti prima di usarli. "
"Aggiungi try/catch attorno alle operazioni rischiose."
),
ErrorCategory.NETWORK: (
"ERRORE RETE: aggiungi retry con exponential backoff (max 3 tentativi). "
"Gestisci offline/timeout con fallback utili. "
"Non bloccare la UI durante le chiamate β usa stato di loading."
),
ErrorCategory.FRAME: (
"IFRAME: aspetta che l'iframe sia caricato prima di accedere al suo DOM "
"(usa `waitForLoadState` o `frameLocator`). "
"Verifica che il frame esista con `page.frames()` prima di operare."
),
ErrorCategory.SYNTAX: (
"SINTASSI: correggi l'errore di sintassi PRIMA di qualsiasi altra cosa. "
"Verifica l'indentazione (Python), le parentesi bilanciate e i punti e virgola (JS). "
"Usa un linter per identificare tutti gli errori nella stessa sessione."
),
ErrorCategory.LOGIC: (
"LOGICA: rivedi i casi edge e le assunzioni. "
"Aggiungi asserzioni esplicite sui valori attesi. "
"Traccia il flusso di dati passo per passo per trovare dove diverge dal previsto."
),
ErrorCategory.LIMIT: (
"RATE LIMIT / QUOTA: implementa retry con backoff esponenziale e jitter. "
"Riduci la frequenza delle chiamate. "
"Considera caching dei risultati per evitare chiamate ridondanti."
),
ErrorCategory.DB_ERROR: (
"ERRORE DATABASE: controlla vincoli di integritΓ (FK, UNIQUE, NOT NULL). "
"Per IntegrityError: verifica che i dati rispettino i vincoli prima dell'insert. "
"Per connection pool exhausted: chiudi le connessioni non usate, riduci max_connections. "
"Per deadlock: usa retry con backoff, considera SKIP LOCKED per job queue. "
"Per 'relation does not exist': esegui le migrazioni pendenti prima dell'avvio."
),
ErrorCategory.UNKNOWN: (
"Analizza il contesto completo dell'errore. "
"Prova un approccio COMPLETAMENTE diverso da quello usato finora."
),
}
# ββ Pattern regex per classificazione βββββββββββββββββββββββββββββββββββββββββ
# Ordinati per prioritΓ (il primo match vince)
_PATTERNS: list[tuple[ErrorCategory, re.Pattern]] = [
(ErrorCategory.SYNTAX, re.compile(
r"SyntaxError|IndentationError|ParseError|parse error|"
r"unexpected token|unexpected EOF|invalid syntax|"
r"unterminated string|missing \)", re.IGNORECASE,
)),
(ErrorCategory.SELECTOR, re.compile(
r"selector|querySelector|getElementById|no element|"
r"element not found|locator|aria|getByRole|"
r"TimeoutError.*waiting for|strict mode violation", re.IGNORECASE,
)),
(ErrorCategory.AUTH, re.compile(
r"401|403|Unauthorized|Forbidden|Authentication|"
r"token.*expired|session.*expired|login.*failed|"
r"invalid.*credentials|permission denied", re.IGNORECASE,
)),
(ErrorCategory.NETWORK, re.compile(
r"ECONNREFUSED|ENOTFOUND|ETIMEDOUT|timeout|"
r"network error|fetch.*failed|connection refused|"
r"503|502|504|unreachable|offline", re.IGNORECASE,
)),
(ErrorCategory.LIMIT, re.compile(
r"429|rate.?limit|quota.*exceeded|too many requests|"
r"OOM|out of memory|memory.*error", re.IGNORECASE,
)),
(ErrorCategory.FRAME, re.compile(
r"frame|iframe|frameLocator|frame.*not found|"
r"cross.?origin|sandboxed.*frame", re.IGNORECASE,
)),
(ErrorCategory.NAVIGATION, re.compile(
r"404|page not found|navigation.*failed|goto.*timeout|"
r"ERR_NAME_NOT_RESOLVED|net::ERR|redirect.*loop|"
r"wrong.*page|URL.*invalid", re.IGNORECASE,
)),
(ErrorCategory.RUNTIME, re.compile(
r"TypeError|ReferenceError|is not defined|"
r"Cannot read|Cannot set|undefined is not|"
r"null.*property|property.*null|"
r"is not a function|is not iterable|"
r"UnhandledPromiseRejection", re.IGNORECASE,
)),
(ErrorCategory.LOGIC, re.compile(
r"AssertionError|assertion.*failed|expected.*got|"
r"mismatch|wrong.*value|incorrect.*result|"
r"test.*failed|expected.*received", re.IGNORECASE,
)),
# Sprint 3b: DB_ERROR β IntegrityError, FK, connection pool, deadlock
(ErrorCategory.DB_ERROR, re.compile(
r"IntegrityError|ForeignKey|ForeignKeyViolation|"
r"connection pool|deadlock|"
r"relation.*not.*exist|table.*not.*exist|"
r"duplicate key|violates.*constraint|"
r"UniqueViolation|CheckViolation|NotNullViolation|"
r"OperationalError.*database|psycopg|asyncpg|"
r"FATAL.*database|could not connect.*database",
re.IGNORECASE,
)),
]
@dataclass
class ErrorResult:
category: ErrorCategory
repair_strategy: str
confidence: float # 0.0-1.0 β quanto siamo sicuri della classificazione
matched_pattern: str # substring che ha fatto match (debug)
def classify_error(errors: list[str]) -> ErrorResult:
"""
Classifica una lista di messaggi di errore nella categoria piΓΉ probabile.
Algoritmo:
1. Concatena gli errori (max 1500 chars)
2. Prova i pattern in ordine di prioritΓ (il primo match vince)
3. Se nessun match β UNKNOWN con confidence 0.0
Non usa LLM β Γ¨ sincrono e a zero latency.
Chiamato da _reflective_debug prima di invocare ARCHITECT.
"""
combined = " | ".join(str(e)[:500] for e in errors[-4:])[:1500] # S604: 400β500 per error string completa
for category, pattern in _PATTERNS:
m = pattern.search(combined)
if m:
matched = m.group(0)[:60]
strategy = _REPAIR_STRATEGIES[category]
return ErrorResult(
category = category,
repair_strategy = strategy,
confidence = 0.85,
matched_pattern = matched,
)
return ErrorResult(
category = ErrorCategory.UNKNOWN,
repair_strategy = _REPAIR_STRATEGIES[ErrorCategory.UNKNOWN],
confidence = 0.0,
matched_pattern = "",
)
def format_for_context(result: ErrorResult) -> str:
"""
Produce la stringa da iniettare nel context del loop prima del retry.
Formato: [ERRORE CLASSIFICATO: <Categoria>] + strategia.
"""
label = f"ERRORE CLASSIFICATO: {result.category.value.upper()}"
if result.matched_pattern:
label += f" (match: \"{result.matched_pattern}\")"
return f"\n\n[{label}]\n{result.repair_strategy}"
|