sync: 157 file da Baida98/AI@f57660fe (2026-08-15 12:26 UTC) [deploy-all]

#37
by Baida07 - opened
api/benchmark_handler.py CHANGED
@@ -20,64 +20,60 @@ from typing import Any
20
  logger = logging.getLogger("agente_ai.benchmark_handler")
21
 
22
  # ── Percorsi server Railway ────────────────────────────────────────────────────
23
- _REPO_ROOT = os.getenv("REPO_ROOT", "/home/ubuntu/Baida98_AI")
 
24
 
25
- # v7 (GAP-BENCH-2)
26
- _BENCH_SCRIPT = os.path.join(_REPO_ROOT, "scripts", "benchmark-extended.mjs")
27
- _REPORT_V7 = "/tmp/agente-ai/benchmark-v7-latest.json"
28
- _BENCH_TIMEOUT = float(os.getenv("BENCH_TIMEOUT_SECS", "720")) # 12 min (era 360s)
 
29
 
30
  # v6.2 — usato come fallback in get_smart_summary per compatibilità
31
  _REPORT_V6 = os.path.join(_REPO_ROOT, "benchmark-stress-report.json")
32
 
33
 
34
- async def run_benchmark_task(chat_id: int, send_reply_fn) -> None:
35
- """Esegue benchmark-extended.mjs v7 con --json e invia risultati via Telegram.
 
 
 
 
36
 
37
- Flag --json scrive /tmp/agente-ai/benchmark-v7-latest.json.
38
- Variabili env richieste (Railway): GROQ_API_KEY, INTERNAL_TOKEN.
39
- """
40
  await send_reply_fn(
41
  chat_id,
42
- "🚀 <b>Avvio Benchmark Extended v7…</b>\n"
43
- "<i>10+ categorie · HF datasets · ref vs Replit/Cursor/Devin/Manus · ~10-12 min.</i>",
44
  )
45
  env = {
46
  **os.environ,
47
- "GROQ_API_KEY": os.getenv("GROQ_API_KEY", ""),
48
- "NVIDIA_API_KEY": os.getenv("NVIDIA_API_KEY", ""),
49
  "INTERNAL_TOKEN": os.getenv("INTERNAL_TOKEN", ""),
 
50
  }
51
  process: asyncio.subprocess.Process | None = None
52
  try:
53
  process = await asyncio.create_subprocess_exec(
54
- "node", _BENCH_SCRIPT, "--json",
55
  stdout=asyncio.subprocess.PIPE,
56
  stderr=asyncio.subprocess.PIPE,
57
  env=env,
58
  )
59
- stdout, stderr = await asyncio.wait_for(
60
- process.communicate(), timeout=_BENCH_TIMEOUT
61
- )
62
  if process.returncode != 0:
63
  err = stderr.decode(errors="replace")[:400]
64
- logger.error("Benchmark v7 failed rc=%d: %s", process.returncode, err)
65
- await send_reply_fn(chat_id, f"❌ <b>Errore benchmark v7:</b>\n<code>{err}</code>")
66
  return
67
  except asyncio.TimeoutError:
68
- # FIX-1: kill del processo figlio prima di notificare
69
  if process is not None:
70
  try:
71
  process.kill()
72
  await process.wait()
73
  except Exception:
74
  pass
75
- logger.warning("Benchmark v7 timeout (>%.0fs) — process killed", _BENCH_TIMEOUT)
76
- await send_reply_fn(
77
- chat_id,
78
- f"⏱ <b>Timeout benchmark v7</b> (>{int(_BENCH_TIMEOUT // 60)} min) — "
79
- "processo terminato, controlla log Railway.",
80
- )
81
  return
82
  except Exception as exc:
83
  if process is not None:
@@ -86,22 +82,25 @@ async def run_benchmark_task(chat_id: int, send_reply_fn) -> None:
86
  await process.wait()
87
  except Exception:
88
  pass
89
- logger.error("run_benchmark_task v7 error: %s", exc, exc_info=True)
90
- await send_reply_fn(chat_id, f"💥 <b>Errore critico:</b> <code>{exc}</code>")
91
  return
92
 
93
  report_exists = await asyncio.to_thread(os.path.exists, _REPORT_V7)
94
  if not report_exists:
95
- await send_reply_fn(chat_id, "⚠️ <b>Benchmark terminato ma report v7 non trovato.</b>")
96
  return
97
-
98
  try:
99
- # FIX-3: lettura file in thread — non blocca l'event loop
100
  report: dict[str, Any] = await asyncio.to_thread(_read_json, _REPORT_V7)
101
  except Exception as exc:
102
- await send_reply_fn(chat_id, f"⚠️ <b>Report v7 non leggibile:</b> <code>{exc}</code>")
103
  return
104
 
 
 
 
 
 
105
  await send_reply_fn(chat_id, _format_v7_report(report))
106
 
107
 
@@ -146,6 +145,8 @@ def _format_v7_report(report: dict[str, Any]) -> str:
146
  if isinstance(sc, (int, float)):
147
  by_cat.setdefault(cat, []).append(float(sc))
148
  if by_cat:
 
 
149
  lines.append("\n📂 <b>Per categoria:</b>\n")
150
  for cat, scores in sorted(by_cat.items()):
151
  avg_cat = sum(scores) / len(scores)
 
20
  logger = logging.getLogger("agente_ai.benchmark_handler")
21
 
22
  # ── Percorsi server Railway ────────────────────────────────────────────────────
23
+ # Lo Space HF esegue il backend in /app; Railway può impostare REPO_ROOT.
24
+ _REPO_ROOT = os.getenv("REPO_ROOT", "/app")
25
 
26
+ # Extended v5: 20 categorie. Il percorso è alla radice della repository.
27
+ _BENCH_SCRIPT = os.path.join(_REPO_ROOT, "benchmark-extended.mjs")
28
+ _REPORT_V7 = "/tmp/agente-ai/benchmark-v5-latest.json"
29
+ # 20 task seriali possono richiedere più di 12 minuti con provider gratuiti.
30
+ _BENCH_TIMEOUT = float(os.getenv("BENCH_TIMEOUT_SECS", "3600"))
31
 
32
  # v6.2 — usato come fallback in get_smart_summary per compatibilità
33
  _REPORT_V6 = os.path.join(_REPO_ROOT, "benchmark-stress-report.json")
34
 
35
 
36
+ async def run_benchmark_task(chat_id: int, send_reply_fn, mode: str = "full") -> None:
37
+ """Esegue il benchmark Extended v5 su tutte le 20 categorie via API task moderna."""
38
+ if not await asyncio.to_thread(os.path.isfile, _BENCH_SCRIPT):
39
+ await send_reply_fn(chat_id, "❌ <b>Runner benchmark esteso non disponibile.</b>\n"
40
+ "Il deployment non ha incluso <code>benchmark-extended.mjs</code>.")
41
+ return
42
 
43
+ flags = ["--full", "--json", f"--output={_REPORT_V7}", "--gap-analysis"]
 
 
44
  await send_reply_fn(
45
  chat_id,
46
+ "🚀 <b>Benchmark Extended v5 avviato</b>\n"
47
+ "<i>20/20 categorie · seed 1337 · task API moderna · durata variabile fino a ~60 min.</i>",
48
  )
49
  env = {
50
  **os.environ,
 
 
51
  "INTERNAL_TOKEN": os.getenv("INTERNAL_TOKEN", ""),
52
+ "BENCHMARK_BASE_URL": os.getenv("BENCHMARK_BASE_URL", "http://127.0.0.1:7860"),
53
  }
54
  process: asyncio.subprocess.Process | None = None
55
  try:
56
  process = await asyncio.create_subprocess_exec(
57
+ "node", _BENCH_SCRIPT, *flags,
58
  stdout=asyncio.subprocess.PIPE,
59
  stderr=asyncio.subprocess.PIPE,
60
  env=env,
61
  )
62
+ _stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=_BENCH_TIMEOUT)
 
 
63
  if process.returncode != 0:
64
  err = stderr.decode(errors="replace")[:400]
65
+ logger.error("Extended benchmark failed rc=%d: %s", process.returncode, err)
66
+ await send_reply_fn(chat_id, f"❌ <b>Errore benchmark Extended:</b>\n<code>{err}</code>")
67
  return
68
  except asyncio.TimeoutError:
 
69
  if process is not None:
70
  try:
71
  process.kill()
72
  await process.wait()
73
  except Exception:
74
  pass
75
+ logger.warning("Extended benchmark timeout (>%.0fs) — process killed", _BENCH_TIMEOUT)
76
+ await send_reply_fn(chat_id, f"⏱ <b>Timeout benchmark Extended</b> (>{int(_BENCH_TIMEOUT // 60)} min) — processo terminato.")
 
 
 
 
77
  return
78
  except Exception as exc:
79
  if process is not None:
 
82
  await process.wait()
83
  except Exception:
84
  pass
85
+ logger.exception("run_benchmark_task extended error")
86
+ await send_reply_fn(chat_id, f"💥 <b>Errore critico benchmark:</b> <code>{exc}</code>")
87
  return
88
 
89
  report_exists = await asyncio.to_thread(os.path.exists, _REPORT_V7)
90
  if not report_exists:
91
+ await send_reply_fn(chat_id, "⚠️ <b>Benchmark Extended terminato ma report non trovato.</b>")
92
  return
 
93
  try:
 
94
  report: dict[str, Any] = await asyncio.to_thread(_read_json, _REPORT_V7)
95
  except Exception as exc:
96
+ await send_reply_fn(chat_id, f"⚠️ <b>Report Extended non leggibile:</b> <code>{exc}</code>")
97
  return
98
 
99
+ categories = {str(task.get("cat", "")) for task in report.get("tasks", []) if task.get("cat")}
100
+ if len(categories) != 20:
101
+ await send_reply_fn(chat_id, f"⚠️ <b>Run incompleta:</b> <code>{len(categories)}/20</code> categorie nel report."
102
+ " Nessun risultato incompleto viene presentato come benchmark completo.")
103
+ return
104
  await send_reply_fn(chat_id, _format_v7_report(report))
105
 
106
 
 
145
  if isinstance(sc, (int, float)):
146
  by_cat.setdefault(cat, []).append(float(sc))
147
  if by_cat:
148
+ coverage = len(by_cat)
149
+ lines.append(f"🧪 <b>Copertura:</b> <code>{coverage}/20 categorie · {len(tasks)} task</code>\n")
150
  lines.append("\n📂 <b>Per categoria:</b>\n")
151
  for cat, scores in sorted(by_cat.items()):
152
  avg_cat = sum(scores) / len(scores)
api/telegram_webhook.py CHANGED
@@ -1620,78 +1620,14 @@ def _format_live_quality_benchmark(report: dict) -> str:
1620
 
1621
 
1622
  async def _cmd_bench(chat_id: int, mode: str = "default") -> None:
1623
- """Esegue il benchmark quality live del backend senza dipendere da GitHub Actions.
1624
-
1625
- Il workflow storico ``bench.yml`` e il relativo runner non esistono più su main;
1626
- usare il loro report come fallback produceva un falso "benchmark avviato". Il
1627
- backend espone invece ``/api/benchmark/run-self``: è autenticato, restituisce la
1628
- run corrente e non richiede CI o billing GitHub.
1629
- """
1630
- internal_token = os.getenv("INTERNAL_TOKEN", "").strip()
1631
- if not internal_token:
1632
- await _tg_reply(
1633
- chat_id,
1634
- "⚠️ <b>Benchmark live non configurato.</b>\n"
1635
- "Manca il token interno del backend: nessun report storico verrà mostrato.",
1636
- keyboard=_BACK_KB,
1637
- )
1638
- return
1639
-
1640
- base_url = (
1641
- os.getenv("BENCHMARK_SELF_URL")
1642
- or os.getenv("BENCHMARK_BASE_URL")
1643
- or os.getenv("BACKEND_URL")
1644
- or "http://127.0.0.1:7860"
1645
- ).rstrip("/")
1646
- await _tg_reply(
1647
- chat_id,
1648
- "🚀 <b>Benchmark live Quality avviato</b>\n"
1649
- "<i>Misuro ora il backend corrente; attesa tipica 20–30 secondi.</i>",
1650
- keyboard=_BACK_KB,
1651
- )
1652
-
1653
- try:
1654
- timeout = httpx.Timeout(connect=5.0, read=90.0, write=15.0, pool=5.0)
1655
- async with httpx.AsyncClient(timeout=timeout, trust_env=False) as client:
1656
- response = await client.post(
1657
- f"{base_url}/api/benchmark/run-self",
1658
- headers={"X-Internal-Token": internal_token},
1659
- json={},
1660
- )
1661
- try:
1662
- report = response.json()
1663
- except ValueError:
1664
- report = {}
1665
- if response.status_code >= 400:
1666
- detail = str(report.get("detail") or report.get("error") or response.text[:180] or "errore non specificato")
1667
- await _tg_reply(
1668
- chat_id,
1669
- "❌ <b>Benchmark live non completato.</b>\n"
1670
- f"HTTP <code>{response.status_code}</code>: <code>{html.escape(detail[:220])}</code>\n"
1671
- "<i>Nessun report storico è stato usato come sostituto.</i>",
1672
- keyboard=_BACK_KB,
1673
- )
1674
- return
1675
- if not isinstance(report, dict):
1676
- raise ValueError("Risposta benchmark non valida")
1677
- except Exception as exc:
1678
- _logger.warning("[bench-live] request error: %s", exc)
1679
- await _tg_reply(
1680
- chat_id,
1681
- "❌ <b>Benchmark live non raggiungibile.</b>\n"
1682
- f"<code>{html.escape(str(exc)[:220])}</code>\n"
1683
- "<i>Nessun report storico è stato usato come sostituto.</i>",
1684
- keyboard=_BACK_KB,
1685
- )
1686
- return
1687
-
1688
- _BENCH_CACHE[chat_id] = {
1689
- "mode": "quality-live",
1690
- "requested_mode": mode,
1691
- "run_url": "",
1692
- "timestamp": report.get("timestamp"),
1693
- }
1694
- await _tg_reply(chat_id, _format_live_quality_benchmark(report), keyboard=_BENCH_ACTION_KB)
1695
 
1696
 
1697
 
 
1620
 
1621
 
1622
  async def _cmd_bench(chat_id: int, mode: str = "default") -> None:
1623
+ """Avvia il benchmark Extended su tutte le 20 categorie in background."""
1624
+ from .benchmark_handler import run_benchmark_task
1625
+
1626
+ _BENCH_CACHE[chat_id] = {"mode": "extended-20", "run_url": "", "started_at": time.time()}
1627
+ task = asyncio.create_task(run_benchmark_task(chat_id, _tg_reply, mode="full"))
1628
+ task.add_done_callback(lambda completed: _logger.error(
1629
+ "[bench-extended] background task failed: %s", completed.exception()
1630
+ ) if not completed.cancelled() and completed.exception() else None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1631
 
1632
 
1633
 
benchmark-extended.mjs ADDED
The diff for this file is too large to render. See raw diff
 
tests/test_telegram_benchmark_live.py CHANGED
@@ -1,82 +1,20 @@
1
- import os
2
  import unittest
3
  from unittest.mock import AsyncMock, patch
4
 
5
  from api.telegram_webhook import _cmd_bench
6
 
7
 
8
- class _Response:
9
- def __init__(self, status_code=200, payload=None, text=""):
10
- self.status_code = status_code
11
- self._payload = payload if payload is not None else {}
12
- self.text = text
13
-
14
- def json(self):
15
- return self._payload
16
-
17
-
18
- class _Client:
19
- last_post = None
20
- response = _Response()
21
-
22
- def __init__(self, **kwargs):
23
- self.kwargs = kwargs
24
-
25
- async def __aenter__(self):
26
- return self
27
-
28
- async def __aexit__(self, *_args):
29
- return False
30
-
31
- async def post(self, *args, **kwargs):
32
- type(self).last_post = (args, kwargs)
33
- return type(self).response
34
-
35
-
36
- class TelegramLiveBenchmarkTests(unittest.IsolatedAsyncioTestCase):
37
- def setUp(self):
38
- _Client.last_post = None
39
-
40
- async def test_bench_uses_authenticated_self_benchmark_and_only_live_result(self):
41
- _Client.response = _Response(payload={
42
- "ok": True,
43
- "total_score": 73,
44
- "timestamp": "2026-08-15T12:00:00Z",
45
- "results": [{"id": "DA", "label": "Data analysis", "score": 80}],
46
- "errors": [],
47
- })
48
- replies = AsyncMock()
49
- with patch.dict(
50
- os.environ,
51
- {"INTERNAL_TOKEN": "internal-test", "BENCHMARK_SELF_URL": "http://backend"},
52
- clear=False,
53
- ), patch("httpx.AsyncClient", _Client), patch("api.telegram_webhook._tg_reply", replies):
54
  await _cmd_bench(123)
55
-
56
- args, kwargs = _Client.last_post
57
- self.assertEqual(args[0], "http://backend/api/benchmark/run-self")
58
- self.assertEqual(kwargs["headers"], {"X-Internal-Token": "internal-test"})
59
- self.assertEqual(kwargs["json"], {})
60
- self.assertEqual(replies.await_count, 2)
61
- final_text = replies.await_args_list[-1].args[1]
62
- self.assertIn("Benchmark live Quality", final_text)
63
- self.assertIn("73/100", final_text)
64
- self.assertNotIn("GitHub Actions", final_text)
65
-
66
- async def test_bench_does_not_substitute_historical_report_after_live_failure(self):
67
- _Client.response = _Response(status_code=503, payload={"detail": "unavailable"})
68
- replies = AsyncMock()
69
- with patch.dict(
70
- os.environ,
71
- {"INTERNAL_TOKEN": "internal-test", "BENCHMARK_SELF_URL": "http://backend"},
72
- clear=False,
73
- ), patch("httpx.AsyncClient", _Client), patch("api.telegram_webhook._tg_reply", replies):
74
- await _cmd_bench(123)
75
-
76
- final_text = replies.await_args_list[-1].args[1]
77
- self.assertIn("Benchmark live non completato", final_text)
78
- self.assertIn("Nessun report storico", final_text)
79
- self.assertNotIn("Score: AI", final_text)
80
 
81
 
82
  if __name__ == "__main__":
 
1
+ import asyncio
2
  import unittest
3
  from unittest.mock import AsyncMock, patch
4
 
5
  from api.telegram_webhook import _cmd_bench
6
 
7
 
8
+ class TelegramExtendedBenchmarkTests(unittest.IsolatedAsyncioTestCase):
9
+ async def test_bench_starts_background_extended_run(self):
10
+ runner = AsyncMock()
11
+ with patch("api.benchmark_handler.run_benchmark_task", runner):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  await _cmd_bench(123)
13
+ await asyncio.sleep(0)
14
+ runner.assert_awaited_once()
15
+ args, kwargs = runner.await_args
16
+ self.assertEqual(args[0], 123)
17
+ self.assertEqual(kwargs["mode"], "full")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
 
20
  if __name__ == "__main__":
tests/test_telegram_extended_benchmark.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ import tempfile
4
+ import unittest
5
+ from pathlib import Path
6
+ from unittest.mock import AsyncMock, patch
7
+
8
+ from api import benchmark_handler as handler
9
+
10
+
11
+ class _Process:
12
+ returncode = 0
13
+
14
+ async def communicate(self):
15
+ return b"", b""
16
+
17
+
18
+ class ExtendedBenchmarkHandlerTests(unittest.IsolatedAsyncioTestCase):
19
+ async def test_extended_runner_uses_full_mode_and_reports_20_categories(self):
20
+ categories = [
21
+ "bug_fix", "refactor", "feature", "devops", "security", "performance",
22
+ "autonomy", "code_correct", "sql", "context_window", "adversarial", "mmlu",
23
+ "reasoning", "data_analysis", "technical_writing", "research_synthesis",
24
+ "orchestration", "memory_context", "recovery", "robustness",
25
+ ]
26
+ report = {
27
+ "timestamp": "2026-08-15T12:00:00Z",
28
+ "version": "extended-v5",
29
+ "summary": {"avgScore": 70, "avgReplit": 60, "avgCursor": 65, "avgDevin": 70, "avgManus": 75, "gapCount": 0, "verdict": "PARI_REPLIT"},
30
+ "tasks": [{"cat": category, "score": 70} for category in categories],
31
+ "gapCards": [],
32
+ }
33
+ replies = AsyncMock()
34
+ with tempfile.TemporaryDirectory() as directory:
35
+ script = Path(directory) / "benchmark-extended.mjs"
36
+ output = Path(directory) / "benchmark-v5-latest.json"
37
+ script.write_text("// runner")
38
+ output.write_text(json.dumps(report))
39
+ with patch.object(handler, "_BENCH_SCRIPT", str(script)), \
40
+ patch.object(handler, "_REPORT_V7", str(output)), \
41
+ patch("asyncio.create_subprocess_exec", AsyncMock(return_value=_Process())) as create_process:
42
+ await handler.run_benchmark_task(123, replies)
43
+
44
+ command = create_process.await_args.args
45
+ self.assertEqual(command[:2], ("node", str(script)))
46
+ self.assertIn("--full", command)
47
+ self.assertIn("--json", command)
48
+ self.assertIn("--gap-analysis", command)
49
+ final_text = replies.await_args_list[-1].args[1]
50
+ self.assertIn("20/20 categorie", final_text)
51
+
52
+
53
+ if __name__ == "__main__":
54
+ unittest.main()