282 lines
11 KiB
Python
282 lines
11 KiB
Python
"""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", "journal_reconstruct"}
|
|
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 = "",
|
|
reconstruction: str = "",
|
|
day_spec: dict | None = None,
|
|
writing_profile: str | None = None,
|
|
style_examples: str = "",
|
|
transformation_instructions: str = "",
|
|
detail_instructions: str = "",
|
|
voice_instructions: str = "",
|
|
narrative_instructions: 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 and purpose != "journal_generate":
|
|
spec = {
|
|
"kind": "day_messages",
|
|
"journal_day_id": journal_day_id,
|
|
"conversation_ids": selected_ids or None,
|
|
}
|
|
if day_spec:
|
|
spec.update({key: value for key, value in day_spec.items() if key != "kind"})
|
|
day_messages = retrieve(profile_id, spec)
|
|
items.append({"type": "day_messages", "messages": day_messages})
|
|
elif conversation_id and purpose == "dialogue_turn":
|
|
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":
|
|
if writing_profile is None:
|
|
from writing_profile_store import compile_task_brief
|
|
|
|
brief = compile_task_brief(profile_id, "journal_generate")
|
|
else:
|
|
brief = writing_profile
|
|
items.append({"type": "writing_profile", "compiled_brief": brief})
|
|
items.append({"type": "style_examples", "body": style_examples or ""})
|
|
items.append({"type": "transformation_instructions", "text": transformation_instructions or ""})
|
|
items.append({"type": "detail_instructions", "text": detail_instructions or ""})
|
|
items.append({"type": "voice_instructions", "text": voice_instructions or ""})
|
|
items.append({"type": "narrative_instructions", "text": narrative_instructions or ""})
|
|
if reconstruction:
|
|
items.append({"type": "reconstruction", "body": reconstruction})
|
|
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 = ""
|
|
reconstruction = ""
|
|
style_examples = ""
|
|
transformation_instructions = ""
|
|
detail_instructions = ""
|
|
voice_instructions = ""
|
|
narrative_instructions = ""
|
|
user_bodies: list[str] = []
|
|
purpose = context.get("purpose") or ""
|
|
for item in context.get("items") or []:
|
|
kind = item.get("type")
|
|
if kind == "day_messages":
|
|
day_messages = item.get("messages") or []
|
|
if purpose == "journal_reconstruct":
|
|
from journal_reconstruct import assign_source_ids
|
|
|
|
day_messages = assign_source_ids(day_messages)
|
|
for message in day_messages:
|
|
role = message.get("role") or "user"
|
|
body = message.get("body") or ""
|
|
source_id = message.get("source_id")
|
|
if purpose == "journal_reconstruct" and source_id:
|
|
dialogue_parts.append(f"[{source_id}] {role}: {body}")
|
|
else:
|
|
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 ""
|
|
elif kind == "reconstruction":
|
|
reconstruction = item.get("body") or ""
|
|
elif kind == "style_examples":
|
|
style_examples = item.get("body") or ""
|
|
elif kind == "transformation_instructions":
|
|
transformation_instructions = item.get("text") or ""
|
|
elif kind == "detail_instructions":
|
|
detail_instructions = item.get("text") or ""
|
|
elif kind == "voice_instructions":
|
|
voice_instructions = item.get("text") or ""
|
|
elif kind == "narrative_instructions":
|
|
narrative_instructions = item.get("text") or ""
|
|
elif kind == "opening":
|
|
pass
|
|
opening_hint = ""
|
|
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,
|
|
"style_examples": style_examples,
|
|
"transformation_instructions": transformation_instructions,
|
|
"detail_instructions": detail_instructions,
|
|
"voice_instructions": voice_instructions,
|
|
"narrative_instructions": narrative_instructions,
|
|
"interaction_hint": interaction_hint,
|
|
"existing_text": existing_text,
|
|
"reconstruction": reconstruction,
|
|
"space_title": context.get("space_title") or "",
|
|
"register_hint": infer_register(user_bodies),
|
|
"opening_hint": opening_hint,
|
|
}
|