Feat: Agent-Memory auf Mem0 (auto-lernend, semantisch) via Sidecar
Paket A des Plans. Ersetzt die flache SQLite-Fakten-DB durch Mem0 (LLM-Auto-
Extraktion + Vektor/Chroma-Suche). Architektur erzwungen durch Python-Split:
MC2-Backend laeuft auf 3.14 (kann mem0 nicht importieren), mem0+chromadb nur
auf 3.12 (~/.mem0/venv) -> Mem0-Sidecar (FastAPI, localhost:8765), MC2 spricht
ihn per HTTP. /api/memory-Form bleibt unveraendert (UI + MCP kompatibel).
- mem0_service/: Sidecar (app.py), Migration (migrate.py), deps.
- Embeddings: neue llama-swap embed-Rolle (Qwen3-Embedding-0.6B, 1024 Dim,
pooling last) ueber /v1/embeddings.
- LLM-Extraktion: lokales fast-Hirn; NoThinkLLM schaltet Qwen3-Thinking ab
(sonst bricht json_object-Extraktion ab), custom_instructions halten Deutsch.
- backend/services/memory.py: duenner HTTP-Client auf den Sidecar (semantische
Suche mit score, verbatim add, learn()). Router: /api/memory/learn.
- mcp/mcp_memory.py: neues learn-Tool (Auto-Lernen aus Gespraechs-Turns),
search jetzt semantisch.
- Frontend: Relevanz-Score + Auto/Manuell-Herkunft im Gedaechtnis-Tab.
- deploy/: mem0-service.service + deploy.sh (uv-Install, Migration, Restart).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+22
-3
@@ -68,12 +68,16 @@ def get_memories(category: str = "") -> str:
|
||||
|
||||
@mcp.tool()
|
||||
def search_memories(q: str) -> str:
|
||||
"""Sucht per Stichwort in den Fakten. Nutze dies VOR add_memory (Dubletten-Check)
|
||||
oder wenn du eine konkrete frühere Entscheidung suchst."""
|
||||
"""Sucht SEMANTISCH (nach Bedeutung, nicht nur Stichwort) in den Fakten. Nutze dies
|
||||
VOR add_memory (Dubletten-Check) oder wenn du eine konkrete frühere Entscheidung suchst.
|
||||
Treffer sind nach Relevanz sortiert."""
|
||||
items = _get("/api/memory", q=q)
|
||||
if not items:
|
||||
return f"Keine Treffer für '{q}'."
|
||||
return "\n".join(f"[{m['category']}] {m['content']} (ID: {m['id'][:8]})" for m in items)
|
||||
def _line(m):
|
||||
sc = f" {m['score']:.2f}" if isinstance(m.get("score"), (int, float)) else ""
|
||||
return f"[{m['category']}{sc}] {m['content']} (ID: {m['id'][:8]})"
|
||||
return "\n".join(_line(m) for m in items)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@@ -104,5 +108,20 @@ def delete_memory(memory_id: str) -> str:
|
||||
return f"Eintrag {memory_id[:8]} gelöscht."
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def learn(text: str) -> str:
|
||||
"""Lässt das Gedächtnis AUTOMATISCH aus einem Gesprächsausschnitt lernen: reiche
|
||||
rohe Nutzer-Aussagen/Turns durch — das Gedächtnis EXTRAHIERT die dauerhaften Fakten
|
||||
selbst und mischt sie ein (ohne Dubletten). Nutze dies, wenn der Nutzer beiläufig etwas
|
||||
Dauerhaftes über sich/seine Vorlieben/das Projekt erwähnt hat — du musst nicht selbst
|
||||
den Fakt formulieren. Für einen bereits fertig formulierten Einzel-Fakt → add_memory."""
|
||||
res = _post("/api/memory/learn", {"text": text})
|
||||
items = res.get("results", []) if isinstance(res, dict) else []
|
||||
learned = [r for r in items if r.get("event") in (None, "ADD", "UPDATE")]
|
||||
if not learned:
|
||||
return "Nichts Neues gelernt (keine dauerhaften Fakten erkannt)."
|
||||
return "Gelernt:\n" + "\n".join(f"· {r.get('content')}" for r in learned)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
|
||||
Reference in New Issue
Block a user