diff --git a/apps/mlstore/__init__.py b/apps/mlstore/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/mlstore/admin.py b/apps/mlstore/admin.py new file mode 100644 index 0000000..770593c --- /dev/null +++ b/apps/mlstore/admin.py @@ -0,0 +1,57 @@ +from django.contrib import admin +from django.contrib.admin import ModelAdmin, TabularInline +from apps.mlstore.models import AgentModel, AgentRun, Agent, AgentEvent + + +class AgentInline(TabularInline): + model = Agent + extra = 0 + raw_id_fields = ('model',) + + +class AgentRunInline(TabularInline): + model = AgentRun + extra = 0 + raw_id_fields = ('agent', 'user') + + +class AgentEventInline(TabularInline): + model = AgentEvent + extra = 0 + raw_id_fields = ('execution',) + + +@admin.register(AgentModel) +class AgentModelAdmin(ModelAdmin): + list_display = ('id', 'uuid', 'name', 'version') + search_fields = ('name', 'version') + inlines = (AgentInline,) + readonly_fields = ('uuid',) + + +@admin.register(Agent) +class AgentAdmin(ModelAdmin): + list_display = ('id', 'uuid', 'model', 'status', 'started_at', 'completed_at') + search_fields = ('model__name', 'uuid') + list_filter = ('status',) + inlines = (AgentRunInline,) + raw_id_fields = ('model',) + readonly_fields = ('uuid', 'started_at', 'completed_at') + + +@admin.register(AgentRun) +class AgentRunAdmin(ModelAdmin): + list_display = ('id', 'uuid', 'agent', 'user', 'status', 'started_at', 'completed_at') + search_fields = ('uuid', 'agent__model__name', 'user__email_address') + list_filter = ('status',) + inlines = (AgentEventInline,) + raw_id_fields = ('agent', 'user') + readonly_fields = ('uuid', 'started_at', 'completed_at') + + +@admin.register(AgentEvent) +class AgentEventAdmin(ModelAdmin): + list_display = ('id', 'event_type', 'execution', 'timestamp') + search_fields = ('event_type', 'execution__uuid', 'execution__agent__model__name') + list_filter = ('event_type',) + raw_id_fields = ('execution',) diff --git a/apps/mlstore/apps.py b/apps/mlstore/apps.py new file mode 100644 index 0000000..85b3031 --- /dev/null +++ b/apps/mlstore/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MlstoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.mlstore' diff --git a/apps/mlstore/migrations/0001_initial.py b/apps/mlstore/migrations/0001_initial.py new file mode 100644 index 0000000..0633cba --- /dev/null +++ b/apps/mlstore/migrations/0001_initial.py @@ -0,0 +1,86 @@ +# Generated by Django 5.2.10 on 2026-01-17 16:12 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='AgentModel', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('name', models.CharField(max_length=255)), + ('version', models.CharField(max_length=50)), + ], + options={ + 'verbose_name': 'Model', + 'verbose_name_plural': 'Models', + }, + ), + migrations.CreateModel( + name='Agent', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('status', models.CharField(choices=[('idle', 'Idle'), ('running', 'Running'), ('paused', 'Paused'), ('completed', 'Completed'), ('failed', 'Failed')], default='idle', max_length=20)), + ('description', models.TextField(blank=True, default='')), + ('started_at', models.DateTimeField(blank=True, null=True)), + ('completed_at', models.DateTimeField(blank=True, null=True)), + ('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='agents', to='mlstore.agentmodel')), + ], + options={ + 'verbose_name': 'Agent Instance', + 'verbose_name_plural': 'Agent Instances', + }, + ), + migrations.CreateModel( + name='AgentRun', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('status', models.CharField(choices=[('queued', 'Queued'), ('running', 'Running'), ('completed', 'Completed'), ('failed', 'Failed')], default='queued', max_length=20)), + ('input_data', models.JSONField(default=dict)), + ('output_data', models.JSONField(blank=True, default=dict)), + ('error_message', models.TextField(blank=True, default='')), + ('started_at', models.DateTimeField(blank=True, null=True)), + ('completed_at', models.DateTimeField(blank=True, null=True)), + ('agent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='runs', to='mlstore.agent')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='agent_runs', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Agent Run', + 'verbose_name_plural': 'Agent Runs', + }, + ), + migrations.CreateModel( + name='AgentEvent', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('event_type', models.CharField(choices=[('started', 'Started'), ('message', 'Message'), ('progress', 'Progress'), ('completed', 'Completed'), ('error', 'Error'), ('step', 'Step')], max_length=20)), + ('content', models.JSONField()), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('execution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='events', to='mlstore.agentrun')), + ], + options={ + 'verbose_name': 'Agent Event', + 'verbose_name_plural': 'Agent Events', + 'ordering': ['timestamp'], + }, + ), + ] diff --git a/apps/mlstore/migrations/__init__.py b/apps/mlstore/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/mlstore/models.py b/apps/mlstore/models.py new file mode 100644 index 0000000..50bde33 --- /dev/null +++ b/apps/mlstore/models.py @@ -0,0 +1,99 @@ +from django.db.models import BigAutoField, CASCADE, CharField, DateTimeField, ForeignKey, JSONField, Model, TextField, UUIDField +from apps.users.mixins import TimeStampMixin +from apps.users.models import User +from uuid import uuid4 + +class AgentModel(Model): + + id = BigAutoField(primary_key=True) + uuid = UUIDField(default=uuid4, unique=True, editable=False) + name = CharField(max_length=255) + version = CharField(max_length=50) + + class Meta: + verbose_name = 'Model' + verbose_name_plural = 'Models' + + def __str__(self): + return self.name + +class Agent(TimeStampMixin, Model): + + STATUS_CHOICES = [ + ('idle', 'Idle'), + ('running', 'Running'), + ('paused', 'Paused'), + ('completed', 'Completed'), + ('failed', 'Failed'), + ] + + id = BigAutoField(primary_key = True) + uuid = UUIDField(default = uuid4, unique = True, editable = False) + + model = ForeignKey(AgentModel, on_delete = CASCADE, related_name='agents') + status = CharField(max_length=20, choices=STATUS_CHOICES, default='idle') + + description = TextField(blank=True, default='') + + started_at = DateTimeField(null=True, blank=True) + completed_at = DateTimeField(null=True, blank=True) + + class Meta: + verbose_name = 'Agent Instance' + verbose_name_plural = 'Agent Instances' + + def __str__(self): + return f'{self.model.name} - {self.uuid}' + +class AgentRun(TimeStampMixin, Model): + + RUN_CHOICES = [ + ('queued', 'Queued'), + ('running', 'Running'), + ('completed', 'Completed'), + ('failed', 'Failed'), + ] + + id = BigAutoField(primary_key=True) + uuid = UUIDField(default=uuid4, editable=False, unique=True) + agent = ForeignKey(Agent, on_delete=CASCADE, related_name='runs') + user = ForeignKey(User, on_delete=CASCADE, related_name='agent_runs') + status = CharField(max_length=20, choices=RUN_CHOICES, default='queued') + + input_data = JSONField(default=dict) + output_data = JSONField(default=dict, blank=True) + error_message = TextField(blank=True, default="") + started_at = DateTimeField(null=True, blank=True) + completed_at = DateTimeField(null=True, blank=True) + + def __str__(self) -> str: + return f"Execution {self.uuid} - {self.agent.name} ({self.status})" + + class Meta: + verbose_name = "Agent Run" + verbose_name_plural = "Agent Runs" + +class AgentEvent(Model): + EVENT_TYPES = [ + ('started', 'Started'), + ('message', 'Message'), + ('progress', 'Progress'), + ('completed', 'Completed'), + ('error', 'Error'), + ('step', 'Step'), + ] + + uuid = UUIDField(default = uuid4, editable=False, unique=True) + execution = ForeignKey(AgentRun, on_delete=CASCADE, related_name='events') + event_type = CharField(max_length=20, choices=EVENT_TYPES) + + content = JSONField() + timestamp = DateTimeField(auto_now_add=True) + + def __str__(self) -> str: + return f"{self.id} - {self.event_type} - {self.execution.agent.name}" + + class Meta: + ordering = ['timestamp'] + verbose_name = "Agent Event" + verbose_name_plural = "Agent Events" diff --git a/config/settings.py b/config/settings.py index 46d08c7..caff210 100644 --- a/config/settings.py +++ b/config/settings.py @@ -66,6 +66,7 @@ THIRD_PARTY_APPS = [ LOCAL_APPS = [ 'apps.users', 'apps.orgs', + 'apps.mlstore', ] INSTALLED_APPS = OVERRIDE_APPS + DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS