acb / src /utils /conversation_summarizer.py
ktek's picture
feat: follow-up question improvements and context tracking
92925b6
Raw
History Blame Contribute Delete
4.75 kB
import hashlib
import logging
import time
from collections import OrderedDict
from typing import Any, Dict, List, Optional
from groq import Groq
from config import GROQ_API_KEY
from prompts import SUMMARIZATION_PROMPT
logger = logging.getLogger(__name__)
class ConversationSummarizer:
MAX_HISTORY_TURNS = 10
SUMMARY_MAX_TOKENS = 300
RECENT_MESSAGES_TO_KEEP = 8
MAX_INPUT_TEXT_LENGTH = 2000
MAX_CACHE_SIZE = 100
def __init__(
self,
api_key: Optional[str] = None,
model: str = "llama-3.1-8b-instant",
max_history_turns: int = 10,
):
self._api_key = api_key or GROQ_API_KEY
self._model = model
self._max_turns = max_history_turns
self._client: Optional[Groq] = None
self._summary_cache: OrderedDict[str, str] = OrderedDict()
@property
def client(self) -> Groq:
if self._client is None:
if not self._api_key:
raise ValueError("GROQ_API_KEY not configured")
self._client = Groq(api_key=self._api_key, timeout=5.0)
return self._client
def _generate_cache_key(self, messages: List[Dict[str, str]]) -> str:
parts = []
for msg in messages:
content = str(msg.get("content", ""))[:50]
parts.append(content)
raw = "||".join(parts)
return hashlib.md5(raw.encode("utf-8")).hexdigest()
def summarize_if_needed(self, chat_history: List[Dict[str, str]]) -> List[Dict[str, str]]:
if len(chat_history) <= self._max_turns * 2:
return chat_history
try:
recent_messages = chat_history[-self.RECENT_MESSAGES_TO_KEEP:]
older_messages = chat_history[:-self.RECENT_MESSAGES_TO_KEEP]
db_messages = [m for m in older_messages if m.get("is_database")]
non_db_older = [m for m in older_messages if not m.get("is_database")]
summary = self._summarize_conversation(non_db_older) if non_db_older else ""
result = []
if summary:
result.append({
"role": "system",
"content": f"Önceki konuşma özeti: {summary}",
})
result.extend(db_messages)
result.extend(recent_messages)
return result
except Exception as e:
logger.warning(f"Summarization failed, returning original history: {e}")
return chat_history
def _summarize_conversation(self, messages: List[Dict[str, str]]) -> str:
cache_key = self._generate_cache_key(messages)
if cache_key in self._summary_cache:
self._summary_cache.move_to_end(cache_key)
logger.debug("Conversation summary cache hit")
return self._summary_cache[cache_key]
conversation_parts = []
for msg in messages:
role = msg.get("role", "unknown")
content = str(msg.get("content", ""))
conversation_parts.append(f"{role}: {content}")
conversation_text = "\n".join(conversation_parts)
if len(conversation_text) > self.MAX_INPUT_TEXT_LENGTH:
conversation_text = conversation_text[-self.MAX_INPUT_TEXT_LENGTH:]
prompt = SUMMARIZATION_PROMPT.format(conversation_text=conversation_text)
try:
response = self.client.chat.completions.create(
model=self._model,
messages=[{"role": "user", "content": prompt}],
temperature=0.3,
max_tokens=self.SUMMARY_MAX_TOKENS,
)
summary = response.choices[0].message.content.strip()
if not summary:
summary = "Konuşma geçmişi mevcut ama özeti oluşturulamadı."
except Exception as e:
logger.warning(f"Summary generation failed: {e}")
summary = "Konuşma geçmişi mevcut ama özeti oluşturulamadı."
while len(self._summary_cache) >= self.MAX_CACHE_SIZE:
self._summary_cache.popitem(last=False)
self._summary_cache[cache_key] = summary
return summary
def clear_cache(self) -> None:
self._summary_cache.clear()
_summarizer_instance: Optional[ConversationSummarizer] = None
def get_conversation_summarizer(
api_key: Optional[str] = None,
model: Optional[str] = None,
) -> ConversationSummarizer:
global _summarizer_instance
if _summarizer_instance is None:
_summarizer_instance = ConversationSummarizer(
api_key=api_key,
model=model or "llama-3.1-8b-instant",
)
return _summarizer_instance
def reset_conversation_summarizer() -> None:
global _summarizer_instance
_summarizer_instance = None