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 / qwen/qwen3.6-27b — 170ms 100% ← FASTEST | |
| #2 Cerebras / gpt-oss-120b — 207ms 100% ← REASONING (max_tokens≥500) | |
| #3 Groq / qwen/qwen3.6-27b — 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 / openrouter/free — 2160ms 100% | |
| Role assignments 2026-06-14 FINALE: | |
| FAST → Groq qwen/qwen3.6-27b (170ms, 100%) ← #1 assoluto | |
| ARCHITECT → Groq llama-4-scout-17b 10M ctx (244ms, 67% — best per contesto lungo) | |
| CODER → Groq qwen/qwen3.6-27b (235ms, 100%) ← #3 qualità | |
| TESTER → Groq qwen/qwen3.6-27b | |
| CONTEXT → Groq qwen/qwen3.6-27b | |
| 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 (qwen/qwen3.6-27b o primo disponibile) | |
| OpenRouter tenuto come fallback secondario (openrouter/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 — qwen/qwen3.6-27b | |
| ARCHITECT = "architect" # planning, ragionamento complesso — GPT-OSS 120B | |
| CODER = "coder" # coding, debug — qwen/qwen3.6-27b | |
| TESTER = "tester" # test gen, debug hints — qwen/qwen3.6-27b | |
| CONTEXT = "context" # summarization, context compression — qwen/qwen3.6-27b | |
| DEFAULT = "default" # AIClient() primary | |
| RESEARCHER = "researcher" # web research + document synthesis — GPT-OSS 120B | |
| REASONER = "reasoner" # throughput massimo — Cerebras GPT-OSS 120B | |
| 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 _prioritize_profile_pool(client: Any, provider_name: str) -> Any: | |
| """Promote all configured profiles for one provider without collapsing them.""" | |
| profiles = [p for p in client.providers if p.name == provider_name] | |
| if not profiles: | |
| return None | |
| client.providers = profiles + [p for p in client.providers if p.name != provider_name] | |
| client.provider_name = profiles[0].name | |
| client.default_model = profiles[0].default_model | |
| client.client = client._client_for(profiles[0]) | |
| return client | |
| def _profiled_client(client: Any, provider_names: tuple[str, ...]) -> Any: | |
| for provider_name in provider_names: | |
| env_name = f"{provider_name.upper()}_PROFILES_JSON" | |
| if os.getenv(env_name): | |
| profiled = RoleRouter._prioritize_profile_pool(client, provider_name) | |
| if profiled: | |
| return profiled | |
| return None | |
| def _fast_client() -> Any: | |
| """Groq GPT-OSS 20B per query brevi e a bassa latenza. | |
| Usato per: greetings, calcoli semplici, identity, domande 1-liner.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| client = AIClient() | |
| profiled = RoleRouter._profiled_client(client, ("groq",)) | |
| if profiled: | |
| return profiled | |
| 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", "qwen/qwen3.6-27b"), | |
| ) | |
| 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 come primario per architettura. | |
| Fallback 1: Groq GPT-OSS 120B. Fallback 2: OpenRouter GPT-OSS 20B gratuito.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| client = AIClient() | |
| profiled = RoleRouter._profiled_client(client, ("nvidia", "groq", "openrouter")) | |
| if profiled: | |
| return profiled | |
| 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 GPT-OSS 120B, modello production supportato. | |
| 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", "qwen/qwen3.6-27b"), | |
| ) | |
| 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: usa il pool multi-profilo, se configurato. | |
| if os.getenv("OPENROUTER_API_KEY") or os.getenv("OPENROUTER_PROFILES_JSON"): | |
| client = AIClient() | |
| profiles = [p for p in client.providers if p.name == "openrouter"] | |
| if not profiles and os.getenv("OPENROUTER_API_KEY"): | |
| profiles = [ProviderConfig( | |
| name="openrouter", api_key=os.getenv("OPENROUTER_API_KEY", ""), | |
| base_url="https://openrouter.ai/api/v1", | |
| default_model=os.getenv("OPENROUTER_MODEL", "openrouter/free"), | |
| profile="legacy", | |
| )] | |
| if profiles: | |
| client.providers = profiles + [p for p in client.providers if p.name != "openrouter"] | |
| client.provider_name = profiles[0].name | |
| client.default_model = profiles[0].default_model | |
| client.client = client._client_for(profiles[0]) | |
| return client | |
| return AIClient() | |
| def _coder_client() -> Any: | |
| """Groq GPT-OSS 120B per coding e debug. | |
| Fallback: provider ordinari del router se GROQ_API_KEY manca.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| client = AIClient() | |
| profiled = RoleRouter._profiled_client(client, ("groq", "nvidia", "openrouter")) | |
| if profiled: | |
| return profiled | |
| groq_key = os.getenv("GROQ_API_KEY") | |
| nvidia_key = os.getenv("NVIDIA_API_KEY") | |
| model = os.getenv("CODER_MODEL", "qwen/qwen3.6-27b") | |
| 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, | |
| purpose="coding", | |
| ) | |
| dedicated_fallbacks: list[ProviderConfig] = [] | |
| if nvidia_key: | |
| dedicated_fallbacks.append( | |
| ProviderConfig( | |
| name="nvidia-coder", | |
| api_key=nvidia_key, | |
| base_url="https://integrate.api.nvidia.com/v1", | |
| default_model=os.getenv( | |
| "NVIDIA_MODEL", "nvidia/nemotron-3-ultra-550b-a55b" | |
| ), | |
| purpose="coding", | |
| ) | |
| ) | |
| rest = [ | |
| provider for provider in client.providers | |
| if provider.name not in ("groq", "groq-coder", "nvidia", "nvidia-coder") | |
| ] | |
| client.providers = [coder, *dedicated_fallbacks, *rest] | |
| client.provider_name = coder.name | |
| client.default_model = coder.default_model | |
| client.client = client._client_for(coder) | |
| return client | |
| # Fallback OpenRouter: usa il pool multi-profilo, se configurato. | |
| if os.getenv("OPENROUTER_API_KEY") or os.getenv("OPENROUTER_PROFILES_JSON"): | |
| client = AIClient() | |
| profiles = [p for p in client.providers if p.name == "openrouter"] | |
| if not profiles and os.getenv("OPENROUTER_API_KEY"): | |
| profiles = [ProviderConfig( | |
| name="openrouter", api_key=os.getenv("OPENROUTER_API_KEY", ""), | |
| base_url="https://openrouter.ai/api/v1", | |
| default_model=os.getenv("OPENROUTER_MODEL", "openrouter/free"), | |
| profile="legacy", | |
| )] | |
| if profiles: | |
| client.providers = profiles + [p for p in client.providers if p.name != "openrouter"] | |
| client.provider_name = profiles[0].name | |
| client.default_model = profiles[0].default_model | |
| client.client = client._client_for(profiles[0]) | |
| return client | |
| return AIClient() | |
| def _researcher_client() -> Any: | |
| """Groq GPT-OSS 120B per analisi e sintesi; la flotta restante è fallback. | |
| Gemini può essere configurato ma ha una quota indipendente e più stretta: | |
| non deve quindi bloccare i task della persona analyst/researcher quando | |
| Groq è sano. L'ordine conserva tutti i provider ordinari dopo Groq. | |
| """ | |
| from models.ai_client import AIClient, ProviderConfig | |
| client = AIClient() | |
| profiled = RoleRouter._profiled_client(client, ("groq", "gemini", "openrouter")) | |
| if profiled: | |
| return profiled | |
| groq_key = os.getenv("GROQ_API_KEY") | |
| if groq_key: | |
| researcher = ProviderConfig( | |
| name="groq-researcher", | |
| api_key=groq_key, | |
| base_url="https://api.groq.com/openai/v1", | |
| default_model=os.getenv( | |
| "GROQ_RESEARCH_MODEL", | |
| os.getenv("GROQ_MODEL", "qwen/qwen3.6-27b"), | |
| ), | |
| ) | |
| rest = [ | |
| provider for provider in client.providers | |
| if provider.name not in ("groq", "groq-researcher") | |
| ] | |
| client.providers = [researcher, *rest] | |
| client.provider_name = researcher.name | |
| client.default_model = researcher.default_model | |
| client.client = client._client_for(researcher) | |
| return client | |
| return AIClient() | |
| def _reasoner_client() -> Any: | |
| """Priorità a Groq GPT-OSS per reasoning/MMLU, con flotta runtime come fallback. | |
| Gemini è soggetto a quote RPM e non deve essere il percorso iniziale per | |
| risposte deterministiche a scelta multipla. Cerebras resta un fallback | |
| compatibile quando Groq non è configurato.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| client = AIClient() | |
| profiled = RoleRouter._profiled_client(client, ("groq", "cerebras", "gemini", "openrouter")) | |
| if profiled: | |
| return profiled | |
| groq_key = os.getenv("GROQ_API_KEY") | |
| if groq_key: | |
| reasoner = ProviderConfig( | |
| name="groq-reasoner", | |
| api_key=groq_key, | |
| base_url="https://api.groq.com/openai/v1", | |
| default_model=os.getenv( | |
| "GROQ_REASONER_MODEL", | |
| os.getenv("GROQ_MODEL", "qwen/qwen3.6-27b"), | |
| ), | |
| ) | |
| rest = [ | |
| provider for provider in client.providers | |
| if provider.name not in ("groq", "groq-reasoner") | |
| ] | |
| client.providers = [reasoner, *rest] | |
| client.provider_name = reasoner.name | |
| client.default_model = reasoner.default_model | |
| client.client = client._client_for(reasoner) | |
| return client | |
| 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", "gpt-oss-120b"), | |
| ) | |
| rest = [provider for provider in client.providers if not provider.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 | |
| client = AIClient() | |
| profiled = RoleRouter._profiled_client(client, ("sambanova",)) | |
| if profiled: | |
| return profiled | |
| 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 | |
| client = AIClient() | |
| profiled = RoleRouter._profiled_client(client, ("nvidia",)) | |
| if profiled: | |
| return profiled | |
| 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 GPT-OSS 20B per test rapidi e debug hints.""" | |
| from models.ai_client import AIClient, ProviderConfig | |
| client = AIClient() | |
| profiled = RoleRouter._profiled_client(client, ("groq",)) | |
| if profiled: | |
| return profiled | |
| groq_key = os.getenv("GROQ_API_KEY") | |
| if not groq_key: | |
| return client | |
| 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", "qwen/qwen3.6-27b"), | |
| purpose="coding", | |
| ) | |
| 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 | |