sync: 155 file da Baida98/AI@1db7f191 (2026-08-15 12:08 UTC) [deploy-all]

#36
by Baida07 - opened
api/telegram_webhook.py CHANGED
@@ -1588,209 +1588,110 @@ async def _cmd_score(chat_id: int) -> None:
1588
  await _tg_reply(chat_id, det[_TG_MAX:_TG_MAX*2][:_TG_MAX], keyboard=_BENCH_ACTION_KB)
1589
 
1590
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1591
  async def _cmd_bench(chat_id: int, mode: str = "default") -> None:
1592
- """📊 Benchmark via bench.yml (benchmark-extended.mjs) + quickchart.io.
1593
 
1594
- GAP-TGB: workflow_dispatch su bench.yml usa benchmark-extended.mjs
1595
- (20 categorie, seed canonico 1337, tutte le fix v5).
1596
- Risultati inviati via Telegram da ab-bench.mjs --notify al completamento.
 
1597
  """
1598
- gh_token = os.getenv("GITHUB_TOKEN", "").strip()
1599
-
1600
- # ── Tenta fetch ultimo run completato da GitHub Actions artifact ─────────
1601
- last_report: dict | None = None
1602
- if gh_token:
1603
- try:
1604
- import httpx as _hx
1605
- async with _hx.AsyncClient(timeout=8.0) as _c:
1606
- _r = await _c.get(
1607
- "https://api.github.com/repos/Baida98/AI/actions/workflows/bench.yml/runs"
1608
- "?status=completed&per_page=1",
1609
- headers={"Authorization": f"Bearer {gh_token}",
1610
- "Accept": "application/vnd.github.v3+json",
1611
- "User-Agent": "AgenteAI-Bot"},
1612
- )
1613
- if _r.status_code == 200:
1614
- _runs = _r.json().get("workflow_runs", [])
1615
- if _runs:
1616
- last_report = {
1617
- "run_id": _runs[0]["id"],
1618
- "run_url": _runs[0]["html_url"],
1619
- "conclusion":_runs[0].get("conclusion","?"),
1620
- "updated": _runs[0].get("updated_at",""),
1621
- }
1622
- except Exception as _exc:
1623
- _logger.debug("bench fetch last run: %s", _exc)
1624
-
1625
- # ── Trigger nuovo run via workflow_dispatch ───────────────────────────────
1626
- run_url = "https://github.com/Baida98/AI/actions/workflows/bench.yml"
1627
- if gh_token:
1628
- try:
1629
- import httpx as _hx
1630
- async with _hx.AsyncClient(timeout=10.0) as _c:
1631
- _r = await _c.post(
1632
- "https://api.github.com/repos/Baida98/AI/actions/workflows/bench.yml/dispatches",
1633
- json={"ref": "main", "inputs": {
1634
- "mode": mode,
1635
- "run_improve": "false",
1636
- "force_update_baseline": "false",
1637
- }},
1638
- headers={"Authorization": f"Bearer {gh_token}",
1639
- "Accept": "application/vnd.github.v3+json",
1640
- "User-Agent": "AgenteAI-Bot"},
1641
- )
1642
- if _r.status_code == 204:
1643
- _logger.info("[bench] workflow_dispatch OK (mode=%s)", mode)
1644
- # Attendi 2s e leggi il run ID appena creato
1645
- await asyncio.sleep(2.0)
1646
- async with _hx.AsyncClient(timeout=8.0) as _c2:
1647
- _r2 = await _c2.get(
1648
- "https://api.github.com/repos/Baida98/AI/actions/workflows/"
1649
- "bench.yml/runs?per_page=1",
1650
- headers={"Authorization": f"Bearer {gh_token}",
1651
- "Accept": "application/vnd.github.v3+json",
1652
- "User-Agent": "AgenteAI-Bot"},
1653
- )
1654
- if _r2.status_code == 200:
1655
- _rr = _r2.json().get("workflow_runs", [])
1656
- if _rr:
1657
- run_url = _rr[0]["html_url"]
1658
- else:
1659
- _logger.warning("[bench] workflow_dispatch status=%d", _r.status_code)
1660
- except Exception as _exc:
1661
- _logger.warning("[bench] workflow_dispatch error: %s", _exc)
1662
 
1663
- # ── Costruisci messaggio con quickchart dell'ultimo run (se disponibile) ──
1664
- _BENCH_CACHE[chat_id] = {"mode": mode, "run_url": run_url}
 
 
 
 
 
 
 
 
 
 
1665
 
1666
- # ── Fetch benchmark-report.json dal repo per quickchart reale ──────────────
1667
- bench_report: dict | None = None
1668
- if gh_token:
 
 
 
 
 
1669
  try:
1670
- import httpx as _hx, base64 as _b64, json as _json
1671
- async with _hx.AsyncClient(timeout=8.0) as _c:
1672
- _br = await _c.get(
1673
- "https://api.github.com/repos/Baida98/AI/contents/benchmark-report.json?ref=main",
1674
- headers={"Authorization": f"Bearer {gh_token}",
1675
- "Accept": "application/vnd.github.v3+json",
1676
- "User-Agent": "AgenteAI-Bot"},
1677
- )
1678
- if _br.status_code == 200:
1679
- _content = _b64.b64decode(_br.json()["content"]).decode()
1680
- bench_report = _json.loads(_content)
1681
- except Exception as _exc:
1682
- _logger.debug("bench fetch benchmark-report.json: %s", _exc)
1683
-
1684
- chart_url: str | None = None
1685
-
1686
- def _build_quickchart(report: dict) -> str:
1687
- """Costruisce URL quickchart.io da benchmark-report.json."""
1688
- import json as _j, urllib.parse as _ul
1689
- tasks = report.get("tasks", [])
1690
- summary = report.get("summary", {})
1691
- avg_ai = summary.get("avgScore", 0)
1692
- avg_rpl = summary.get("avgReplit", 57.9)
1693
- avg_mns = summary.get("avgManus", 71.2)
1694
- cat_map: dict[str, list[float]] = {}
1695
- for t in tasks:
1696
- cat = (t.get("cat") or "other").replace("_", " ")[:14]
1697
- cat_map.setdefault(cat, []).append(t.get("score", 0))
1698
- if not cat_map:
1699
- return ""
1700
- labels = list(cat_map.keys())
1701
- scores = [round(sum(v)/len(v)) for v in cat_map.values()]
1702
- colors = ["#4CAF50" if s >= avg_rpl else "#FF9800" if s >= 50 else "#F44336" for s in scores]
1703
- cfg = {
1704
- "type": "horizontalBar",
1705
- "data": {
1706
- "labels": labels,
1707
- "datasets": [
1708
- {"label": "Agente AI", "data": scores,
1709
- "backgroundColor": colors, "borderColor": colors, "borderWidth": 1},
1710
- {"label": f"Replit {avg_rpl}",
1711
- "data": [avg_rpl]*len(labels),
1712
- "type": "line", "borderColor": "#2196F3", "borderDash": [5,3],
1713
- "pointRadius": 0, "fill": False, "borderWidth": 2},
1714
- {"label": f"Manus {avg_mns}",
1715
- "data": [avg_mns]*len(labels),
1716
- "type": "line", "borderColor": "#9C27B0", "borderDash": [5,3],
1717
- "pointRadius": 0, "fill": False, "borderWidth": 2},
1718
- ],
1719
- },
1720
- "options": {
1721
- "title": {"display": True,
1722
- "text": f"Agente AI {avg_ai}% | Replit {avg_rpl}% | Manus {avg_mns}%"},
1723
- "scales": {"xAxes": [{"ticks": {"min": 0, "max": 100, "stepSize": 20}}]},
1724
- "legend": {"display": True, "position": "bottom"},
1725
- "plugins": {"datalabels": {"display": False}},
1726
- },
1727
- }
1728
- return ("https://quickchart.io/chart?c=" +
1729
- _ul.quote(_j.dumps(cfg, separators=(",",":"))) +
1730
- "&width=720&height=420&backgroundColor=white")
1731
-
1732
- if bench_report:
1733
- chart_url = _build_quickchart(bench_report)
1734
-
1735
- summary = (bench_report or {}).get("summary", {})
1736
- avg_ai = summary.get("avgScore")
1737
- avg_rpl = summary.get("avgReplit")
1738
- avg_mns = summary.get("avgManus")
1739
- # ── Tabella ASCII con barre per caption Telegram ──────────────────────────
1740
- def _text_table_bench(report: dict, rpl: float) -> str:
1741
- tasks = report.get("tasks", [])
1742
- cat_map: dict[str, list[float]] = {}
1743
- for t in tasks:
1744
- cat = (t.get("cat") or "other").replace("_", " ")[:12]
1745
- cat_map.setdefault(cat, []).append(float(t.get("score", 0)))
1746
- if not cat_map:
1747
- return ""
1748
- rows = []
1749
- for cat, vals in sorted(cat_map.items(), key=lambda x: -sum(x[1]) / len(x[1])):
1750
- sc = round(sum(vals) / len(vals))
1751
- bar = "█" * round(sc / 10) + "░" * (10 - round(sc / 10))
1752
- delta_rpl = sc - rpl
1753
- vs = ("+" if delta_rpl >= 0 else "") + str(round(delta_rpl)) + "vsRpl"
1754
- rows.append(f"{cat:<12} {bar} {sc:>3}% {vs}")
1755
- return "\n".join(rows)
1756
-
1757
- text_table = ""
1758
- if bench_report and avg_rpl is not None:
1759
- text_table = _text_table_bench(bench_report, float(avg_rpl))
1760
-
1761
- score_line = ""
1762
- if avg_ai is not None:
1763
- score_line = (
1764
- f"\n📈 <b>Score:</b> AI <b>{avg_ai}%</b>"
1765
- + (f" | Replit {avg_rpl}%" if avg_rpl else "")
1766
- + (f" | Manus {avg_mns}%" if avg_mns else "")
1767
- + "\n"
1768
  )
 
1769
 
1770
- def _build_bench_caption(header: str) -> str:
1771
- tbl = ("\n<code>" + text_table + "</code>") if text_table else ""
1772
- link = f'\n🔗 <a href="{html.escape(run_url)}">GitHub Actions</a>'
1773
- full = header + score_line + tbl + link
1774
- if len(full) > 1020 and text_table:
1775
- avail = max(0, 1020 - len(header) - len(score_line) - len(link) - 14)
1776
- tbl = "\n<code>" + text_table[:avail] + "…</code>"
1777
- full = header + score_line + tbl + link
1778
- return full[:1024]
1779
-
1780
- if last_report:
1781
- _conclusion = last_report.get("conclusion", "?")
1782
- _em = "✅" if _conclusion == "success" else ("❌" if _conclusion == "failure" else "⚠️")
1783
- _upd = last_report.get("updated", "")[:16].replace("T", " ")
1784
- header = f"📊 <b>Benchmark avviato</b> — {_em} {_conclusion}\n🕐 {_upd} UTC"
1785
- else:
1786
- header = "📊 <b>Benchmark avviato</b> (benchmark-extended.mjs)"
1787
-
1788
- caption = _build_bench_caption(header)
1789
-
1790
- if chart_url:
1791
- await _tg_photo(chat_id, chart_url, caption=caption, keyboard=_BENCH_ACTION_KB)
1792
- else:
1793
- await _tg_reply(chat_id, caption, keyboard=_BENCH_ACTION_KB)
1794
 
1795
 
1796
 
 
1588
  await _tg_reply(chat_id, det[_TG_MAX:_TG_MAX*2][:_TG_MAX], keyboard=_BENCH_ACTION_KB)
1589
 
1590
 
1591
+ def _format_live_quality_benchmark(report: dict) -> str:
1592
+ """Formatta solo risultati prodotti dal benchmark quality corrente."""
1593
+ timestamp = str(report.get("timestamp") or "")[:19].replace("T", " ")
1594
+ score = report.get("total_score", "N/A")
1595
+ results = report.get("results") if isinstance(report.get("results"), list) else []
1596
+ errors = report.get("errors") if isinstance(report.get("errors"), list) else []
1597
+ outcome = "✅" if report.get("ok") else "⚠️"
1598
+ lines = [
1599
+ f"📊 <b>Benchmark live Quality</b> — {outcome}",
1600
+ f"🕐 <code>{html.escape(timestamp or 'ora non disponibile')}</code>",
1601
+ f"📈 <b>Score live:</b> <code>{html.escape(str(score))}/100</code>",
1602
+ f"🧪 Categorie eseguite: <code>{len(results)}</code>",
1603
+ ]
1604
+ if results:
1605
+ lines.append("\n<b>Risultati della run corrente:</b>")
1606
+ for item in results[:12]:
1607
+ label = html.escape(str(item.get("label") or item.get("id") or "categoria")[:42])
1608
+ value = item.get("score", "N/A")
1609
+ try:
1610
+ icon = "🟢" if float(value) >= 75 else "🟡" if float(value) >= 50 else "🔴"
1611
+ except (TypeError, ValueError):
1612
+ icon = "⚪"
1613
+ lines.append(f"{icon} <code>{str(value):>5}</code> {label}")
1614
+ if errors:
1615
+ lines.append(f"\n⚠️ <b>Errori della run:</b> <code>{len(errors)}</code>")
1616
+ for error in errors[:2]:
1617
+ lines.append("<i>" + html.escape(str(error)[:180]) + "</i>")
1618
+ lines.append("\n<i>Misura live del backend: nessun report storico o workflow GitHub è stato usato.</i>")
1619
+ return "\n".join(lines)[:3900]
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
 
tests/test_telegram_benchmark_live.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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__":
83
+ unittest.main()