212 lines
7.0 KiB
Python
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),
|
|
}
|