Browse Source

Refactor Agent app to enhance settings management and introduce prompts

- Updated `AgentSettings` model to serve as a singleton container, removing unnecessary fields and adding an `updated_at` timestamp.
- Introduced `AgentPrompt` model to manage instruction prompts associated with agent settings.
- Enhanced `AgentSettingsAdmin` to include inline management of prompts, improving the admin interface for better usability.
- Created migrations to reflect the updated model structure and ensure database integrity.
master
Mohsen Taba 3 months ago
parent
commit
e00b3ac208
  1. 74
      apps/agent/admin.py
  2. 56
      apps/agent/migrations/0002_alter_agentsettings_options_and_more.py
  3. 38
      apps/agent/migrations/0003_alter_agentprompt_options_and_more.py
  4. 37
      apps/agent/models.py

74
apps/agent/admin.py

@ -1,61 +1,57 @@
from django.contrib import admin
from django.db import models
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from unfold.admin import ModelAdmin
from unfold.admin import ModelAdmin, TabularInline
from utils.admin import dovoodi_admin_site, project_admin_site from utils.admin import dovoodi_admin_site, project_admin_site
from apps.agent.models import AgentSettings
from .models import AgentSettings, AgentPrompt
from unfold.contrib.forms.widgets import WysiwygWidget
class AgentSettingsAdmin(ModelAdmin):
"""
Singleton Admin for Agent Configuration.
Acts as a 'Settings Page' by redirecting list view to the edit page of ID=1.
"""
class AgentPromptInline(TabularInline):
model = AgentPrompt
extra = 0
fields = ('is_active', 'content')
formfield_overrides = {
models.TextField: {
'widget': admin.widgets.AdminTextareaWidget(attrs={
# 1. REDUCE HEIGHT: Set rows to 1 or 2
'rows': 2,
# 🎨 STYLING
# w-full: Fills the available space
# bg-black: Black background
# text-white: White text (Fixed typo from 'text-blacka')
# border-gray-600: Border color
# leading-normal: Adjusts line height for better vertical centering
'class': 'w-full p-2 border rounded-md bg-black text-white border-gray-600 focus:ring-primary-500 focus:border-primary-500 leading-normal',
'placeholder': 'Enter instruction prompt here...',
# 2. INCREASE WIDTH: 'min-width' forces the table cell to expand
'style': 'width: 100%; min-width: 600px; resize: vertical;'
})
},
}
class AgentSettingsAdmin(ModelAdmin):
def has_add_permission(self, request): def has_add_permission(self, request):
# Disable 'Add' button to prevent creating multiple configs
return False return False
def has_delete_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None):
# Disable 'Delete' button to ensure settings always exist
return False return False
def changelist_view(self, request, extra_context=None): def changelist_view(self, request, extra_context=None):
"""
Redirect the 'List View' directly to the 'Edit View' of ID=1.
Auto-creates the default config if it doesn't exist.
"""
# Ensure ID=1 exists
obj, created = self.model.objects.get_or_create(pk=1, defaults={
"system_prompt": "You are a helpful assistant.",
})
# Build the URL dynamically based on the registered admin site
obj, created = self.model.objects.get_or_create(pk=1)
url = reverse( url = reverse(
f"{self.admin_site.name}:{self.model._meta.app_label}_{self.model._meta.model_name}_change", f"{self.admin_site.name}:{self.model._meta.app_label}_{self.model._meta.model_name}_change",
args=[obj.pk] args=[obj.pk]
) )
return redirect(url) return redirect(url)
fieldsets = (
("Status", {
"fields": ("is_maintenance_mode",),
"classes": ("tab",),
}),
("Brain (System Instructions)", {
"fields": ("system_prompt",),
"classes": ("tab",),
"description": "Define the core personality and rules for the AI Agent."
}),
("Model Parameters", {
"fields": ("model_id", "temperature"),
"classes": ("tab",),
"description": "Technical settings for the inference engine."
}),
)
inlines = [AgentPromptInline]
# Register with your custom admin site
# Register
dovoodi_admin_site.register(AgentSettings, AgentSettingsAdmin) dovoodi_admin_site.register(AgentSettings, AgentSettingsAdmin)
project_admin_site.register(AgentSettings, AgentSettingsAdmin) project_admin_site.register(AgentSettings, AgentSettingsAdmin)

56
apps/agent/migrations/0002_alter_agentsettings_options_and_more.py

@ -0,0 +1,56 @@
# Generated by Django 4.2.27 on 2026-02-15 13:58
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('agent', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='agentsettings',
options={'verbose_name': 'Agent Control Panel', 'verbose_name_plural': 'Agent Control Panel'},
),
migrations.RemoveField(
model_name='agentsettings',
name='model_id',
),
migrations.RemoveField(
model_name='agentsettings',
name='system_prompt',
),
migrations.RemoveField(
model_name='agentsettings',
name='temperature',
),
migrations.AddField(
model_name='agentsettings',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AlterField(
model_name='agentsettings',
name='is_maintenance_mode',
field=models.BooleanField(default=False, help_text='If checked, the agent will reply with a maintenance message.'),
),
migrations.CreateModel(
name='AgentPrompt',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(help_text="Internal Label (e.g., 'Core Identity')", max_length=100)),
('content', models.TextField(help_text='The actual instruction text.')),
('is_active', models.BooleanField(default=True)),
('order', models.PositiveIntegerField(default=0, help_text='Order of injection (1, 2, 3...)')),
('settings', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='prompts', to='agent.agentsettings')),
],
options={
'verbose_name': 'System Instruction',
'verbose_name_plural': 'System Instructions',
'ordering': ['order'],
},
),
]

38
apps/agent/migrations/0003_alter_agentprompt_options_and_more.py

@ -0,0 +1,38 @@
# Generated by Django 4.2.27 on 2026-02-15 14:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agent', '0002_alter_agentsettings_options_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='agentprompt',
options={},
),
migrations.AlterModelOptions(
name='agentsettings',
options={'verbose_name': 'Agent Configuration', 'verbose_name_plural': 'Agent Configuration'},
),
migrations.RemoveField(
model_name='agentprompt',
name='order',
),
migrations.RemoveField(
model_name='agentprompt',
name='title',
),
migrations.RemoveField(
model_name='agentsettings',
name='is_maintenance_mode',
),
migrations.AlterField(
model_name='agentprompt',
name='content',
field=models.TextField(help_text='The instruction text.'),
),
]

37
apps/agent/models.py

@ -1,25 +1,20 @@
from django.db import models from django.db import models
from django.core.cache import cache from django.core.cache import cache
class AgentSettings(models.Model): class AgentSettings(models.Model):
# Fixed Settings
system_prompt = models.TextField(default="You are a helpful assistant.")
model_id = models.CharField(max_length=50, default="deepseek/deepseek-r1")
temperature = models.FloatField(default=0.3)
# Switches
is_maintenance_mode = models.BooleanField(default=False)
"""
Singleton Container.
Now just a wrapper to hold the list of prompts.
"""
updated_at = models.DateTimeField(auto_now=True)
class Meta: class Meta:
verbose_name = "Agent Configuration" verbose_name = "Agent Configuration"
verbose_name_plural = "Agent Configuration" verbose_name_plural = "Agent Configuration"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.pk = 1
self.pk = 1 # Force Singleton
super().save(*args, **kwargs) super().save(*args, **kwargs)
# Clear cache whenever you save
cache.delete("agent_config_1") cache.delete("agent_config_1")
def __str__(self): def __str__(self):
@ -27,6 +22,24 @@ class AgentSettings(models.Model):
@classmethod @classmethod
def load(cls): def load(cls):
"""Helper to get the singleton instance, creating it if missing."""
obj, created = cls.objects.get_or_create(pk=1) obj, created = cls.objects.get_or_create(pk=1)
return obj return obj
class AgentPrompt(models.Model):
"""
Simple Prompt Block.
Just text and a switch.
"""
settings = models.ForeignKey(AgentSettings, on_delete=models.CASCADE, related_name="prompts")
content = models.TextField(help_text="The instruction text.")
is_active = models.BooleanField(default=True)
def __str__(self):
# Display the first 50 chars as the name in admin
# if self.is_active:
# return f"Active Prompt" if self.content else "Empty Prompt"
# else:
# return f"Inactive Prompt: {self.content[:50]}..." if self.content else "Empty Prompt"
return ""
Loading…
Cancel
Save