179 lines
6.5 KiB
Python
179 lines
6.5 KiB
Python
|
|
"""SQLAlchemy models for the AI Assistant plugin."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import uuid
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
from sqlalchemy import (
|
||
|
|
Boolean,
|
||
|
|
DateTime,
|
||
|
|
Float,
|
||
|
|
ForeignKey,
|
||
|
|
Index,
|
||
|
|
Integer,
|
||
|
|
String,
|
||
|
|
Text,
|
||
|
|
)
|
||
|
|
from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID
|
||
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
||
|
|
|
||
|
|
from app.core.db import Base, TenantMixin
|
||
|
|
|
||
|
|
|
||
|
|
# --- Providers ---
|
||
|
|
|
||
|
|
class AIProvider(Base, TenantMixin):
|
||
|
|
"""LLM provider configuration (OpenAI, Anthropic, Ollama, etc.)."""
|
||
|
|
|
||
|
|
__tablename__ = "ai_providers"
|
||
|
|
__table_args__ = (
|
||
|
|
Index("ix_ai_providers_tenant", "tenant_id"),
|
||
|
|
)
|
||
|
|
|
||
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||
|
|
)
|
||
|
|
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||
|
|
provider_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
||
|
|
api_key: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||
|
|
base_url: Mapped[str] = mapped_column(String(500), nullable=False, default="")
|
||
|
|
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||
|
|
is_default: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||
|
|
config: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
||
|
|
|
||
|
|
|
||
|
|
# --- Models ---
|
||
|
|
|
||
|
|
class AIModel(Base, TenantMixin):
|
||
|
|
"""Available model per provider."""
|
||
|
|
|
||
|
|
__tablename__ = "ai_models"
|
||
|
|
__table_args__ = (
|
||
|
|
Index("ix_ai_models_provider", "provider_id"),
|
||
|
|
Index("ix_ai_models_tenant", "tenant_id"),
|
||
|
|
)
|
||
|
|
|
||
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||
|
|
)
|
||
|
|
provider_id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True),
|
||
|
|
ForeignKey("ai_providers.id", ondelete="CASCADE"),
|
||
|
|
nullable=False,
|
||
|
|
)
|
||
|
|
model_id: Mapped[str] = mapped_column(String(200), nullable=False)
|
||
|
|
display_name: Mapped[str] = mapped_column(String(200), nullable=False)
|
||
|
|
context_window: Mapped[int] = mapped_column(Integer, nullable=False, default=4096)
|
||
|
|
supports_tools: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||
|
|
supports_streaming: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||
|
|
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||
|
|
config: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
||
|
|
|
||
|
|
|
||
|
|
# --- Presets ---
|
||
|
|
|
||
|
|
class AIPreset(Base, TenantMixin):
|
||
|
|
"""Model preset: model + parameters + optional system prompt."""
|
||
|
|
|
||
|
|
__tablename__ = "ai_presets"
|
||
|
|
__table_args__ = (
|
||
|
|
Index("ix_ai_presets_tenant", "tenant_id"),
|
||
|
|
)
|
||
|
|
|
||
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||
|
|
)
|
||
|
|
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||
|
|
model_id: Mapped[str] = mapped_column(String(200), nullable=False)
|
||
|
|
provider_id: Mapped[uuid.UUID | None] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True),
|
||
|
|
ForeignKey("ai_providers.id", ondelete="SET NULL"),
|
||
|
|
nullable=True,
|
||
|
|
)
|
||
|
|
temperature: Mapped[float] = mapped_column(Float, nullable=False, default=0.7)
|
||
|
|
max_tokens: Mapped[int] = mapped_column(Integer, nullable=False, default=2048)
|
||
|
|
top_p: Mapped[float] = mapped_column(Float, nullable=False, default=1.0)
|
||
|
|
system_prompt: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||
|
|
config: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
||
|
|
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||
|
|
|
||
|
|
|
||
|
|
# --- Agents ---
|
||
|
|
|
||
|
|
class AIAgent(Base, TenantMixin):
|
||
|
|
"""AI agent with system prompt and assigned tools."""
|
||
|
|
|
||
|
|
__tablename__ = "ai_agents"
|
||
|
|
__table_args__ = (
|
||
|
|
Index("ix_ai_agents_tenant", "tenant_id"),
|
||
|
|
)
|
||
|
|
|
||
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||
|
|
)
|
||
|
|
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||
|
|
description: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||
|
|
system_prompt: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||
|
|
preset_id: Mapped[uuid.UUID | None] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True),
|
||
|
|
ForeignKey("ai_presets.id", ondelete="SET NULL"),
|
||
|
|
nullable=True,
|
||
|
|
)
|
||
|
|
tool_ids: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
|
||
|
|
is_default: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||
|
|
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||
|
|
config: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
||
|
|
|
||
|
|
|
||
|
|
# --- Chat Sessions ---
|
||
|
|
|
||
|
|
class AIChatSession(Base, TenantMixin):
|
||
|
|
"""Chat session for a user with a specific agent."""
|
||
|
|
|
||
|
|
__tablename__ = "ai_chat_sessions"
|
||
|
|
__table_args__ = (
|
||
|
|
Index("ix_ai_sessions_user", "user_id"),
|
||
|
|
Index("ix_ai_sessions_tenant", "tenant_id"),
|
||
|
|
)
|
||
|
|
|
||
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||
|
|
)
|
||
|
|
user_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
|
||
|
|
agent_id: Mapped[uuid.UUID | None] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True),
|
||
|
|
ForeignKey("ai_agents.id", ondelete="SET NULL"),
|
||
|
|
nullable=True,
|
||
|
|
)
|
||
|
|
title: Mapped[str] = mapped_column(String(255), nullable=False, default="Neuer Chat")
|
||
|
|
is_pinned: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||
|
|
is_sidebar: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||
|
|
|
||
|
|
|
||
|
|
# --- Chat Messages ---
|
||
|
|
|
||
|
|
class AIChatMessage(Base, TenantMixin):
|
||
|
|
"""Individual message in a chat session."""
|
||
|
|
|
||
|
|
__tablename__ = "ai_chat_messages"
|
||
|
|
__table_args__ = (
|
||
|
|
Index("ix_ai_messages_session", "session_id"),
|
||
|
|
Index("ix_ai_messages_tenant", "tenant_id"),
|
||
|
|
)
|
||
|
|
|
||
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||
|
|
)
|
||
|
|
session_id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True),
|
||
|
|
ForeignKey("ai_chat_sessions.id", ondelete="CASCADE"),
|
||
|
|
nullable=False,
|
||
|
|
)
|
||
|
|
role: Mapped[str] = mapped_column(String(20), nullable=False)
|
||
|
|
content: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||
|
|
tool_calls: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
||
|
|
tool_results: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
||
|
|
tokens: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||
|
|
model_used: Mapped[str] = mapped_column(String(200), nullable=False, default="")
|