Spaces:
Configuration error
Configuration error
Pulka
feat(telegram): bot UX 100% — WebApp button, /logs, /ping, inline query, _BACK_KB
4cbd6c4 verified | """ | |
| reflection.py — Reflection Memory | |
| Impara dagli errori, pattern di fallimento, strategie vincenti. | |
| """ | |
| import json | |
| from pathlib import Path | |
| from datetime import datetime | |
| import logging | |
| _logger = logging.getLogger("memory.reflection") | |
| # D1: usa /data/ se disponibile (HF Spaces volume persistente) — evita perdita a restart | |
| import os as _os_ref | |
| _REFLECT_DATA_DIR = _os_ref.getenv('CHROMA_DATA_DIR') or ('/data' if Path('/data').exists() else '.') | |
| REFLECTION_PATH = Path(_REFLECT_DATA_DIR) / 'reflection_memory.json' | |
| class ReflectionMemory: | |
| def __init__(self): | |
| self._data: dict = {"failures": [], "successes": [], "patterns": {}} | |
| self._load() | |
| def _load(self): | |
| if REFLECTION_PATH.exists(): | |
| try: | |
| self._data = json.loads(REFLECTION_PATH.read_text()) | |
| except Exception as _exc: | |
| _logger.debug("[reflection] silenced %s", type(_exc).__name__) # noqa: BLE001 | |
| def _save(self): | |
| REFLECTION_PATH.write_text(json.dumps(self._data, indent=2, ensure_ascii=False)) | |
| def record_failure(self, task: str, error: str, attempted_strategy: str = ""): | |
| entry = { | |
| "ts": datetime.now().isoformat(), | |
| # S600: task 300→500 — task description può essere più lunga | |
| "task": task[:500], | |
| "error": error[:500], | |
| "strategy": attempted_strategy, | |
| } | |
| self._data["failures"].append(entry) | |
| # Track pattern frequency | |
| key = error[:120].lower() # S582: 80→120 (chiave pattern freq — conservativo) | |
| self._data["patterns"][key] = self._data["patterns"].get(key, 0) + 1 | |
| # Cap at 200 entries | |
| self._data["failures"] = self._data["failures"][-200:] | |
| self._save() | |
| def record_success(self, task: str, strategy: str): | |
| entry = { | |
| "ts": datetime.now().isoformat(), | |
| # S600: task 300→500, strategy 300→500 — più contesto per replay futuro | |
| "task": task[:500], | |
| "strategy": strategy[:500], | |
| } | |
| self._data["successes"].append(entry) | |
| self._data["successes"] = self._data["successes"][-100:] | |
| self._save() | |
| def get_relevant_lessons(self, task: str, n: int = 3) -> list[dict]: | |
| """Find past failures/successes relevant to current task.""" | |
| task_lower = task.lower() | |
| words = set(task_lower.split()) | |
| def score(entry): | |
| text = (entry.get("task", "") + " " + entry.get("strategy", "") + entry.get("error", "")).lower() | |
| return sum(1 for w in words if w in text and len(w) > 3) | |
| failures = sorted(self._data["failures"], key=score, reverse=True)[:n] | |
| successes = sorted(self._data["successes"], key=score, reverse=True)[:n] | |
| lessons = [] | |
| for f in failures[:2]: | |
| if score(f) > 0: | |
| lessons.append({"type": "failure", "task": f["task"], "avoid": f["error"]}) | |
| for s in successes[:1]: | |
| if score(s) > 0: | |
| lessons.append({"type": "success", "task": s["task"], "strategy": s["strategy"]}) | |
| return lessons | |
| def get_top_patterns(self, n: int = 5) -> list[dict]: | |
| return sorted( | |
| [{"pattern": k, "count": v} for k, v in self._data["patterns"].items()], | |
| key=lambda x: x["count"], reverse=True | |
| )[:n] | |
| def stats(self) -> dict: | |
| return { | |
| "failures": len(self._data["failures"]), | |
| "successes": len(self._data["successes"]), | |
| "patterns": len(self._data["patterns"]), | |
| } | |