Spaces:
Running
Running
| """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, | |
| ) | |
| # 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 ──────────────── | |
| 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 <think> 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 <think> sempre â Qwen3 e DeepSeek-R1 li iniettano indipendentemente dagli altri segnali | |
| import re as _re # BV-3: hoisted — stdlib, always in sys.modules | |
| if '<think>' in text: | |
| text = _re.sub(r'<think>[\s\S]*?</think>', '', text, flags=_re.IGNORECASE) # blocco chiuso | |
| text = _re.sub(r'<think>[\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, | |
| ) | |