""" AI Agent Models: AgentConfig (Singleton), ChatSession, ChatMessage. """ import uuid from django.contrib.auth.models import User from django.db import models DEFAULT_SYSTEM_PROMPT = """Du bist RentmeisterAI, der KI-Assistent der van Hees-Theyssen-Vogel'schen Stiftung. Du hast Zugriff auf die Stiftungsdatenbank und kannst Informationen zu Destinatären, Ländereien, \ Finanzen, Förderungen und weiteren Stiftungsdaten abrufen. Regeln: - Antworte stets auf Deutsch, präzise und sachlich. - Schütze personenbezogene Daten – gib keine unnötigen Details heraus. - Du kannst keine Daten ändern, nur lesen. - Bei rechtlichen oder steuerlichen Fragen weise auf Fachberatung hin. - Wenn du dir unsicher bist, sage das klar. """ class AgentConfig(models.Model): """Singleton-Konfiguration für den AI Agent.""" PROVIDER_CHOICES = [ ("ollama", "Ollama (lokal)"), ("openai", "OpenAI"), ("anthropic", "Anthropic"), ] provider = models.CharField( max_length=20, choices=PROVIDER_CHOICES, default="ollama", verbose_name="LLM-Provider", ) model_name = models.CharField( max_length=100, default="qwen2.5:3b", verbose_name="Modell-Name", ) ollama_url = models.CharField( max_length=255, default="http://ollama:11434", verbose_name="Ollama-URL", ) openai_api_key = models.CharField( max_length=255, blank=True, verbose_name="OpenAI API-Key", help_text="Nur erforderlich wenn Provider = OpenAI", ) anthropic_api_key = models.CharField( max_length=255, blank=True, verbose_name="Anthropic API-Key", help_text="Nur erforderlich wenn Provider = Anthropic", ) system_prompt = models.TextField( default=DEFAULT_SYSTEM_PROMPT, verbose_name="System-Prompt", ) allow_write = models.BooleanField( default=False, verbose_name="Schreib-Tools erlaubt", help_text="Achtung: Schreib-Zugriff auf Stiftungsdaten aktivieren", ) chat_retention_days = models.IntegerField( default=30, verbose_name="Chat-Verlauf Aufbewahrung (Tage)", ) class Meta: verbose_name = "Agent-Konfiguration" verbose_name_plural = "Agent-Konfiguration" def __str__(self): return f"Agent Config ({self.get_provider_display()} / {self.model_name})" def save(self, *args, **kwargs): # Singleton: always use pk=1 self.pk = 1 super().save(*args, **kwargs) def delete(self, *args, **kwargs): pass # Singleton cannot be deleted @classmethod def get_config(cls): config, _ = cls.objects.get_or_create(pk=1) return config class ChatSession(models.Model): """Chat-Sitzung eines Benutzers mit dem AI Agent.""" id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user = models.ForeignKey( User, on_delete=models.CASCADE, related_name="agent_sessions", verbose_name="Benutzer", ) title = models.CharField( max_length=200, blank=True, verbose_name="Titel", help_text="Automatisch aus erster Nachricht generiert", ) created_at = models.DateTimeField(auto_now_add=True, verbose_name="Erstellt") updated_at = models.DateTimeField(auto_now=True, verbose_name="Zuletzt aktiv") class Meta: verbose_name = "Chat-Sitzung" verbose_name_plural = "Chat-Sitzungen" ordering = ["-updated_at"] def __str__(self): return f"{self.user.username} – {self.title or str(self.id)[:8]} ({self.created_at.strftime('%d.%m.%Y')})" def message_count(self): return self.messages.count() class ChatMessage(models.Model): """Einzelne Nachricht in einer Chat-Sitzung.""" ROLE_CHOICES = [ ("user", "Benutzer"), ("assistant", "Assistent"), ("tool", "Tool-Ergebnis"), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) session = models.ForeignKey( ChatSession, on_delete=models.CASCADE, related_name="messages", verbose_name="Sitzung", ) role = models.CharField( max_length=20, choices=ROLE_CHOICES, verbose_name="Rolle", ) content = models.TextField(verbose_name="Inhalt") tool_name = models.CharField( max_length=100, blank=True, verbose_name="Tool-Name", ) tool_call_id = models.CharField( max_length=100, blank=True, verbose_name="Tool-Call-ID", ) created_at = models.DateTimeField(auto_now_add=True, verbose_name="Erstellt") class Meta: verbose_name = "Chat-Nachricht" verbose_name_plural = "Chat-Nachrichten" ordering = ["created_at"] def __str__(self): return f"[{self.role}] {self.content[:60]}"