| """ |
| SAAP Database Models - SQLAlchemy ORM (FIXED VERSION) |
| Production-ready database schema with circular reference fixes |
| """ |
|
|
| from sqlalchemy import Column, Integer, String, Text, DateTime, Float, Boolean, JSON, ForeignKey, Index |
| from sqlalchemy.ext.declarative import declarative_base |
| from sqlalchemy.orm import relationship |
| from sqlalchemy.sql import func |
| from sqlalchemy.dialects.postgresql import TIMESTAMP |
| from datetime import datetime |
| from typing import Dict, Any, Optional |
| import json |
|
|
| Base = declarative_base() |
|
|
| class DBAgent(Base): |
| """ |
| Database model for SAAP Agents (FIXED - No circular references) |
| Stores complete agent configuration and runtime state |
| """ |
| __tablename__ = "agents" |
| |
| |
| id = Column(String(100), primary_key=True, index=True) |
| |
| |
| name = Column(String(200), nullable=False, index=True) |
| agent_type = Column(String(50), nullable=False, index=True) |
| color = Column(String(10), nullable=False) |
| avatar = Column(String(500), nullable=True) |
| description = Column(Text, nullable=True) |
| |
| |
| llm_config = Column(JSON, nullable=False) |
| |
| |
| capabilities = Column(JSON, nullable=True) |
| personality = Column(JSON, nullable=True) |
| |
| |
| status = Column(String(20), nullable=False, default="inactive", index=True) |
| last_active = Column(DateTime, nullable=True) |
| |
| |
| metrics = Column(JSON, nullable=True) |
| |
| |
| created_at = Column(TIMESTAMP(timezone=True), default=func.now(), nullable=False, index=True) |
| updated_at = Column(TIMESTAMP(timezone=True), default=func.now(), onupdate=func.now(), nullable=False) |
| tags = Column(JSON, nullable=True) |
| |
| |
| |
| chat_messages = relationship( |
| "DBChatMessage", |
| back_populates="agent", |
| lazy="select", |
| cascade="all, delete-orphan" |
| ) |
| agent_sessions = relationship( |
| "DBAgentSession", |
| back_populates="agent", |
| lazy="select", |
| cascade="all, delete-orphan" |
| ) |
| |
| |
| __table_args__ = ( |
| Index('idx_agent_status_updated', 'status', 'updated_at'), |
| Index('idx_agent_type_status', 'agent_type', 'status'), |
| Index('idx_agent_created', 'created_at'), |
| ) |
| |
| def to_saap_agent(self): |
| """Convert database model to SaapAgent pydantic model""" |
| try: |
| from models.agent_schema import SaapAgent, AgentType, AgentStatus, LLMConfig, AgentMetadata, AgentAppearance, CommunicationConfig, UIComponents |
| |
| |
| agent_type = AgentType(self.agent_type) if hasattr(AgentType, self.agent_type) else AgentType.SPECIALIST |
| agent_status = AgentStatus(self.status) if hasattr(AgentStatus, self.status) else AgentStatus.INACTIVE |
| |
| |
| llm_config = LLMConfig(**self.llm_config) if self.llm_config else None |
| |
| |
| metadata = AgentMetadata( |
| version="1.0.0", |
| created=self.created_at, |
| updated=self.updated_at, |
| tags=self.tags or [] |
| ) |
| |
| |
| appearance = AgentAppearance( |
| color=self.color or "#6B7280", |
| avatar=self.avatar |
| ) |
| |
| |
| communication = CommunicationConfig( |
| input_queue=f"{self.id}_input", |
| output_queue=f"{self.id}_output", |
| message_types=["request", "response"] |
| ) |
| |
| |
| ui_components = UIComponents( |
| dashboard_widget="AgentCard", |
| detail_view="AgentDetail", |
| configuration_form="AgentConfig" |
| ) |
| |
| return SaapAgent( |
| id=self.id, |
| name=self.name, |
| type=agent_type, |
| status=agent_status, |
| description=self.description or "", |
| metadata=metadata, |
| appearance=appearance, |
| ui_components=ui_components, |
| capabilities=self.capabilities or [], |
| llm_config=llm_config, |
| communication=communication |
| ) |
| except Exception as e: |
| print(f"β οΈ Error converting DBAgent to SaapAgent: {e}") |
| import traceback |
| traceback.print_exc() |
| return None |
| |
| @classmethod |
| def from_saap_agent(cls, agent): |
| """Create database model from SaapAgent pydantic model (FIXED - accesses nested fields correctly)""" |
| try: |
| |
| color = agent.appearance.color if hasattr(agent, 'appearance') and agent.appearance else '#6B7280' |
| avatar = agent.appearance.avatar if hasattr(agent, 'appearance') and agent.appearance else None |
| |
| |
| last_active = None |
| if hasattr(agent, 'metrics') and agent.metrics: |
| last_active = getattr(agent.metrics, 'last_active', None) |
| |
| |
| created_at = None |
| updated_at = None |
| if hasattr(agent, 'metadata') and agent.metadata: |
| created_at = getattr(agent.metadata, 'created', None) |
| updated_at = getattr(agent.metadata, 'updated', None) |
| |
| |
| if not created_at and hasattr(agent, 'created_at'): |
| created_at = agent.created_at |
| if not updated_at and hasattr(agent, 'updated_at'): |
| updated_at = agent.updated_at |
| |
| |
| tags = [] |
| if hasattr(agent, 'tags'): |
| tags = agent.tags |
| elif hasattr(agent, 'metadata') and agent.metadata and hasattr(agent.metadata, 'tags'): |
| tags = agent.metadata.tags |
| |
| return cls( |
| id=agent.id, |
| name=agent.name, |
| agent_type=agent.type.value if hasattr(agent.type, 'value') else str(agent.type), |
| color=color, |
| avatar=avatar, |
| description=agent.description if hasattr(agent, 'description') else '', |
| llm_config=agent.llm_config.model_dump() if agent.llm_config else {}, |
| capabilities=agent.capabilities if hasattr(agent, 'capabilities') else [], |
| personality=agent.personality.model_dump() if hasattr(agent, 'personality') and agent.personality else None, |
| status=agent.status.value if hasattr(agent.status, 'value') else str(agent.status), |
| last_active=last_active, |
| metrics=agent.metrics.model_dump() if hasattr(agent, 'metrics') and agent.metrics else None, |
| created_at=created_at, |
| updated_at=updated_at, |
| tags=tags |
| ) |
| except Exception as e: |
| print(f"β οΈ Error creating DBAgent from SaapAgent: {e}") |
| import traceback |
| traceback.print_exc() |
| return None |
|
|
| class DBChatMessage(Base): |
| """ |
| Database model for chat message history (FIXED) |
| Stores all agent conversations with metadata |
| """ |
| __tablename__ = "chat_messages" |
| |
| |
| id = Column(Integer, primary_key=True, index=True, autoincrement=True) |
| |
| |
| agent_id = Column(String(100), ForeignKey("agents.id"), nullable=False, index=True) |
| |
| |
| user_message = Column(Text, nullable=False) |
| agent_response = Column(Text, nullable=False) |
| |
| |
| response_time = Column(Float, nullable=True) |
| tokens_used = Column(Integer, nullable=True) |
| |
| |
| message_metadata = Column(JSON, nullable=True) |
| created_at = Column(TIMESTAMP(timezone=True), default=func.now(), nullable=False, index=True) |
| |
| |
| agent = relationship( |
| "DBAgent", |
| back_populates="chat_messages", |
| lazy="select" |
| ) |
| |
| |
| __table_args__ = ( |
| Index('idx_chat_agent_created', 'agent_id', 'created_at'), |
| Index('idx_chat_response_time', 'response_time'), |
| Index('idx_chat_created', 'created_at'), |
| ) |
|
|
| class DBAgentSession(Base): |
| """ |
| Database model for agent sessions (FIXED) |
| Tracks agent uptime and session metrics |
| """ |
| __tablename__ = "agent_sessions" |
| |
| |
| id = Column(Integer, primary_key=True, index=True, autoincrement=True) |
| |
| |
| agent_id = Column(String(100), ForeignKey("agents.id"), nullable=False, index=True) |
| |
| |
| session_start = Column(TIMESTAMP(timezone=True), default=func.now(), nullable=False, index=True) |
| session_end = Column(TIMESTAMP(timezone=True), nullable=True, index=True) |
| duration_seconds = Column(Integer, nullable=True) |
| |
| |
| messages_processed = Column(Integer, default=0, nullable=False) |
| total_tokens_used = Column(Integer, default=0, nullable=False) |
| average_response_time = Column(Float, nullable=True) |
| error_count = Column(Integer, default=0, nullable=False) |
| |
| |
| status = Column(String(20), default="active", nullable=False, index=True) |
| end_reason = Column(String(100), nullable=True) |
| |
| |
| session_metadata = Column(JSON, nullable=True) |
| |
| |
| agent = relationship( |
| "DBAgent", |
| back_populates="agent_sessions", |
| lazy="select" |
| ) |
| |
| |
| __table_args__ = ( |
| Index('idx_session_agent_start', 'agent_id', 'session_start'), |
| Index('idx_session_status', 'status'), |
| Index('idx_session_duration', 'duration_seconds'), |
| ) |
| |
| def calculate_duration(self): |
| """Calculate and update session duration""" |
| if self.session_end and self.session_start: |
| delta = self.session_end - self.session_start |
| self.duration_seconds = int(delta.total_seconds()) |
| return self.duration_seconds |
|
|
| class DBSystemLog(Base): |
| """ |
| Database model for system logs and events |
| Stores SAAP system events for monitoring and debugging |
| """ |
| __tablename__ = "system_logs" |
| |
| |
| id = Column(Integer, primary_key=True, index=True, autoincrement=True) |
| |
| |
| level = Column(String(10), nullable=False, index=True) |
| logger_name = Column(String(100), nullable=False, index=True) |
| message = Column(Text, nullable=False) |
| |
| |
| agent_id = Column(String(100), nullable=True, index=True) |
| function_name = Column(String(100), nullable=True) |
| line_number = Column(Integer, nullable=True) |
| |
| |
| extra_data = Column(JSON, nullable=True) |
| |
| |
| created_at = Column(TIMESTAMP(timezone=True), default=func.now(), nullable=False, index=True) |
| |
| |
| __table_args__ = ( |
| Index('idx_log_level_created', 'level', 'created_at'), |
| Index('idx_log_agent_created', 'agent_id', 'created_at'), |
| Index('idx_log_logger_created', 'logger_name', 'created_at'), |
| ) |
|
|
| class DBHealthCheck(Base): |
| """ |
| Database model for system health monitoring |
| Stores periodic health check results |
| """ |
| __tablename__ = "health_checks" |
| |
| |
| id = Column(Integer, primary_key=True, index=True, autoincrement=True) |
| |
| |
| component = Column(String(50), nullable=False, index=True) |
| status = Column(String(20), nullable=False, index=True) |
| response_time_ms = Column(Float, nullable=True) |
| |
| |
| agent_count = Column(Integer, default=0) |
| active_agent_count = Column(Integer, default=0) |
| memory_usage_mb = Column(Float, nullable=True) |
| cpu_usage_percent = Column(Float, nullable=True) |
| |
| |
| details = Column(JSON, nullable=True) |
| error_message = Column(Text, nullable=True) |
| |
| |
| created_at = Column(TIMESTAMP(timezone=True), default=func.now(), nullable=False, index=True) |
| |
| |
| __table_args__ = ( |
| Index('idx_health_component_created', 'component', 'created_at'), |
| Index('idx_health_status_created', 'status', 'created_at'), |
| ) |
|
|
| |
| def get_table_info(): |
| """Get information about all database tables""" |
| tables = {} |
| |
| for table_name, table in Base.metadata.tables.items(): |
| tables[table_name] = { |
| "columns": [col.name for col in table.columns], |
| "indexes": [idx.name for idx in table.indexes], |
| "primary_keys": [col.name for col in table.primary_key.columns] |
| } |
| |
| return tables |
|
|
| if __name__ == "__main__": |
| |
| print("ποΈ SAAP Database Schema (FIXED):") |
| print("=" * 50) |
| |
| tables = get_table_info() |
| for table_name, info in tables.items(): |
| print(f"\nπ Table: {table_name}") |
| print(f" Columns: {', '.join(info['columns'])}") |
| print(f" Indexes: {', '.join(info['indexes'])}") |
| print(f" Primary Keys: {', '.join(info['primary_keys'])}") |
| |
| print(f"\nβ
Total Tables: {len(tables)}") |
| print("β
Database schema FIXED - no more circular references!") |
| print("π§ Key fixes applied:") |
| print(" - Added lazy='select' to all relationships") |
| print(" - Safe conversion methods with error handling") |
| print(" - Prevented recursive model loading") |
|
|