- Updated python_coding_microproject.csv to use NVIDIA NIM Kimi K2 - Updated kali_linux_shell_simulator.csv to use NVIDIA NIM Kimi K2 - Made more general-purpose (flexible targets, expanded tools) - Added nemotron-mini-agent.csv for fast local inference via Ollama - Added nemotron-agent.csv for advanced reasoning via Ollama - Added wiki page: Projects for NVIDIA NIMs and Nemotron
200 lines
7.1 KiB
Python
200 lines
7.1 KiB
Python
"""
|
|
Conversation Summarization Service for GT 2.0
|
|
|
|
Automatically generates meaningful conversation titles using a specialized
|
|
summarization agent with llama-3.1-8b-instant.
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
from typing import List, Optional, Dict, Any
|
|
|
|
from app.core.config import get_settings
|
|
from app.core.resource_client import ResourceClusterClient
|
|
|
|
logger = logging.getLogger(__name__)
|
|
settings = get_settings()
|
|
|
|
|
|
class ConversationSummarizer:
|
|
"""Service for generating conversation summaries and titles"""
|
|
|
|
def __init__(self, tenant_id: str, user_id: str):
|
|
self.tenant_id = tenant_id
|
|
self.user_id = user_id
|
|
self.resource_client = ResourceClusterClient()
|
|
self.summarization_model = "llama-3.1-8b-instant"
|
|
|
|
async def generate_conversation_title(self, messages: List[Dict[str, Any]]) -> str:
|
|
"""
|
|
Generate a concise conversation title based on the conversation content.
|
|
|
|
Args:
|
|
messages: List of message dictionaries from the conversation
|
|
|
|
Returns:
|
|
Generated conversation title (3-6 words)
|
|
"""
|
|
try:
|
|
# Extract conversation context for summarization
|
|
conversation_text = self._prepare_conversation_context(messages)
|
|
|
|
if not conversation_text.strip():
|
|
return "New Conversation"
|
|
|
|
# Generate title using specialized summarization prompt
|
|
title = await self._call_summarization_agent(conversation_text)
|
|
|
|
# Validate and clean the generated title
|
|
clean_title = self._clean_title(title)
|
|
|
|
logger.info(f"Generated conversation title: '{clean_title}' from {len(messages)} messages")
|
|
return clean_title
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error generating conversation title: {e}")
|
|
return self._fallback_title(messages)
|
|
|
|
def _prepare_conversation_context(self, messages: List[Dict[str, Any]]) -> str:
|
|
"""Prepare conversation context for summarization"""
|
|
if not messages:
|
|
return ""
|
|
|
|
# Limit to first few exchanges for title generation
|
|
context_messages = messages[:6] # First 3 user-agent exchanges
|
|
|
|
context_parts = []
|
|
for msg in context_messages:
|
|
role = "User" if msg.get("role") == "user" else "Agent"
|
|
# Truncate very long messages for context
|
|
content = msg.get("content", "")
|
|
content = content[:200] + "..." if len(content) > 200 else content
|
|
context_parts.append(f"{role}: {content}")
|
|
|
|
return "\n".join(context_parts)
|
|
|
|
async def _call_summarization_agent(self, conversation_text: str) -> str:
|
|
"""Call the resource cluster AI inference for summarization"""
|
|
|
|
summarization_prompt = f"""You are a conversation title generator. Your job is to create concise, descriptive titles for conversations.
|
|
|
|
Given this conversation:
|
|
---
|
|
{conversation_text}
|
|
---
|
|
|
|
Generate a title that:
|
|
- Is 3-6 words maximum
|
|
- Captures the main topic or purpose
|
|
- Is clear and descriptive
|
|
- Uses title case
|
|
- Does NOT include quotes or special characters
|
|
|
|
Examples of good titles:
|
|
- "Python Code Review"
|
|
- "Database Migration Help"
|
|
- "React Component Design"
|
|
- "System Architecture Discussion"
|
|
|
|
Title:"""
|
|
|
|
request_data = {
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": summarization_prompt
|
|
}
|
|
],
|
|
"model": self.summarization_model,
|
|
"temperature": 0.3, # Lower temperature for consistent, focused titles
|
|
"max_tokens": 20, # Short response for title generation
|
|
"stream": False
|
|
}
|
|
|
|
try:
|
|
# Use the resource client instead of direct HTTP calls
|
|
result = await self.resource_client.call_inference_endpoint(
|
|
tenant_id=self.tenant_id,
|
|
user_id=self.user_id,
|
|
endpoint="chat/completions",
|
|
data=request_data
|
|
)
|
|
|
|
if result and "choices" in result and len(result["choices"]) > 0:
|
|
title = result["choices"][0]["message"]["content"].strip()
|
|
return title
|
|
else:
|
|
logger.error("Invalid response format from summarization API")
|
|
return ""
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error calling summarization agent: {e}")
|
|
return ""
|
|
|
|
def _clean_title(self, raw_title: str) -> str:
|
|
"""Clean and validate the generated title"""
|
|
if not raw_title:
|
|
return "New Conversation"
|
|
|
|
# Remove quotes, extra whitespace, and special characters
|
|
cleaned = raw_title.strip().strip('"\'').strip()
|
|
|
|
# Remove common prefixes that AI might add
|
|
prefixes_to_remove = [
|
|
"Title:", "title:", "TITLE:",
|
|
"Conversation:", "conversation:",
|
|
"Topic:", "topic:",
|
|
"Subject:", "subject:"
|
|
]
|
|
|
|
for prefix in prefixes_to_remove:
|
|
if cleaned.startswith(prefix):
|
|
cleaned = cleaned[len(prefix):].strip()
|
|
|
|
# Limit length and ensure it's reasonable
|
|
if len(cleaned) > 50:
|
|
cleaned = cleaned[:47] + "..."
|
|
|
|
# Ensure it's not empty after cleaning
|
|
if not cleaned or len(cleaned.split()) > 8:
|
|
return "New Conversation"
|
|
|
|
return cleaned
|
|
|
|
def _fallback_title(self, messages: List[Dict[str, Any]]) -> str:
|
|
"""Generate fallback title when AI summarization fails"""
|
|
if not messages:
|
|
return "New Conversation"
|
|
|
|
# Try to use the first user message for context
|
|
first_user_msg = next((msg for msg in messages if msg.get("role") == "user"), None)
|
|
|
|
if first_user_msg and first_user_msg.get("content"):
|
|
# Extract first few words from the user's message
|
|
words = first_user_msg["content"].strip().split()[:4]
|
|
if len(words) >= 2:
|
|
fallback = " ".join(words).capitalize()
|
|
# Remove common question words for cleaner titles
|
|
for word in ["How", "What", "Can", "Could", "Please", "Help"]:
|
|
if fallback.startswith(word + " "):
|
|
fallback = fallback[len(word):].strip()
|
|
break
|
|
return fallback if fallback else "New Conversation"
|
|
|
|
return "New Conversation"
|
|
|
|
|
|
async def generate_conversation_title(messages: List[Dict[str, Any]], tenant_id: str, user_id: str) -> str:
|
|
"""
|
|
Convenience function to generate a conversation title.
|
|
|
|
Args:
|
|
messages: List of message dictionaries from the conversation
|
|
tenant_id: Tenant identifier
|
|
user_id: User identifier
|
|
|
|
Returns:
|
|
Generated conversation title
|
|
"""
|
|
summarizer = ConversationSummarizer(tenant_id, user_id)
|
|
return await summarizer.generate_conversation_title(messages) |