- SQLAlchemy async database layer (SQLite dev, PostgreSQL prod) - Conversation and Message models with UUID primary keys - Token counting utilities using litellm - Context summarization at 80% token threshold - REST API endpoints for multi-turn conversations - 19 conversation tests, 6 token tests (176 total passing) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
73 lines
2.5 KiB
Python
73 lines
2.5 KiB
Python
"""
|
|
Database models for conversations.
|
|
|
|
Following core-api patterns: SQLAlchemy 2.0 with async support.
|
|
"""
|
|
from datetime import datetime
|
|
from uuid import UUID, uuid4
|
|
|
|
from sqlalchemy import ForeignKey, String, Text
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from src.db.models import Base
|
|
|
|
|
|
class Conversation(Base):
|
|
"""
|
|
A conversation session with an agent.
|
|
|
|
Tracks message history, token usage, and metadata.
|
|
"""
|
|
__tablename__ = "conversations"
|
|
|
|
id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
|
|
user_id: Mapped[str] = mapped_column(String(255), index=True)
|
|
agent_type: Mapped[str] = mapped_column(String(50), default="explore", insert_default="explore")
|
|
title: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None)
|
|
working_dir: Mapped[str] = mapped_column(String(1024), default=".", insert_default=".")
|
|
total_tokens: Mapped[int] = mapped_column(default=0, insert_default=0)
|
|
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
|
updated_at: Mapped[datetime | None] = mapped_column(
|
|
default=datetime.utcnow,
|
|
onupdate=datetime.utcnow,
|
|
nullable=True
|
|
)
|
|
|
|
# Relationships
|
|
messages: Mapped[list["Message"]] = relationship(
|
|
back_populates="conversation",
|
|
cascade="all, delete-orphan",
|
|
order_by="Message.created_at",
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<Conversation {self.id} agent={self.agent_type}>"
|
|
|
|
|
|
class Message(Base):
|
|
"""
|
|
A single message in a conversation.
|
|
|
|
Tracks role, content, token count, and summarization state.
|
|
"""
|
|
__tablename__ = "messages"
|
|
|
|
id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
|
|
conversation_id: Mapped[UUID] = mapped_column(
|
|
ForeignKey("conversations.id", ondelete="CASCADE"),
|
|
index=True
|
|
)
|
|
role: Mapped[str] = mapped_column(String(20)) # user, assistant, system, summary
|
|
content: Mapped[str] = mapped_column(Text)
|
|
token_count: Mapped[int] = mapped_column(default=0, insert_default=0)
|
|
is_summary: Mapped[bool] = mapped_column(default=False, insert_default=False)
|
|
summarizes_up_to: Mapped[UUID | None] = mapped_column(nullable=True, default=None)
|
|
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
|
|
|
# Relationships
|
|
conversation: Mapped["Conversation"] = relationship(back_populates="messages")
|
|
|
|
def __repr__(self) -> str:
|
|
preview = self.content[:30] + "..." if len(self.content) > 30 else self.content
|
|
return f"<Message {self.role}: {preview}>"
|