"""Branch-aware prompt builder — assembles AI context with cross-branch awareness. Pure function: takes data, returns dict matching _call_ai parameter names. No DB access, no LLM calls. The caller pre-fetches all data. Return keys: system_base, rag_context, history, new_message, images - system_base: stable system prompt (cached by Anthropic) - rag_context: cross-branch summaries + attachment descriptions (NOT cached) """ from typing import Any from app.services.assistant_chat_service import ASSISTANT_SYSTEM_PROMPT class BranchAwarePromptBuilder: """Assembles prompt components for branch-aware AI calls.""" def build( self, branch_messages: list[dict[str, Any]], sibling_summaries: str, session_context: str, attachments: list[dict[str, Any]], new_message: str, revival_context: str | None = None, token_budget: int = 100_000, ) -> dict[str, Any]: """Build prompt components for _call_ai. Returns dict with keys: system_base, rag_context, history, new_message, images. """ # 1. system_base — stable, cached across turns system_base = ASSISTANT_SYSTEM_PROMPT + "\n\n## Session Context\n" + session_context # 2. rag_context — changes per query, NOT cached rag_parts = [] if revival_context: rag_parts.append(f"\n## Branch Revival\n{revival_context}") if sibling_summaries: rag_parts.append(sibling_summaries) rag_context = "\n".join(rag_parts) # 3. history — branch messages filtered to user/assistant history = [] for msg in branch_messages: if msg.get("role") in ("user", "assistant"): history.append({"role": msg["role"], "content": msg["content"]}) # 4. images images = attachments if attachments else None return { "system_base": system_base, "rag_context": rag_context, "history": history, "new_message": new_message, "images": images, }