Hausmeister Kumpel-Modus: OpenMemory (CT 122) angebunden
- openmemory_client.py: REST-API Client für search/add/list - tools/openmemory.py: openmemory_add, openmemory_search, Kumpel SYSTEM_PROMPT_EXTRA - llm.py: OpenMemory-Block in Prompt (Text + Bild) - homelab.conf: OPENMEMORY Optionen dokumentiert
This commit is contained in:
parent
fe7e197c35
commit
dbf2497cd6
4 changed files with 207 additions and 2 deletions
|
|
@ -349,8 +349,14 @@ def ask_with_tools(question: str, tool_handlers: dict, session_id: str = None) -
|
|||
except Exception:
|
||||
memory_block = ""
|
||||
|
||||
try:
|
||||
import openmemory_client
|
||||
openmemory_block = openmemory_client.get_openmemory_for_prompt(question, top_k=8)
|
||||
except Exception:
|
||||
openmemory_block = ""
|
||||
|
||||
_extra = tool_loader.get_extra_prompt()
|
||||
_full_prompt = SYSTEM_PROMPT + ("\n\n" + _extra if _extra else "") + memory_block
|
||||
_full_prompt = SYSTEM_PROMPT + ("\n\n" + _extra if _extra else "") + memory_block + (("\n" + openmemory_block) if openmemory_block else "")
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": _full_prompt},
|
||||
|
|
@ -474,6 +480,13 @@ def ask_with_image(image_base64: str, caption: str, tool_handlers: dict, session
|
|||
except Exception:
|
||||
memory_block = ""
|
||||
|
||||
try:
|
||||
import openmemory_client
|
||||
q = caption if caption else "Bild-Analyse"
|
||||
openmemory_block = openmemory_client.get_openmemory_for_prompt(q, top_k=8)
|
||||
except Exception:
|
||||
openmemory_block = ""
|
||||
|
||||
default_prompt = (
|
||||
"Analysiere dieses Bild. "
|
||||
"Wenn es ein Dokument ist (Ticket, Rechnung, Beleg, Buchung): "
|
||||
|
|
@ -489,7 +502,7 @@ def ask_with_image(image_base64: str, caption: str, tool_handlers: dict, session
|
|||
]
|
||||
|
||||
_extra = tool_loader.get_extra_prompt()
|
||||
_full_prompt = SYSTEM_PROMPT + ("\n\n" + _extra if _extra else "") + memory_block
|
||||
_full_prompt = SYSTEM_PROMPT + ("\n\n" + _extra if _extra else "") + memory_block + (("\n" + openmemory_block) if openmemory_block else "")
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": _full_prompt},
|
||||
|
|
|
|||
105
homelab-ai-bot/openmemory_client.py
Normal file
105
homelab-ai-bot/openmemory_client.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
"""OpenMemory REST-Client (CT 122).
|
||||
|
||||
Nutzt die REST-API von OpenMemory (mem0/Qdrant) für langfristige User-Memories.
|
||||
Ergänzt den Memory-Service (CT 117) um semantische/langfristige Erinnerungen.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
|
||||
from core import config
|
||||
|
||||
log = logging.getLogger("openmemory_client")
|
||||
|
||||
_cfg = None
|
||||
_base_url = None
|
||||
_user_id = None
|
||||
|
||||
|
||||
def _ensure_config():
|
||||
global _cfg, _base_url, _user_id
|
||||
if _base_url is not None:
|
||||
return
|
||||
_cfg = config.parse_config()
|
||||
_base_url = (_cfg.raw.get("OPENMEMORY_API_URL") or "http://10.10.10.122:8765").rstrip("/")
|
||||
_user_id = _cfg.raw.get("OPENMEMORY_USER_ID") or "orbitalo"
|
||||
if not _base_url:
|
||||
log.warning("OPENMEMORY_API_URL nicht in homelab.conf")
|
||||
|
||||
|
||||
def search(query: str, limit: int = 10) -> list[dict]:
|
||||
"""Sucht Memories per Text (GET mit search_query). Liefert Liste von {content, id, ...}."""
|
||||
_ensure_config()
|
||||
if not _base_url:
|
||||
return []
|
||||
try:
|
||||
r = requests.get(
|
||||
f"{_base_url}/api/v1/memories/",
|
||||
params={"user_id": _user_id, "search_query": query, "size": limit},
|
||||
timeout=5,
|
||||
)
|
||||
if r.ok:
|
||||
data = r.json()
|
||||
items = data.get("items", [])
|
||||
return [{"content": i.get("content", ""), "id": str(i.get("id", ""))} for i in items]
|
||||
log.debug("OpenMemory search %s: %s", r.status_code, r.text[:150])
|
||||
except Exception as e:
|
||||
log.debug("OpenMemory search: %s", e)
|
||||
return []
|
||||
|
||||
|
||||
def add(text: str, app: str = "hausmeister") -> Optional[dict]:
|
||||
"""Fügt eine neue Memory hinzu. Gibt {id, ...} oder None zurück."""
|
||||
_ensure_config()
|
||||
if not _base_url or not text.strip():
|
||||
return None
|
||||
try:
|
||||
r = requests.post(
|
||||
f"{_base_url}/api/v1/memories/",
|
||||
json={"user_id": _user_id, "text": text.strip(), "metadata": {}, "infer": True, "app": app},
|
||||
timeout=10,
|
||||
)
|
||||
if r.ok:
|
||||
return r.json()
|
||||
log.warning("OpenMemory add %s: %s", r.status_code, r.text[:200])
|
||||
except Exception as e:
|
||||
log.warning("OpenMemory add: %s", e)
|
||||
return None
|
||||
|
||||
|
||||
def list_memories(limit: int = 15) -> list[dict]:
|
||||
"""Listet die neuesten Memories (ohne semantische Suche)."""
|
||||
_ensure_config()
|
||||
if not _base_url:
|
||||
return []
|
||||
try:
|
||||
r = requests.get(
|
||||
f"{_base_url}/api/v1/memories/",
|
||||
params={"user_id": _user_id, "page": 1, "size": limit},
|
||||
timeout=5,
|
||||
)
|
||||
if r.ok:
|
||||
data = r.json()
|
||||
items = data.get("items", [])
|
||||
return [{"content": i.get("content", ""), "id": str(i.get("id", ""))} for i in items]
|
||||
except Exception as e:
|
||||
log.debug("OpenMemory list: %s", e)
|
||||
return []
|
||||
|
||||
|
||||
def get_openmemory_for_prompt(query: str, top_k: int = 8) -> str:
|
||||
"""Holt OpenMemory-Ergebnisse und formatiert sie für den System-Prompt."""
|
||||
items = search(query, limit=top_k)
|
||||
if not items:
|
||||
items = list_memories(limit=5)
|
||||
if not items:
|
||||
return ""
|
||||
lines = ["", "=== OPENMEMORY (langfristige Erinnerungen) ==="]
|
||||
for item in items:
|
||||
c = (item.get("content") or "").strip()
|
||||
if c:
|
||||
lines.append(f"• {c}")
|
||||
lines.append("=== ENDE OPENMEMORY ===")
|
||||
return "\n".join(lines)
|
||||
82
homelab-ai-bot/tools/openmemory.py
Normal file
82
homelab-ai-bot/tools/openmemory.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
"""OpenMemory Tools — Langfristiges Gedächtnis (CT 122).
|
||||
|
||||
Ergänzt tools/memory.py: OpenMemory speichert Erinnerungen/Vorlieben
|
||||
für den Kumpel-Modus (warnen, erinnern, nachfragen).
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from openmemory_client import add as _om_add, search as _om_search
|
||||
|
||||
_log = logging.getLogger("tools.openmemory")
|
||||
|
||||
TOOLS = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "openmemory_add",
|
||||
"description": "Speichert eine langfristige Erinnerung oder Vorliebe in OpenMemory. Nutze dies für: persönliche Fakten, Vorlieben, wichtige Termine, Vereinbarungen die du im Gespräch erfährst. Nicht für technische Homelab-Fakten (dafür memory_suggest).",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {"type": "string", "description": "Der zu speichernde Inhalt"},
|
||||
},
|
||||
"required": ["text"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "openmemory_search",
|
||||
"description": "Sucht in OpenMemory nach Erinnerungen/Vorlieben zu einem Thema. Nutze wenn du gezielt nach etwas suchst.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Suchbegriffe"},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _handle_openmemory_add(text, **kw):
|
||||
if not text or not str(text).strip():
|
||||
return "Kein Text zum Speichern."
|
||||
result = _om_add(str(text).strip())
|
||||
if result:
|
||||
return "In OpenMemory gespeichert."
|
||||
return "OpenMemory nicht erreichbar oder Speichern fehlgeschlagen."
|
||||
|
||||
|
||||
def _handle_openmemory_search(query, **kw):
|
||||
if not query or not str(query).strip():
|
||||
return "Kein Suchbegriff."
|
||||
items = _om_search(str(query).strip(), limit=8)
|
||||
if not items:
|
||||
return f"Keine OpenMemory-Einträge für '{query}' gefunden."
|
||||
lines = [f"OpenMemory Suche '{query}':"]
|
||||
for i in items:
|
||||
c = (i.get("content") or "").strip()
|
||||
if c:
|
||||
lines.append(f" • {c}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def get_handlers(session_id=None):
|
||||
return {
|
||||
"openmemory_add": _handle_openmemory_add,
|
||||
"openmemory_search": _handle_openmemory_search,
|
||||
}
|
||||
|
||||
|
||||
SYSTEM_PROMPT_EXTRA = """
|
||||
KUMPEL-MODUS (kritischer Freund):
|
||||
Du kennst den User aus langfristigen Erinnerungen (OpenMemory) und dem strukturierten Gedächtnis.
|
||||
- ERINNERN: Wenn du weisst dass etwas ansteht (Termin, Plan), erinnere kurz daran wenn es passt.
|
||||
- WARNEN: Bei riskanten Aktionen (löschen, umkonfigurieren) kurz nachfragen oder warnen.
|
||||
- NACHFRAGEN: Bei vagen Aussagen ("vielleicht", "irgendwann") optional nachfragen — aber nicht übertreiben.
|
||||
- Tonalität: Direkt wie ein Kumpel, nicht unterwürfig. Kurz.
|
||||
"""
|
||||
|
|
@ -299,3 +299,8 @@ TUNNEL_601_MU3="rss-manager|:8080|standby"
|
|||
# helmut-pve → pve-he (Ramsin, bei Helmut)
|
||||
# PBS → pbs-mu (PBS Muldenstein)
|
||||
# ============================================================
|
||||
|
||||
# OpenMemory (CT 122) - optional, Default: http://10.10.10.122:8765
|
||||
# OPENMEMORY_API_URL="http://10.10.10.122:8765"
|
||||
# OPENMEMORY_USER_ID="orbitalo"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue