Files
voyage/backend/server/integrations/serializers.py
alex 9d5681b1ef feat(ai): implement agent-redesign plan with enhanced AI travel features
Phase 1 - Configuration Infrastructure (WS1):
- Add instance-level AI env vars (VOYAGE_AI_PROVIDER, VOYAGE_AI_MODEL, VOYAGE_AI_API_KEY)
- Implement fallback chain: user key → instance key → error
- Add UserAISettings model for per-user provider/model preferences
- Enhance provider catalog with instance_configured and user_configured flags
- Optimize provider catalog to avoid N+1 queries

Phase 1 - User Preference Learning (WS2):
- Add Travel Preferences tab to Settings page
- Improve preference formatting in system prompt with emoji headers
- Add multi-user preference aggregation for shared collections

Phase 2 - Day-Level Suggestions Modal (WS3):
- Create ItinerarySuggestionModal with 3-step flow (category → filters → results)
- Add AI suggestions button to itinerary Add dropdown
- Support restaurant, activity, event, and lodging categories
- Backend endpoint POST /api/chat/suggestions/day/ with context-aware prompts

Phase 3 - Collection-Level Chat Improvements (WS4):
- Inject collection context (destination, dates) into chat system prompt
- Add quick action buttons for common queries
- Add 'Add to itinerary' button on search_places results
- Update chat UI with travel-themed branding and improved tool result cards

Phase 3 - Web Search Capability (WS5):
- Add web_search agent tool using DuckDuckGo
- Support location_context parameter for biased results
- Handle rate limiting gracefully

Phase 4 - Extensibility Architecture (WS6):
- Implement decorator-based @agent_tool registry
- Convert existing tools to use decorators
- Add GET /api/chat/capabilities/ endpoint for tool discovery
- Refactor execute_tool() to use registry pattern
2026-03-08 23:53:14 +00:00

115 lines
3.6 KiB
Python

from django.db import IntegrityError
from .models import (
EncryptionConfigurationError,
ImmichIntegration,
UserAISettings,
UserAPIKey,
UserRecommendationPreferenceProfile,
)
from rest_framework import serializers
class ImmichIntegrationSerializer(serializers.ModelSerializer):
class Meta:
model = ImmichIntegration
fields = "__all__"
read_only_fields = ["id", "user"]
def to_representation(self, instance):
representation = super().to_representation(instance)
representation.pop("user", None)
return representation
class UserAPIKeySerializer(serializers.ModelSerializer):
api_key = serializers.CharField(write_only=True, required=True, allow_blank=False)
masked_api_key = serializers.CharField(read_only=True)
class Meta:
model = UserAPIKey
fields = [
"id",
"provider",
"api_key",
"masked_api_key",
"created_at",
"updated_at",
]
read_only_fields = ["id", "masked_api_key", "created_at", "updated_at"]
def validate_provider(self, value):
return (value or "").strip().lower()
def create(self, validated_data):
api_key = validated_data.pop("api_key")
user = self.context["request"].user
provider = validated_data.get("provider")
try:
instance, _ = UserAPIKey.objects.get_or_create(
user=user,
provider=provider,
defaults={"encrypted_api_key": ""},
)
instance.set_api_key(api_key)
except EncryptionConfigurationError as exc:
raise serializers.ValidationError({"api_key": str(exc)}) from exc
except IntegrityError:
# Defensive retry: in highly concurrent requests a competing create can
# still race. Fall back to updating the existing row instead of 500.
instance = UserAPIKey.objects.get(user=user, provider=provider)
try:
instance.set_api_key(api_key)
except EncryptionConfigurationError as exc:
raise serializers.ValidationError({"api_key": str(exc)}) from exc
instance.save(update_fields=["encrypted_api_key", "updated_at"])
return instance
def update(self, instance, validated_data):
api_key = validated_data.pop("api_key", None)
for attr, value in validated_data.items():
setattr(instance, attr, value)
if api_key is not None:
try:
instance.set_api_key(api_key)
except EncryptionConfigurationError as exc:
raise serializers.ValidationError({"api_key": str(exc)}) from exc
instance.save()
return instance
def to_representation(self, instance):
representation = super().to_representation(instance)
representation.pop("api_key", None)
return representation
class UserRecommendationPreferenceProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserRecommendationPreferenceProfile
fields = [
"id",
"cuisines",
"interests",
"trip_style",
"notes",
"created_at",
"updated_at",
]
read_only_fields = ["id", "created_at", "updated_at"]
class UserAISettingsSerializer(serializers.ModelSerializer):
class Meta:
model = UserAISettings
fields = [
"id",
"preferred_provider",
"preferred_model",
"created_at",
"updated_at",
]
read_only_fields = ["id", "created_at", "updated_at"]