From 84b4643f1a84d4b8e21db04320776e20a51ca990 Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Sun, 23 Aug 2026 21:27:32 +0200 Subject: [PATCH] fix(backend): Mem0-Reste aus Steward, Voice-Metrics und Backup entfernt --- backend/services/backup.py | 2 +- backend/services/voice_metrics.py | 25 +++++++++++++------------ backend/steward.py | 13 ++++--------- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/backend/services/backup.py b/backend/services/backup.py index db42a1c..14120f3 100644 --- a/backend/services/backup.py +++ b/backend/services/backup.py @@ -1,5 +1,5 @@ """ -Voll-Zustands-Backup (mem0 + Hermes-Configs/Secrets + llama-swap config). +Voll-Zustands-Backup (Hermes-Configs/Secrets + llama-swap config). Delegiert an deploy/backup.sh (eine Quelle der Wahrheit, identisch zum systemd-Timer); Restore läuft bewusst nur per CLI (deploy/restore.sh) — siehe docs/BACKUP.md. """ diff --git a/backend/services/voice_metrics.py b/backend/services/voice_metrics.py index e31433c..2909705 100644 --- a/backend/services/voice_metrics.py +++ b/backend/services/voice_metrics.py @@ -11,11 +11,12 @@ Zwei Sichten auf dieselben Messungen: **Was MC2 messen kann — und was nicht:** MC2 proxyt den Chat nur an Hermes (:8642). Die Stufen STT, Vision, Hirn-TTFT (Zeit bis zum ersten Inhalts-Token) und Generierung sind hier direkt messbar. Der -**Mem0-Retrieve** läuft zwar in Hermes, ruft aber MC2s `/api/memory` per HTTP zurück → messbar und als -Unter-Detail INNERHALB der Hirn-Zeit ausgewiesen (kein Doppelzählen). Die **Tool-Runden** dagegen laufen -im Hermes-LLM-Loop ohne Callback an MC2 → für MC2 unsichtbar, sie stecken im „Generierung"-Bucket. +**Gedächtnis-Retrieve** läuft zwar in Hermes, ruft aber MC2s Memory-Endpoint per HTTP zurück → messbar +und als Unter-Detail INNERHALB der Hirn-Zeit ausgewiesen (kein Doppelzählen). Die **Tool-Runden** +dagegen laufen im Hermes-LLM-Loop ohne Callback an MC2 → für MC2 unsichtbar, sie stecken im +„Generierung"-Bucket. -STT (davor) und Mem0-Retrieve (währenddessen) sind separate HTTP-Requests ohne Turn-ID. Auf einem +STT (davor) und Gedächtnis-Retrieve (währenddessen) sind separate HTTP-Requests ohne Turn-ID. Auf einem EIN-Nutzer-Gerät genügt eine schlanke Best-Effort-Korrelation: die zuletzt gemessene STT-Dauer bzw. der letzte Retrieve werden global „geparkt" und vom nächsten Chat-Turn eingesammelt (mit Frist-/Reihenfolge- Check). Kein Turn-ID-Durchreichen durch den Lucy-Client nötig. @@ -36,7 +37,7 @@ _TURNS: deque = deque(maxlen=60) # letzte N vollständige Chat-Turns (Per-Turn- # Bekannte Stufen (für stabile UI-Reihenfolge); unbekannte werden trotzdem erfasst. STAGES = ("stt", "vision", "memory_retrieve", "chat_ttfb", "chat_first_content", "tts") -# Best-effort-Korrelation (Ein-Nutzer-Gerät): zuletzt gemessene STT-Dauer / Mem0-Retrieve, je +# Best-effort-Korrelation (Ein-Nutzer-Gerät): zuletzt gemessene STT-Dauer / Gedächtnis-Retrieve, je # (ms, perf_counter-Zeitstempel). Der nächste passende Chat-Turn sammelt sie ein und leert sie. _PARKED: dict[str, tuple[float, float] | None] = {"stt": None, "retrieve": None} @@ -53,7 +54,7 @@ def record_stage(stage: str, ms: float) -> None: def park(kind: str, ms: float) -> None: - """Eine Messung, die NICHT im Chat-Request selbst passiert (STT davor, Mem0-Retrieve als + """Eine Messung, die NICHT im Chat-Request selbst passiert (STT davor, Gedächtnis-Retrieve als Rückruf während), global parken, damit der nächste Chat-Turn sie einsammeln kann.""" if ms is None or ms < 0 or kind not in _PARKED: return @@ -72,7 +73,7 @@ def _take_stt(max_age: float = 20.0) -> float | None: def _take_retrieve(since_perf: float, max_age: float = 90.0) -> float | None: - """Geparkten Mem0-Retrieve einsammeln, wenn er NACH dem Turn-Start kam (Rückruf während des + """Geparkten Gedächtnis-Retrieve einsammeln, wenn er NACH dem Turn-Start kam (Rückruf während des Turns) und frisch ist.""" with _LOCK: v = _PARKED.get("retrieve") @@ -103,7 +104,7 @@ class TurnTrace: gehalten; `commit()` schreibt den Datensatz in den Ringpuffer UND speist die rollenden Stats. Balken-Stufen (zeitlich DISJUNKT, ergeben zusammen den Turn): stt · vision · hirn · gen. - Unter-Detail: mem0 (Teil VON hirn, wird separat ausgewiesen, aber NICHT zum Balken addiert).""" + Unter-Detail: memory (Teil VON hirn, wird separat ausgewiesen, aber NICHT zum Balken addiert).""" def __init__(self, session_id: str = "", kind: str = "voice") -> None: self.id = uuid.uuid4().hex[:8] @@ -113,7 +114,7 @@ class TurnTrace: self.session_id = (session_id or "")[:24] self.kind = kind self.vision_ms: float | None = None # Bildschirm-Beschreibung (falls Bilder) - self.hirn_ms: float | None = None # Zeit bis zum ersten Inhalts-Token (Agent + Mem0 + TTFT) + self.hirn_ms: float | None = None # Zeit bis zum ersten Inhalts-Token (Agent + Gedächtnis + TTFT) self.had_images = False self.error: str | None = None @@ -130,7 +131,7 @@ class TurnTrace: record_stage("chat_ttfb", (time.perf_counter() - self._brain0) * 1000.0) # SSE-Start (~5 ms), nur rollend def note_first_content(self) -> None: - """Erster Inhalts-Delta = echte Hirn-Latenz (Agent-Overhead + Mem0 + LLM-TTFT).""" + """Erster Inhalts-Delta = echte Hirn-Latenz (Agent-Overhead + Gedächtnis + LLM-TTFT).""" ms = (time.perf_counter() - self._brain0) * 1000.0 self.hirn_ms = round(ms, 1) record_stage("chat_first_content", ms) @@ -138,7 +139,7 @@ class TurnTrace: def commit(self) -> dict: total = (time.perf_counter() - self.perf0) * 1000.0 stt = _take_stt() # rollend bereits in /voice/stt erfasst - mem0 = _take_retrieve(self.perf0) # rollend bereits in /api/memory erfasst + memory = _take_retrieve(self.perf0) # Gedächtnis-Retrieve (HTTP-Rückruf), best-effort # Generierung = alles nach dem ersten Inhalts-Token bis Stream-Ende. gen = round(total - self.hirn_ms, 1) if self.hirn_ms is not None else None rec = { @@ -151,7 +152,7 @@ class TurnTrace: "vision_ms": self.vision_ms, "hirn_ms": self.hirn_ms, "gen_ms": gen if (gen is None or gen >= 0) else 0.0, - "mem0_ms": mem0, + "memory_ms": memory, "total_ms": round(total, 1), "error": self.error, } diff --git a/backend/steward.py b/backend/steward.py index b77ab76..43176c6 100644 --- a/backend/steward.py +++ b/backend/steward.py @@ -1,7 +1,7 @@ """ MC2-Steward — die Wächter-Loops als EIGENER Prozess (UMBAU v3, P2). -Bisher hingen Re-Warm-Wächter, Health-Wächter (sentry) und Mem0-Auto-Dedupe am +Bisher hingen Re-Warm-Wächter und Health-Wächter (sentry) am Lebenszyklus des Steuerpult-Webservers (app.py-Lifespan): jeder MC2-Neustart riss den Wächtern Timing und Flanken-Gedächtnis weg — und ein TOTES Steuerpult konnte sich prinzipbedingt nicht selbst melden. Hier laufen DIESELBEN Loops (unveränderte @@ -10,15 +10,15 @@ Module) als eigener Mini-Dienst (mc2-steward.service, Restart=always): • warmer.rewarm_loop — Warm-Set nachladen (+ Config-Watch ersetzt den In-Process-Nudge aus llamaswap.write_config) • sentry.sentry_loop — Health-Flanken → Briefkasten (HTTP an MC2) + Telegram; - beobachtet im Steward-Modus AUCH MC2 selbst + mc2-gateway - • memory.auto_dedupe_loop — Gedächtnis-Dubletten (HTTP an den Mem0-Sidecar) + beobachtet im Steward-Modus AUCH MC2 selbst + mc2-gateway + BEWUSST NICHT hier: reminders_loop — der teilt sich Datei UND CRUD-Pfade mit dem /api/reminders-Router (Zwei-Schreiber-Risiko auf mc2-reminders.json); er bleibt im Steuerpult. Der Briefkasten-Store gehört weiter EXKLUSIV dem MC2-Prozess — dieser Prozess liefert Meldungen per HTTP ab (announce.py, MC_ANNOUNCE_HTTP). -Die Loop-Schalter (MC_REWARM_ENABLED / MC_SENTRY_ENABLED / MC_MEM_DEDUPE_ENABLED) +Die Loop-Schalter (MC_REWARM_ENABLED / MC_SENTRY_ENABLED) stehen in der MC2-Unit auf 0 und hier auf Default 1 — reiner Konfig-Split, kein Verhaltens-Code im Steuerpult angefasst. Zeilen dort entfernen = Rollback. """ @@ -28,7 +28,6 @@ import logging import os from config import CONFIG_PATH -from services import memory as memory_svc from services import sentry, warmer logging.basicConfig( @@ -66,10 +65,6 @@ async def main() -> None: warmer.INTERVAL, CONFIG_WATCH_S) if sentry.ENABLED: tasks.append(asyncio.create_task(sentry.sentry_loop())) - if memory_svc.AUTO_DEDUPE_ENABLED: - tasks.append(asyncio.create_task(memory_svc.auto_dedupe_loop())) - log.info("Mem0-Auto-Dedupe aktiv (alle %ss, Schwelle %s)", - memory_svc.AUTO_DEDUPE_INTERVAL, memory_svc.AUTO_DEDUPE_THRESHOLD) if not tasks: log.warning("steward: alle Loops per Env deaktiviert — nichts zu tun, Ende.") return