feat(chat): add admin-editable assistant system prompt
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
from django.contrib import admin
|
||||
from django.db import models
|
||||
from django.forms import Textarea
|
||||
|
||||
from .models import ChatConversation, ChatMessage
|
||||
from .models import ChatConversation, ChatMessage, ChatSystemPrompt
|
||||
|
||||
|
||||
@admin.register(ChatConversation)
|
||||
@@ -15,3 +17,14 @@ class ChatMessageAdmin(admin.ModelAdmin):
|
||||
list_display = ("id", "conversation", "role", "name", "created_at")
|
||||
search_fields = ("conversation__id", "content", "name")
|
||||
list_filter = ("role", "created_at")
|
||||
|
||||
|
||||
@admin.register(ChatSystemPrompt)
|
||||
class ChatSystemPromptAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ("updated_at",)
|
||||
formfield_overrides = {
|
||||
models.TextField: {"widget": Textarea(attrs={"rows": 20, "cols": 120})},
|
||||
}
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return not ChatSystemPrompt.objects.exists()
|
||||
|
||||
@@ -9,6 +9,33 @@ from integrations.models import UserAPIKey
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_SYSTEM_PROMPT = """You are a helpful travel planning assistant for the Voyage travel app. You help users discover places, plan trips, and organize their itineraries.
|
||||
|
||||
Your capabilities:
|
||||
- Search for interesting places (restaurants, tourist attractions, hotels) near any location
|
||||
- View and manage the user's trip collections and itineraries
|
||||
- Add new locations to trip itineraries
|
||||
- Check weather/temperature data for travel dates
|
||||
|
||||
When suggesting places:
|
||||
- Be specific with names, addresses, and why a place is worth visiting
|
||||
- Consider the user's travel dates and weather conditions
|
||||
- Group suggestions logically (by area, by type, by day)
|
||||
|
||||
When modifying itineraries:
|
||||
- Confirm with the user before the first add_to_itinerary action in a conversation
|
||||
- After the user clearly approves adding items (for example: "yes", "go ahead", "add them", "just add things there"), stop re-confirming and call add_to_itinerary directly for subsequent additions in that conversation
|
||||
- Suggest logical ordering based on geography
|
||||
- Consider travel time between locations
|
||||
|
||||
When chat context includes a trip collection:
|
||||
- Treat context as itinerary-wide (potentially multiple stops), not a single destination
|
||||
- Use get_trip_details first when you need complete collection context before searching for places
|
||||
- Ground place searches in trip stops and dates from the provided trip context
|
||||
- Only call search_places when you have a concrete, non-empty location string; if location is missing or unclear, ask a clarifying question to obtain it first
|
||||
|
||||
Be conversational, helpful, and enthusiastic about travel. Keep responses concise but informative."""
|
||||
|
||||
PROVIDER_MODEL_PREFIX = {
|
||||
"openai": "openai",
|
||||
"anthropic": "anthropic",
|
||||
@@ -321,34 +348,15 @@ def get_aggregated_preferences(collection):
|
||||
|
||||
def get_system_prompt(user, collection=None):
|
||||
"""Build the system prompt with user context."""
|
||||
from chat.models import ChatSystemPrompt
|
||||
from integrations.models import UserRecommendationPreferenceProfile
|
||||
|
||||
base_prompt = """You are a helpful travel planning assistant for the Voyage travel app. You help users discover places, plan trips, and organize their itineraries.
|
||||
|
||||
Your capabilities:
|
||||
- Search for interesting places (restaurants, tourist attractions, hotels) near any location
|
||||
- View and manage the user's trip collections and itineraries
|
||||
- Add new locations to trip itineraries
|
||||
- Check weather/temperature data for travel dates
|
||||
|
||||
When suggesting places:
|
||||
- Be specific with names, addresses, and why a place is worth visiting
|
||||
- Consider the user's travel dates and weather conditions
|
||||
- Group suggestions logically (by area, by type, by day)
|
||||
|
||||
When modifying itineraries:
|
||||
- Confirm with the user before the first add_to_itinerary action in a conversation
|
||||
- After the user clearly approves adding items (for example: "yes", "go ahead", "add them", "just add things there"), stop re-confirming and call add_to_itinerary directly for subsequent additions in that conversation
|
||||
- Suggest logical ordering based on geography
|
||||
- Consider travel time between locations
|
||||
|
||||
When chat context includes a trip collection:
|
||||
- Treat context as itinerary-wide (potentially multiple stops), not a single destination
|
||||
- Use get_trip_details first when you need complete collection context before searching for places
|
||||
- Ground place searches in trip stops and dates from the provided trip context
|
||||
- Only call search_places when you have a concrete, non-empty location string; if location is missing or unclear, ask a clarifying question to obtain it first
|
||||
|
||||
Be conversational, helpful, and enthusiastic about travel. Keep responses concise but informative."""
|
||||
custom = ChatSystemPrompt.load()
|
||||
base_prompt = (
|
||||
custom.prompt_text
|
||||
if custom and custom.prompt_text and custom.prompt_text.strip()
|
||||
else DEFAULT_SYSTEM_PROMPT
|
||||
)
|
||||
|
||||
if collection and collection.shared_with.count() > 0:
|
||||
base_prompt += get_aggregated_preferences(collection)
|
||||
|
||||
37
backend/server/chat/migrations/0002_chatsystemprompt.py
Normal file
37
backend/server/chat/migrations/0002_chatsystemprompt.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# Generated by Django 5.2.12 on 2026-03-10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("chat", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="ChatSystemPrompt",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"prompt_text",
|
||||
models.TextField(
|
||||
help_text="Base system prompt for the travel assistant. User and party travel preferences are appended automatically."
|
||||
),
|
||||
),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Chat System Prompt",
|
||||
"verbose_name_plural": "Chat System Prompt",
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -45,3 +45,37 @@ class ChatMessage(models.Model):
|
||||
|
||||
class Meta:
|
||||
ordering = ["created_at"]
|
||||
|
||||
|
||||
class ChatSystemPrompt(models.Model):
|
||||
"""Admin-editable system prompt for the travel assistant.
|
||||
|
||||
Only one instance should exist (singleton pattern via Django admin).
|
||||
The prompt text replaces the hardcoded base prompt in get_system_prompt().
|
||||
Dynamic user/party preference injection is appended automatically.
|
||||
"""
|
||||
|
||||
prompt_text = models.TextField(
|
||||
help_text="Base system prompt for the travel assistant. "
|
||||
"User and party travel preferences are appended automatically."
|
||||
)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Chat System Prompt"
|
||||
verbose_name_plural = "Chat System Prompt"
|
||||
|
||||
def __str__(self):
|
||||
return f"System Prompt (updated {self.updated_at})"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.pk = 1
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
"""Return the singleton instance or None if not configured."""
|
||||
try:
|
||||
return cls.objects.get(pk=1)
|
||||
except cls.DoesNotExist:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user