Newer
Older
navi-1 / navi / core / name_generator.py
"""Generate a short session name from the conversation via LLM."""

from navi.llm.base import LLMBackend, Message

_SYSTEM = (
    "You are a session title generator. "
    "Given the user's messages (and, possibly, the assistant's replies) from a conversation, "
    "produce ONE short title (3–6 words, no punctuation at the end). "
    "The title must reflect the actual topic. "
    "Respond in the same language as the user's messages. "
    "If there's still no clear topic (e.g. only greetings, very short or ambiguous text), "
    "reply with exactly: NO_TITLE"
)

# Assistant replies can be huge; a tail slice is enough context for a title.
_ASSISTANT_MSG_CHARS = 600
_ASSISTANT_MSG_COUNT = 2


async def generate_session_name(
    user_messages: list[str],
    backend: LLMBackend,
    model: str,
    assistant_messages: list[str] | None = None,
) -> str | None:
    """Return a short title or None if content isn't substantial enough."""
    if not user_messages:
        return None

    combined = "\n".join(f"- {m}" for m in user_messages[:10])

    # Naming runs right after the first exchange, when user messages are often
    # greetings or one-liners with no topic. The agent's final response is
    # where the actual subject shows up — include a trimmed tail of it.
    if assistant_messages:
        combined += "\n\nAssistant replies:\n" + "\n---\n".join(
            m[-_ASSISTANT_MSG_CHARS:]
            for m in assistant_messages[-_ASSISTANT_MSG_COUNT:]
            if m
        )

    messages = [
        Message(role="system", content=_SYSTEM),
        Message(role="user", content=f"User messages:\n{combined}"),
    ]

    resp = await backend.complete(messages, tools=[], model=model, temperature=0.4)
    text = (resp.content or "").strip()

    if not text or text.upper() == "NO_TITLE" or len(text) > 80:
        return None

    # Strip surrounding quotes if the model added them
    return text.strip('"\'')