feat(complete): Phase 6 - Loop-Detector, Error-Healer, Grounding-Regel, erweiterte CLI
This commit is contained in:
211
src/error_healer.py
Normal file
211
src/error_healer.py
Normal file
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
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),
|
||||
}
|
||||
Reference in New Issue
Block a user