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:
|
except Exception:
|
||||||
memory_block = ""
|
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()
|
_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 = [
|
messages = [
|
||||||
{"role": "system", "content": _full_prompt},
|
{"role": "system", "content": _full_prompt},
|
||||||
|
|
@ -474,6 +480,13 @@ def ask_with_image(image_base64: str, caption: str, tool_handlers: dict, session
|
||||||
except Exception:
|
except Exception:
|
||||||
memory_block = ""
|
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 = (
|
default_prompt = (
|
||||||
"Analysiere dieses Bild. "
|
"Analysiere dieses Bild. "
|
||||||
"Wenn es ein Dokument ist (Ticket, Rechnung, Beleg, Buchung): "
|
"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()
|
_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 = [
|
messages = [
|
||||||
{"role": "system", "content": _full_prompt},
|
{"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)
|
# helmut-pve → pve-he (Ramsin, bei Helmut)
|
||||||
# PBS → pbs-mu (PBS Muldenstein)
|
# 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