diff --git a/homelab-ai-bot/context.py b/homelab-ai-bot/context.py index 7d7741a3..5361abff 100644 --- a/homelab-ai-bot/context.py +++ b/homelab-ai-bot/context.py @@ -323,7 +323,12 @@ def _tool_session_search(query): return "\n".join(lines) -def get_tool_handlers() -> dict: +def _tool_session_summary(session_id): + import memory_client + return memory_client.get_session_summary(session_id, limit=20) + + +def get_tool_handlers(session_id: str = None) -> dict: """Registry: Tool-Name -> Handler-Funktion. Wird von llm.ask_with_tools() genutzt.""" return { "get_all_containers": lambda: gather_status(), @@ -349,4 +354,5 @@ def get_tool_handlers() -> dict: "memory_read": lambda scope="": _tool_memory_read(scope), "memory_suggest": lambda scope, kind, content: _tool_memory_suggest(scope, kind, content), "session_search": lambda query: _tool_session_search(query), + "session_summary": lambda: _tool_session_summary(session_id) if session_id else "Keine Session aktiv.", } diff --git a/homelab-ai-bot/llm.py b/homelab-ai-bot/llm.py index 70e4e4eb..81b0c317 100644 --- a/homelab-ai-bot/llm.py +++ b/homelab-ai-bot/llm.py @@ -15,16 +15,32 @@ from core import config MODEL = "openai/gpt-4o-mini" MAX_TOOL_ROUNDS = 3 -SYSTEM_PROMPT = """Du bist der Hausmeister-Bot für ein Homelab mit mehreren Proxmox-Servern. -Du antwortest kurz, präzise und auf Deutsch. -Du hast Tools um Live-Daten abzufragen. Nutze sie um Fragen zu beantworten. -Wenn alles in Ordnung ist, sag das kurz. Bei Problemen erkläre was los ist und schlage Lösungen vor. -Nutze Emojis sparsam. Formatiere für Telegram (kein Markdown, nur einfacher Text). +SYSTEM_PROMPT = """Du bist der Hausmeister-Bot fuer ein Homelab. Deutsch, kurz, direkt, operativ. -WICHTIG — Gedaechtnis: -- Wenn der User persoenliche Infos teilt (Reiseplaene, Vorlieben, Gewohnheiten, Korrekturen), nutze SOFORT memory_suggest um es als Kandidat zu speichern. -- Wenn der User fragt was frueher besprochen wurde, nutze session_search. -- Du darfst KEINE Passwoerter, Tokens oder API-Keys in memory_suggest speichern.""" +STIL: +- So wenig Worte wie moeglich, solange nichts Wichtiges fehlt. +- KEINE Abschlussformeln ("Wenn du weitere Informationen benoetigst..."). +- KEINE kuenstlichen Wuensche ("Guten Flug!", "Viel Erfolg!"). +- KEINE Rueckfragen ob der User mehr wissen will. +- Emojis nur wenn sie Information tragen. Telegram-Format (kein Markdown). + +GEDAECHTNIS — memory_suggest: +Du MUSST memory_suggest aufrufen wenn der User etwas sagt das spaeter nuetzlich ist: +- Reiseplaene ("fliege nach...", "bin naechste Woche in...") +- Zeitliche Plaene ("Montag mache ich...", "ab Mai...") +- Neue stabile Fakten ("mein neuer Server...", "IP hat sich geaendert...") +- Projektstatus ("Jarvis ist jetzt aktiv", "Flugscanner laeuft wieder") +- Vorlieben/Korrekturen ("nenn mich...", "ich bevorzuge...") +Nach dem Aufruf sagst du kurz: "Notiert." — kein langes Erklaeren. +NICHT speichern: Passwoerter, Tokens, Smalltalk, Hoeflichkeiten, reine Fragen. + +SESSION-RUECKBLICK: +- "Was haben wir besprochen?" → session_summary aufrufen (liefert alle Themen der aktuellen Session) +- "Erinnerst du dich an X?" mit konkretem Stichwort → session_search +- Antworte mit 2-5 knappen Kernthemen, nicht mit einem einzelnen Fakt. + +TOOLS: +Nutze Tools fuer Live-Daten. Wenn alles OK: kurz sagen. Bei Problemen: erklaeren + Loesung.""" TOOLS = [ { @@ -265,13 +281,13 @@ TOOLS = [ "type": "function", "function": { "name": "memory_suggest", - "description": "Schlage vor, einen neuen Fakt zu merken. Nutze dieses Tool PROAKTIV wenn der User etwas sagt das dauerhaft relevant ist (Vorlieben, Gewohnheiten, Umgebungsfakten). Der Vorschlag wird als Kandidat gespeichert.", + "description": "Speichert einen neuen Fakt als Kandidat. IMMER aufrufen wenn der User Reiseplaene, zeitliche Vorhaben, Projektstatus, Vorlieben oder stabile Fakten mitteilt. Beispiele: 'Ich fliege nach X', 'Ab Mai nutze ich Y', 'Mein neuer Server heisst Z'. NICHT fuer Smalltalk, Fragen oder Passwoerter.", "parameters": { "type": "object", "properties": { - "scope": {"type": "string", "enum": ["user", "environment", "project"], "description": "Kategorie des Fakts"}, - "kind": {"type": "string", "enum": ["fact", "preference", "rule", "note"], "description": "Art des Eintrags"}, - "content": {"type": "string", "description": "Der Fakt der gemerkt werden soll (kurz, praezise)"}, + "scope": {"type": "string", "enum": ["user", "environment", "project"], "description": "user=persoenlich, environment=Infrastruktur, project=Projekt"}, + "kind": {"type": "string", "enum": ["fact", "preference", "rule", "note"], "description": "fact=Tatsache, preference=Vorliebe, note=Notiz"}, + "content": {"type": "string", "description": "Der Fakt (kurz, 3. Person, z.B. 'Fliegt naechste Woche nach Frankfurt')"}, }, "required": ["scope", "kind", "content"], }, @@ -281,16 +297,24 @@ TOOLS = [ "type": "function", "function": { "name": "session_search", - "description": "Durchsucht vergangene Gespraeche nach Stichworten. Nutze dieses Tool wenn der User fragt 'was haben wir besprochen', 'erinnerst du dich', 'letzte Woche' oder aehnlich.", + "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 (Woerter mit Leerzeichen getrennt)"}, + "query": {"type": "string", "description": "Suchbegriffe"}, }, "required": ["query"], }, }, }, + { + "type": "function", + "function": { + "name": "session_summary", + "description": "Zusammenfassung aller Themen der aktuellen Session. Nutze dieses Tool bei Fragen wie 'Was haben wir besprochen?', 'Worüber haben wir geredet?', 'Was war heute Thema?'. Liefert alle Frage-Antwort-Paare kompakt.", + "parameters": {"type": "object", "properties": {}, "required": []}, + }, + }, ] @@ -360,7 +384,7 @@ def ask_with_tools(question: str, tool_handlers: dict, session_id: str = None) - if session_id: try: import memory_client - history = memory_client.get_session_messages(session_id, limit=10) + history = memory_client.get_session_messages(session_id, limit=20) for msg in history: if msg.get("role") in ("user", "assistant") and msg.get("content"): messages.append({"role": msg["role"], "content": msg["content"]}) diff --git a/homelab-ai-bot/memory_client.py b/homelab-ai-bot/memory_client.py index d9316b89..c1caa385 100644 --- a/homelab-ai-bot/memory_client.py +++ b/homelab-ai-bot/memory_client.py @@ -121,3 +121,38 @@ def get_session_messages(session_id: str, limit: int = 10) -> list[dict]: if result and "messages" in result: return result["messages"] return [] + + +def get_session_summary(session_id: str, limit: int = 20) -> str: + """Kompakte Zusammenfassung der aktuellen Session als Themen-Liste.""" + if not session_id: + return "Keine aktive Session." + messages = get_session_messages(session_id, limit=limit) + if not messages: + return "Noch keine Nachrichten in dieser Session." + + exchanges = [] + current_q = None + for msg in messages: + role = msg.get("role", "") + content = (msg.get("content") or "").strip() + if not content: + continue + if role == "user": + current_q = content[:120] + elif role == "assistant" and current_q: + exchanges.append((current_q, content[:120])) + current_q = None + if current_q: + exchanges.append((current_q, None)) + + if not exchanges: + return "Keine Themen in dieser Session." + + lines = [f"Session ({len(exchanges)} Themen):"] + for i, (q, a) in enumerate(exchanges, 1): + line = f"{i}. Frage: {q}" + if a: + line += f"\n Antwort: {a}" + lines.append(line) + return "\n".join(lines) diff --git a/homelab-ai-bot/telegram_bot.py b/homelab-ai-bot/telegram_bot.py index 7387468b..062bfc99 100644 --- a/homelab-ai-bot/telegram_bot.py +++ b/homelab-ai-bot/telegram_bot.py @@ -313,7 +313,7 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("🤔 Denke nach...") try: - handlers = context.get_tool_handlers() + handlers = context.get_tool_handlers(session_id=session_id) answer = llm.ask_with_tools(text, handlers, session_id=session_id) if session_id: memory_client.log_message(session_id, "assistant", answer)