"""Internal context for later Gateway use. Never sends originals to a provider. Selection goes through retrieval specs. Recency/SQL limits live in retrieval.py. """ from __future__ import annotations import re from data_layer import read from dialogue_store import get_conversation from retrieval import retrieve from journal_body import plain_text PURPOSES = {"dialogue_turn", "journal_generate"} CLOSURE = re.compile( r"\bauf den heimweg\b|" r"\bheimweg\b|" r"\bauf den rückweg\b|" r"\bdamit (?:war|ist) der tag\b|" r"\bdas war (?:der|so ein|unser) tag\b|" r"\bende des tages\b|" r"\bgenug für heute\b|" r"\bso endete\b|" r"müde.{0,80}(?:heimweg|rückweg)|" r"(?:eindr[uü]cke|erschöpft).{0,80}(?:heimweg|rückweg)|" r"machten uns.{0,40}auf den (?:heimweg|rückweg)|" r"\bbald ins bett\b|" r"\blegten uns.{0,40}ins bett\b|" r"\bgleich ins bett\b", re.I, ) def is_closing_turn(text: str) -> bool: return bool(CLOSURE.search((text or "").strip())) def infer_register(user_bodies: list[str]) -> str: last = (user_bodies[-1] if user_bodies else "").strip() if not last: return "Noch kein Erzählstand. Warte auf den ersten Satz, setze keine Szene." if is_closing_turn(last): return ( "Die Szene oder der Abschnitt ist zu Ende. Halte den Schluss. " "Keine Frage, keinen Bogen, keine neue Station." ) words = last.split() emotion = re.search(r"aufgeregt|traurig|berührt|ängst|wütend|freude|ruhig|unsicher", last, re.I) plan = re.search(r"geplant|wollen|werde|sollten|vorhaben", last, re.I) dense = len(words) >= 40 or last.count(".") + last.count("!") >= 3 if dense: return ( "[[SELF]] erzählt gerade ausführlich. Am letzten erzählten Punkt bleiben. " "Nicht nacherzählen. Nacherzählen nur bei Widerspruch oder Logikbruch. " "Keine erfundene Empfindung, keine erfundene nächste Szene." ) if emotion: return ( "[[SELF]] hat Erleben geöffnet. Bei Bedeutung oder Wirkung bleiben, " "die schon benannte Empfindung nicht nachplappern." ) if plan: return "Ein Vorhaben ist genannt. Als Plan behandeln, nicht als vollzogen." return "Kurz und natürlich antworten, am offenen Punkt, wie jemand der zugehört hat." def build_internal_context( profile_id: str, conversation_id: str | None = None, space_id: str | None = None, journal_day_id: str | None = None, purpose: str = "dialogue_turn", include_existing: bool = False, conversation_ids: list[str] | None = None, existing_text: str = "", ) -> dict: purpose = purpose if purpose in PURPOSES else "dialogue_turn" if conversation_id and not space_id: conv = get_conversation(profile_id, conversation_id) space_id = conv.get("space_id") journal_day_id = journal_day_id or conv.get("journal_day_id") items: list[dict] = [] space_title = "" if space_id: from dialogue_store import get_space try: space_title = get_space(profile_id, space_id).get("title") or "" except Exception: space_title = "" items.append({"type": "space", "space_id": space_id, "title": space_title}) selected_ids = list(conversation_ids or []) if conversation_id and conversation_id not in selected_ids and purpose == "dialogue_turn": selected_ids.append(conversation_id) day_messages = [] if journal_day_id: day_messages = retrieve( profile_id, { "kind": "day_messages", "journal_day_id": journal_day_id, "conversation_ids": selected_ids or None, }, ) items.append({"type": "day_messages", "messages": day_messages}) elif conversation_id: day_messages = read("conversation_messages", profile_id=profile_id, context={"conversation_id": conversation_id}) items.append({"type": "day_messages", "messages": day_messages}) if purpose == "dialogue_turn" and space_id: prior = retrieve( profile_id, { "kind": "space_entries", "space_id": space_id, "exclude_day_id": journal_day_id, }, ) items.append( { "type": "prior_entries", "entries": [ { "entry_id": item["entry_id"], "calendar_date": item.get("calendar_date"), "excerpt": item.get("excerpt") or plain_text(item.get("body") or ""), } for item in prior ], } ) recent_sources = retrieve( profile_id, { "kind": "space_recent_sources", "space_id": space_id, "exclude_conversation_id": conversation_id, "exclude_journal_day_id": journal_day_id, }, ) items.append({"type": "space_recent_sources", "sources": recent_sources}) if purpose == "journal_generate": from writing_profile_store import compile_task_brief brief = compile_task_brief(profile_id, "journal_generate") items.append({"type": "writing_profile", "compiled_brief": brief}) if include_existing and existing_text: items.append({"type": "existing_text", "body": plain_text(existing_text)}) elif purpose == "dialogue_turn": items.append({"type": "interaction_hint", "text": ""}) return { "profile_id": profile_id, "conversation_id": conversation_id, "space_id": space_id, "space_title": space_title, "journal_day_id": journal_day_id, "purpose": purpose, "items": items, "messages": day_messages, "egress": False, "note": "Volltext bleibt lokal. Minimierung und Egress nur über das Privacy Gateway.", } def assemble_text(context: dict) -> dict[str, str]: dialogue_parts: list[str] = [] prior_parts: list[str] = [] source_parts: list[str] = [] writing_profile = "" interaction_hint = "" existing_text = "" user_bodies: list[str] = [] purpose = context.get("purpose") or "" for item in context.get("items") or []: kind = item.get("type") if kind == "day_messages": for message in item.get("messages") or []: role = message.get("role") or "user" body = message.get("body") or "" dialogue_parts.append(f"{role}: {body}") if role == "user": user_bodies.append(body) elif kind == "prior_entries": for entry in item.get("entries") or []: date = entry.get("calendar_date") or "" excerpt = entry.get("excerpt") or "" if excerpt: prior_parts.append(f"{date}: {excerpt}") elif kind == "space_recent_sources": for source in item.get("sources") or []: excerpt = source.get("excerpt") or "" if excerpt: source_parts.append(excerpt) elif kind == "writing_profile": writing_profile = item.get("compiled_brief") or "" elif kind == "interaction_hint": interaction_hint = item.get("text") or "" elif kind == "existing_text": existing_text = item.get("body") or "" if purpose == "dialogue_turn": from conversation_signals import infer_signals from interaction_profile_store import assemble_interaction_hint interaction_hint = assemble_interaction_hint( context.get("profile_id"), infer_signals(user_bodies), ) if prior_parts: dialogue_parts.append("Frühere Einträge im Space (nur Hinweise, keine Gewissheit):") dialogue_parts.extend(prior_parts) if source_parts and purpose == "dialogue_turn": dialogue_parts.append("Frühere Originalgespräche im Space (Ausschnitt, keine Gewissheit):") dialogue_parts.extend(source_parts) return { "dialogue_context": "\n".join(dialogue_parts).strip(), "writing_profile": writing_profile, "interaction_hint": interaction_hint, "existing_text": existing_text, "space_title": context.get("space_title") or "", "register_hint": infer_register(user_bodies), }