feat(integration): OpenClaw Bridge + Dashboard + Proaktivität
- src/openclaw_bridge.py: Session-Save, Heartbeat, Fehlerbehandlung, Feedback - src/dashboard.py: HTML-Dashboard (keine externen Abhängigkeiten) Issues: #4, #5, #6
This commit is contained in:
292
src/openclaw_bridge.py
Normal file
292
src/openclaw_bridge.py
Normal file
@@ -0,0 +1,292 @@
|
||||
"""
|
||||
OpenClaw Bridge - Verbindung zwischen OpenClaw-System und Second Brain.
|
||||
|
||||
Ermöglicht:
|
||||
1. Automatisches Speichern von Session-Inhalten als Engramme
|
||||
2. Proaktive Erinnerungen (Heartbeat)
|
||||
3. Selbstheilende Fehlerbehandlung
|
||||
4. Feedback-Integration (richtig/falsch)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
# Second Brain Import
|
||||
from .store import EngramStore
|
||||
from .engram import Engram, Grounding
|
||||
from .retriever import Retriever
|
||||
|
||||
|
||||
# --- Konfiguration ---
|
||||
BRAIN_DB = Path(__file__).parent.parent / "data" / "brain.sqlite"
|
||||
|
||||
|
||||
def get_brain() -> EngramStore:
|
||||
"""Gibt initialisierten Brain-Store."""
|
||||
BRAIN_DB.parent.mkdir(parents=True, exist_ok=True)
|
||||
return EngramStore(str(BRAIN_DB))
|
||||
|
||||
|
||||
# --- 1. Auto-Save: Session-Inhalt als Engramm ---
|
||||
|
||||
def save_session_learned(
|
||||
content: str,
|
||||
source: str = "agent",
|
||||
tags: Optional[List[str]] = None,
|
||||
session_id: Optional[str] = None,
|
||||
confidence: float = 0.5,
|
||||
grounding: Grounding = Grounding.ASSUMPTION,
|
||||
) -> Engram:
|
||||
"""
|
||||
Speichert eine gelernte Information aus einer OpenClaw-Session.
|
||||
|
||||
Nutzung im Agent-Code:
|
||||
from second_brain.src.openclaw_bridge import save_session_learned
|
||||
eg = save_session_learned(
|
||||
"Neues Faktum",
|
||||
tags=["project", "wichtig"],
|
||||
confidence=0.7,
|
||||
)
|
||||
"""
|
||||
store = get_brain()
|
||||
eg = Engram.create(
|
||||
content=content,
|
||||
source=source,
|
||||
tags=tags or [],
|
||||
session_id=session_id,
|
||||
confidence=confidence,
|
||||
grounding=grounding,
|
||||
)
|
||||
store.save(eg)
|
||||
return eg
|
||||
|
||||
|
||||
# --- 2. Proaktivität: Was soll ich tun? ---
|
||||
|
||||
def heartbeat_check() -> Optional[str]:
|
||||
"""
|
||||
Prüft ob proaktive Aktion sinnvoll ist.
|
||||
|
||||
Rückgabe: Nachricht für den User, oder None wenn nichts zu tun.
|
||||
"""
|
||||
store = get_brain()
|
||||
ret = Retriever(store)
|
||||
|
||||
# A: Unbestätigte Engramme die seit längerem nicht geprüft wurden
|
||||
# B: Hohe-Prioritäts-Themen (tags wie "wichtig", "dringend")
|
||||
# C: Fehler-Engramme die repeating sind
|
||||
|
||||
# Prüfe auf wichtige unbestätigte Engramme
|
||||
egs = store.get_all(limit=50)
|
||||
unconfirmed = [
|
||||
eg for eg in egs
|
||||
if not eg.correctness.confirmed and eg.compute_confidence() > 0.5
|
||||
][:5]
|
||||
|
||||
if unconfirmed:
|
||||
ids = ", ".join([str(eg.id)[:8] for eg in unconfirmed])
|
||||
contents = "\n".join([f" - {eg.content[:80]}" for eg in unconfimed])
|
||||
return (
|
||||
f"🧠 Second Brain Heartbeat\n"
|
||||
f"Unbestätigte Engramme mit gutem Confidence-Score:\n{contents}\n"
|
||||
f"Bestätige mit: `python -m src.cli confirm <id>`"
|
||||
)
|
||||
|
||||
# Prüfe auf Fehler-Muster
|
||||
errors = store.search_tag("error", limit=5)
|
||||
if len(errors) >= 3:
|
||||
return (
|
||||
f"⚠️ Second Brain: {len(errors)} Fehler-Engramme gespeichert.\n"
|
||||
f"Möglicherweise wiederholendes Problem. Prüfe mit `python -m src.cli search --tag error`"
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# --- 3. Fehlerbehandlung ---
|
||||
|
||||
def handle_tool_error(
|
||||
tool_name: str,
|
||||
error_message: str,
|
||||
context: Optional[Dict[str, Any]] = None,
|
||||
) -> Engram:
|
||||
"""
|
||||
Speichert einen Fehler als Engramm für spätere Analyse.
|
||||
|
||||
Nutzung bei try/except um Tool-Calls:
|
||||
try:
|
||||
result = some_tool_call()
|
||||
except Exception as e:
|
||||
handle_tool_error("tool_name", str(e), context={"args": args})
|
||||
"""
|
||||
store = get_brain()
|
||||
content = f"FEHLER: {tool_name}\n{error_message}\n"
|
||||
if context:
|
||||
content += f"\nContext: {json.dumps(context, default=str, ensure_ascii=False)}"
|
||||
|
||||
eg = Engram.create(
|
||||
content=content,
|
||||
source="system",
|
||||
tags=["error", tool_name],
|
||||
confidence=0.0,
|
||||
grounding=Grounding.UNKNOWN,
|
||||
)
|
||||
store.save(eg)
|
||||
|
||||
# Prüfe auf Wiederholung
|
||||
similar = store.search_text(error_message[:50], limit=10)
|
||||
same_errors = [e for e in similar if e.metadata.get("source") == "system" and "error" in e.metadata.get("tags", [])]
|
||||
if len(same_errors) >= 3:
|
||||
# Auto-Fix: Erstelle Engramm mit Lösungsstrategie
|
||||
fix_eg = Engram.create(
|
||||
content=f"AUTO-FIX-STRATEGIE für wiederholten Fehler '{tool_name}':\n"
|
||||
f"1. Retry mit alternativem Ansatz\n"
|
||||
f"2. Fallback auf simpleres Tool\n"
|
||||
f"3. User benachrichtigen wenn Persistenz > 5x",
|
||||
source="system",
|
||||
tags=["auto-fix", tool_name, "strategy"],
|
||||
confidence=0.6,
|
||||
grounding=Grounding.INFERRED,
|
||||
)
|
||||
store.save(fix_eg)
|
||||
|
||||
return eg
|
||||
|
||||
|
||||
# --- 4. Feedback-Integration ---
|
||||
|
||||
def user_feedback(engram_id: str, is_correct: bool, note: str = "") -> Engram:
|
||||
"""
|
||||
Nimmt User-Feedback (richtig/falsch) auf.
|
||||
|
||||
Nutzung nach jeder Agent-Antwort:
|
||||
if user_says_correct:
|
||||
user_feedback(engram_id, True)
|
||||
elif user_says_wrong:
|
||||
user_feedback(engram_id, False, note="Falsches Datum")
|
||||
"""
|
||||
store = get_brain()
|
||||
eg = store.get(engram_id)
|
||||
if not eg:
|
||||
raise ValueError(f"Engram {engram_id} nicht gefunden")
|
||||
|
||||
if is_correct:
|
||||
eg.correctness.confirm(by="user", note=note)
|
||||
else:
|
||||
eg.correctness.reject(by="user", note=note)
|
||||
|
||||
store.save(eg)
|
||||
return eg
|
||||
|
||||
|
||||
# --- 5. Kontext-anreicherung für Agenten ---
|
||||
|
||||
def enrich_context(topic: str, limit: int = 3) -> str:
|
||||
"""
|
||||
Holt relevante Engramme und formatiert sie als Kontext für den Agent.
|
||||
|
||||
Nutzung vor Prompt-Generierung:
|
||||
memory_context = enrich_context("Projekt X")
|
||||
# memory_context in das Prompt einbauen
|
||||
"""
|
||||
store = get_brain()
|
||||
ret = Retriever(store)
|
||||
results = ret.retrieve(topic, limit=limit, min_confidence=0.3)
|
||||
if not results:
|
||||
return ""
|
||||
|
||||
lines = ["\n📚 Second Brain Kontext:"]
|
||||
for r in results:
|
||||
eg = r["engram"]
|
||||
conf = eg.compute_confidence()
|
||||
marker = "✅" if conf > 0.7 else "⚠️" if conf > 0.4 else "❌"
|
||||
g = Grounding(eg.metadata.get("grounding", 0)).name
|
||||
lines.append(f" {marker} [{g}] {eg.content[:150]}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# --- 6. Backup & Wartung ---
|
||||
|
||||
def backup_daily() -> str:
|
||||
"""
|
||||
Tägliches Backup als JSONL.
|
||||
|
||||
Sollte via Cron aufgerufen werden:
|
||||
0 2 * * * python -m second_brain.src.openclaw_bridge backup
|
||||
"""
|
||||
store = get_brain()
|
||||
ts = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
||||
path = BRAIN_DB.parent / f"backup_{ts}.jsonl"
|
||||
count = store.export_jsonl(str(path))
|
||||
return f"Backup: {count} Engramme -> {path}"
|
||||
|
||||
|
||||
def stats() -> Dict[str, Any]:
|
||||
"""Gibt Statistiken zurück."""
|
||||
store = get_brain()
|
||||
return Retriever(store).stats()
|
||||
|
||||
|
||||
# --- CLI-Entrypoint ---
|
||||
|
||||
def main():
|
||||
"""CLI für die Bridge-Funktionen."""
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="Second Brain OpenClaw Bridge")
|
||||
sub = parser.add_subparsers(dest="cmd")
|
||||
|
||||
p_save = sub.add_parser("save", help="Speichere Lerngut")
|
||||
p_save.add_argument("content")
|
||||
p_save.add_argument("--tag", action="append", default=[])
|
||||
p_save.add_argument("--confidence", type=float, default=0.5)
|
||||
|
||||
p_feedback = sub.add_parser("feedback", help="User-Feedback")
|
||||
p_feedback.add_argument("id")
|
||||
p_feedback.add_argument("--correct", action="store_true")
|
||||
p_feedback.add_argument("--note", default="")
|
||||
|
||||
p_heartbeat = sub.add_parser("heartbeat", help="Heartbeat-Check")
|
||||
|
||||
p_backup = sub.add_parser("backup", help="Backup erstellen")
|
||||
|
||||
p_stats = sub.add_parser("stats", help="Statistiken")
|
||||
|
||||
p_context = sub.add_parser("context", help="Kontext holen")
|
||||
p_context.add_argument("topic")
|
||||
p_context.add_argument("--limit", type=int, default=3)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.cmd == "save":
|
||||
eg = save_session_learned(args.content, tags=args.tag, confidence=args.confidence)
|
||||
print(f"Saved: {eg.id} (conf: {eg.compute_confidence():.2f})")
|
||||
|
||||
elif args.cmd == "feedback":
|
||||
eg = user_feedback(args.id, args.correct, args.note)
|
||||
print(f"Feedback recorded: {eg.id} -> {eg.compute_confidence():.2f}")
|
||||
|
||||
elif args.cmd == "heartbeat":
|
||||
msg = heartbeat_check()
|
||||
print(msg or "HEARTBEAT_OK")
|
||||
|
||||
elif args.cmd == "backup":
|
||||
print(backup_daily())
|
||||
|
||||
elif args.cmd == "stats":
|
||||
print(json.dumps(stats(), indent=2))
|
||||
|
||||
elif args.cmd == "context":
|
||||
print(enrich_context(args.topic, args.limit))
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user