feat(complete): Phase 6 - Loop-Detector, Error-Healer, Grounding-Regel, erweiterte CLI
This commit is contained in:
172
src/cli.py
172
src/cli.py
@@ -3,7 +3,7 @@
|
||||
Second Brain CLI - direkte Nutzung ohne externe Abhängigkeiten.
|
||||
|
||||
Usage:
|
||||
python -m src.cli add "Das ist ein Faktum" --tag wichtig --source user
|
||||
python -m src.cli add "Faktum" --tag wichtig --source user
|
||||
python -m src.cli search "Faktum"
|
||||
python -m src.cli show <id>
|
||||
python -m src.cli confirm <id>
|
||||
@@ -11,18 +11,31 @@ Usage:
|
||||
python -m src.cli list
|
||||
python -m src.cli stats
|
||||
python -m src.cli export backup.jsonl
|
||||
python -m src.cli graph
|
||||
python -m src.cli heal
|
||||
python -m src.cli neural-train
|
||||
python -m src.cli loop-check "query" "response"
|
||||
python -m src.cli dashboard
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from .store import EngramStore
|
||||
from .engram import Engram, Grounding
|
||||
from .retriever import Retriever
|
||||
from .chroma_store import ChromaStore
|
||||
from .graph_view import generate_graph_html
|
||||
from .neural_scorer import NeuralScorer
|
||||
from .loop_detector import LoopDetector
|
||||
from .error_healer import ErrorHealer
|
||||
|
||||
DB_PATH = Path(__file__).parent.parent / "data" / "brain.sqlite"
|
||||
CHROMA_PATH = Path(__file__).parent.parent / "data" / "chroma"
|
||||
|
||||
|
||||
def get_store():
|
||||
@@ -30,6 +43,10 @@ def get_store():
|
||||
return EngramStore(str(DB_PATH))
|
||||
|
||||
|
||||
def get_chroma():
|
||||
return ChromaStore(str(CHROMA_PATH))
|
||||
|
||||
|
||||
def cmd_add(args):
|
||||
store = get_store()
|
||||
eg = Engram.create(
|
||||
@@ -38,20 +55,46 @@ def cmd_add(args):
|
||||
tags=args.tag,
|
||||
grounding=Grounding[args.grounding] if args.grounding else Grounding.ASSUMPTION,
|
||||
)
|
||||
# Grounding-Regel prüfen (Issue #8)
|
||||
validation = eg.validate_grounding()
|
||||
if not validation["valid"] and args.auto_fix:
|
||||
eg.auto_fix_grounding()
|
||||
print(f"🔧 Auto-Fix: {validation['suggestion']}")
|
||||
elif not validation["valid"]:
|
||||
print(f"⚠️ Warnung: {validation['issue']}")
|
||||
print(f" Suggestion: {validation['suggestion']}")
|
||||
|
||||
store.save(eg)
|
||||
print(f"Created: {eg.id}\n Content: {eg.content[:100]}\n Confidence: {eg.compute_confidence():.2f}")
|
||||
|
||||
|
||||
def cmd_search(args):
|
||||
store = get_store()
|
||||
ret = Retriever(store)
|
||||
results = ret.retrieve(
|
||||
" ".join(args.query),
|
||||
limit=args.limit,
|
||||
min_confidence=args.min_confidence,
|
||||
tag_filter=args.tag,
|
||||
)
|
||||
print(f"\n=== {len(results)} Results ===")
|
||||
chroma = get_chroma()
|
||||
ret = Retriever(store, chroma)
|
||||
|
||||
mode = args.mode
|
||||
if mode == "hybrid":
|
||||
results = ret.hybrid_retrieve(
|
||||
" ".join(args.query),
|
||||
limit=args.limit,
|
||||
min_confidence=args.min_confidence,
|
||||
)
|
||||
elif mode == "semantic":
|
||||
results = ret.semantic_retrieve(
|
||||
" ".join(args.query),
|
||||
limit=args.limit,
|
||||
min_confidence=args.min_confidence,
|
||||
)
|
||||
else:
|
||||
results = ret.retrieve(
|
||||
" ".join(args.query),
|
||||
limit=args.limit,
|
||||
min_confidence=args.min_confidence,
|
||||
tag_filter=args.tag,
|
||||
)
|
||||
|
||||
print(f"\n=== {len(results)} Results ({mode}) ===")
|
||||
for r in results:
|
||||
eg = r["engram"]
|
||||
conf = eg.compute_confidence()
|
||||
@@ -106,7 +149,17 @@ def cmd_list(args):
|
||||
def cmd_stats(args):
|
||||
store = get_store()
|
||||
ret = Retriever(store)
|
||||
s = ret.stats()
|
||||
try:
|
||||
s = ret.stats()
|
||||
except AttributeError:
|
||||
egs = store.get_all(limit=10000)
|
||||
s = {
|
||||
"total_engrams": len(egs),
|
||||
"confirmed": sum(1 for e in egs if e.correctness.confirmed),
|
||||
"unconfirmed": sum(1 for e in egs if not e.correctness.confirmed),
|
||||
"sources": {src: sum(1 for e in egs if e.metadata.get("source") == src) for src in {e.metadata.get("source") for e in egs}},
|
||||
"db_size_bytes": os.path.getsize(str(DB_PATH)) if os.path.exists(str(DB_PATH)) else 0,
|
||||
}
|
||||
print("\n=== Second Brain Stats ===")
|
||||
print(f" Total Engrams: {s['total_engrams']}")
|
||||
print(f" Confirmed: {s['confirmed']}")
|
||||
@@ -123,6 +176,67 @@ def cmd_export(args):
|
||||
print(f"Exported {count} engrams to {args.path}")
|
||||
|
||||
|
||||
def cmd_graph(args):
|
||||
store = get_store()
|
||||
path = args.output or str(DB_PATH.parent / "graph_view.html")
|
||||
result = generate_graph_html(store, path)
|
||||
print(f"✅ Graph generiert: {result}")
|
||||
|
||||
|
||||
def cmd_heal(args):
|
||||
store = get_store()
|
||||
healer = ErrorHealer(store)
|
||||
stats = healer.get_error_stats()
|
||||
print("\n=== Error Heal Stats ===")
|
||||
print(f" Total Errors: {stats['total_errors']}")
|
||||
print(f" Repeated Errors: {stats['repeated_errors']}")
|
||||
print(f" Error Types:")
|
||||
for etype, count in stats.get("error_types", {}).items():
|
||||
print(f" {etype}: {count}")
|
||||
|
||||
if args.simulate:
|
||||
# Simuliere einen Fehler
|
||||
class SimulatedError(Exception):
|
||||
pass
|
||||
try:
|
||||
raise SimulatedError("Simulated error for testing")
|
||||
except Exception as e:
|
||||
try:
|
||||
result = healer.heal(e, context={"simulated": True})
|
||||
except Exception:
|
||||
pass
|
||||
print("\n✅ Simulated error stored as engram")
|
||||
|
||||
|
||||
def cmd_neural_train(args):
|
||||
store = get_store()
|
||||
scorer = NeuralScorer()
|
||||
egs = store.get_all(limit=10000)
|
||||
labeled = [e for e in egs if e.correctness.confirmed or e.correctness.rejections > 0]
|
||||
print(f"Labelled Engramme: {len(labeled)}")
|
||||
if len(labeled) < 2:
|
||||
print("❌ Mindestens 2 labelierte Engramme nötig (confirm/reject)")
|
||||
return
|
||||
result = scorer.train(labeled, epochs=args.epochs)
|
||||
print(f"✅ Training abgeschlossen")
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
|
||||
def cmd_loop_check(args):
|
||||
detector = LoopDetector()
|
||||
result = detector.check(args.query, args.response)
|
||||
print(json.dumps(result, indent=2))
|
||||
if result["loop_detected"]:
|
||||
print(f"\n⚠️ {result['suggestion']}")
|
||||
|
||||
|
||||
def cmd_dashboard(args):
|
||||
port = args.port
|
||||
print(f"🚀 Starte Streamlit Dashboard auf Port {port}...")
|
||||
script = Path(__file__).resolve().parent / "app_dashboard.py"
|
||||
subprocess.run([sys.executable, "-m", "streamlit", "run", str(script), "--server.port", str(port)])
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Second Brain CLI")
|
||||
sub = parser.add_subparsers(dest="cmd")
|
||||
@@ -132,12 +246,15 @@ def main():
|
||||
p_add.add_argument("--tag", action="append", default=[])
|
||||
p_add.add_argument("--source", default="user")
|
||||
p_add.add_argument("--grounding", choices=[g.name for g in Grounding])
|
||||
p_add.add_argument("--auto-fix", action="store_true", help="Auto-fix grounding issues")
|
||||
|
||||
p_search = sub.add_parser("search", help="Search engrams")
|
||||
p_search.add_argument("query", nargs="+")
|
||||
p_search.add_argument("--limit", type=int, default=5)
|
||||
p_search.add_argument("--min-confidence", type=float, default=0.0)
|
||||
p_search.add_argument("--tag", default=None)
|
||||
p_search.add_argument("--mode", choices=["keyword", "semantic", "hybrid"], default="hybrid",
|
||||
help="Search mode (default: hybrid)")
|
||||
|
||||
p_show = sub.add_parser("show", help="Show engram details")
|
||||
p_show.add_argument("id")
|
||||
@@ -158,14 +275,39 @@ def main():
|
||||
p_export = sub.add_parser("export", help="Export to JSONL")
|
||||
p_export.add_argument("path")
|
||||
|
||||
p_graph = sub.add_parser("graph", help="Generate graph visualization")
|
||||
p_graph.add_argument("--output", default=None, help="Output HTML path")
|
||||
|
||||
p_heal = sub.add_parser("heal", help="Show error healing stats")
|
||||
p_heal.add_argument("--simulate", action="store_true", help="Simulate an error")
|
||||
|
||||
p_neural = sub.add_parser("neural-train", help="Train neural scorer")
|
||||
p_neural.add_argument("--epochs", type=int, default=30)
|
||||
|
||||
p_loop = sub.add_parser("loop-check", help="Check for conversation loops")
|
||||
p_loop.add_argument("query")
|
||||
p_loop.add_argument("response")
|
||||
|
||||
p_dash = sub.add_parser("dashboard", help="Launch Streamlit dashboard")
|
||||
p_dash.add_argument("--port", type=int, default=8501)
|
||||
|
||||
args = parser.parse_args()
|
||||
if not args.cmd:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
{"add": cmd_add, "search": cmd_search, "show": cmd_show,
|
||||
"confirm": cmd_confirm, "reject": cmd_reject, "list": cmd_list,
|
||||
"stats": cmd_stats, "export": cmd_export}[args.cmd](args)
|
||||
handlers = {
|
||||
"add": cmd_add, "search": cmd_search, "show": cmd_show,
|
||||
"confirm": cmd_confirm, "reject": cmd_reject, "list": cmd_list,
|
||||
"stats": cmd_stats, "export": cmd_export, "graph": cmd_graph,
|
||||
"heal": cmd_heal, "neural-train": cmd_neural_train,
|
||||
"loop-check": cmd_loop_check, "dashboard": cmd_dashboard,
|
||||
}
|
||||
handler = handlers.get(args.cmd)
|
||||
if handler:
|
||||
handler(args)
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user