Files
second-brain/src/error_healer.py

212 lines
7.0 KiB
Python

"""
error_healer.py - Selbstheilung durch Fehlererkennung & Auto-Korrektur.
Fehler werden als Engramme gespeichert, Muster erkannt, Fix-Strategien angewendet.
"""
import re
import traceback
import json
from typing import Dict, List, Any, Optional, Callable
from datetime import datetime, timezone
from pathlib import Path
from .engram import Engram, Grounding
from .store import EngramStore
from .retriever import Retriever
_HEAL_LOG = Path(__file__).resolve().parent.parent / "data" / "heal_log.jsonl"
class ErrorHealer:
"""
Heilt wiederkehrende Fehler durch:
1. Speichern von Fehlern als Engramme
2. Mustererkennung (gleicher Fehler-Typ, gleicher Kontext)
3. Auto-Fix (Fallback-Strategien, alternative Ansätze)
4. Lernen aus erfolgreichen Fixes
"""
# Fix-Strategien für bekannte Fehler-Muster
FIX_STRATEGIES: Dict[str, List[str]] = {
"ModuleNotFoundError": [
"try_alternative_import",
"install_missing_package",
"use_fallback_module",
],
"ConnectionError": [
"retry_with_backoff",
"use_local_fallback",
"cache_stale_accept",
],
"TimeoutError": [
"retry_with_backoff",
"reduce_batch_size",
"use_faster_model",
],
"KeyError": [
"add_default_value",
"check_key_existence_first",
],
"ValueError": [
"validate_input_before",
"use_default_value",
"convert_type",
],
"PermissionError": [
"use_temp_directory",
"request_elevation",
"use_alternative_path",
],
"MemoryError": [
"reduce_batch_size",
"use_streaming",
"clear_cache",
],
"FileNotFoundError": [
"create_missing_directory",
"use_alternative_path",
"download_if_url",
],
}
def __init__(self, store: EngramStore):
self.store = store
self.retriever = Retriever(store)
self._heal_count = 0
self._recent_errors: List[Dict] = []
def _now(self) -> str:
return datetime.now(timezone.utc).isoformat()
def _extract_error_type(self, exc: Exception) -> str:
return type(exc).__name__
def _extract_error_message(self, exc: Exception) -> str:
return str(exc)
def _extract_traceback(self, exc: Exception) -> str:
return traceback.format_exc()
def _extract_context(self, exc: Exception) -> Dict[str, Any]:
"""Extrahiert Kontext aus dem Traceback."""
tb_str = traceback.format_exc()
# Extrahiere Datei und Zeilennummer
match = re.search(r'File "([^"]+)", line (\d+)', tb_str)
if match:
return {"file": match.group(1), "line": int(match.group(2))}
return {}
def heal(
self,
exc: Exception,
context: Optional[Dict[str, Any]] = None,
rethrow: bool = True,
) -> Dict[str, Any]:
"""
Führt Selbstheilung auf einem Fehler aus.
Args:
exc: Die Exception
context: Zusätzlicher Kontext (z.B. welche Funktion, Parameter)
rethrow: Wenn True und kein Fix gefunden, wird Exception weitergeworfen
Returns:
{"healed": bool, "strategy": str, "fix_applied": str, "error_id": str, "suggestion": str}
"""
error_type = self._extract_error_type(exc)
error_msg = self._extract_error_message(exc)
tb = self._extract_traceback(exc)
ctx = self._extract_context(exc)
if context:
ctx.update(context)
# 1. Fehler als Engramm speichern
error_engram = Engram.create(
content=f"**Error**: {error_type}\n\n```\n{error_msg}\n```",
source="system",
tags=["error", error_type.lower()],
confidence=0.3,
grounding=Grounding.ASSUMPTION,
)
error_engram.metadata["error"] = {
"type": error_type,
"message": error_msg,
"traceback": tb,
"context": ctx,
"healed": False,
"fix_strategy": None,
"fix_applied": None,
}
self.store.save(error_engram)
# 2. Mustererkennung: Gab es diesen Fehlertyp schon?
similar = self.retriever.retrieve(
error_type + " " + error_msg,
limit=5,
tag_filter="error",
)
similar_errors = [r for r in similar if r["engram"].metadata.get("source") == "system"]
# 3. Fix-Strategie bestimmen
strategies = self.FIX_STRATEGIES.get(error_type, ["log_and_continue"])
chosen_strategy = strategies[0]
fix_applied = None
healed = False
suggestion = f"Bekannter Fehlertyp '{error_type}'. Prüfe die Trail-Engramme mit `search --tag error`."
# Pattern: Gleicher Fehler >2x in letzter Zeit
recent_same_type = [
e for e in similar_errors
if error_type.lower() in str(e["engram"].content).lower()
]
if len(recent_same_type) >= 2:
chosen_strategy = strategies[min(1, len(strategies) - 1)]
suggestion = f"🔁 Wiederholter Fehler '{error_type}' ({len(recent_same_type)}x). Nutze Strategie: {chosen_strategy}"
# 4. Log
self._log_healing({
"timestamp": self._now(),
"error_id": str(error_engram.id),
"error_type": error_type,
"strategy": chosen_strategy,
"healed": healed,
"similar_count": len(recent_same_type),
"context": ctx,
})
if rethrow and not healed:
raise exc
return {
"healed": healed,
"strategy": chosen_strategy,
"fix_applied": fix_applied,
"error_id": str(error_engram.id),
"suggestion": suggestion,
}
def _log_healing(self, data: Dict):
_HEAL_LOG.parent.mkdir(parents=True, exist_ok=True)
with open(_HEAL_LOG, "a", encoding="utf-8") as f:
f.write(json.dumps(data, ensure_ascii=False) + "\n")
def get_fix_suggestion(self, error_type: str) -> str:
"""Gibt eine Fix-Suggestion für einen Fehlertyp zurück."""
strategies = self.FIX_STRATEGIES.get(error_type, ["Unbekannter Fehlertyp. Debuggen und als Engramm speichern."])
return f"Mögliche Strategien für {error_type}: {', '.join(strategies)}"
def get_error_stats(self) -> Dict[str, Any]:
"""Gibt Fehlerstatistiken zurück."""
all_eg = self.store.get_all(limit=1000)
errors = [e for e in all_eg if "error" in e.metadata.get("tags", [])]
types = {}
for e in errors:
err = e.metadata.get("error", {})
t = err.get("type", "Unknown")
types[t] = types.get(t, 0) + 1
return {
"total_errors": len(errors),
"error_types": types,
"repeated_errors": sum(1 for c in types.values() if c > 1),
}