Spaces:
Running
Running
| """backend/api/skills.py — P17-B2: Endpoint /api/skills/patterns. | |
| Espone i pattern di tool-usage aggregati dal SkillTracker per sync frontend. | |
| GET /api/skills/patterns → aggregato globale (tutte le sessioni) | |
| GET /api/skills/patterns/{session_id} → singola sessione | |
| fix(backend): corregge IndentationError presente al commit 1e1fa65 | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import math | |
| from fastapi import APIRouter, Depends, Query | |
| from .auth_guard import require_role, AuthRole | |
| _logger = logging.getLogger("agente_ai.api.skills") | |
| router = APIRouter(prefix="/api/skills", tags=["skills"], dependencies=[Depends(require_role(AuthRole.MACHINE))]) # GAP-1-fix: router-level auth | |
| def _wilson(success: int, total: int) -> float: | |
| if total == 0: | |
| return 0.5 | |
| p, z = success / total, 1.96 | |
| num = p + z*z/(2*total) - z*math.sqrt((p*(1-p) + z*z/(4*total))/total) | |
| return max(0.0, min(1.0, num / (1 + z*z/total))) | |
| async def get_skill_patterns( | |
| limit: int = Query(default=50, ge=1, le=200), | |
| min_uses: int = Query(default=1, ge=1, description="Minimo utilizzi totali"), | |
| ): | |
| """Pattern aggregati da tutte le sessioni in memoria, ordinati per Wilson score.""" | |
| try: | |
| from agents.skill_tracker import get_skill_tracker | |
| tracker = get_skill_tracker() | |
| agg: dict[str, dict] = {} | |
| for _sid, sess in tracker._stats.items(): | |
| for tool, stats in sess.items(): | |
| if tool not in agg: | |
| agg[tool] = {"s": 0, "f": 0, "lat": 0.0, "sessions": 0, "last": 0.0} | |
| agg[tool]["s"] += stats.success_count | |
| agg[tool]["f"] += stats.fail_count | |
| agg[tool]["lat"] += stats.total_latency_ms | |
| agg[tool]["sessions"] += 1 | |
| agg[tool]["last"] = max(agg[tool]["last"], stats.last_used) | |
| out = [] | |
| for tool, a in agg.items(): | |
| total = a["s"] + a["f"] | |
| if total < min_uses: | |
| continue | |
| out.append({ | |
| "tool": tool, | |
| "total": total, | |
| "success": a["s"], | |
| "fail": a["f"], | |
| "success_rate": round(a["s"] / total if total else 1.0, 4), | |
| "wilson_score": round(_wilson(a["s"], total), 4), | |
| "avg_latency_ms": round(a["lat"] / total if total else 0.0, 1), | |
| "sessions": a["sessions"], | |
| "last_used": a["last"], | |
| }) | |
| out.sort(key=lambda x: x["wilson_score"], reverse=True) | |
| return {"patterns": out[:limit], "total": len(out)} | |
| except Exception as exc: | |
| _logger.error("[skills/patterns] %s", exc) | |
| return {"patterns": [], "total": 0, "error": str(exc)[:200]} | |
| async def get_session_patterns(session_id: str): | |
| """Pattern per una singola sessione.""" | |
| try: | |
| from agents.skill_tracker import get_skill_tracker | |
| tracker = get_skill_tracker() | |
| sess = tracker._stats.get(session_id, {}) | |
| out = [] | |
| for tool, stats in sess.items(): | |
| total = stats.total_count | |
| out.append({ | |
| "tool": tool, | |
| "total": total, | |
| "success": stats.success_count, | |
| "fail": stats.fail_count, | |
| "success_rate": round(stats.success_rate, 4), | |
| "wilson_score": round(stats.wilson_score(), 4), | |
| "avg_latency_ms": round(stats.avg_latency_ms, 1), | |
| "last_used": stats.last_used, | |
| }) | |
| out.sort(key=lambda x: x["wilson_score"], reverse=True) | |
| return {"session_id": session_id, "patterns": out, "total": len(out)} | |
| except Exception as exc: | |
| _logger.error("[skills/patterns/%s] %s", session_id, exc) | |
| return {"session_id": session_id, "patterns": [], "total": 0, "error": str(exc)[:200]} | |