183 lines
7 KiB
Python
183 lines
7 KiB
Python
"""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),
|
|
}
|