Files
lollms_hub/app/database/models.py
Saifeddine ALOUI 2d2bc728c0 feat: enhance system architecture, introduce space_hunter, and update admin interfaces
Summary of Changes:
- [INFRA] Added `space_hunter.py` to the project root for new system utilities.
- [CONFIG] Updated `.gitignore` to include `*.db-wal` files and exclude `docs/article` directory.
- [UI] Refreshed admin templates (`models_manager.html`, `settings.html`) to reflect new architectural capabilities.
- [CORE] Modified `architect_manager.py` to support image generation capabilities (`supports_images`).
- [CORE] Updated `memory_manager.py` to improve context formatting and category handling.
- [API] Implemented new route logic and rate limiting dependencies in `proxy.py` and `admin.py`.
- [MODELS] Updated `models.py` to support new fields and `migrations.py` to handle schema evolution.

Technical Notes:
- The `architect_manager` now manages assets including image nodes.
- Memory formatting has been streamlined to handle multiple titles per category.
- Admin settings and model management pages have been updated to visualize these new capabilities.

BREAKING CHANGE: None.
2026-04-17 19:22:34 +02:00

323 lines
14 KiB
Python

import datetime
from sqlalchemy import (
Column,
Integer,
Float,
String,
Boolean,
DateTime,
ForeignKey,
UniqueConstraint,
JSON,
TEXT
)
from sqlalchemy.orm import relationship
from app.database.base import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True, nullable=False)
hashed_password = Column(String, nullable=False)
is_active = Column(Boolean, default=True)
is_admin = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
api_keys = relationship("APIKey", back_populates="user", cascade="all, delete-orphan")
class APIKey(Base):
__tablename__ = "api_keys"
id = Column(Integer, primary_key=True, index=True)
key_name = Column(String, nullable=False)
hashed_key = Column(String, unique=True, index=True, nullable=False)
key_prefix = Column(String, unique=True, nullable=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
expires_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
is_active = Column(Boolean, default=True, nullable=False)
is_revoked = Column(Boolean, default=False, nullable=False)
rate_limit_requests = Column(Integer, nullable=True)
rate_limit_window_minutes = Column(Integer, nullable=True)
user = relationship("User", back_populates="api_keys")
usage_logs = relationship("UsageLog", back_populates="api_key", cascade="all, delete-orphan")
__table_args__ = (UniqueConstraint("user_id", "key_name", name="uq_user_key_name"),)
class UsageLog(Base):
__tablename__ = "usage_logs"
id = Column(Integer, primary_key=True, index=True)
api_key_id = Column(Integer, ForeignKey("api_keys.id"), nullable=False)
endpoint = Column(String, nullable=False)
status_code = Column(Integer, nullable=False)
request_timestamp = Column(DateTime, default=datetime.datetime.utcnow)
server_id = Column(Integer, ForeignKey("ollama_servers.id"), nullable=True)
model = Column(String, nullable=True, index=True)
# Token usage tracking
prompt_tokens = Column(Integer, nullable=True)
completion_tokens = Column(Integer, nullable=True)
total_tokens = Column(Integer, nullable=True)
api_key = relationship("APIKey", back_populates="usage_logs")
server = relationship("OllamaServer")
class OllamaServer(Base):
__tablename__ = "ollama_servers"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
url = Column(String, nullable=False)
server_type = Column(String, nullable=False, default="ollama", server_default="ollama")
encrypted_api_key = Column(String, nullable=True)
is_active = Column(Boolean, default=True)
max_parallel_queries = Column(Integer, default=1, nullable=False)
available_models = Column(JSON, nullable=True)
allowed_models = Column(JSON, nullable=True) # Whitelist of model names
models_last_updated = Column(DateTime, nullable=True)
last_error = Column(String, nullable=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
@property
def has_api_key(self) -> bool:
return bool(self.encrypted_api_key)
class BenchmarkDataset(Base):
__tablename__ = "benchmark_datasets"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True, nullable=False)
description = Column(String, nullable=True)
content = Column(JSON, nullable=False)
dataset_card = Column(TEXT, nullable=True) # HF format Markdown
created_at = Column(DateTime, default=datetime.datetime.utcnow)
class BenchmarkRun(Base):
__tablename__ = "benchmark_runs"
id = Column(Integer, primary_key=True, index=True)
dataset_id = Column(Integer, ForeignKey("benchmark_datasets.id"), nullable=False)
name = Column(String, nullable=False)
models = Column(JSON, nullable=False)
evaluator_config = Column(JSON, nullable=False)
results = Column(JSON, nullable=False)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
class AppSettings(Base):
__tablename__ = "app_settings"
id = Column(Integer, primary_key=True)
settings_data = Column(JSON, nullable=False)
class ManagedInstance(Base):
__tablename__ = "managed_instances"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
backend_type = Column(String, default="ollama") # ollama, llamacpp, vllm
port = Column(Integer, nullable=False, unique=True)
gpu_ids = Column(String, nullable=True)
model_path = Column(String, nullable=True) # Used by llamacpp/vllm
# llamacpp specific
n_gpu_layers = Column(Integer, default=99)
ctx_size = Column(Integer, default=8192)
threads = Column(Integer, default=8)
# vllm specific
tensor_parallel_size = Column(Integer, default=1)
is_enabled = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
class ModelMetadata(Base):
__tablename__ = "model_metadata"
id = Column(Integer, primary_key=True, index=True)
model_name = Column(String, unique=True, index=True, nullable=False)
description = Column(String, nullable=True)
supports_images = Column(Boolean, default=False, nullable=False)
is_code_model = Column(Boolean, default=False, nullable=False)
supports_thinking = Column(Boolean, default=False, nullable=False)
is_chat_model = Column(Boolean, default=True, nullable=False)
is_embedding_model = Column(Boolean, default=False, nullable=False)
is_fast_model = Column(Boolean, default=False, nullable=False)
is_reasoning_model = Column(Boolean, default=False, nullable=False)
max_context = Column(Integer, default=4096, nullable=False)
priority = Column(Integer, default=10, nullable=False)
model_scale = Column(Integer, default=1, nullable=False) # 1: Small, 2: Medium, 3: Large
model_size = Column(Float, default=-1.0, nullable=False) # Parameters in Billions (-1.0 for unknown)
__table_args__ = (UniqueConstraint("model_name", name="uq_model_name"),)
class EnsembleOrchestrator(Base):
__tablename__ = "model_bundles" # Keep table name for data stability
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True, nullable=False)
description = Column(String, nullable=True)
parallel_participants = Column(JSON, nullable=False) # Names of models or agents
# Legacy support: matching the physical NOT NULL constraint in older DBs
parallel_models = Column(JSON, nullable=True)
master_model = Column(String, nullable=False)
vision_processor = Column(String, nullable=True) # Model used to extract image descriptions (for multimodal bundles)
show_monologue = Column(Boolean, default=False)
send_status_update = Column(Boolean, default=False)
report_success_failure = Column(Boolean, default=False) # NEW: Reports which agents worked
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
def __init__(self, **kwargs):
# Synchronize parallel_participants and parallel_models for DB compatibility
if 'parallel_participants' in kwargs and 'parallel_models' not in kwargs:
kwargs['parallel_models'] = kwargs['parallel_participants']
super().__init__(**kwargs)
class ChainOrchestrator(Base):
__tablename__ = "model_chains"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True, nullable=False)
description = Column(String, nullable=True)
steps = Column(JSON, nullable=False) # List of model names:["vision-model", "text-model"]
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
class SmartRouter(Base): # Keep table name for data stability
__tablename__ = "model_pools"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True, nullable=False)
description = Column(String, nullable=True)
targets = Column(JSON, nullable=False) # Names of models or agents
strategy = Column(String, default='priority', nullable=False)
# The model used to classify intent if fast rules fail
classifier_model = Column(String, nullable=True)
# Structured as List[RuleGroup]
rules = Column(JSON, nullable=True)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
# LEGACY: This field is deprecated, kept for backward compatibility with old DBs
# New code should use 'targets' instead
models = Column(JSON, nullable=True)
def __init__(self, **kwargs):
# Sync targets to models for backward compatibility
if 'targets' in kwargs and 'models' not in kwargs:
kwargs['models'] = kwargs['targets']
super().__init__(**kwargs)
class LogAnalysis(Base):
__tablename__ = "log_analyses"
id = Column(Integer, primary_key=True, index=True)
timestamp = Column(DateTime, default=datetime.datetime.utcnow, index=True)
content = Column(String, nullable=False)
class VisionAugmenter(Base):
__tablename__ = "vision_augmenters"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True, nullable=False)
text_model = Column(String, nullable=False)
vision_model = Column(String, nullable=False)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
class VirtualAgent(Base):
__tablename__ = "virtual_agents"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True, nullable=False)
description = Column(String, nullable=True)
base_model = Column(String, nullable=False)
system_prompt = Column(String, nullable=False) # The "Soul"
# List of SKILL filenames to inject
skills = Column(JSON, nullable=True, default=list)
# MCP: List of Model Context Protocol server configurations (Includes RAG, Tools, etc)
mcp_servers = Column(JSON, nullable=True)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
class MemoryEntry(Base):
__tablename__ = "memory_entries"
id = Column(Integer, primary_key=True, index=True)
user_identifier = Column(String, nullable=False, index=True)
agent_name = Column(String, nullable=False, index=True)
category = Column(String, nullable=False, index=True) # 'living', 'rom_core', 'rom_deep'
is_immutable = Column(Boolean, default=False) # True for Lollms Tool Knowledge
title = Column(String, nullable=False)
content = Column(String, nullable=False)
importance = Column(Integer, default=50)
last_accessed = Column(DateTime, default=datetime.datetime.utcnow)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
class UserToolData(Base):
__tablename__ = "user_tool_data"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
library_name = Column(String, nullable=False, index=True)
key = Column(String, nullable=False, index=True)
value = Column(JSON, nullable=False)
is_persistent = Column(Boolean, default=True)
updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
__table_args__ = (UniqueConstraint("user_id", "library_name", "key", name="uq_user_tool_key"),)
class BotConfig(Base):
__tablename__ = "bot_configs"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
platform = Column(String, nullable=False) # 'telegram', 'discord', 'slack'
encrypted_token = Column(String, nullable=False)
target_workflow = Column(String, nullable=False)
is_active = Column(Boolean, default=False)
history_limit = Column(Integer, default=10) # How many messages/tokens to keep
history_limit_unit = Column(String, default="messages") # 'messages' or 'tokens'
# Extra config like specific channel IDs or server IDs
extra_settings = Column(JSON, nullable=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
class DreamLog(Base):
__tablename__ = "dream_logs"
id = Column(Integer, primary_key=True, index=True)
memory_system = Column(String, index=True, nullable=False)
user_identifier = Column(String, index=True, nullable=False)
summary = Column(String, nullable=False)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
class MemorySystem(Base):
__tablename__ = "memory_systems"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True, nullable=False)
description = Column(String, nullable=True)
system_instruction = Column(TEXT, nullable=False) # The prompt telling the AI how to use tags
importance_decay = Column(Integer, default=2) # Points lost per maintenance cycle
use_affective = Column(Boolean, default=False) # Default to deactivated
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
class Workflow(Base):
__tablename__ = "workflows"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True, nullable=False)
description = Column(String, nullable=True)
graph_data = Column(JSON, nullable=False) # LiteGraph JSON state
# Type hints for the proxy (e.g. 'chain', 'ensemble', 'router')
workflow_type = Column(String, default="custom")
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
class DataStore(Base):
__tablename__ = "datastores"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True, nullable=False)
description = Column(String, nullable=True)
db_path = Column(String, nullable=False)
vectorizer_name = Column(String, nullable=False, default="tf_idf")
chunking_strategy = Column(String, default="recursive")
chunk_size = Column(Integer, default=512)
chunk_overlap = Column(Integer, default=50)
vectorizer_config = Column(JSON, nullable=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow)