feat(chat): add admin-editable assistant system prompt
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
from django.contrib import admin
|
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)
|
@admin.register(ChatConversation)
|
||||||
@@ -15,3 +17,14 @@ class ChatMessageAdmin(admin.ModelAdmin):
|
|||||||
list_display = ("id", "conversation", "role", "name", "created_at")
|
list_display = ("id", "conversation", "role", "name", "created_at")
|
||||||
search_fields = ("conversation__id", "content", "name")
|
search_fields = ("conversation__id", "content", "name")
|
||||||
list_filter = ("role", "created_at")
|
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__)
|
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 = {
|
PROVIDER_MODEL_PREFIX = {
|
||||||
"openai": "openai",
|
"openai": "openai",
|
||||||
"anthropic": "anthropic",
|
"anthropic": "anthropic",
|
||||||
@@ -321,34 +348,15 @@ def get_aggregated_preferences(collection):
|
|||||||
|
|
||||||
def get_system_prompt(user, collection=None):
|
def get_system_prompt(user, collection=None):
|
||||||
"""Build the system prompt with user context."""
|
"""Build the system prompt with user context."""
|
||||||
|
from chat.models import ChatSystemPrompt
|
||||||
from integrations.models import UserRecommendationPreferenceProfile
|
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.
|
custom = ChatSystemPrompt.load()
|
||||||
|
base_prompt = (
|
||||||
Your capabilities:
|
custom.prompt_text
|
||||||
- Search for interesting places (restaurants, tourist attractions, hotels) near any location
|
if custom and custom.prompt_text and custom.prompt_text.strip()
|
||||||
- View and manage the user's trip collections and itineraries
|
else DEFAULT_SYSTEM_PROMPT
|
||||||
- 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."""
|
|
||||||
|
|
||||||
if collection and collection.shared_with.count() > 0:
|
if collection and collection.shared_with.count() > 0:
|
||||||
base_prompt += get_aggregated_preferences(collection)
|
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:
|
class Meta:
|
||||||
ordering = ["created_at"]
|
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