ragify / app /session.py
lloydakresi's picture
updated logic
880a3c3
Raw
History Blame Contribute Delete
3.73 kB
import time
import uuid
import hashlib
from dataclasses import dataclass, field
from typing import Optional
import chromadb
from chromadb.utils import embedding_functions
from collections import deque
@dataclass
class ChatTurn:
role: str # "user" or "assistant"
content: str
timestamp: float = field(default_factory=time.time)
@dataclass
class Session:
session_id: str
filename: str
doc_hash: str
collection: chromadb.Collection
chat_history: deque[ChatTurn] = field(default_factory=lambda: deque(maxlen=7))
created_at: float = field(default_factory=time.time)
last_accessed: float = field(default_factory=time.time)
def touch(self):
self.last_accessed = time.time()
def add_turn(self, role: str, content: str):
self.chat_history.append(ChatTurn(role, content))
self.touch()
class SessionManager:
def __init__(self, max_sessions: int = 10, ttl_seconds: int = 3600):
self.client = chromadb.EphemeralClient() # in-memory
self.embed_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="all-MiniLM-L6-v2",
device="cpu"
)
self.sessions: dict[str, Session] = {}
self.max_sessions = max_sessions
self.ttl_seconds = ttl_seconds
def _hash_file(self, file_bytes: bytes) -> str:
return hashlib.sha256(file_bytes).hexdigest()[:16]
def create_session(self, filename: str, file_bytes: bytes, chunks: dict[str, list]) -> Session:
self._evict_if_needed()
doc_hash = self._hash_file(file_bytes)
# reuse an existing session if the same doc is already loaded
for s in self.sessions.values():
if s.doc_hash == doc_hash:
s.touch()
return s
session_id = str(uuid.uuid4())
t = time.perf_counter()
collection = self.client.get_or_create_collection(
name=f"doc_{session_id}",
embedding_function=self.embed_fn,
)
print(f" creating collection: {time.perf_counter()-t:.2f}s")
t = time.perf_counter()
texts = chunks["text"]
ids = [f"{session_id}_{i}" for i in range(len(texts))]
metadatas = chunks["metadata"]
batch_size = 512
for start in range(0, len(texts), batch_size):
end = start + batch_size
collection.upsert(
documents=texts[start:end],
ids=ids[start:end],
metadatas=metadatas[start:end]
)
print(f" chromadb insert: {time.perf_counter()-t:.2f}s")
session = Session(
session_id=session_id,
filename=filename,
doc_hash=doc_hash,
collection=collection,
)
self.sessions[session_id] = session
return session
def get_session(self, session_id: str) -> Optional[Session]:
session = self.sessions.get(session_id)
if session:
session.touch()
return session
def close_session(self, session_id: str):
session = self.sessions.pop(session_id, None)
if session:
self.client.delete_collection(session.collection.name)
def _evict_if_needed(self):
now = time.time()
# TTL eviction first
expired = [sid for sid, s in self.sessions.items()
if now - s.last_accessed > self.ttl_seconds]
for sid in expired:
self.close_session(sid)
# LRU eviction if still over capacity
if len(self.sessions) >= self.max_sessions:
oldest = min(self.sessions.values(), key=lambda s: s.last_accessed)
self.close_session(oldest.session_id)