Feat: Fundierte 4-Typen-Memory-Taxonomie + LLM-Auto-Einordnung
Saubere Neuordnung der Gedaechtnis-Kategorien an der etablierten Memory-Taxonomie (semantisch/prozedural/episodisch), bewusst knapp (Best Practice: 3-5, klar beschrieben): identity (Identitaet & Vorlieben) · knowledge (Wissen & Fakten) · rules (Regeln & Konventionen) · events (Ereignisse & Entscheidungen) Loest die alten gemischten 5 (user/instruction/stable/versioned/ephemeral) ab. Auto-Einordnung: OSS-mem0 kann nicht nativ kategorisieren (Cloud-Feature) -> nach der Fakt-Extraktion ordnet dasselbe (Thinking-freie) Hirn jeden neuen Fakt per JSON-Call genau einer Kategorie zu (classify_facts im Sidecar /learn). Behebt den "alles ist stable"- Bug. Manuelle Eintraege: Kategorie weiter waehlbar (Default knowledge). Umgesetzt in mem0_service, backend (services/routers), mcp_memory (Tool-Docs) und Frontend (MemoryView + GraphView: Labels/Farben/Filter). Kein Migrationsbedarf (leerer Start). Live verifiziert: gemischter Absatz -> identity/rules/knowledge/events korrekt zugeordnet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+64
-8
@@ -45,9 +45,19 @@ API_KEY = os.environ.get("MEM0_API_KEY", "sk-local") # llama.cpp ignoriert den
|
||||
LLM_MAX_TOKENS = int(os.environ.get("MEM0_LLM_MAX_TOKENS", "2048"))
|
||||
SEARCH_TOP_K = int(os.environ.get("MEM0_SEARCH_TOP_K", "50"))
|
||||
|
||||
# 5 Kategorien wie im alten System (UI-Kompatibilität). Mem0 selbst kennt keine
|
||||
# Kategorien — wir führen sie als Metadaten mit.
|
||||
CATEGORIES = ("user", "instruction", "stable", "versioned", "ephemeral")
|
||||
# Kategorien = fundierte Memory-Taxonomie (semantisch/prozedural/episodisch), bewusst knapp (4).
|
||||
# Mem0-OSS kann nicht nativ klassifizieren (Cloud-Feature) → wir lassen das Hirn beim Lernen
|
||||
# einordnen (classify_facts). Die Beschreibungen steuern die Treffsicherheit der Auto-Zuordnung.
|
||||
CATEGORIES = ("identity", "knowledge", "rules", "events")
|
||||
DEFAULT_CATEGORY = "knowledge"
|
||||
CATEGORY_DESCRIPTIONS = {
|
||||
"identity": "Identität & Vorlieben des Nutzers: wer er ist (Name, Rolle), wie er angesprochen werden "
|
||||
"will, persönliche Vorlieben und seine Arbeitsweise.",
|
||||
"knowledge": "Wissen & Fakten: stabile Fakten über Stack, Projekte, Tools, Infrastruktur und Umgebung.",
|
||||
"rules": "Regeln & Konventionen: verbindliche Anweisungen und Workflows — wie etwas gemacht werden "
|
||||
"soll, Do's und Don'ts.",
|
||||
"events": "Ereignisse & Entscheidungen: was passiert ist, getroffene Entscheidungen, zeitgebundene Vorgänge.",
|
||||
}
|
||||
|
||||
|
||||
class NoThinkLLM(OpenAILLM):
|
||||
@@ -140,7 +150,7 @@ def _to_item(r: dict, score=None) -> dict:
|
||||
|
||||
class MemIn(BaseModel):
|
||||
content: str
|
||||
category: str = "stable"
|
||||
category: str = DEFAULT_CATEGORY
|
||||
source: str = "manual"
|
||||
|
||||
|
||||
@@ -153,7 +163,8 @@ class LearnIn(BaseModel):
|
||||
text: str | None = None
|
||||
messages: list[dict] | None = None
|
||||
source: str = "auto"
|
||||
category: str = "stable"
|
||||
category: str = DEFAULT_CATEGORY
|
||||
classify: bool = True # Hirn ordnet die gelernten Fakten automatisch ein
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
@@ -277,10 +288,38 @@ def graph(min_score: float = 0.45, top_k: int = 3) -> dict:
|
||||
return {"nodes": nodes, "edges": edges}
|
||||
|
||||
|
||||
def classify_facts(facts: list[tuple[str, str]]) -> dict:
|
||||
"""Ordnet jeden Fakt per LLM GENAU einer Kategorie zu → {id: category}.
|
||||
Nutzt dasselbe (Thinking-freie) Hirn wie die Extraktion. Fehler = leeres Mapping (Fallback)."""
|
||||
if not facts:
|
||||
return {}
|
||||
import json as _json
|
||||
import re as _re
|
||||
cat_lines = "\n".join(f"- {k}: {v}" for k, v in CATEGORY_DESCRIPTIONS.items())
|
||||
system = (
|
||||
"Du bist ein präziser Klassifikator für ein Langzeitgedächtnis. Ordne jeden Fakt GENAU EINER "
|
||||
"Kategorie zu. Verfügbare Kategorien (nur diese Slugs verwenden):\n" + cat_lines +
|
||||
"\nAntworte NUR als JSON-Objekt der Form {\"<id>\": \"<slug>\", …}."
|
||||
)
|
||||
body = "\n".join(f"{fid}: {txt}" for fid, txt in facts)
|
||||
try:
|
||||
resp = mem().llm.generate_response(
|
||||
messages=[{"role": "system", "content": system},
|
||||
{"role": "user", "content": "Fakten:\n" + body}],
|
||||
response_format={"type": "json_object"},
|
||||
)
|
||||
m = _re.search(r"\{.*\}", resp or "", _re.S)
|
||||
data = _json.loads(m.group(0)) if m else {}
|
||||
return {str(k): v for k, v in data.items() if v in CATEGORIES}
|
||||
except Exception:
|
||||
log.exception("Auto-Klassifikation fehlgeschlagen")
|
||||
return {}
|
||||
|
||||
|
||||
@app.post("/learn")
|
||||
def learn(body: LearnIn) -> dict:
|
||||
"""Auto-Lernen: Gesprächs-Turns/Text durchreichen → Mem0 EXTRAHIERT Fakten selbst
|
||||
(infer=True) und entscheidet ADD/UPDATE/NONE gegen das bestehende Gedächtnis."""
|
||||
(infer=True). Anschließend ordnet das Hirn jeden neuen Fakt automatisch einer Kategorie zu."""
|
||||
msgs = body.messages
|
||||
if not msgs and body.text:
|
||||
msgs = [{"role": "user", "content": body.text}]
|
||||
@@ -290,9 +329,26 @@ def learn(body: LearnIn) -> dict:
|
||||
msgs, user_id=USER_ID, infer=True,
|
||||
metadata={"category": body.category, "source": body.source},
|
||||
)
|
||||
results = res.get("results", [])
|
||||
|
||||
cats: dict = {}
|
||||
if body.classify:
|
||||
to_classify = [(r["id"], r.get("memory", "")) for r in results
|
||||
if r.get("id") and r.get("memory") and r.get("event") in ("ADD", "UPDATE")]
|
||||
cats = classify_facts(to_classify)
|
||||
for r in results:
|
||||
cat = cats.get(r.get("id"))
|
||||
if cat:
|
||||
try:
|
||||
mem().update(r["id"], data=r.get("memory", ""),
|
||||
metadata={"category": cat, "source": body.source})
|
||||
except Exception:
|
||||
log.debug("Kategorie-Update fehlgeschlagen für %s", r.get("id"))
|
||||
|
||||
return {"results": [
|
||||
{"id": r.get("id"), "content": r.get("memory"), "event": r.get("event")}
|
||||
for r in res.get("results", [])
|
||||
{"id": r.get("id"), "content": r.get("memory"), "event": r.get("event"),
|
||||
"category": cats.get(r.get("id"), body.category)}
|
||||
for r in results
|
||||
]}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user