ZARAI / backend /app /db /database.py
Srajal2579
Deploy JARVIS
3e646d9
Raw History Blame Contribute Delete
6.5 kB
"""
JARVIS β€” Database Layer
Async SQLAlchemy with SQLite.
Tables: sessions, conversations, embeddings_meta
"""
from datetime import datetime
from typing import Optional, List
from sqlalchemy import (
Column, String, Text, DateTime, Integer,
ForeignKey, Index, event
)
from sqlalchemy.ext.asyncio import (
AsyncSession, create_async_engine, async_sessionmaker
)
from sqlalchemy.orm import DeclarativeBase, relationship
from sqlalchemy.future import select
from loguru import logger
from app.core.config import settings
# ─────────────────────────────────────────────
# Engine & session factory
# ─────────────────────────────────────────────
engine = create_async_engine(
settings.DATABASE_URL,
echo=settings.DEBUG,
connect_args={"check_same_thread": False},
)
AsyncSessionLocal = async_sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False,
)
# ─────────────────────────────────────────────
# Base model
# ─────────────────────────────────────────────
class Base(DeclarativeBase):
pass
# ─────────────────────────────────────────────
# ORM Models
# ─────────────────────────────────────────────
class Session(Base):
"""A chat session (conversation thread)."""
__tablename__ = "sessions"
id = Column(String(36), primary_key=True) # UUID
title = Column(String(200), default="New Chat")
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
messages = relationship("Message", back_populates="session", cascade="all, delete-orphan")
class Message(Base):
"""A single message in a session."""
__tablename__ = "messages"
id = Column(Integer, primary_key=True, autoincrement=True)
session_id = Column(String(36), ForeignKey("sessions.id"), nullable=False)
role = Column(String(20), nullable=False) # "user" | "assistant" | "system"
content = Column(Text, nullable=False)
modality = Column(String(20), default="text") # text | image | voice | tool
metadata_ = Column("metadata", Text, default="{}") # JSON string
created_at = Column(DateTime, default=datetime.utcnow)
session = relationship("Session", back_populates="messages")
__table_args__ = (
Index("ix_messages_session_id", "session_id"),
Index("ix_messages_created_at", "created_at"),
)
class EmbeddingMeta(Base):
"""Metadata for stored FAISS embeddings."""
__tablename__ = "embeddings_meta"
id = Column(Integer, primary_key=True, autoincrement=True)
faiss_id = Column(Integer, unique=True, nullable=False) # index in FAISS
session_id = Column(String(36), nullable=True)
text = Column(Text, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
# ─────────────────────────────────────────────
# Init
# ─────────────────────────────────────────────
async def init_db():
"""Create all tables if they don't exist."""
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
logger.info("Database tables created/verified.")
# ─────────────────────────────────────────────
# Dependency
# ─────────────────────────────────────────────
async def get_db() -> AsyncSession:
"""FastAPI dependency β€” yields an async DB session."""
async with AsyncSessionLocal() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
# ─────────────────────────────────────────────
# Helper CRUD functions
# ─────────────────────────────────────────────
async def create_session(db: AsyncSession, session_id: str, title: str = "New Chat") -> Session:
sess = Session(id=session_id, title=title)
db.add(sess)
await db.flush()
return sess
async def get_session(db: AsyncSession, session_id: str) -> Optional[Session]:
result = await db.execute(select(Session).where(Session.id == session_id))
return result.scalar_one_or_none()
async def list_sessions(db: AsyncSession, limit: int = 50) -> List[Session]:
result = await db.execute(
select(Session).order_by(Session.updated_at.desc()).limit(limit)
)
return result.scalars().all()
async def add_message(
db: AsyncSession,
session_id: str,
role: str,
content: str,
modality: str = "text",
metadata: str = "{}",
) -> Message:
msg = Message(
session_id=session_id,
role=role,
content=content,
modality=modality,
metadata_=metadata,
)
db.add(msg)
await db.flush()
# Update session timestamp
sess = await get_session(db, session_id)
if sess:
sess.updated_at = datetime.utcnow()
return msg
async def get_messages(
db: AsyncSession,
session_id: str,
limit: int = 50,
) -> List[Message]:
result = await db.execute(
select(Message)
.where(Message.session_id == session_id)
.order_by(Message.created_at.asc())
.limit(limit)
)
return result.scalars().all()
async def add_embedding_meta(
db: AsyncSession,
faiss_id: int,
text: str,
session_id: Optional[str] = None,
) -> EmbeddingMeta:
em = EmbeddingMeta(faiss_id=faiss_id, text=text, session_id=session_id)
db.add(em)
await db.flush()
return em