"""Memory/RAG Tools — Gedaechtnis, Sessions, Fakten-Speicherung.""" import logging _log = logging.getLogger("tools.memory") VALID_MEMORY_TYPES = {"fact", "preference", "relationship", "plan", "temporary", "uncertain"} VALID_CONFIDENCE = {"high", "medium", "low"} NEEDS_EXPIRY = {"plan", "temporary"} last_suggest_result = {"type": None} _current_source_type = "telegram_text" def set_source_type(st: str): global _current_source_type _current_source_type = st TOOLS = [ { "type": "function", "function": { "name": "memory_read", "description": "Liest gespeicherte Fakten/Erinnerungen. Optional nach Scope filtern (z.B. 'user', 'system').", "parameters": { "type": "object", "properties": { "scope": {"type": "string", "description": "Filter nach Scope (optional)", "default": ""} }, "required": [], }, }, }, { "type": "function", "function": { "name": "memory_suggest", "description": "Speichert einen neuen Fakt/Erinnerung. Duplikate werden automatisch erkannt, Widersprueche aufgeloest.", "parameters": { "type": "object", "properties": { "scope": {"type": "string", "description": "Bereich: 'user', 'homelab', 'project'"}, "kind": {"type": "string", "description": "Unterkategorie: 'reise', 'server', 'kontakt', 'todo', 'allgemein'"}, "content": {"type": "string", "description": "Der zu speichernde Fakt"}, "memory_type": {"type": "string", "enum": ["fact", "preference", "relationship", "plan", "temporary", "uncertain"], "description": "fact=stabiler Fakt, preference=Vorliebe, relationship=Beziehung/Rolle, plan=Vorhaben mit Zeitbezug, temporary=kurzfristiger Zustand, uncertain=vage Aussage"}, "confidence": {"type": "string", "enum": ["high", "medium", "low"], "description": "high=klare Aussage, medium=wahrscheinlich, low=vage"}, "expires_at": {"type": "string", "description": "Bei plan/temporary: Zeitangabe wann es ablaeuft (z.B. 'naechste Woche', 'morgen', '22.03.2026'). Leer bei dauerhaften Fakten."}, }, "required": ["scope", "kind", "content", "memory_type", "confidence"], }, }, }, { "type": "function", "function": { "name": "session_search", "description": "Volltextsuche in vergangenen Sessions nach konkreten Stichworten. Fuer gezielte Suche wie 'Was habe ich ueber Backup gesagt?' oder 'Wann war das mit Seafile?'.", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "Suchbegriffe"}, }, "required": ["query"], }, }, }, { "type": "function", "function": { "name": "session_summary", "description": "Zusammenfassung der aktuellen Session. Ohne topic = alle Themen. Mit topic = nur thematisch passende Punkte.", "parameters": { "type": "object", "properties": { "topic": {"type": "string", "description": "Themenbegriff zum Filtern (optional)"}, }, "required": [], }, }, }, ] def _handle_memory_read(scope="", **kw): import memory_client items = memory_client.get_active_memory() if scope: items = [i for i in items if i.get("scope") == scope] if not items: return "Keine Memory-Eintraege gefunden." lines = [] for i in items: lines.append(f"[{i['scope']}/{i['kind']}] {i['content']}") return "\n".join(lines) def _handle_memory_suggest(scope, kind, content, memory_type="fact", confidence="high", expires_at=None, **kw): import memory_client from datetime import datetime global last_suggest_result if memory_type not in VALID_MEMORY_TYPES: memory_type = "fact" if confidence not in VALID_CONFIDENCE: confidence = "high" _log.info("memory_suggest: type=%s conf=%s src=%s content=%s", memory_type, confidence, _current_source_type, content[:80]) exp_epoch = None if memory_type in NEEDS_EXPIRY: if expires_at: exp_epoch = memory_client.parse_expires_from_text(expires_at) if not exp_epoch: exp_epoch = memory_client.parse_expires_from_text(content) if not exp_epoch: exp_epoch = memory_client.default_expires() data = { "scope": scope, "kind": kind, "content": content, "source": "bot-suggest", "status": "active", "confidence": confidence, "memory_type": memory_type, "source_type": _current_source_type, } if exp_epoch: data["expires_at"] = exp_epoch result = memory_client._post("/memory", data) if result and result.get("duplicate"): ex_type = result.get("existing_memory_type", "") ex_exp = result.get("existing_expires_at") last_suggest_result = {"type": "duplicate"} if ex_type in NEEDS_EXPIRY and ex_exp: return f"Weiss ich schon (bis {datetime.fromtimestamp(ex_exp).strftime('%d.%m.%Y')})." return "Weiss ich schon." if result and result.get("ok"): sup = result.get("superseded_id") last_suggest_result = {"type": "saved", "item_id": result.get("id"), "superseded": sup} msg = f"Gemerkt ({memory_type}, {confidence})." if sup: msg += f" Alten Eintrag #{sup} ersetzt." _log.info("Superseded: #%s -> #%s", sup, result.get("id")) _log.info("Gespeichert: ID=%s type=%s conf=%s", result.get("id"), memory_type, confidence) return msg return "Konnte nicht speichern." def _handle_session_search(query, **kw): import memory_client result = memory_client._get("/sessions/search", {"q": query, "limit": 20}) if not result or not result.get("results"): return f"Keine Ergebnisse fuer '{query}'." lines = [f"Suche '{query}': {result['count']} Treffer"] for r in result["results"][:10]: role = r.get("role", "?") content = (r.get("content") or "")[:150] lines.append(f" [{role}] {content}") return "\n".join(lines) def _handle_session_summary(session_id, topic=None, **kw): import memory_client if not session_id: return "Keine Session aktiv." return memory_client.get_session_summary(session_id, limit=20, topic=topic or None) def get_handlers(session_id=None): """Factory — session_id wird fuer session_summary gebraucht.""" return { "memory_read": _handle_memory_read, "memory_suggest": _handle_memory_suggest, "session_search": _handle_session_search, "session_summary": lambda topic="", **kw: _handle_session_summary(session_id, topic=topic), }