55 lines
2.0 KiB
Python
55 lines
2.0 KiB
Python
|
|
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."""
|
||
|
|
...
|