Spaces:
Running
Running
sync: 157 file da Baida98/AI@f57660fe (2026-08-15 12:26 UTC) [deploy-all]
#37
by Baida07 - opened
- api/benchmark_handler.py +34 -33
- api/telegram_webhook.py +8 -72
- benchmark-extended.mjs +0 -0
- tests/test_telegram_benchmark_live.py +10 -72
- tests/test_telegram_extended_benchmark.py +54 -0
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 |
-
|
|
|
|
| 24 |
|
| 25 |
-
#
|
| 26 |
-
_BENCH_SCRIPT = os.path.join(_REPO_ROOT, "
|
| 27 |
-
_REPORT_V7 = "/tmp/agente-ai/benchmark-
|
| 28 |
-
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
-
|
| 38 |
-
Variabili env richieste (Railway): GROQ_API_KEY, INTERNAL_TOKEN.
|
| 39 |
-
"""
|
| 40 |
await send_reply_fn(
|
| 41 |
chat_id,
|
| 42 |
-
"🚀 <b>
|
| 43 |
-
"<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,
|
| 55 |
stdout=asyncio.subprocess.PIPE,
|
| 56 |
stderr=asyncio.subprocess.PIPE,
|
| 57 |
env=env,
|
| 58 |
)
|
| 59 |
-
|
| 60 |
-
process.communicate(), timeout=_BENCH_TIMEOUT
|
| 61 |
-
)
|
| 62 |
if process.returncode != 0:
|
| 63 |
err = stderr.decode(errors="replace")[:400]
|
| 64 |
-
logger.error("
|
| 65 |
-
await send_reply_fn(chat_id, f"❌ <b>Errore benchmark
|
| 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("
|
| 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.
|
| 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
|
| 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
|
| 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 |
-
"""
|
| 1624 |
-
|
| 1625 |
-
|
| 1626 |
-
|
| 1627 |
-
|
| 1628 |
-
|
| 1629 |
-
|
| 1630 |
-
|
| 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
|
| 2 |
import unittest
|
| 3 |
from unittest.mock import AsyncMock, patch
|
| 4 |
|
| 5 |
from api.telegram_webhook import _cmd_bench
|
| 6 |
|
| 7 |
|
| 8 |
-
class
|
| 9 |
-
def
|
| 10 |
-
|
| 11 |
-
|
| 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 |
-
|
| 57 |
-
|
| 58 |
-
self.assertEqual(
|
| 59 |
-
self.assertEqual(kwargs["
|
| 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()
|