/
alexefan136
/
flowstack
Обзор
Документация
Войти
/
alexefan136
/
flowstack
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
core/engine/src/db/models.py
486 строк
19 KB
Alexander Efanov
upd fix
31 июл 2026, 19:17
31 июл 2026, 19:17
d146d86
Код
Авторство
О чём код?
"""SQLAlchemy модели для FlowStack Engine.""" from __future__ import annotations import uuid from datetime import datetime from typing import Any from sqlalchemy import ( Boolean, DateTime, Float, ForeignKey, Index, Integer, String, Text, UniqueConstraint, ) from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.sql import func from src.db.session import Base class User(Base): """Пользователь системы (кэш/ссылка на пользователя из Gateway).""" __tablename__ = "users" __table_args__ = ( Index("idx_users_workspace_id", "workspace_id"), Index("idx_users_email", "email"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True) email: Mapped[str | None] = mapped_column(String(255), unique=True) workspace_id: Mapped[str] = mapped_column(String(100), nullable=False) display_name: Mapped[str | None] = mapped_column(String(255)) preferences: Mapped[dict[str, Any]] = mapped_column(JSONB, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) chats: Mapped[list[Chat]] = relationship( back_populates="user", cascade="all, delete-orphan", foreign_keys="[Chat.user_id]", primaryjoin="User.id == Chat.user_id", lazy="noload", ) tasks: Mapped[list[Task]] = relationship( back_populates="user", cascade="all, delete-orphan", foreign_keys="[Task.user_id]", primaryjoin="User.id == Task.user_id", lazy="noload", ) skills: Mapped[list[Skill]] = relationship( back_populates="user", cascade="all, delete-orphan", foreign_keys="[Skill.user_id]", primaryjoin="User.id == Skill.user_id", lazy="noload", ) agents: Mapped[list[Agent]] = relationship( back_populates="user", cascade="all, delete-orphan", foreign_keys="[Agent.user_id]", primaryjoin="User.id == Agent.user_id", lazy="noload", ) flows: Mapped[list[Flow]] = relationship( cascade="all, delete-orphan", foreign_keys="[Flow.user_id]", primaryjoin="User.id == Flow.user_id", lazy="noload", ) mcp_servers: Mapped[list[MCPServer]] = relationship( back_populates="user", cascade="all, delete-orphan", foreign_keys="[MCPServer.user_id]", primaryjoin="User.id == MCPServer.user_id", lazy="noload", ) def __repr__(self) -> str: return f"<User {self.email or self.id}>" class Chat(Base): """Чат пользователя.""" __tablename__ = "chats" __table_args__ = ( Index("idx_chats_user_id", "user_id"), Index("idx_chats_workspace_id", "workspace_id"), Index("idx_chats_created_at", "created_at"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False) workspace_id: Mapped[str] = mapped_column(String(100), nullable=False) title: Mapped[str | None] = mapped_column(String(500)) rag_enabled: Mapped[bool] = mapped_column(Boolean, default=False) rag_workspace_id: Mapped[str | None] = mapped_column(String(100)) # ✅ Default соответствует проекту (Cloud.ru) model: Mapped[str] = mapped_column(String(100), default="deepseek-ai/DeepSeek-V4-Pro") temperature: Mapped[float] = mapped_column(Float, default=0.7) top_k: Mapped[int] = mapped_column(Integer, default=5) metadata_: Mapped[dict[str, Any]] = mapped_column("metadata", JSONB, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) user: Mapped[User] = relationship( back_populates="chats", foreign_keys="[Chat.user_id]", primaryjoin="Chat.user_id == User.id", lazy="noload", ) messages: Mapped[list[Message]] = relationship( back_populates="chat", cascade="all, delete-orphan", order_by="Message.created_at", lazy="noload", ) memories: Mapped[list[ChatMemory]] = relationship( back_populates="chat", cascade="all, delete-orphan", lazy="noload", ) def __repr__(self) -> str: return f"<Chat {self.title or self.id}>" class Message(Base): """Сообщение в чате.""" __tablename__ = "messages" __table_args__ = ( Index("idx_messages_chat_id", "chat_id"), Index("idx_messages_created_at", "created_at"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) chat_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("chats.id", ondelete="CASCADE"), nullable=False ) role: Mapped[str] = mapped_column(String(20), nullable=False) content: Mapped[str] = mapped_column(Text, nullable=False) sources: Mapped[list[Any]] = mapped_column(JSONB, default=list) tokens_used: Mapped[int] = mapped_column(Integer, default=0) metadata_: Mapped[dict[str, Any]] = mapped_column("metadata", JSONB, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) chat: Mapped[Chat] = relationship( back_populates="messages", lazy="noload", ) def __repr__(self) -> str: return f"<Message {self.role} ({self.id})>" class ChatMemory(Base): """Долгосрочная память чата.""" __tablename__ = "chat_memories" __table_args__ = ( Index("idx_chat_memories_chat_id", "chat_id"), Index("idx_chat_memories_key", "key"), # ✅ Защита от дубликатов при race condition UniqueConstraint("chat_id", "key", name="uq_chat_memories_chat_key"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) chat_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("chats.id", ondelete="CASCADE"), nullable=False ) key: Mapped[str] = mapped_column(String(255), nullable=False) value: Mapped[str] = mapped_column(Text, nullable=False) metadata_: Mapped[dict[str, Any]] = mapped_column("metadata", JSONB, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) chat: Mapped[Chat] = relationship( back_populates="memories", lazy="noload", ) def __repr__(self) -> str: return f"<ChatMemory {self.key}>" class Task(Base): """Пользовательская задача.""" __tablename__ = "tasks" __table_args__ = ( Index("idx_tasks_user_id", "user_id"), Index("idx_tasks_workspace_id", "workspace_id"), Index("idx_tasks_status", "status"), Index("idx_tasks_priority", "priority"), Index("idx_tasks_deadline", "deadline"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False) workspace_id: Mapped[str] = mapped_column(String(100), nullable=False) title: Mapped[str] = mapped_column(String(500), nullable=False) description: Mapped[str | None] = mapped_column(Text) status: Mapped[str] = mapped_column(String(20), default="todo") priority: Mapped[str] = mapped_column(String(20), default="medium") deadline: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) assigned_agent_id: Mapped[str | None] = mapped_column(String(100)) skill_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("skills.id", ondelete="SET NULL") ) workflow_id: Mapped[str | None] = mapped_column(String(100)) chat_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("chats.id", ondelete="SET NULL") ) recurrence_pattern: Mapped[str | None] = mapped_column(String(100)) tags: Mapped[list[str]] = mapped_column(JSONB, default=list) estimated_duration_minutes: Mapped[int | None] = mapped_column(Integer) actual_duration_minutes: Mapped[int | None] = mapped_column(Integer) runs: Mapped[list[dict[str, Any]]] = mapped_column(JSONB, default=list) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) user: Mapped[User] = relationship( back_populates="tasks", foreign_keys="[Task.user_id]", primaryjoin="Task.user_id == User.id", lazy="noload", ) skill: Mapped[Skill | None] = relationship( back_populates="tasks", foreign_keys="[Task.skill_id]", lazy="noload", ) chat: Mapped[Chat | None] = relationship( foreign_keys="[Task.chat_id]", lazy="noload", ) def __repr__(self) -> str: return f"<Task {self.title} ({self.status})>" class Skill(Base): """Скилл — переиспользуемый шаблон промпта.""" __tablename__ = "skills" __table_args__ = ( Index("idx_skills_user_id", "user_id"), Index("idx_skills_workspace_id", "workspace_id"), Index("idx_skills_category", "category"), Index("idx_skills_is_public", "is_public"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False) workspace_id: Mapped[str] = mapped_column(String(100), nullable=False) name: Mapped[str] = mapped_column(String(200), nullable=False) description: Mapped[str | None] = mapped_column(Text) icon: Mapped[str] = mapped_column(String(50), default="Sparkles") category: Mapped[str] = mapped_column(String(50), default="productivity") prompt_template: Mapped[str] = mapped_column(Text, nullable=False) parameters: Mapped[list[dict[str, Any]]] = mapped_column(JSONB, default=list) default_model: Mapped[str] = mapped_column(String(100), default="deepseek-ai/DeepSeek-V4-Pro") agent_compatible: Mapped[bool] = mapped_column(Boolean, default=True) tags: Mapped[list[str]] = mapped_column(JSONB, default=list) is_public: Mapped[bool] = mapped_column(Boolean, default=False) version: Mapped[int] = mapped_column(Integer, default=1) complexity: Mapped[str | None] = mapped_column(String(20)) expected_duration_seconds: Mapped[int | None] = mapped_column(Integer) examples: Mapped[list[dict[str, Any]]] = mapped_column(JSONB, default=list) usage_count: Mapped[int] = mapped_column(Integer, default=0) average_rating: Mapped[float | None] = mapped_column(Float) is_archived: Mapped[bool] = mapped_column(Boolean, default=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) user: Mapped[User] = relationship( back_populates="skills", foreign_keys="[Skill.user_id]", primaryjoin="Skill.user_id == User.id", lazy="noload", ) tasks: Mapped[list[Task]] = relationship( back_populates="skill", foreign_keys="[Task.skill_id]", lazy="noload", ) def __repr__(self) -> str: return f"<Skill {self.name} ({self.category})>" class Agent(Base): """AI агент с конфигурацией и статистикой использования.""" __tablename__ = "agents" __table_args__ = ( Index("idx_agents_user_id", "user_id"), Index("idx_agents_workspace_id", "workspace_id"), Index("idx_agents_agent_type", "agent_type"), Index("idx_agents_status", "status"), Index("idx_agents_model", "model"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False) workspace_id: Mapped[str] = mapped_column(String(100), nullable=False) name: Mapped[str] = mapped_column(String(200), nullable=False) description: Mapped[str | None] = mapped_column(Text) system_prompt: Mapped[str] = mapped_column(Text, nullable=False) model: Mapped[str] = mapped_column(String(100), default="deepseek-ai/DeepSeek-V4-Pro") agent_type: Mapped[str] = mapped_column(String(50), default="researcher") tools: Mapped[list[str]] = mapped_column(JSONB, default=list) mcp_server_ids: Mapped[list[str]] = mapped_column(JSONB, default=list) status: Mapped[str] = mapped_column(String(20), default="idle") current_task_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True)) tasks_count: Mapped[int] = mapped_column(Integer, default=0) total_tokens_used: Mapped[int] = mapped_column(Integer, default=0) total_cost_usd: Mapped[float] = mapped_column(Float, default=0.0) success_rate: Mapped[float] = mapped_column(Float, default=0.0) tags: Mapped[list[str]] = mapped_column(JSONB, default=list) metadata_: Mapped[dict[str, Any]] = mapped_column("metadata", JSONB, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) last_run_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) user: Mapped[User] = relationship( back_populates="agents", foreign_keys="[Agent.user_id]", primaryjoin="Agent.user_id == User.id", lazy="noload", ) def __repr__(self) -> str: return f"<Agent {self.name} ({self.agent_type})>" class Flow(Base): """Flow — граф агентов (nodes + edges).""" __tablename__ = "flows" __table_args__ = ( Index("idx_flows_user_id", "user_id"), Index("idx_flows_workspace_id", "workspace_id"), Index("idx_flows_status", "status"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False) workspace_id: Mapped[str] = mapped_column(String(100), nullable=False) name: Mapped[str] = mapped_column(String(200), nullable=False) description: Mapped[str | None] = mapped_column(Text) nodes: Mapped[list[dict[str, Any]]] = mapped_column(JSONB, default=list) edges: Mapped[list[dict[str, Any]]] = mapped_column(JSONB, default=list) status: Mapped[str] = mapped_column(String(20), default="draft") tags: Mapped[list[str]] = mapped_column(JSONB, default=list) metadata_: Mapped[dict[str, Any]] = mapped_column("metadata", JSONB, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) user: Mapped[User] = relationship( back_populates="flows", foreign_keys="[Flow.user_id]", primaryjoin="Flow.user_id == User.id", lazy="noload", ) def __repr__(self) -> str: return f"<Flow {self.name} ({self.status})>" class MCPServer(Base): """MCP Server — Model Context Protocol сервер.""" __tablename__ = "mcp_servers" __table_args__ = ( Index("idx_mcp_servers_user_id", "user_id"), Index("idx_mcp_servers_workspace_id", "workspace_id"), Index("idx_mcp_servers_type", "type"), Index("idx_mcp_servers_status", "status"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False) workspace_id: Mapped[str] = mapped_column(String(100), nullable=False) name: Mapped[str] = mapped_column(String(200), nullable=False) description: Mapped[str | None] = mapped_column(Text) icon: Mapped[str] = mapped_column(String(50), default="Server") type: Mapped[str] = mapped_column(String(20), nullable=False) url: Mapped[str | None] = mapped_column(String(500)) command: Mapped[str | None] = mapped_column(String(500)) args: Mapped[list[str]] = mapped_column(JSONB, default=list) env: Mapped[dict[str, str]] = mapped_column(JSONB, default=dict) # ⚠️ Секреты — рекомендуется шифровать (Fernet) или использовать vault auth: Mapped[dict[str, Any] | None] = mapped_column(JSONB) tools: Mapped[list[dict[str, Any]]] = mapped_column(JSONB, default=list) resources: Mapped[list[dict[str, Any]]] = mapped_column(JSONB, default=list) is_active: Mapped[bool] = mapped_column(Boolean, default=True) auto_start: Mapped[bool] = mapped_column(Boolean, default=False) timeout_seconds: Mapped[int] = mapped_column(Integer, default=30) tags: Mapped[list[str]] = mapped_column(JSONB, default=list) is_public: Mapped[bool] = mapped_column(Boolean, default=False) version: Mapped[str] = mapped_column(String(20), default="1.0.0") usage_count: Mapped[int] = mapped_column(Integer, default=0) last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) error_count: Mapped[int] = mapped_column(Integer, default=0) status: Mapped[str] = mapped_column(String(20), default="stopped") last_error: Mapped[str | None] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) user: Mapped[User] = relationship( back_populates="mcp_servers", foreign_keys="[MCPServer.user_id]", primaryjoin="MCPServer.user_id == User.id", lazy="noload", ) def __repr__(self) -> str: return f"<MCPServer {self.name} ({self.type})>" __all__ = [ "Agent", "Chat", "ChatMemory", "Flow", "MCPServer", "Message", "Skill", "Task", "User", ]