# Experimental from sqlalchemy import Column, String, Enum, ForeignKey, DateTime from sqlalchemy.dialects.postgresql import UUID, ENUM, JSONB from sqlalchemy.orm import relationship from sqlalchemy.sql import func from enum import Enum from sqlalchemy.ext.declarative import as_declarative, declared_attr from llama_index.core.callbacks.schema import CBEventType # Model @as_declarative() class Base: id = Column(UUID, primary_key=True, index=True, default=func.uuid_generate_v4()) created_at = Column(DateTime, server_default=func.now(), nullable=False) updated_at = Column( DateTime, server_default=func.now(), onupdate=func.now(), nullable=False ) __name__: str # Generate __tablename__ automatically @declared_attr def __tablename__(cls) -> str: return cls.__name__.lower() # DB class MessageRoleEnum(str, Enum): user = "user" assistant = "assistant" class MessageStatusEnum(str, Enum): PENDING = "PENDING" SUCCESS = "SUCCESS" ERROR = "ERROR" class MessageSubProcessStatusEnum(str, Enum): PENDING = "PENDING" FINISHED = "FINISHED" # python doesn't allow enums to be extended, so we have to do this additional_message_subprocess_fields = { "CONSTRUCTED_QUERY_ENGINE": "constructed_query_engine", "SUB_QUESTIONS": "sub_questions", } MessageSubProcessSourceEnum = Enum( "MessageSubProcessSourceEnum", [(event_type.name, event_type.value) for event_type in CBEventType] + list(additional_message_subprocess_fields.items()), ) def to_pg_enum(enum_class) -> ENUM: return ENUM(enum_class, name=enum_class.__name__) class Document(Base): """ A document along with its metadata """ # URL to the actual document (e.g. a PDF) url = Column(String, nullable=False, unique=True) metadata_map = Column(JSONB, nullable=True) conversations = relationship("ConversationDocument", back_populates="document") class Conversation(Base): """ A conversation with messages and linked documents """ messages = relationship("Message", back_populates="conversation") conversation_documents = relationship( "ConversationDocument", back_populates="conversation" ) class ConversationDocument(Base): """ A many-to-many relationship between a conversation and a document """ conversation_id = Column( UUID(as_uuid=True), ForeignKey("conversation.id"), index=True ) document_id = Column(UUID(as_uuid=True), ForeignKey("document.id"), index=True) conversation = relationship("Conversation", back_populates="conversation_documents") document = relationship("Document", back_populates="conversations") class Message(Base): """ A message in a conversation """ conversation_id = Column( UUID(as_uuid=True), ForeignKey("conversation.id"), index=True ) content = Column(String) role = Column(to_pg_enum(MessageRoleEnum)) status = Column(to_pg_enum(MessageStatusEnum), default=MessageStatusEnum.PENDING) conversation = relationship("Conversation", back_populates="messages") sub_processes = relationship("MessageSubProcess", back_populates="message") class MessageSubProcess(Base): """ A record of a sub-process that occurred as part of the generation of a message from an AI assistant """ message_id = Column(UUID(as_uuid=True), ForeignKey("message.id"), index=True) source = Column(to_pg_enum(MessageSubProcessSourceEnum)) message = relationship("Message", back_populates="sub_processes") status = Column( to_pg_enum(MessageSubProcessStatusEnum), default=MessageSubProcessStatusEnum.FINISHED, nullable=False, ) metadata_map = Column(JSONB, nullable=True)