Baida07 commited on
Commit
ef0a3b4
·
1 Parent(s): 2e21096

sync: 158 file da Baida98/AI@c4f6430d (2026-08-15 16:49 UTC) [deploy-all] (#41)

Browse files

- sync: 158 file da Baida98/AI@c4f6430d (2026-08-15 16:49 UTC) [deploy-all] (28207b9d22a8dca431e1e96056469e901c1b8f33)

.env.example CHANGED
@@ -61,5 +61,5 @@ UPSTASH_REDIS_REST_TOKEN=
61
  # ── 9. Feature Flags ─────────────────────────────────────────
62
  VITE_ENABLE_BROWSER_SANDBOX=false
63
  UNIFIED_LOOP_MAX_STEPS=8
64
- LLM_MODEL=google/gemini-2.0-flash-exp:free
65
 
 
61
  # ── 9. Feature Flags ─────────────────────────────────────────
62
  VITE_ENABLE_BROWSER_SANDBOX=false
63
  UNIFIED_LOOP_MAX_STEPS=8
64
+ LLM_MODEL=openai/gpt-oss-20b:free
65
 
api/exec.py CHANGED
@@ -574,8 +574,8 @@ async def llm_fix_code(
574
  or_key = os.getenv('OPENROUTER_API_KEY', '')
575
  if or_key:
576
  for m in [
 
577
  'meta-llama/llama-3.1-8b-instruct:free',
578
- 'google/gemini-2.0-flash-exp:free',
579
  'qwen/qwen-2.5-coder-7b-instruct:free',
580
  ]:
581
  _FIX_CHAIN.append(('https://openrouter.ai/api/v1', or_key, m))
 
574
  or_key = os.getenv('OPENROUTER_API_KEY', '')
575
  if or_key:
576
  for m in [
577
+ 'openai/gpt-oss-20b:free',
578
  'meta-llama/llama-3.1-8b-instruct:free',
 
579
  'qwen/qwen-2.5-coder-7b-instruct:free',
580
  ]:
581
  _FIX_CHAIN.append(('https://openrouter.ai/api/v1', or_key, m))
api/providers.py CHANGED
@@ -883,37 +883,40 @@ async def update_provider_models(role: AuthRole = Depends(require_role(AuthRole.
883
  if _sb is None:
884
  return {"ok": False, "error": "Supabase non configurato", "updated": 0}
885
 
886
- # Mappa: modello_vecchio -> modello_nuovo
 
 
 
887
  MODEL_FIXES = [
888
- ("llama-3.1-70b-versatile", "openai/gpt-oss-120b"),
889
- ("llama3.1-70b", "llama-4-scout"),
890
- ("llama-3.1-405b-instruct", "meta/llama-3.3-70b-instruct"),
891
- ("llama-3.1-405b", "meta-llama/llama-4-scout:free"),
892
- ("llama3-70b", "DeepSeek-V3.2"),
893
- ("gemini-1.5-flash", "gemini-2.5-flash-lite"),
894
- ("gemini-1.5-pro", "gemini-2.5-flash-lite"),
895
- ("gpt-oss-120b", "llama-4-scout"),
896
- ("claude-3.5-sonnet", "meta-llama/llama-4-scout:free"),
897
  ]
898
 
899
  import asyncio as _aio
900
  total_updated = 0
901
  results = []
902
 
903
- for old_model, new_model in MODEL_FIXES:
904
  try:
905
  r = await _aio.to_thread(
906
- lambda om=old_model, nm=new_model: _sb.table("ai_providers")
907
  .update({"default_model": nm})
 
908
  .eq("default_model", om)
909
  .execute()
910
  )
911
  n = len(r.data) if r.data else 0
912
  total_updated += n
913
  if n > 0:
914
- results.append({"old": old_model, "new": new_model, "rows": n})
915
  except Exception as exc:
916
- results.append({"old": old_model, "new": new_model, "error": str(exc)[:100]})
917
 
918
  # Disattiva provider E2B (non sono LLM provider)
919
  try:
 
883
  if _sb is None:
884
  return {"ok": False, "error": "Supabase non configurato", "updated": 0}
885
 
886
+ # Mappa provider-specifica: (provider, modello_vecchio, modello_nuovo).
887
+ # Lo stesso ID modello può essere valido su un provider e non su un altro:
888
+ # filtrare per `name` evita di applicare un formato incompatibile alla riga sbagliata.
889
+ # GPT-OSS 120B non compare come vecchio valore perché è già un modello supportato.
890
  MODEL_FIXES = [
891
+ ("groq", "llama-3.1-70b-versatile", "openai/gpt-oss-120b"),
892
+ ("cerebras", "llama3.1-70b", "gpt-oss-120b"),
893
+ ("nvidia", "llama-3.1-405b-instruct", "meta/llama-3.3-70b-instruct"),
894
+ ("openrouter", "llama-3.1-405b", "openai/gpt-oss-20b:free"),
895
+ ("sambanova", "llama3-70b", "DeepSeek-V3.2"),
896
+ ("gemini", "gemini-1.5-flash", "gemini-3.5-flash-lite"),
897
+ ("gemini", "gemini-1.5-pro", "gemini-3.6-flash"),
898
+ ("openrouter", "claude-3.5-sonnet", "openai/gpt-oss-20b:free"),
 
899
  ]
900
 
901
  import asyncio as _aio
902
  total_updated = 0
903
  results = []
904
 
905
+ for provider_name, old_model, new_model in MODEL_FIXES:
906
  try:
907
  r = await _aio.to_thread(
908
+ lambda pn=provider_name, om=old_model, nm=new_model: _sb.table("ai_providers")
909
  .update({"default_model": nm})
910
+ .eq("name", pn)
911
  .eq("default_model", om)
912
  .execute()
913
  )
914
  n = len(r.data) if r.data else 0
915
  total_updated += n
916
  if n > 0:
917
+ results.append({"provider": provider_name, "old": old_model, "new": new_model, "rows": n})
918
  except Exception as exc:
919
+ results.append({"provider": provider_name, "old": old_model, "new": new_model, "error": str(exc)[:100]})
920
 
921
  # Disattiva provider E2B (non sono LLM provider)
922
  try:
models/role_router.py CHANGED
@@ -37,13 +37,13 @@ _logger = logging.getLogger("models.role_router")
37
 
38
  class Role(str, Enum):
39
  FAST = "fast" # greetings, math semplice, identity — openai/gpt-oss-20b
40
- ARCHITECT = "architect" # planning, ragionamento complesso — llama-4-scout (10M ctx)
41
  CODER = "coder" # coding, debug — openai/gpt-oss-120b
42
  TESTER = "tester" # test gen, debug hints — openai/gpt-oss-120b
43
  CONTEXT = "context" # summarization, context compression — openai/gpt-oss-120b
44
  DEFAULT = "default" # AIClient() primary
45
- RESEARCHER = "researcher" # web research + document synthesis — gemini-2.0-flash-exp
46
- REASONER = "reasoner" # throughput massimo — Cerebras llama-4-scout (2000+ tok/s)
47
  SAMBANOVA = "sambanova"
48
  NVIDIA = "nvidia" # NVIDIA NIM — nemotron-3-ultra-550b (1M ctx) # DeepSeek-V3.2 via SambaNova (404ms, 100% qualità benchmark)
49
 
@@ -107,8 +107,8 @@ class RoleRouter:
107
 
108
  @staticmethod
109
  def _architect_client() -> Any:
110
- """NVIDIA NIM deepseek-v4-flash (1M ctx) come primario — massima potenza per architettura.
111
- Fallback 1: Groq llama-4-scout (10M ctx, 480ms). Fallback 2: OpenRouter llama-4-scout:free."""
112
  from models.ai_client import AIClient, ProviderConfig
113
  nvidia_key = os.getenv("NVIDIA_API_KEY")
114
  if nvidia_key:
@@ -125,7 +125,7 @@ class RoleRouter:
125
  client.default_model = nvidia.default_model
126
  client.client = client._client_for(nvidia)
127
  return client
128
- # Fallback 1: Groq llama-4-scout (10M ctx, 480ms)
129
  groq_key = os.getenv("GROQ_API_KEY")
130
  if groq_key:
131
  client = AIClient()
@@ -133,7 +133,7 @@ class RoleRouter:
133
  name="groq-architect",
134
  api_key=groq_key,
135
  base_url="https://api.groq.com/openai/v1",
136
- default_model=os.getenv("ARCHITECT_MODEL", "llama-4-scout"),
137
  )
138
  rest = [p for p in client.providers if p.name not in ("groq", "groq-architect")]
139
  client.providers = [architect, *rest]
@@ -141,7 +141,7 @@ class RoleRouter:
141
  client.default_model = architect.default_model
142
  client.client = client._client_for(architect)
143
  return client
144
- # Fallback: OpenRouter meta-llama/llama-4-scout:free (1645ms ma 100% qualità)
145
  openrouter_key = os.getenv("OPENROUTER_API_KEY")
146
  if openrouter_key:
147
  client = AIClient()
@@ -149,7 +149,7 @@ class RoleRouter:
149
  name="openrouter-architect",
150
  api_key=openrouter_key,
151
  base_url="https://openrouter.ai/api/v1",
152
- default_model="meta-llama/llama-4-scout:free",
153
  )
154
  rest = [p for p in client.providers if not p.name.startswith("openrouter")]
155
  client.providers = [fallback, *rest]
@@ -205,7 +205,7 @@ class RoleRouter:
205
  name="openrouter-coder",
206
  api_key=openrouter_key,
207
  base_url="https://openrouter.ai/api/v1",
208
- default_model="meta-llama/llama-4-scout:free",
209
  purpose="coding",
210
  )
211
  rest = [p for p in client.providers if not p.name.startswith("openrouter")]
@@ -286,7 +286,7 @@ class RoleRouter:
286
  name="cerebras-reasoner",
287
  api_key=cerebras_key,
288
  base_url="https://api.cerebras.ai/v1",
289
- default_model=os.getenv("CEREBRAS_MODEL", "llama-4-scout"),
290
  )
291
  rest = [provider for provider in client.providers if not provider.name.startswith("cerebras")]
292
  client.providers = [reasoner, *rest]
 
37
 
38
  class Role(str, Enum):
39
  FAST = "fast" # greetings, math semplice, identity — openai/gpt-oss-20b
40
+ ARCHITECT = "architect" # planning, ragionamento complesso — GPT-OSS 120B
41
  CODER = "coder" # coding, debug — openai/gpt-oss-120b
42
  TESTER = "tester" # test gen, debug hints — openai/gpt-oss-120b
43
  CONTEXT = "context" # summarization, context compression — openai/gpt-oss-120b
44
  DEFAULT = "default" # AIClient() primary
45
+ RESEARCHER = "researcher" # web research + document synthesis — GPT-OSS 120B
46
+ REASONER = "reasoner" # throughput massimo — Cerebras GPT-OSS 120B
47
  SAMBANOVA = "sambanova"
48
  NVIDIA = "nvidia" # NVIDIA NIM — nemotron-3-ultra-550b (1M ctx) # DeepSeek-V3.2 via SambaNova (404ms, 100% qualità benchmark)
49
 
 
107
 
108
  @staticmethod
109
  def _architect_client() -> Any:
110
+ """NVIDIA NIM come primario per architettura.
111
+ Fallback 1: Groq GPT-OSS 120B. Fallback 2: OpenRouter GPT-OSS 20B gratuito."""
112
  from models.ai_client import AIClient, ProviderConfig
113
  nvidia_key = os.getenv("NVIDIA_API_KEY")
114
  if nvidia_key:
 
125
  client.default_model = nvidia.default_model
126
  client.client = client._client_for(nvidia)
127
  return client
128
+ # Fallback 1: Groq GPT-OSS 120B, modello production supportato.
129
  groq_key = os.getenv("GROQ_API_KEY")
130
  if groq_key:
131
  client = AIClient()
 
133
  name="groq-architect",
134
  api_key=groq_key,
135
  base_url="https://api.groq.com/openai/v1",
136
+ default_model=os.getenv("ARCHITECT_MODEL", "openai/gpt-oss-120b"),
137
  )
138
  rest = [p for p in client.providers if p.name not in ("groq", "groq-architect")]
139
  client.providers = [architect, *rest]
 
141
  client.default_model = architect.default_model
142
  client.client = client._client_for(architect)
143
  return client
144
+ # Fallback: OpenRouter GPT-OSS 20B gratuito, con ID verificato nel catalogo.
145
  openrouter_key = os.getenv("OPENROUTER_API_KEY")
146
  if openrouter_key:
147
  client = AIClient()
 
149
  name="openrouter-architect",
150
  api_key=openrouter_key,
151
  base_url="https://openrouter.ai/api/v1",
152
+ default_model=os.getenv("OPENROUTER_MODEL", "openai/gpt-oss-20b:free"),
153
  )
154
  rest = [p for p in client.providers if not p.name.startswith("openrouter")]
155
  client.providers = [fallback, *rest]
 
205
  name="openrouter-coder",
206
  api_key=openrouter_key,
207
  base_url="https://openrouter.ai/api/v1",
208
+ default_model=os.getenv("OPENROUTER_MODEL", "openai/gpt-oss-20b:free"),
209
  purpose="coding",
210
  )
211
  rest = [p for p in client.providers if not p.name.startswith("openrouter")]
 
286
  name="cerebras-reasoner",
287
  api_key=cerebras_key,
288
  base_url="https://api.cerebras.ai/v1",
289
+ default_model=os.getenv("CEREBRAS_MODEL", "gpt-oss-120b"),
290
  )
291
  rest = [provider for provider in client.providers if not provider.name.startswith("cerebras")]
292
  client.providers = [reasoner, *rest]
tests/test_provider_model_migrations.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import unittest
3
+ from types import SimpleNamespace
4
+ from unittest.mock import patch
5
+
6
+ from api import providers
7
+ from models.ai_client import AIClient
8
+ from models.role_router import Role, RoleRouter
9
+
10
+
11
+ class _RecordingTable:
12
+ def __init__(self):
13
+ self.calls: list[dict] = []
14
+ self._pending: dict = {}
15
+
16
+ def update(self, values: dict):
17
+ self._pending = {"values": values, "filters": []}
18
+ return self
19
+
20
+ def eq(self, field: str, value: str):
21
+ self._pending["filters"].append((field, value))
22
+ return self
23
+
24
+ def execute(self):
25
+ self.calls.append(self._pending)
26
+ return SimpleNamespace(data=[{"id": len(self.calls)}])
27
+
28
+
29
+ class _RecordingSupabase:
30
+ def __init__(self):
31
+ self.table_ref = _RecordingTable()
32
+
33
+ def table(self, name: str):
34
+ if name != "ai_providers":
35
+ raise AssertionError(f"Tabella inattesa: {name}")
36
+ return self.table_ref
37
+
38
+
39
+ class RoleRouterModelMigrationTests(unittest.TestCase):
40
+ @patch.object(AIClient, "_load_providers", return_value=[])
41
+ @patch.dict(
42
+ os.environ,
43
+ {"GROQ_API_KEY": "test-groq-key"},
44
+ clear=True,
45
+ )
46
+ def test_architect_uses_supported_groq_default(self, _load_providers):
47
+ client = RoleRouter.get_client(Role.ARCHITECT)
48
+
49
+ self.assertEqual(client.provider_name, "groq-architect")
50
+ self.assertEqual(client.default_model, "openai/gpt-oss-120b")
51
+
52
+ @patch.object(AIClient, "_load_providers", return_value=[])
53
+ @patch.dict(
54
+ os.environ,
55
+ {"OPENROUTER_API_KEY": "test-openrouter-key"},
56
+ clear=True,
57
+ )
58
+ def test_openrouter_role_fallbacks_use_available_free_model(self, _load_providers):
59
+ architect = RoleRouter.get_client(Role.ARCHITECT)
60
+ coder = RoleRouter.get_client(Role.CODER)
61
+
62
+ self.assertEqual(architect.default_model, "openai/gpt-oss-20b:free")
63
+ self.assertEqual(coder.default_model, "openai/gpt-oss-20b:free")
64
+
65
+ @patch.object(AIClient, "_load_providers", return_value=[])
66
+ @patch.dict(
67
+ os.environ,
68
+ {"CEREBRAS_API_KEY": "test-cerebras-key"},
69
+ clear=True,
70
+ )
71
+ def test_reasoner_uses_cerebras_gpt_oss_default(self, _load_providers):
72
+ client = RoleRouter.get_client(Role.REASONER)
73
+
74
+ self.assertEqual(client.provider_name, "cerebras-reasoner")
75
+ self.assertEqual(client.default_model, "gpt-oss-120b")
76
+
77
+
78
+ class ProviderTableMigrationTests(unittest.IsolatedAsyncioTestCase):
79
+ async def test_update_models_filters_by_provider_and_never_downgrades_gpt_oss(self):
80
+ original_supabase = providers._sb
81
+ fake_supabase = _RecordingSupabase()
82
+ providers._sb = fake_supabase
83
+ try:
84
+ payload = await providers.update_provider_models(role=None)
85
+ finally:
86
+ providers._sb = original_supabase
87
+
88
+ self.assertTrue(payload["ok"])
89
+ self.assertEqual(payload["total_updated"], 8)
90
+ self.assertEqual(len(fake_supabase.table_ref.calls), 8)
91
+
92
+ expected = {
93
+ ("groq", "llama-3.1-70b-versatile", "openai/gpt-oss-120b"),
94
+ ("cerebras", "llama3.1-70b", "gpt-oss-120b"),
95
+ ("nvidia", "llama-3.1-405b-instruct", "meta/llama-3.3-70b-instruct"),
96
+ ("openrouter", "llama-3.1-405b", "openai/gpt-oss-20b:free"),
97
+ ("sambanova", "llama3-70b", "DeepSeek-V3.2"),
98
+ ("gemini", "gemini-1.5-flash", "gemini-3.5-flash-lite"),
99
+ ("gemini", "gemini-1.5-pro", "gemini-3.6-flash"),
100
+ ("openrouter", "claude-3.5-sonnet", "openai/gpt-oss-20b:free"),
101
+ }
102
+ actual = {
103
+ (
104
+ dict(call["filters"])["name"],
105
+ dict(call["filters"])["default_model"],
106
+ call["values"]["default_model"],
107
+ )
108
+ for call in fake_supabase.table_ref.calls
109
+ }
110
+ self.assertEqual(actual, expected)
111
+ self.assertNotIn("llama-4-scout", {new for _, _, new in actual})
112
+ self.assertNotIn(("cerebras", "gpt-oss-120b", "llama-4-scout"), actual)
113
+
114
+
115
+ if __name__ == "__main__":
116
+ unittest.main()