Memory-Service: Client + Session-Logging + Memory-Injection fuer Hausmeister-Bot
This commit is contained in:
parent
8982100025
commit
f253b5d410
4 changed files with 132 additions and 1 deletions
|
|
@ -296,8 +296,15 @@ def ask_with_tools(question: str, tool_handlers: dict) -> str:
|
||||||
if not api_key:
|
if not api_key:
|
||||||
return "OpenRouter API Key fehlt in homelab.conf"
|
return "OpenRouter API Key fehlt in homelab.conf"
|
||||||
|
|
||||||
|
try:
|
||||||
|
import memory_client
|
||||||
|
memory_items = memory_client.get_active_memory()
|
||||||
|
memory_block = memory_client.format_memory_for_prompt(memory_items)
|
||||||
|
except Exception:
|
||||||
|
memory_block = ""
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{"role": "system", "content": SYSTEM_PROMPT},
|
{"role": "system", "content": SYSTEM_PROMPT + memory_block},
|
||||||
{"role": "user", "content": question},
|
{"role": "user", "content": question},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
113
homelab-ai-bot/memory_client.py
Normal file
113
homelab-ai-bot/memory_client.py
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
"""Client fuer den Memory-Service (CT 117).
|
||||||
|
|
||||||
|
Stellt Session-Management und Memory-Zugriff bereit.
|
||||||
|
Kein Import von Bot- oder LLM-Logik — reiner HTTP-Client.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from core import config
|
||||||
|
|
||||||
|
log = logging.getLogger("memory_client")
|
||||||
|
|
||||||
|
_cfg = None
|
||||||
|
_base_url = None
|
||||||
|
_token = None
|
||||||
|
|
||||||
|
SESSION_TIMEOUT = 1800 # 30 Minuten Inaktivitaet = neue Session
|
||||||
|
_active_sessions: dict[str, dict] = {} # channel_key -> {id, last_activity}
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_config():
|
||||||
|
global _cfg, _base_url, _token
|
||||||
|
if _base_url:
|
||||||
|
return
|
||||||
|
_cfg = config.parse_config()
|
||||||
|
_base_url = _cfg.raw.get("MEMORY_API_URL", "").rstrip("/")
|
||||||
|
_token = _cfg.raw.get("MEMORY_API_TOKEN", "")
|
||||||
|
if not _base_url or not _token:
|
||||||
|
log.warning("MEMORY_API_URL oder MEMORY_API_TOKEN nicht in homelab.conf")
|
||||||
|
|
||||||
|
|
||||||
|
def _headers():
|
||||||
|
return {"Authorization": f"Bearer {_token}", "Content-Type": "application/json"}
|
||||||
|
|
||||||
|
|
||||||
|
def _post(path: str, data: dict) -> Optional[dict]:
|
||||||
|
_ensure_config()
|
||||||
|
if not _base_url:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
r = requests.post(f"{_base_url}{path}", json=data, headers=_headers(), timeout=5)
|
||||||
|
if r.ok:
|
||||||
|
return r.json()
|
||||||
|
log.warning("Memory API %s: %s %s", path, r.status_code, r.text[:200])
|
||||||
|
except Exception as e:
|
||||||
|
log.warning("Memory API %s: %s", path, e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _get(path: str, params: dict = None) -> Optional[dict]:
|
||||||
|
_ensure_config()
|
||||||
|
if not _base_url:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
r = requests.get(f"{_base_url}{path}", params=params, headers=_headers(), timeout=5)
|
||||||
|
if r.ok:
|
||||||
|
return r.json()
|
||||||
|
log.warning("Memory API %s: %s %s", path, r.status_code, r.text[:200])
|
||||||
|
except Exception as e:
|
||||||
|
log.warning("Memory API %s: %s", path, e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_or_create_session(channel_key: str, source: str = "telegram") -> Optional[str]:
|
||||||
|
"""Gibt eine aktive Session-ID zurueck oder erstellt eine neue."""
|
||||||
|
now = time.time()
|
||||||
|
cached = _active_sessions.get(channel_key)
|
||||||
|
if cached and (now - cached["last_activity"]) < SESSION_TIMEOUT:
|
||||||
|
cached["last_activity"] = now
|
||||||
|
return cached["id"]
|
||||||
|
|
||||||
|
result = _post("/sessions", {"source": source, "channel_key": channel_key})
|
||||||
|
if result and "id" in result:
|
||||||
|
_active_sessions[channel_key] = {"id": result["id"], "last_activity": now}
|
||||||
|
return result["id"]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def log_message(session_id: str, role: str, content: str, source: str = None, meta: str = None):
|
||||||
|
"""Speichert eine Nachricht in der Session."""
|
||||||
|
if not session_id or not content:
|
||||||
|
return
|
||||||
|
data = {"role": role, "content": content}
|
||||||
|
if source:
|
||||||
|
data["source"] = source
|
||||||
|
if meta:
|
||||||
|
data["meta_json"] = meta
|
||||||
|
_post(f"/sessions/{session_id}/messages", data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_active_memory() -> list[dict]:
|
||||||
|
"""Holt alle aktiven Memory-Items fuer den System-Prompt."""
|
||||||
|
result = _get("/memory", {"status": "active", "limit": 100})
|
||||||
|
if result and "items" in result:
|
||||||
|
return result["items"]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def format_memory_for_prompt(items: list[dict]) -> str:
|
||||||
|
"""Formatiert Memory-Items als Text-Block fuer den System-Prompt."""
|
||||||
|
if not items:
|
||||||
|
return ""
|
||||||
|
lines = ["", "=== GEDAECHTNIS (persistente Fakten) ==="]
|
||||||
|
for item in items:
|
||||||
|
prefix = f"[{item['scope']}/{item['kind']}]"
|
||||||
|
lines.append(f"{prefix} {item['content']}")
|
||||||
|
lines.append("=== ENDE GEDAECHTNIS ===")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
@ -77,6 +77,7 @@ BUTTON_MAP = {
|
||||||
import context
|
import context
|
||||||
import requests as _req
|
import requests as _req
|
||||||
import llm
|
import llm
|
||||||
|
import memory_client
|
||||||
import monitor
|
import monitor
|
||||||
from core import config
|
from core import config
|
||||||
|
|
||||||
|
|
@ -305,10 +306,17 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||||
elif cmd == "silence":
|
elif cmd == "silence":
|
||||||
return await cmd_silence(update, ctx)
|
return await cmd_silence(update, ctx)
|
||||||
|
|
||||||
|
channel_key = str(update.effective_chat.id)
|
||||||
|
session_id = memory_client.get_or_create_session(channel_key, source="telegram")
|
||||||
|
if session_id:
|
||||||
|
memory_client.log_message(session_id, "user", text)
|
||||||
|
|
||||||
await update.message.reply_text("🤔 Denke nach...")
|
await update.message.reply_text("🤔 Denke nach...")
|
||||||
try:
|
try:
|
||||||
handlers = context.get_tool_handlers()
|
handlers = context.get_tool_handlers()
|
||||||
answer = llm.ask_with_tools(text, handlers)
|
answer = llm.ask_with_tools(text, handlers)
|
||||||
|
if session_id:
|
||||||
|
memory_client.log_message(session_id, "assistant", answer)
|
||||||
await update.message.reply_text(answer[:4000], reply_markup=KEYBOARD)
|
await update.message.reply_text(answer[:4000], reply_markup=KEYBOARD)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception("Fehler bei Freitext")
|
log.exception("Fehler bei Freitext")
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ CT_112_HZ="fuenfvoracht|100.73.171.62|FuenfVorAcht Telegram Bot"
|
||||||
CT_113_HZ="redax-wp|100.69.243.16|Redakteur WordPress KI-Autor + DeutschlandBlog"
|
CT_113_HZ="redax-wp|100.69.243.16|Redakteur WordPress KI-Autor + DeutschlandBlog"
|
||||||
CT_115_HZ="flugscanner-hub|100.92.161.97|Flugpreisscanner Hub + Scheduler"
|
CT_115_HZ="flugscanner-hub|100.92.161.97|Flugpreisscanner Hub + Scheduler"
|
||||||
CT_116_HZ="homelab-ai-bot|100.123.47.7|Hausmeister Telegram Bot"
|
CT_116_HZ="homelab-ai-bot|100.123.47.7|Hausmeister Telegram Bot"
|
||||||
|
CT_117_HZ="memory-service|100.121.192.94|Memory Service API (FastAPI + SQLite)"
|
||||||
CT_144_HZ="muldenstein-backup|—|Backup-Archiv (Read-Only)"
|
CT_144_HZ="muldenstein-backup|—|Backup-Archiv (Read-Only)"
|
||||||
CT_999_HZ="cluster-docu|100.79.8.49|Dokumentation"
|
CT_999_HZ="cluster-docu|100.79.8.49|Dokumentation"
|
||||||
|
|
||||||
|
|
@ -175,6 +176,8 @@ FORGEJO_TOKEN="b874766bdf357bd4c32fa4369d0c588fc6193336"
|
||||||
FORGEJO_SYNC_TOKEN="5402da0447b0eb6aede721a8748a08974ddc5c42"
|
FORGEJO_SYNC_TOKEN="5402da0447b0eb6aede721a8748a08974ddc5c42"
|
||||||
GITHUB_PAT="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"
|
GITHUB_PAT="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"
|
||||||
OPENROUTER_KEY="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"
|
OPENROUTER_KEY="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"
|
||||||
|
MEMORY_API_TOKEN="Ai8eeQibV6Z1RWc7oNPim4PXB4vILU1nRW2-XgRcX2M"
|
||||||
|
MEMORY_API_URL="http://100.121.192.94:8400"
|
||||||
|
|
||||||
# --- HOMELAB MCP-SERVER (auf pve-hetzner Host) ---
|
# --- HOMELAB MCP-SERVER (auf pve-hetzner Host) ---
|
||||||
MCP_PATH="/root/homelab-mcp"
|
MCP_PATH="/root/homelab-mcp"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue