Generate conversation titles via LLM instead of truncating first line
- _maybe_set_title sets a quick truncated fallback immediately - Then fires a background thread to ask the LLM for a 5-8 word summary - Background thread doesn't block the streaming response - Title appears in sidebar on first chunk, then upgrades when LLM responds Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>cora-start
parent
d771dd5c80
commit
30757b5bcf
|
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import threading
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -294,19 +295,55 @@ class Agent:
|
||||||
self._memory.auto_flush(conv_id)
|
self._memory.auto_flush(conv_id)
|
||||||
|
|
||||||
def _maybe_set_title(self, conv_id: str, user_input: str):
|
def _maybe_set_title(self, conv_id: str, user_input: str):
|
||||||
"""Set conversation title from first user message if still 'New Chat'."""
|
"""Set conversation title from first user message if still 'New Chat'.
|
||||||
|
|
||||||
|
Sets a quick truncated fallback immediately, then fires a background
|
||||||
|
thread to generate a proper 5-8 word LLM summary.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
current_title = self.db.get_conversation_title(conv_id)
|
current_title = self.db.get_conversation_title(conv_id)
|
||||||
if current_title and current_title != "New Chat":
|
if current_title and current_title != "New Chat":
|
||||||
return
|
return
|
||||||
title = user_input.split("\n", 1)[0].strip()
|
# Immediate fallback: first line, truncated
|
||||||
if len(title) > 50:
|
fallback = user_input.split("\n", 1)[0].strip()
|
||||||
title = title[:47] + "..."
|
if len(fallback) > 50:
|
||||||
if title:
|
fallback = fallback[:47] + "..."
|
||||||
self.db.update_conversation_title(conv_id, title)
|
if fallback:
|
||||||
|
self.db.update_conversation_title(conv_id, fallback)
|
||||||
|
# Fire background LLM call to generate a better title
|
||||||
|
threading.Thread(
|
||||||
|
target=self._generate_title,
|
||||||
|
args=(conv_id, user_input),
|
||||||
|
daemon=True,
|
||||||
|
).start()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning("Failed to set conversation title: %s", e)
|
log.warning("Failed to set conversation title: %s", e)
|
||||||
|
|
||||||
|
def _generate_title(self, conv_id: str, user_input: str):
|
||||||
|
"""Background: ask the LLM for a 5-8 word conversation title."""
|
||||||
|
try:
|
||||||
|
prompt = user_input[:500] # cap input to keep it cheap
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": (
|
||||||
|
"Generate a short 5-8 word title summarizing this conversation opener. "
|
||||||
|
"Reply with ONLY the title — no quotes, no punctuation at the end, no explanation."
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{"role": "user", "content": prompt},
|
||||||
|
]
|
||||||
|
parts = []
|
||||||
|
for chunk in self.llm.chat(messages, tools=None, stream=False):
|
||||||
|
if chunk.get("type") == "text":
|
||||||
|
parts.append(chunk["content"])
|
||||||
|
title = "".join(parts).strip().strip('"').strip("'")
|
||||||
|
if title and len(title) <= 60:
|
||||||
|
self.db.update_conversation_title(conv_id, title)
|
||||||
|
log.debug("Generated conversation title: %s", title)
|
||||||
|
except Exception as e:
|
||||||
|
log.warning("Background title generation failed: %s", e)
|
||||||
|
|
||||||
def respond_to_prompt(self, prompt: str) -> str:
|
def respond_to_prompt(self, prompt: str) -> str:
|
||||||
"""Non-streaming response for scheduled tasks / internal use."""
|
"""Non-streaming response for scheduled tasks / internal use."""
|
||||||
result_parts = []
|
result_parts = []
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue