Terminal / models /role_router.py
Baida-A
Initial clean deploy (Reverse Proxy removed)
28a08e7
Raw
History Blame
15.2 kB
"""
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.
"""
@staticmethod
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 ─────────────────────────────────────────────────
@staticmethod
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
@staticmethod
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()
@staticmethod
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()
@staticmethod
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()
@staticmethod
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
@staticmethod
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
@staticmethod
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
@staticmethod
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