"""Note summarizer — generates a compact AI summary for a note. Called fire-and-forget from create_note / update_note tools so the ``notes.ai_summary`` column stays current without blocking the agent loop. """ from __future__ import annotations import logging from langchain_core.messages import HumanMessage, SystemMessage from app.core.langfuse_client import get_prompt_or_fallback from app.core.llm import get_agent_llm logger = logging.getLogger(__name__) _FALLBACK_PROMPT = """\ Summarize this note in <=250 characters. Be terse and dense. Keep proper nouns, dates, decisions, and action items. Do not start with "This note". Respond with the summary text only — no intro, no labels. Title: {title} Content: {content}""" _MAX_CONTENT_CHARS = 4000 async def generate_note_summary(title: str, content: str) -> str: """Return a <=250-char summary of *title* + *content*. Uses the Langfuse ``note_summary`` prompt (hot-swappable) with a local fallback. Truncates *content* to 4000 chars before sending to avoid token waste on large notes. """ template, _ = get_prompt_or_fallback("note_summary", _FALLBACK_PROMPT) trimmed = content[:_MAX_CONTENT_CHARS] system_prompt = template.format(title=title, content=trimmed) try: llm = get_agent_llm("note-summarizer") response = await llm.ainvoke([ SystemMessage(content=system_prompt), HumanMessage(content="Generate the summary."), ]) text = response.content if isinstance(response.content, str) else "" return text.strip()[:250] except Exception as exc: logger.warning("note_summarizer: failed to generate summary: %s", exc) return ""