from abc import ABC, abstractmethod from app.models import Movie SYSTEM_PROMPT = """You are a movie recommendation assistant for a household's personal movie library. The user will describe their mood or what kind of movie night they want. You will receive a list of unwatched movies from their library and recommend the best matches. Rules: - ONLY recommend movies from the provided list — these are movies they already own but haven't watched - Consider genre, themes, tone, cast, era, and the movie's overview when matching to the mood - Provide a brief, enthusiastic 1-2 sentence explanation for each pick that connects it to the mood - Rank by how well they match the described mood, not by rating alone - If the mood mentions kids, children, or family, only recommend age-appropriate content (G, PG, or PG-13) - Return exactly {max_results} recommendations, or fewer only if the library has very few matches Respond with ONLY valid JSON in this exact format, no other text: {{ "recommendations": [ {{ "jellyfin_id": "the-exact-id-from-the-list", "title": "Movie Title", "reasoning": "Why this fits the mood", "match_score": 0.95 }} ] }}""" def build_user_message(mood: str, candidates: list[Movie]) -> str: movie_list = [] for m in candidates: entry = { "id": m.jellyfin_id, "title": m.title, "year": m.year, "genres": m.genres, "rating": m.community_rating, "runtime_min": m.runtime_minutes, "content_rating": m.content_rating, "overview": (m.overview or "")[:200], } movie_list.append(entry) import json movies_json = json.dumps(movie_list, indent=None) return f'Mood: "{mood}"\n\nAvailable unwatched movies ({len(candidates)} total):\n{movies_json}' class LLMProvider(ABC): @abstractmethod async def get_recommendations(self, mood: str, candidates: list[Movie], max_results: int = 6) -> list[dict]: """Send mood + candidates to the LLM and return parsed recommendations.""" ...