mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-22 23:31:12 +02:00
Added chat renaming and auto naming by the active LLM
This commit is contained in:
parent
b176599c64
commit
d761ca1992
12 changed files with 287 additions and 36 deletions
|
|
@ -104,3 +104,33 @@ SUMMARY_PROMPT = (
|
|||
SUMMARY_PROMPT_TEMPLATE = PromptTemplate(
|
||||
input_variables=["document"], template=SUMMARY_PROMPT
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# Chat Title Generation Prompt
|
||||
# =============================================================================
|
||||
|
||||
TITLE_GENERATION_PROMPT = """Generate a concise, descriptive title for the following conversation.
|
||||
|
||||
<rules>
|
||||
- The title MUST be between 1 and 6 words
|
||||
- The title MUST be on a single line
|
||||
- Capture the main topic or intent of the conversation
|
||||
- Do NOT use quotes, punctuation, or formatting
|
||||
- Do NOT include words like "Chat about" or "Discussion of"
|
||||
- Return ONLY the title, nothing else
|
||||
</rules>
|
||||
|
||||
<user_query>
|
||||
{user_query}
|
||||
</user_query>
|
||||
|
||||
<assistant_response>
|
||||
{assistant_response}
|
||||
</assistant_response>
|
||||
|
||||
Title:"""
|
||||
|
||||
TITLE_GENERATION_PROMPT_TEMPLATE = PromptTemplate(
|
||||
input_variables=["user_query", "assistant_response"],
|
||||
template=TITLE_GENERATION_PROMPT,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -886,30 +886,8 @@ async def append_message(
|
|||
# Update thread's updated_at timestamp
|
||||
thread.updated_at = datetime.now(UTC)
|
||||
|
||||
# Auto-generate title from first user message if title is still default
|
||||
if thread.title == "New Chat" and role_str == "user":
|
||||
# Extract text content for title
|
||||
content = message.content
|
||||
if isinstance(content, str):
|
||||
title_text = content
|
||||
elif isinstance(content, list):
|
||||
# Find first text content
|
||||
title_text = ""
|
||||
for part in content:
|
||||
if isinstance(part, dict) and part.get("type") == "text":
|
||||
title_text = part.get("text", "")
|
||||
break
|
||||
elif isinstance(part, str):
|
||||
title_text = part
|
||||
break
|
||||
else:
|
||||
title_text = str(content)
|
||||
|
||||
# Truncate title
|
||||
if title_text:
|
||||
thread.title = title_text[:100] + (
|
||||
"..." if len(title_text) > 100 else ""
|
||||
)
|
||||
# Note: Title generation now happens in stream_new_chat.py after the first response
|
||||
# using LLM to generate a descriptive title (with truncation as fallback)
|
||||
|
||||
await session.commit()
|
||||
await session.refresh(db_message)
|
||||
|
|
|
|||
|
|
@ -479,6 +479,31 @@ class VercelStreamingService:
|
|||
},
|
||||
)
|
||||
|
||||
def format_thread_title_update(self, thread_id: int, title: str) -> str:
|
||||
"""
|
||||
Format a thread title update notification (SurfSense specific).
|
||||
|
||||
This is sent after the first response in a thread to update the
|
||||
auto-generated title based on the conversation content.
|
||||
|
||||
Args:
|
||||
thread_id: The ID of the thread being updated
|
||||
title: The new title for the thread
|
||||
|
||||
Returns:
|
||||
str: SSE formatted thread title update data part
|
||||
|
||||
Example output:
|
||||
data: {"type":"data-thread-title-update","data":{"threadId":123,"title":"New Title"}}
|
||||
"""
|
||||
return self.format_data(
|
||||
"thread-title-update",
|
||||
{
|
||||
"threadId": thread_id,
|
||||
"title": title,
|
||||
},
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# Error Part
|
||||
# =========================================================================
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ from app.services.chat_session_state_service import (
|
|||
clear_ai_responding,
|
||||
set_ai_responding,
|
||||
)
|
||||
from app.prompts import TITLE_GENERATION_PROMPT_TEMPLATE
|
||||
from app.services.connector_service import ConnectorService
|
||||
from app.services.new_streaming_service import VercelStreamingService
|
||||
from app.utils.content_utils import bootstrap_history_from_db
|
||||
|
|
@ -1208,6 +1209,80 @@ async def stream_new_chat(
|
|||
if completion_event:
|
||||
yield completion_event
|
||||
|
||||
# Generate LLM title for new chats after first response
|
||||
# Check if this is the first assistant response by counting existing assistant messages
|
||||
from app.db import NewChatMessage, NewChatThread
|
||||
from sqlalchemy import func
|
||||
|
||||
assistant_count_result = await session.execute(
|
||||
select(func.count(NewChatMessage.id)).filter(
|
||||
NewChatMessage.thread_id == chat_id,
|
||||
NewChatMessage.role == "assistant",
|
||||
)
|
||||
)
|
||||
assistant_message_count = assistant_count_result.scalar() or 0
|
||||
|
||||
# Only generate title on the first response (no prior assistant messages)
|
||||
if assistant_message_count == 0:
|
||||
print(f"[stream_new_chat] First response - generating title for thread {chat_id}")
|
||||
print(f"[stream_new_chat] Query length: {len(user_query)}, Response length: {len(accumulated_text)}")
|
||||
|
||||
generated_title = None
|
||||
try:
|
||||
# Generate title using the same LLM
|
||||
title_chain = TITLE_GENERATION_PROMPT_TEMPLATE | llm
|
||||
# Truncate inputs to avoid context length issues
|
||||
truncated_query = user_query[:500]
|
||||
truncated_response = accumulated_text[:1000]
|
||||
print(f"[stream_new_chat] Calling LLM for title generation...")
|
||||
title_result = await title_chain.ainvoke({
|
||||
"user_query": truncated_query,
|
||||
"assistant_response": truncated_response,
|
||||
})
|
||||
print(f"[stream_new_chat] LLM title result type: {type(title_result)}")
|
||||
print(f"[stream_new_chat] LLM title result: {title_result}")
|
||||
|
||||
# Extract and clean the title
|
||||
if title_result and hasattr(title_result, "content"):
|
||||
raw_title = title_result.content.strip()
|
||||
print(f"[stream_new_chat] Raw title content: '{raw_title}' (len={len(raw_title)})")
|
||||
|
||||
# Validate the title (1-6 words, reasonable length)
|
||||
if raw_title and len(raw_title) <= 100:
|
||||
# Remove any quotes or extra formatting
|
||||
generated_title = raw_title.strip('"\'')
|
||||
print(f"[stream_new_chat] After stripping quotes: '{generated_title}'")
|
||||
else:
|
||||
print(f"[stream_new_chat] Title validation failed: empty={not raw_title}, len={len(raw_title)}")
|
||||
generated_title = None
|
||||
else:
|
||||
print(f"[stream_new_chat] No content attribute on result")
|
||||
except Exception as title_error:
|
||||
print(f"[stream_new_chat] Title generation failed: {title_error}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
generated_title = None
|
||||
|
||||
# Only update if LLM succeeded (keep truncated prompt title as fallback)
|
||||
if generated_title:
|
||||
print(f"[stream_new_chat] Using LLM-generated title: '{generated_title}'")
|
||||
|
||||
# Fetch thread and update title
|
||||
thread_result = await session.execute(
|
||||
select(NewChatThread).filter(NewChatThread.id == chat_id)
|
||||
)
|
||||
thread = thread_result.scalars().first()
|
||||
if thread:
|
||||
thread.title = generated_title
|
||||
await session.commit()
|
||||
|
||||
# Notify frontend of the title update
|
||||
yield streaming_service.format_thread_title_update(
|
||||
chat_id, generated_title
|
||||
)
|
||||
else:
|
||||
print(f"[stream_new_chat] LLM title generation failed, keeping truncated prompt title")
|
||||
|
||||
# Finish the step and message
|
||||
yield streaming_service.format_finish_step()
|
||||
yield streaming_service.format_finish()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue