"""Conversation context for a capture. When a document is written out of a chat, that chat's subject is useful twice: to find existing documents the user might extend, and as background for section refinement. Both use a short LLM topic summary of the conversation. All LLM traffic goes through `llm/client.py`; nothing here logs content — a failure degrades quietly rather than breaking the capture. """ import logging from pydantic import BaseModel from app.authoring.prompts import render_topic_summary_prompt from app.llm.client import chat_json from app.llm.errors import LLMError from app.models import Conversation, MessageRole logger = logging.getLogger("pablan.authoring") # How many recent turns feed the summary — enough for the subject, bounded so # a long thread cannot blow up the utility prompt. MAX_CONTEXT_MESSAGES = 12 # Skip the reasoning model's hidden thinking: this is a short, latency- # sensitive utility call. _NO_THINKING = {"chat_template_kwargs": {"enable_thinking": False}} class _TopicSummary(BaseModel): topic: str def conversation_transcript(conversation: Conversation) -> str: """The recent user/assistant turns as a plain transcript.""" turns = [ message for message in conversation.messages if message.role in (MessageRole.user, MessageRole.assistant) ][-MAX_CONTEXT_MESSAGES:] return "\n".join( f"{'User' if message.role == MessageRole.user else 'Assistant'}: " f"{message.content}" for message in turns ) async def summarize_transcript(transcript: str) -> str: """A short topic summary of a conversation transcript, or '' if none can be made. Failures degrade quietly.""" if not transcript.strip(): return "" try: result = await chat_json( render_topic_summary_prompt(transcript), _TopicSummary, extra_body=_NO_THINKING, ) except LLMError: logger.info("topic summary failed", extra={"event": "topic_summary_failed"}) return "" return result.topic.strip() async def summarize_conversation(conversation: Conversation) -> str: """A short topic summary of the conversation, or '' if none can be made.""" return await summarize_transcript(conversation_transcript(conversation))