| 1 | """LLM-backed session sidebar preview generation.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import re |
| 6 | |
| 7 | from loguru import logger |
| 8 | |
| 9 | from nanobot.providers.base import LLMProvider |
| 10 | from nanobot.utils.prompt_templates import render_template |
| 11 | |
| 12 | _PREVIEW_MAX_CHARS = 15 |
| 13 | _PREVIEW_SOURCE_KEY = "preview_source" |
| 14 | |
| 15 | |
| 16 | def clamp_preview(text: str, *, max_chars: int = _PREVIEW_MAX_CHARS) -> str: |
| 17 | normalized = " ".join((text or "").split()).strip() |
| 18 | if not normalized: |
| 19 | return "" |
| 20 | if len(normalized) <= max_chars: |
| 21 | return normalized |
| 22 | return normalized[:max_chars] |
| 23 | |
| 24 | |
| 25 | class SessionPreviewGenerator: |
| 26 | """Summarize the first user turn into a short sidebar title via the configured provider.""" |
| 27 | |
| 28 | def __init__(self, provider: LLMProvider, model: str) -> None: |
| 29 | self._provider = provider |
| 30 | self._model = model |
| 31 | |
| 32 | @property |
| 33 | def model(self) -> str: |
| 34 | return self._model |
| 35 | |
| 36 | async def summarize(self, first_user_message: str) -> str: |
| 37 | source = " ".join(first_user_message.split()).strip() |
| 38 | if not source: |
| 39 | return "" |
| 40 | if len(source) <= _PREVIEW_MAX_CHARS: |
| 41 | return source |
| 42 | try: |
| 43 | response = await self._provider.chat_with_retry( |
| 44 | model=self._model, |
| 45 | messages=[ |
| 46 | { |
| 47 | "role": "system", |
| 48 | "content": render_template("agent/session_preview.md", strip=True), |
| 49 | }, |
| 50 | {"role": "user", "content": source}, |
| 51 | ], |
| 52 | tools=None, |
| 53 | max_tokens=64, |
| 54 | temperature=0.2, |
| 55 | ) |
| 56 | except Exception: |
| 57 | logger.opt(exception=True).warning("session preview LLM call failed") |
| 58 | return clamp_preview(source) |
| 59 | text = (response.content or "").strip() |
| 60 | text = re.sub(r'^["\'“”‘’]+|["\'“”‘’]+$', "", text) |
| 61 | text = " ".join(text.split()).strip() |
| 62 | return clamp_preview(text or source) |
| 63 |