Merge branch 'feat/admin-editable-assistant-prompt'

This commit is contained in:
2026-03-10 20:20:30 +00:00
4 changed files with 119 additions and 27 deletions

View File

@@ -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()

View File

@@ -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)

View 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",
},
),
]

View File

@@ -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