Spaces:
Running
Running
| """ | |
| role_router.py — Multi-model role routing (S362, aggiornato 2026-06-14 benchmark) | |
| BENCHMARK RESULTS 2026-06-14 FINALE (14 modelli × 3 test, max_tokens corretti): | |
| 100% qualità (ordinati per TTFT): | |
| #1 Groq / openai/gpt-oss-20b — 170ms 100% ← FASTEST | |
| #2 Cerebras / gpt-oss-120b — 207ms 100% ← REASONING (max_tokens≥500) | |
| #3 Groq / openai/gpt-oss-120b — 235ms 100% | |
| #4 Cerebras / zai-glm-4.7 — 254ms 100% | |
| #5 Groq / compound-mini — 341ms 100% | |
| #6 SambaNova / DeepSeek-V3.1 — 482ms 100% | |
| #7 SambaNova / gemma-4-31B — 2132ms 100% | |
| #8 OpenRouter / gpt-oss-120b:free — 2160ms 100% | |
| Role assignments 2026-06-14 FINALE: | |
| FAST → Groq openai/gpt-oss-20b (170ms, 100%) ← #1 assoluto | |
| ARCHITECT → Groq llama-4-scout-17b 10M ctx (244ms, 67% — best per contesto lungo) | |
| CODER → Groq openai/gpt-oss-120b (235ms, 100%) ← #3 qualità | |
| TESTER → Groq openai/gpt-oss-20b | |
| CONTEXT → Groq openai/gpt-oss-20b | |
| RESEARCHER → Gemini 2.5-flash (599ms, 67% — math prompt-sensitive) | |
| REASONER → Cerebras gpt-oss-120b (207ms, 100%, reasoning model → max_tokens≥500) | |
| SAMBANOVA → SambaNova DeepSeek-V3.1 (482ms, 100%) | |
| DEFAULT → AIClient() primary (openai/gpt-oss-120b o primo disponibile) | |
| OpenRouter tenuto come fallback secondario (gpt-oss-120b:free = 1645ms ma 100% qualità). | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from enum import Enum | |
| from typing import Any | |
| import logging | |
| _logger = logging.getLogger("models.role_router") | |
| class Role(str, Enum): | |
| FAST = "fast" # greetings, math semplice, identity — openai/gpt-oss-20b | |
| ARCHITECT = "architect" # planning, ragionamento complesso — llama-4-scout (10M ctx) | |
| CODER = "coder" # coding, debug — llama-3.3-70b-versatile | |
| TESTER = "tester" # test gen, debug hints — llama-3.3-70b-versatile | |
| CONTEXT = "context" # summarization, context compression — llama-3.3-70b-versatile | |
| DEFAULT = "default" # AIClient() primary | |
| RESEARCHER = "researcher" # web research + document synthesis — gemini-2.0-flash-exp | |
| REASONER = "reasoner" # throughput massimo — Cerebras llama-4-scout (2000+ tok/s) | |
| SAMBANOVA = "sambanova" | |
| NVIDIA = "nvidia" # NVIDIA NIM — nemotron-3-ultra-550b (1M ctx) # DeepSeek-V3.2 via SambaNova (404ms, 100% qualità benchmark) | |
| class RoleRouter: | |
| """ | |
| Factory: `RoleRouter.get_client(Role.ARCHITECT)`. | |
| Restituisce un AIClient configurato per il provider ottimale del ruolo. | |
| Tutti i fallback sono silenziosi — restituisce sempre un AIClient valido. | |
| """ | |
| def get_client(role: Role) -> Any: | |
| """Ritorna un AIClient pre-configurato per il ruolo richiesto.""" | |
| try: | |
| if role == Role.FAST: | |
| return RoleRouter._fast_client() | |
| if role == Role.ARCHITECT: | |
| return RoleRouter._architect_client() | |
| if role == Role.CODER: | |
| return RoleRouter._coder_client() | |
| if role in (Role.TESTER, Role.CONTEXT): | |
| return RoleRouter._tester_client() | |
| if role == Role.RESEARCHER: | |
| return RoleRouter._researcher_client() | |
| if role == Role.REASONER: | |
| return RoleRouter._reasoner_client() | |
| if role == Role.SAMBANOVA: | |
| return RoleRouter._sambanova_client() | |
| if role == Role.NVIDIA: | |
| return RoleRouter._nvidia_client() | |
| except Exception as _exc: | |
| _logger.warning("[role_router] GAP-ROUT: fallback to default AIClient — role=%s raised %s: %s", | |
| role.value, type(_exc).__name__, _exc) # GAP-ROUT-FIX: debug→warning | |
| from models.ai_client import AIClient | |
| return AIClient() | |
| # ── Role-specific builders ───────────────────────────────────────────────── | |
| def _fast_client() -> Any: | |
| """Groq llama-3.3-70b-versatile — 344ms TTFT, 100% benchmark qualità. | |
| Usato per: greetings, calcoli semplici, identity, domande 1-liner.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| groq_key = os.getenv("GROQ_API_KEY") | |
| if not groq_key: | |
| return RoleRouter._tester_client() | |
| client = AIClient() | |
| fast = ProviderConfig( | |
| name="groq-fast", | |
| api_key=groq_key, | |
| base_url="https://api.groq.com/openai/v1", | |
| default_model=os.getenv("GROQ_FAST_MODEL", "llama-3.3-70b-versatile"), | |
| ) | |
| rest = [p for p in client.providers if p.name not in ("groq", "groq-fast", "groq-tester")] | |
| client.providers = [fast, *rest] | |
| client.provider_name = fast.name | |
| client.default_model = fast.default_model | |
| client.client = client._client_for(fast) | |
| return client | |
| def _architect_client() -> Any: | |
| """NVIDIA NIM deepseek-v4-flash (1M ctx) come primario — massima potenza per architettura. | |
| Fallback 1: Groq llama-4-scout (10M ctx, 480ms). Fallback 2: OpenRouter llama-4-scout:free.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| nvidia_key = os.getenv("NVIDIA_API_KEY") | |
| if nvidia_key: | |
| client = AIClient() | |
| nvidia = ProviderConfig( | |
| name="nvidia-architect", | |
| api_key=nvidia_key, | |
| base_url="https://integrate.api.nvidia.com/v1", | |
| default_model=os.getenv("NVIDIA_ARCHITECT_MODEL", "nvidia/deepseek-v4-flash"), | |
| ) | |
| rest = [p for p in client.providers if not p.name.startswith("nvidia")] | |
| client.providers = [nvidia, *rest] | |
| client.provider_name = nvidia.name | |
| client.default_model = nvidia.default_model | |
| client.client = client._client_for(nvidia) | |
| return client | |
| # Fallback 1: Groq llama-4-scout (10M ctx, 480ms) | |
| groq_key = os.getenv("GROQ_API_KEY") | |
| if groq_key: | |
| client = AIClient() | |
| architect = ProviderConfig( | |
| name="groq-architect", | |
| api_key=groq_key, | |
| base_url="https://api.groq.com/openai/v1", | |
| default_model=os.getenv("ARCHITECT_MODEL", "llama-4-scout"), | |
| ) | |
| rest = [p for p in client.providers if p.name not in ("groq", "groq-architect")] | |
| client.providers = [architect, *rest] | |
| client.provider_name = architect.name | |
| client.default_model = architect.default_model | |
| client.client = client._client_for(architect) | |
| return client | |
| # Fallback: OpenRouter meta-llama/llama-4-scout:free (1645ms ma 100% qualità) | |
| openrouter_key = os.getenv("OPENROUTER_API_KEY") | |
| if openrouter_key: | |
| client = AIClient() | |
| fallback = ProviderConfig( | |
| name="openrouter-architect", | |
| api_key=openrouter_key, | |
| base_url="https://openrouter.ai/api/v1", | |
| default_model="meta-llama/llama-4-scout:free", | |
| ) | |
| rest = [p for p in client.providers if not p.name.startswith("openrouter")] | |
| client.providers = [fallback, *rest] | |
| client.provider_name = fallback.name | |
| client.default_model = fallback.default_model | |
| client.client = client._client_for(fallback) | |
| return client | |
| return AIClient() | |
| def _coder_client() -> Any: | |
| """Groq llama-3.3-70b-versatile — 358ms TTFT, 100% benchmark qualità. | |
| AGGIORNATO 2026-08-04: era Groq openai/gpt-oss-120b. | |
| Fallback: OpenRouter llama-4-scout:free se GROQ_API_KEY mancante.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| groq_key = os.getenv("GROQ_API_KEY") | |
| model = os.getenv("CODER_MODEL", "llama-3.3-70b-versatile") | |
| if groq_key: | |
| client = AIClient() | |
| coder = ProviderConfig( | |
| name="groq-coder", | |
| api_key=groq_key, | |
| base_url="https://api.groq.com/openai/v1", | |
| default_model=model, | |
| ) | |
| rest = [p for p in client.providers if p.name not in ("groq", "groq-coder")] | |
| client.providers = [coder, *rest] | |
| client.provider_name = coder.name | |
| client.default_model = coder.default_model | |
| client.client = client._client_for(coder) | |
| return client | |
| openrouter_key = os.getenv("OPENROUTER_API_KEY") | |
| if openrouter_key: | |
| client = AIClient() | |
| fallback = ProviderConfig( | |
| name="openrouter-coder", | |
| api_key=openrouter_key, | |
| base_url="https://openrouter.ai/api/v1", | |
| default_model="meta-llama/llama-4-scout:free", | |
| ) | |
| rest = [p for p in client.providers if not p.name.startswith("openrouter")] | |
| client.providers = [fallback, *rest] | |
| client.provider_name = fallback.name | |
| client.default_model = fallback.default_model | |
| client.client = client._client_for(fallback) | |
| return client | |
| return AIClient() | |
| def _researcher_client() -> Any: | |
| """Gemini 2.0-flash-exp — TTFT 910ms, ottima per research/synthesis/doc analysis.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| gemini_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY") | |
| groq_key = os.getenv("GROQ_API_KEY") | |
| if gemini_key: | |
| client = AIClient() | |
| researcher = ProviderConfig( | |
| name="gemini-researcher", | |
| api_key=gemini_key, | |
| base_url="https://generativelanguage.googleapis.com/v1beta/openai", | |
| default_model=os.getenv("GEMINI_MODEL", "gemini-2.0-flash-exp"), | |
| ) | |
| rest = [p for p in client.providers if not p.name.startswith("gemini")] | |
| client.providers = [researcher, *rest] | |
| client.provider_name = researcher.name | |
| client.default_model = researcher.default_model | |
| client.client = client._client_for(researcher) | |
| return client | |
| elif groq_key: | |
| client = AIClient() | |
| groq_compound = ProviderConfig( | |
| name="groq-compound-researcher", | |
| api_key=groq_key, | |
| base_url="https://api.groq.com/openai/v1", | |
| default_model=os.getenv("GROQ_COMPOUND_MODEL", "groq/compound"), | |
| ) | |
| rest = [p for p in client.providers if p.name not in ("groq", "groq-compound-researcher")] | |
| client.providers = [groq_compound, *rest] | |
| client.provider_name = groq_compound.name | |
| client.default_model = groq_compound.default_model | |
| client.client = client._client_for(groq_compound) | |
| return client | |
| return AIClient() | |
| def _reasoner_client() -> Any: | |
| """Cerebras llama-4-scout — 207ms TTFT, 100% qualità (bench 2026-08-04). | |
| REASONING MODEL: genera "reasoning" field prima del "content". | |
| Richiede max_tokens≥500 per output non-vuoto su task non-triviali. | |
| Fallback: _coder_client (Groq 70B) se CEREBRAS_API_KEY mancante.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| cerebras_key = os.getenv("CEREBRAS_API_KEY") | |
| if not cerebras_key: | |
| return RoleRouter._coder_client() | |
| client = AIClient() | |
| reasoner = ProviderConfig( | |
| name="cerebras-reasoner", | |
| api_key=cerebras_key, | |
| base_url="https://api.cerebras.ai/v1", | |
| default_model=os.getenv("CEREBRAS_MODEL", "llama-4-scout"), | |
| ) | |
| rest = [p for p in client.providers if not p.name.startswith("cerebras")] | |
| client.providers = [reasoner, *rest] | |
| client.provider_name = reasoner.name | |
| client.default_model = reasoner.default_model | |
| client.client = client._client_for(reasoner) | |
| return client | |
| def _sambanova_client() -> Any: | |
| """SambaNova DeepSeek-V3.1 — 482ms TTFT, 100% qualità (bench 2026-06-14). | |
| gemma-4-31B-it: 100% ma 2132ms. Meta-Llama: rate-limited. gpt-oss-120b: ERR. | |
| Fallback: _architect_client (Groq) se SAMBANOVA_API_KEY mancante.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| sn_key = os.getenv("SAMBANOVA_API_KEY") | |
| if not sn_key: | |
| return RoleRouter._architect_client() | |
| client = AIClient() | |
| sambanova = ProviderConfig( | |
| name="sambanova", | |
| api_key=sn_key, | |
| base_url="https://api.sambanova.ai/v1", | |
| default_model=os.getenv("SAMBANOVA_MODEL", "DeepSeek-V3.2"), | |
| ) | |
| rest = [p for p in client.providers if not p.name.startswith("sambanova")] | |
| client.providers = [sambanova, *rest] | |
| client.provider_name = sambanova.name | |
| client.default_model = sambanova.default_model | |
| client.client = client._client_for(sambanova) | |
| return client | |
| def _nvidia_client() -> Any: | |
| """NVIDIA NIM nemotron-3-ultra-550b-a55b — 550B params, 1M ctx, API OpenAI-compat. | |
| Fallback: _architect_client (Groq) se NVIDIA_API_KEY mancante.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| nvidia_key = os.getenv("NVIDIA_API_KEY") | |
| if not nvidia_key: | |
| return RoleRouter._architect_client() | |
| client = AIClient() | |
| nvidia = ProviderConfig( | |
| name="nvidia", | |
| api_key=nvidia_key, | |
| base_url="https://integrate.api.nvidia.com/v1", | |
| default_model=os.getenv("NVIDIA_MODEL", "nvidia/nemotron-3-ultra-550b-a55b"), | |
| ) | |
| rest = [p for p in client.providers if not p.name.startswith("nvidia")] | |
| client.providers = [nvidia, *rest] | |
| client.provider_name = nvidia.name | |
| client.default_model = nvidia.default_model | |
| client.client = client._client_for(nvidia) | |
| return client | |
| def _tester_client() -> Any: | |
| """Groq llama-3.3-70b-versatile — fast, sufficiente per test gen e debug hints.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| groq_key = os.getenv("GROQ_API_KEY") | |
| if not groq_key: | |
| return AIClient() | |
| client = AIClient() | |
| tester = ProviderConfig( | |
| name="groq-tester", | |
| api_key=groq_key, | |
| base_url="https://api.groq.com/openai/v1", | |
| default_model=os.getenv("GROQ_FAST_MODEL", "llama-3.3-70b-versatile"), | |
| ) | |
| rest = [p for p in client.providers if p.name not in ("groq", "groq-tester")] | |
| client.providers = [tester, *rest] | |
| client.provider_name = tester.name | |
| client.default_model = tester.default_model | |
| client.client = client._client_for(tester) | |
| return client | |