"""unified_loop_llm.py — LLMSelectionMixin: selezione LLM, routing e output sanitization. Estratto da unified_loop.py (P20-TD1 Fase 2). Contiene: Block A — LLM selection + routing class attrs: _SKIP_SMOL_RE, _COMPLEX_APP_RE, _MULTI_FEATURE_RE, _CODE_TASK_RE, _NEEDS_PLAN_RE _get_llm_for_goal(): CODER vs default LLM selection (S362/S416) _get_fast_llm(): lazy fast LLM cache (S-FAST) _get_verifier_llm(): P25-B4 cross-model critic selection _is_pure_explanation(): conceptual query detection (B5) _max_tokens_for_goal(): token budget estimation (S373/B13) Block B — Output sanitization + explanation regex attrs: _sanitize_agent_output(): staticmethod rimozione monologue interno (S371) _PURE_EXPLANATION_RE, _EXPL_ACTION_RE, _EXPL_FILE_REF_RE: regex per _is_pure_explanation Invariante B1: nessun corpo duplicato con unified_loop.py. MRO Python garantisce self._SKIP_SMOL_RE / self._sanitize_agent_output() funzionino da qualsiasi metodo di UnifiedAgentLoop. Importato da: unified_loop.py (solo per ereditarietà LLMSelectionMixin) """ from __future__ import annotations import re from typing import Any import logging _logger = logging.getLogger("agents.unified_loop_llm") class LLMSelectionMixin: # ── Block A: LLM selection + routing ───────────────────────────────────── def _get_llm_for_goal(self, goal: str) -> Any: """S362: return CODER-role LLM for code-heavy goals, default otherwise. GAP-ROUT: route SQL/Reasoning/MMLU to REASONER role (Cerebras 120B). S416-Fix3: anche app complesse (tok_budget >= 6144) usano CODER (70B) anche se _CODE_RE non matcha — garantisce qualità su app multi-file.""" g = goal[:500] _is_code = bool(self._CODE_GOAL_RE.search(g)) _is_reasoning = bool(self._REASONING_GOAL_RE.search(g)) or \ bool(self._SQL_GOAL_RE.search(g)) or \ bool(self._MMLU_GOAL_RE.search(g)) _tok = self._max_tokens_for_goal(goal) _needs_heavy = _is_code or _is_reasoning or _tok >= 6144 if not _needs_heavy: return self.llm if _is_reasoning: try: from models.role_router import RoleRouter, Role return RoleRouter.get_client(Role.REASONER) except Exception: pass if self._coder_llm is None: try: from models.role_router import RoleRouter, Role self._coder_llm = RoleRouter.get_client(Role.CODER) except Exception: self._coder_llm = self.llm return self._coder_llm def _get_fast_llm(self) -> Any: """S-FAST: return Role.FAST client (Groq llama-3.1-8b-instant) per query semplici. Caricato lazy e cachato in self._fast_llm — zero overhead dopo il primo accesso. Fallback silenzioso su self.llm se GROQ_API_KEY mancante o RoleRouter non disponibile.""" if self._fast_llm is None: try: from models.role_router import RoleRouter, Role self._fast_llm = RoleRouter.get_client(Role.FAST) except Exception: self._fast_llm = self.llm return self._fast_llm def _get_verifier_llm(self) -> Any: """P25-B4: Cross-model critic — restituisce un provider DIVERSO da self.llm per la verifica. Elimina il bias di conferma: lo stesso modello che ha generato la risposta non dovrebbe giudicare se è corretta. Strategia di selezione: - Generatore Groq → Verifier Gemini RESEARCHER (ragionamento diverso) - Generatore Gemini → Verifier Groq CODER (modello diverso) - Generatore altri → Verifier Gemini RESEARCHER → fallback Groq CODER - Qualsiasi errore → fallback self.llm (comportamento invariato, zero regressioni) Cache lazy (self._verifier_llm) — caricato una volta per sessione. """ if self._verifier_llm is not None: return self._verifier_llm try: from models.role_router import RoleRouter as _RRv, Role as _Rolev _gen_prov = getattr(self.llm, 'provider_name', '') or '' if 'groq' in _gen_prov: # Generatore Groq → Verifier Gemini (diverso reasoning) self._verifier_llm = _RRv.get_client(_Rolev.RESEARCHER) elif 'gemini' in _gen_prov: # Generatore Gemini → Verifier Groq CODER (70B, diverso modello) self._verifier_llm = _RRv.get_client(_Rolev.CODER) elif 'cerebras' in _gen_prov: # Generatore Cerebras → Verifier Gemini self._verifier_llm = _RRv.get_client(_Rolev.RESEARCHER) elif 'nvidia' in _gen_prov: # Generatore NVIDIA → Verifier Groq CODER (per diversificare) self._verifier_llm = _RRv.get_client(_Rolev.CODER) else: # OpenRouter / SambaNova / altri → Verifier Groq CODER come default cross-model self._verifier_llm = _RRv.get_client(_Rolev.CODER) _logger.debug( "P25-B4 verifier_llm: gen_prov=%s → verifier=%s", _gen_prov, getattr(self._verifier_llm, 'provider_name', '?'), ) except Exception as _exc: _logger.debug("P25-B4 _get_verifier_llm fallback: %s", _exc) self._verifier_llm = self.llm # fallback: invariato return self._verifier_llm def _is_pure_explanation(self, goal: str) -> bool: """B5: True se goal è domanda concettuale pura — nessun tool necessario. 4 guard fail-open: len<300 | pattern interrogativo | no action verb | no file ref.""" if len(goal) > 300: return False if not self._PURE_EXPLANATION_RE.search(goal[:200]): return False if self._EXPL_ACTION_RE.search(goal[:200]): return False if self._EXPL_FILE_REF_RE.search(goal[:200]): return False return True # S371: _SKIP_SMOL_RE — skippa smolagents per query semplici (notizie, cerca) → direct tools # S427: ampliato — più query bypassano smolagents → direct tools (più veloce). _SKIP_SMOL_RE = re.compile( r'\b(notizie|news|ultime notizie|cerca|ricerca|meteo|tempo|previsioni|' r'cerca online|cerca su|trova online|guarda su|vai su|' # S427: meteo/clima ampliato r'weather|forecast|temperatura|clima|piove|nevica|temporale|umidità|vento|' # S427: valute/crypto r'valuta|cambio|tasso|euro|dollaro|bitcoin|ethereum|crypto|yen|sterlina|' r'exchange rate|currency|' # S427: knowledge lookup r'wikipedia|enciclopedia|chi [eè]|chi era|storia di|' # S427: calcoli diretti r'calcola|quanto fa|quant[oei]\s+fa|risultato di|computa|' # S427: data/ora r'che ora|che giorno|data di oggi|ora attuale|orario|timezone|' # S427: traduzione r'traduci|traduzione|translate|translation|' # EN-DIRECT: English patterns — bypass planner (save 5-15s latency) for simple EN queries r'calculate|compute|how much is \d|what time is it|current time|today.s date|' r'what.s the (?:time|date|day|weather)|who (?:is|was|are|were) |what is the (?:weather|capital|population)|' r'stock price|crypto price|price of (?:bitcoin|ethereum|gold)|' r'weather in|forecast for|temperature in|' r'convert \d|how many \w+ in|exchange rate (?:of|for|from)|' r'latest news (?:about|on)|search (?:for |on )?wikipedia|look up |' # B7: unit conversion, timezone, date calc, IP — direct tools, skip planner r'converti\s+\d+\s+\w+\s+(?:in|to)\s+\w+|' r'quanti\s+giorni\s+(?:tra|fino|mancano)|' r'che\s+ora\s+[e\xe8]\s+a\s+\w+|what\s+time\s+is\s+it\s+in\s+\w+|' r'(?:mio\s+ip|my\s+ip|ip\s+address)\s*\??)\b', re.IGNORECASE, ) # ── S373: pattern per stima budget token output ──────────────────────── # S427: aggiunti più trigger per app/sistemi complessi + more token budget _COMPLEX_APP_RE = re.compile( r'\b(crea|scrivi|implementa|build|create|write|implement|' r'sviluppa|costruisci|progetta|genera|scaffold|deploy|develop)\b.{0,80}' r'\b(app|applicazione|application|progetto|project|website|sito|' r'dashboard|api|backend|frontend|server|service|platform|piattaforma|' r'sistema|e.?commerce|chatbot|bot|game|gioco|portfolio|blog|crm|cms|' r'saas|marketplace|admin|panel|landing|cli|tool|library|sdk)\b', re.IGNORECASE | re.DOTALL, ) # S427: indicatori di complessità multi-feature ampliati _MULTI_FEATURE_RE = re.compile( r'\b(completo|completa|full.?stack|multi.?file|con\s+test|con\s+typescript|' r'con\s+animazioni?|con\s+filtri?|con\s+localStorag|tipizzato|' r'multiple\s+components?|più\s+component|separati|con\s+routing|' r'con\s+auth(?:entication)?|con\s+login|con\s+database|con\s+deploy|' r'con\s+pagament[io]|con\s+api|con\s+websocket|con\s+i18n|' r'con\s+dark\s+mode|con\s+responsive|multi.?pagina|multi.?step|' r'end.?to.?end|production.?ready|scalabile|enterprise|' r'con\s+docker|con\s+ci.?cd|con\s+testing|con\s+validation)\b', re.IGNORECASE, ) # S427: tipi di artefatti codice ampliati per token budget _CODE_TASK_RE = re.compile( r'\b(codice|funzione|function|classe|class|componente|component|' r'modulo|module|script|algoritmo|algorithm|hook|utility|helper|' r'type|interface|enum|decorator|middleware|service|repository|' r'controller|handler|resolver|store|reducer|action|mutation|' r'schema|dto|validator|mapper|factory|builder|' r'context|provider|consumer|wrapper|composable|mixin)\b', re.IGNORECASE, ) # B7: planner richiesto solo per task di progettazione/implementazione complessa # S427: aggiunti verbi che richiedono pianificazione (patch/debug/ottimizza/ecc.) _NEEDS_PLAN_RE = re.compile( # F18: esteso con pattern IT mancanti — installa/nuovo/clona/avvia/esegui/testa r'\b(crea|implementa|scrivi|refactor|progetta|costruisci|' r'sistema|architettura|deploy|migra|sviluppa|build|create|' r'implement|design|architect|' r'patch|fix|debug|ottimizza|optimize|rinomina|rename|' r'aggiungi|aggiorna|update|integra|integrate|' r'scaffold|bootstrap|genera|generate|' r'ristruttura|restructure|refactorizza|converti|convert|' r'installa|clona|avvia|esegui|testa|verifica|configura|' r'install|clone|run|test|verify|setup|configure)\b', re.IGNORECASE, ) @classmethod # B13: era @staticmethod — usa class attrs già compilati invece di re.search inline def _max_tokens_for_goal(cls, goal: str) -> int: """S373: stima budget max_tokens in base alla complessità del goal. Groq llama-3.3-70b supporta 32768 output token, Gemini 2.5-flash 8192. Default 2048 solo per Q&A semplice; per codice si scala fino a 8192. B13: usa class attrs _COMPLEX_APP_RE/_MULTI_FEATURE_RE/_CODE_TASK_RE già compilati. """ # B6: fast-fix task → risposta atomica breve → 512 token max (-30-60% LLM time) # Conseguenza: "aggiungi import X" non richiede 4096 tokens — 50-100 bastano. if cls._FAST_FIX_RE.search(goal[:180]) and len(goal) < 180: return 512 g = goal[:600] is_app = bool(cls._COMPLEX_APP_RE.search(g)) is_complex = bool(cls._MULTI_FEATURE_RE.search(g)) is_code = bool(cls._CODE_TASK_RE.search(g)) if is_app and is_complex: return 8192 # app multi-feature → output completo garantito if is_app or is_complex: return 6144 # app semplice o feature complessa if is_code: return 4096 # singola funzione/componente return 2048 # Q&A, spiegazioni, risposte brevi # ── Block B: Output sanitization + explanation regex attrs ──────────────── @staticmethod def _sanitize_agent_output(text: str) -> str: """S371: Rimuove monologue interno che può leakare da smolagents/LLM. Patterns rimossi: GOAL:/DONE_WHEN:/OUT_OF_SCOPE: blocks, Proceed.We need to output..., Thought:/Code:/Observation: lines, raw JSON tool call arrays. S390: aggiunto stripping blocks (Qwen3/DeepSeek-R1 reasoning leaks). B12: fast-path — skip tutti i regex se nessun segnale di monologue (risparmia 50-200ms). """ if not text: return text # S390: strip sempre — Qwen3 e DeepSeek-R1 li iniettano indipendentemente dagli altri segnali import re as _re # BV-3: hoisted — stdlib, always in sys.modules if '' in text: text = _re.sub(r'[\s\S]*?', '', text, flags=_re.IGNORECASE) # blocco chiuso text = _re.sub(r'[\s\S]*', '', text, flags=_re.IGNORECASE) # blocco aperto (troncato) text = text.strip() if not text: return text # B12: fast-path exit — ~95% delle risposte normali non contengono questi segnali _MONOLOGUE_SIGNALS = ('GOAL:', 'DONE_WHEN:', 'OUT_OF_SCOPE:', 'Proceed.', 'Thought:', 'Code:', '[{"action"') if not any(s in text for s in _MONOLOGUE_SIGNALS): return text.strip() # BV-3: _re already imported at function top # Strip smolagents internal prompt blocks (GOAL/DONE_WHEN/OUT_OF_SCOPE) text = _re.sub( r'(?m)^(?:GOAL|DONE_WHEN|OUT_OF_SCOPE):\s*.*(?:\n(?!\n)[^\n]*)*', '', text ) # Strip "Proceed. We need to output the tool call now. [{"action":...}]" text = _re.sub( r'Proceed\.?\s*We\s+need\s+to\s+output\s+the\s+tool\s+call\s+now\.?\s*\[.*?\]', '', text, flags=_re.DOTALL ) # Strip raw JSON tool call arrays at line start text = _re.sub(r'^\s*\[\s*\{["\']*action["\']*\s*:', '', text, flags=_re.MULTILINE) # Strip smolagents Thought:/Code:/Observation: line prefixes (when not inside code blocks) text = _re.sub(r'^(?:Thought|Code|Observation|Action Input):\s*', '', text, flags=_re.MULTILINE) # Strip [STEP N/M] markers text = _re.sub(r'\[STEP\s+\d+/\d+\]\s*', '', text) return text.strip() # S375: Format directive classifier — speculare al frontend formatClassifier.ts # Iniettato in _build_messages() per garantire formattazione consistente # anche sui task tool-heavy gestiti interamente dal backend. _FORMAT_DIRECTIVE_CODE = ( "FORMATO RISPOSTA OBBLIGATORIO — CODICE:\n" "• Usa SEMPRE blocchi markdown con linguaggio specificato (```python, ```typescript, ecc.)\n" "• Struttura: breve spiegazione → blocco codice completo → come usarlo\n" "• Ogni blocco deve essere autonomo ed eseguibile senza modifiche\n" "• Aggiungi commenti inline per la logica non ovvia\n" "• Se multi-file: mostra ogni file in un blocco separato con il nome come titolo\n" "• Formato titolo file OBBLIGATORIO: ### src/nomefile.tsx (H3 - risparmia spazio verticale su mobile)" ) _FORMAT_DIRECTIVE_MARKDOWN = ( "FORMATO RISPOSTA OBBLIGATORIO — STRUTTURATO:\n" "• Usa titoli (##), liste puntate, grassetto per punti chiave\n" "• Max 3 livelli di gerarchia — non annidare troppo\n" "• Tabelle markdown per confronti (3+ elementi)\n" "• Paragrafi brevi (2-3 righe) per leggibilità mobile\n" "Su mobile, tabelle 3+ colonne: usa lista chiave-valore o ### + punti (scroll orizzontale non usabile su iPhone)" ) _FORMAT_DIRECTIVE_CONVERSATIONAL = ( "FORMATO RISPOSTA OBBLIGATORIO — CONVERSAZIONALE:\n" "• Tono diretto e naturale, senza formalismi eccessivi\n" "• Niente strutture markdown pesanti per domande semplici\n" "• Rispondi in 1-3 paragrafi se la domanda è semplice\n" "• Usa grassetto solo per termini chiave critici" ) _FORMAT_DIRECTIVE_MATH = ( "FORMATO RISPOSTA OBBLIGATORIO — MATEMATICA:\n" "• Mostra SEMPRE i calcoli passo per passo numerati\n" "• Usa notazione chiara: P(A|B), E[X], Σ, ecc.\n" "• Risultato finale in riga separata con grassetto\n" "• Usa il PUNTO come separatore decimale (non virgola)\n" "• Esprimi probabilità sia come frazione che come percentuale" ) # S-FMT-MOBILE: 4 nuove direttive specializzate iPhone (2026-06-12) _FORMAT_DIRECTIVE_RESEARCH = ( "FORMATO RISPOSTA OBBLIGATORIO - RICERCA:\n" "- Struttura ogni fonte: Fonte -> sintesi 1-2 righe -> punto chiave\n" "- Max 4 fonti, priorita qualita su quantita\n" "- Separazione netta fatti verificati vs interpretazioni\n" "- Termina con sezione VERDETTO (1 paragrafo, risposta diretta)\n" "- Nessuna fonte disponibile: dichiaralo esplicitamente" ) _FORMAT_DIRECTIVE_DEBUG = ( "FORMATO RISPOSTA OBBLIGATORIO - DEBUG:\n" "- Struttura FISSA 4 sezioni: Errore -> Causa -> Fix (codice) -> Prevenzione\n" "- Sezione Fix: codice completo pronto da copiare, non frammenti\n" "- Sezione Causa: spiega PERCHE accade, non solo cosa accade\n" "- Sezione Prevenzione: max 2 punti concreti\n" "- Bug multipli: numera ogni set Errore/Causa/Fix/Prevenzione" ) _FORMAT_DIRECTIVE_MEDIA = ( "FORMATO RISPOSTA OBBLIGATORIO - MEDIA/IMMAGINE:\n" "- URL immagine come link cliccabile: [Visualizza immagine](URL)\n" "- URL in riga separata per copy-paste\n" "- Breve descrizione di cosa raffigura l' immagine generata\n" "- Se non disponibile: fornisci URL Pollinations come fallback esplicito" ) _FORMAT_DIRECTIVE_MOBILE_COMPACT = ( "FORMATO RISPOSTA OBBLIGATORIO - MOBILE RAPIDO:\n" "- Risposta MASSIMO 150 parole, vai dritto al punto\n" "- Prima riga = verdetto/azione (no preamboli)\n" "- Usa emoji come icone stato: OK fatto, WARN attenzione, ERR errore, TIP suggerimento\n" "- Zero spiegazioni non richieste, solo l' essenziale\n" "- Se servono dettagli: offri follow-up" ) # GAP-A: narrazione pre-tool — spiega all'utente PERCHÉ l'agente usa quel tool. # Usato nel _run_subtask (reason field) e come text_chunk prima del gather. # Zero latency: nessuna chiamata LLM — solo lookup di dizionario. _TOOL_NARRATION: dict[str, str] = { "web_search": "🔍 Cerco informazioni aggiornate sul web", "read_page": "🌐 Leggo la pagina web per estrarre i dati", "write_file": "✍️ Implemento il codice richiesto", "read_file": "📂 Leggo il file per analizzare la struttura attuale", "apply_patch": "🔧 Applico la patch mirata al file", "run_python": "⚙️ Eseguo il codice Python per verificare il risultato", "execute_shell": "🖥️ Eseguo il comando shell", "execute_sql": "🗄️ Eseguo la query SQL", "database_query": "🗄️ Eseguo la query sul database", "git_push": "🚀 Invio le modifiche al repository", "git_commit": "💾 Salvo le modifiche con un commit", "git_status": "📊 Verifico lo stato del repository", "git_diff": "🔎 Confronto le modifiche in staging", "git_clone": "📥 Clono il repository", "npm_install": "📦 Installo le dipendenze Node.js", "npm_run": "▶️ Avvio lo script npm", "pip_install": "📦 Installo i pacchetti Python", "type_check": "✅ Verifico i tipi TypeScript", "lint_code": "🔍 Analizzo la qualità del codice", "get_weather": "🌤️ Recupero le previsioni meteo", "get_news": "📰 Recupero le ultime notizie", "recall": "🧠 Consulto la memoria dell'agente", "list_files": "📁 Elenco i file del progetto", "directory_tree": "🗂️ Analizzo la struttura del progetto", "file_search": "🔎 Cerco nel codice del progetto", "create_project": "🏗️ Creo la struttura del progetto", "scaffold_project": "🏗️ Genero la struttura del progetto da template", # Tool planner-only (non in TOOL_REGISTRY) — narrazione pre-gather "code": "💻 Implemento la soluzione richiesta", "send_email": "📧 Invio l'email tramite Resend", "create_pdf": "📄 Genero il documento PDF", "call_api": "🔗 Chiamo l'API esterna", "browser_navigate": "🌐 Navigo verso la pagina", "browser_session_open": "🌐 Apro una sessione browser", "browser_session_act": "🖱️ Interagisco con la pagina", "get_image": "🖼️ Recupero l'immagine", "create_chart": "📊 Creo il grafico", "diff_text": "🔎 Confronto i testi", "validate_json": "✅ Valido il JSON", "calculate": "🔢 Calcolo il risultato matematico", "image": "🎨 Genero un'immagine con AI", "generate_image": "🎨 Genero un'immagine con AI", "delegate_task": "🤝 Delego a un micro-agente specializzato", "browser_session_open": "🖱️ Apro una sessione browser autonoma", "browser_session_act": "🖱️ Interagisco con la pagina nel browser", "web_research": "🔍 Ricerca approfondita multi-fonte sul web", } _TOOL_NARRATION_DEFAULT = "⚙️ Eseguo l'operazione" # GAP-B scaffold preview: albero file mostrato in real-time PRIMA che il tool scriva. # Sincronizzato con i template in registry.py/_scaffold_project — aggiorna entrambi. _SCAFFOLD_FILE_TREE: dict[str, list[str]] = { "react": ["package.json", "index.html", "vite.config.ts", "src/main.tsx", "src/App.tsx", "src/index.css"], "nextjs": ["package.json", "next.config.mjs", "app/layout.tsx", "app/page.tsx"], "fastapi": ["main.py", "requirements.txt", "Dockerfile", ".gitignore"], "flask": ["app.py", "requirements.txt", ".gitignore"], "django": ["manage.py", "requirements.txt", "config/settings.py", "config/urls.py", "api/views.py", "api/urls.py"], "express": ["package.json", "src/index.js", ".gitignore"], } # S427: ampliato con verbi IT/EN + tecnologie — stesso set di _CODE_RE _CODE_GOAL_RE = re.compile( r'\b(scrivi|crea|genera|implementa|refactor|codice|funzione|classe|componente|' r'script|algoritmo|api|endpoint|hook|store|tipo|interface|migration|query|schema|' r'write|create|generate|implement|code|function|class|component|backend|frontend|' r'sistema|correggi|debugga|patch|rinomina|sostituisci|rimpiazza|ottimizza|' r'rename|replace|remove|delete|fix|debug|optimize|deploy|scaffold|' r'typescript|javascript|python|react|vue|svelte|angular|next\.?js|nuxt|' r'fastapi|flask|django|express|nest\.?js|rails|laravel|' r'service|repository|controller|middleware|utility|helper|' r'css|scss|html|sql|graphql|dockerfile|prisma|drizzle)\b', re.IGNORECASE, ) # S427: ampliato con più concetti matematici IT/EN _MATH_GOAL_RE = re.compile( r'\b(calcola|calcolare|probabilit|bayes|integra|derivat|statistic|media|' r'varianza|percentuale|equazione|formula|risolvi|calculate|probability|' r'integral|derivative|statistic|mean|variance|equation|solve|' r'somma|prodotto|divisione|divisore|multiplo|mcd|mcm|modulo|quoziente|' r'logaritmo|radice|potenza|fattoriale|fibonacci|' r'trigonometria|seno|coseno|tangente|algebra|geometria|aritmetica|' r'matrice|determinante|vettore|' r'sum|product|division|lcm|gcd|remainder|quotient|' r'logarithm|sqrt|square\s*root|factorial|power|' r'trigonometry|sine|cosine|tangent|matrix|determinant|vector|' r'fraction|frazioni|decimali|decimal|percentag)\b', re.IGNORECASE, ) # S427: ampliato con più trigger per risposta strutturata markdown _MARKDOWN_GOAL_RE = re.compile( r'\b(elenca|confronta|spiega|differenz|vantaggi|svantaggi|guida|tutorial|' r'passaggi|step|pros?|contro|list|compare|explain|differences?|advantages?|' r'disadvantages?|guide|steps?|pros?|cons?|' r'riassumi|riassunto|summarize|summary|overview|panoramica|' r'tabella|table|sezioni|sections|categorizza|categorie|' r'elencami|dammi una lista|i migliori|le migliori|i principali|le principali|' r'tipi di|types\s+of|examples?\s+of|esempi\s+di|' r'struttura|structure|breakdown|analisi|analysis)\b', re.IGNORECASE, ) # S-FMT-MOBILE: regex per direttive specializzate iPhone (2026-06-12) _RESEARCH_GOAL_RE = re.compile( r'\b(ricerca|research|trova\s+info|notizie|news|articoli|fonti|' r'find\s+information|when\s+did|storia\s+di|' r'ultima\s+notizia|latest\s+on|trending|fact.?check)\b', re.IGNORECASE, ) _DEBUG_GOAL_RE = re.compile( r'\b(errore|error|traceback|exception|stacktrace|stack\s+trace|' r'debug|debugga|crash|fallisce|non\s+funziona|si\s+rompe|' r'undefined\s+is\s+not|cannot\s+read|type\s+error|runtime\s+error|' r'fix\s+(?:the\s+)?(?:bug|error|crash|issue|problem))\b', re.IGNORECASE, ) _MEDIA_GOAL_RE = re.compile( r'\b(genera\s+immagine|generate\s+image|crea\s+immagine|create\s+image|' r'immagine\s+di|image\s+of|foto\s+di|picture\s+of|' r'disegna|draw|illustra|illustrate|render|genera\s+foto|' r'flux|dall.?e|midjourney|stable\s+diffusion|pollinations)\b', re.IGNORECASE, ) _MOBILE_COMPACT_RE = re.compile( r'\b(quick|veloce|breve|dimmi\s+solo|solo\s+il\s+risultato|verdetto|' r'in\s+breve|tl;?dr|sintesi\s+rapida|recap|riassumi\s+in\s+poche|' r'risposta\s+breve|brief\s+answer|short\s+answer)\b', re.IGNORECASE, ) # GAP-ROUT: routing specializzato per benchmark (SQL, Reasoning, MMLU) _SQL_GOAL_RE = re.compile( r'\b(sql|postgresql|cte ricorsiva|recursive cte|with recursive|' r'window functions?|over\(|partition by|rank\(|row_number\(|' r'gerarchia|parent_id|manager_id|recursive)\b', re.IGNORECASE, ) _REASONING_GOAL_RE = re.compile( r'\b(reasoning|gsm8k|math|matematica|logica|ragionamento|' r'ted the t-rex|calcola|calcolare|probabilit|bayes|frazioni|percentuale)\b', re.IGNORECASE, ) _MMLU_GOAL_RE = re.compile( r'\b(mmlu|computer science|informatica|architettura|os|networking|' r'database|complessità|p vs np|modello osi|acid properties)\b', re.IGNORECASE, ) # S-FMT-ORCH: fast-fix detector per bypass ARCHITECT su singola operazione (<180 chars) # B1: espansa con 10 operazioni atomiche — guardata da len(goal)<180 nel chiamante. # Conseguenze: skip ARCHITECT (-15s) per operazioni single-step unambiguamente chiare. # Zero cons: la guard len<180 esclude goal multi-step; il piano sintetico è sufficiente. _FAST_FIX_RE = re.compile( r'\b(typos?|rinomina\s+\w|rename\s+\w|' r'cambia\s+.{1,40}\s+in\s+|change\s+.{1,40}\s+to\s+|' r'sostituisci\s+.{1,40}\s+con\s+|replace\s+.{1,40}\s+with\s+|' r'aggiungi\s+commento|add\s+comment|correggi\s+il\s+typo|fix\s+typo|' r'rimuovi\s+riga|delete\s+line|bump\s+version|update\s+version\s+to|' # Fix-4.2: import banali non hanno bisogno di 10-15s Architect r'aggiungi\s+import|add\s+import|manca\s+import|import\s+missing|' r'install\s+package|installa\s+pacchetto|aggiungi\s+dipendenza|' # B1-a: parametri / campi / proprietà — operazioni single-step su firme r'aggiungi\s+(?:un\s+)?(?:parametro|param|argomento|campo|field|propert\w+)\b|' r'rimuovi\s+(?:il\s+|la\s+|questo\s+|questa\s+)?(?:parametro|param|campo|field|prop|propert\w+)\b|' # B1-b: tipi / annotazioni — aggiunta tipo/interface senza logica r'aggiungi\s+(?:il\s+|un\s+)?(?:tipo|type|annotation|return\s+type|tipo\s+di\s+ritorno)\b|' r'(?:aggiorna|update)\s+(?:il\s+)?(?:tipo|type|interface|schema|signature)\b|' # B1-c: export — aggiunta/rimozione senza logica r'aggiungi\s+(?:il\s+|un\s+)?(?:export|export\s+default|default\s+export)\b|' r'rimuovi\s+(?:il\s+)?(?:export|default\s+export)\b|' # B1-d: log / debug — aggiungi o rimuovi singola istruzione r'aggiungi\s+(?:un\s+)?(?:console\.log|print|log|debug)\b|' r'(?:togli|elimina|cancella|rimuovi)\s+(?:il\s+|i\s+)?(?:console\.log|log\s+di\s+debug|debug\s+log|print)\b|' # B1-e: formattazione / indentazione — nessuna logica, solo stile r'correggi\s+(?:l[a\']?\s+)?(?:indentazione|indent|spaziatura|spacing|formatt\w+)\b|' r'(?:formatta|format)\s+(?:il\s+)?(?:codice|file|questo)\b|' # B1-f: conversioni semplici — async/arrow/const senza cambiare logica r'(?:converti|convert)\s+.{1,50}\s+(?:in|to|a)\s+(?:async|arrow\s+function|const|let)\b)\b', re.IGNORECASE, ) # B5: pure-explanation bypass — nessun tool, LLM diretto (risparmio -20-30s). # Fix storia: cos'?[eè] (char class ['è] era sbagliata: matchava 1 char, non seq 'è). # Fail-open: 4 guard — len<300, pattern, no action verb, no file ref. _PURE_EXPLANATION_RE = re.compile( r"^\s*(?:" r"(?:cos'?[e\xe8]\s+)" r"|(?:che\s+cos'?[a\xe0]?\s*[e\xe8]\s+)" r"|(?:spiegami\b)" r"|(?:dimmi\s+(?:come|cosa|cos|perch[e\xe8]|qual[e\xe8])\b)" r"|(?:qual[e\xe8]\s+|qual\s+[e\xe8]\s+)(?:la\s+)?(?:differenz[ae]|scopo|significato)" r"|(?:come\s+funziona\s+(?!il\s+(?:mio|tuo|nostro|codice|progetto|login|sito|sistema|questo)\b))" r"|(?:cosa\s+(?:fa|significa|vuol\s+dire|rappresenta)\s+)" r"|(?:a\s+cosa\s+serve\s+)" r"|(?:perch[e\xe8]\s+(?:si\s+usa|viene\s+usato|[e\xe8]\s+utile|esiste)\s+)" r"|(?:what\s+(?:is|are|does|means?)\s+(?!my\b|the\s+output\b|this\s+code\b))" r"|(?:how\s+does\s+(?!my\b|this\b|the\s+code\b|it\s+work\s+in\b))" r"|(?:explain\s+(?:me\s+)?(?:briefly\s+)?(?:what|how|why|the)\s+)" r")", re.IGNORECASE | re.DOTALL, ) _EXPL_ACTION_RE = re.compile( r"\b(crea|scrivi|genera|implementa|esegui|correggi|fix|run|create|write|" r"generate|implement|execute|installa|deploy|avvia|configura|aggiorna|update|" r"aggiungi|rimuovi|modifica|refactor|build|pubblica|manda|invia|upload|" r"poi|then|e\s+poi|and\s+then|quindi|dopo|successivamente)\b", re.IGNORECASE, ) _EXPL_FILE_REF_RE = re.compile( r"\b(nel\s+codice|in\s+questo\s+file|nel\s+file|nel\s+progetto|" r"qui\s+sopra|il\s+codice\s+che|in\s+the\s+code|" r"in\s+this\s+file|this\s+function|questa\s+funzione)\b", re.IGNORECASE, )