Spaces:
Paused
Paused
| from typing import List, Tuple, Dict | |
| from langchain_core.prompts import ChatPromptTemplate | |
| from langchain_mistralai.chat_models import ChatMistralAI | |
| import os | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| def create_ranking_chain(): | |
| """Create a ranking chain using new RunnableSequence format""" | |
| prompt = ChatPromptTemplate.from_messages([ | |
| ("system", """You are a movie recommendation expert. Your task is to select the top 10 most relevant movies from a list of recommended movies and provide the final formatted output with brief explanations. | |
| Rules: | |
| 1. Always return exactly 10 movies | |
| 2. Consider both relevance scores and how well each movie matches user preferences | |
| 3. Pay attention to the alpha weighting parameter - it tells you how much to prioritize text preferences vs viewing history | |
| 4. Return only movies from the provided list | |
| 5. NEVER recommend movies that are already in the user's viewing history - these should be completely excluded | |
| 6. Format each movie exactly as: **1. Movie Title**\n[Exactly 2 sentences explaining why this movie matches their taste]\n\n | |
| 7. Number from 1 to 10, no additional text before or after"""), | |
| ("user", """Given these movie recommendations with their relevance scores: | |
| {movie_scores} | |
| User preferences: {preferences} | |
| User's viewing history (DO NOT RECOMMEND ANY OF THESE): {user_movies} | |
| Alpha weighting: {alpha} | |
| (α=0.0 means recommendations were based entirely on viewing history, α=1.0 means entirely on text preferences, α=0.5 means equal balance) | |
| Select the 10 most relevant movies and provide the final formatted output with explanations. Format each as: | |
| **1. Movie Title** | |
| [Exactly 2 sentences explaining why this movie matches their taste based on the weighted combination of their preferences and history] | |
| **2. Movie Title** | |
| [Exactly 2 sentences explaining why this movie matches their taste based on the weighted combination of their preferences and history] | |
| ...continue for all 10 movies. | |
| Remember: NEVER include any movie from the user's viewing history in your recommendations.""") | |
| ]) | |
| model = ChatMistralAI( | |
| mistral_api_key=os.environ["MISTRAL_API_KEY"], | |
| model="mistral-large-latest", | |
| temperature=0.5, | |
| max_tokens=1200, | |
| streaming=True | |
| ) | |
| return prompt | model | |
| def rank_with_ai(recommendations: List[Tuple[str, float]], user_preferences: str = "", alpha: float = 0.5, user_movies: List[str] = None): | |
| """ | |
| Complete reranking and explanation pipeline with streaming: | |
| 1. Takes top 100 candidates from retrieval phase | |
| 2. Reranks to top 10 using AI | |
| 3. Generates explanations with streaming | |
| 4. Yields partial formatted responses | |
| Args: | |
| recommendations: List of (movie_title, relevance_score) tuples from retrieval phase | |
| user_preferences: User's textual preferences/description | |
| alpha: Weighting parameter (0.0 = only history matters, 1.0 = only preferences matter) | |
| user_movies: List of user's selected movies for context | |
| """ | |
| print(f"\n=== RANKING_AGENT DEBUG ===") | |
| print(f"Received {len(recommendations) if recommendations else 0} recommendations") | |
| print(f"User preferences: '{user_preferences}' (length: {len(user_preferences) if user_preferences else 0})") | |
| print(f"Alpha: {alpha}") | |
| print(f"User movies: {user_movies}") | |
| if not recommendations: | |
| yield "No recommendations available." | |
| return | |
| # Take only top 100 recommendations if more are provided | |
| recommendations = recommendations[:100] | |
| try: | |
| # Format movie scores for ranking | |
| movie_scores = "\n".join( | |
| f"{title} (relevance: {score:.3f})" | |
| for title, score in recommendations | |
| ) | |
| # Start with header | |
| result_header = "## 🎬 Your Personalized Movie Recommendations\n\n" | |
| if user_movies and user_preferences: | |
| result_header += f"*Based on α={alpha} weighting: {int((1-alpha)*100)}% your viewing history + {int(alpha*100)}% your preferences*\n\n" | |
| elif user_preferences: | |
| result_header += f"*Based entirely on your preferences: \"{user_preferences}\"*\n\n" | |
| elif user_movies: | |
| result_header += f"*Based entirely on your viewing history*\n\n" | |
| result_header += "---\n\n" | |
| yield result_header | |
| # Single chain that does both ranking and explanation | |
| ranking_chain = create_ranking_chain() | |
| print("Calling unified ranking + explanation chain...") | |
| # Stream the response directly | |
| accumulated_text = result_header | |
| for chunk in ranking_chain.stream({ | |
| "movie_scores": movie_scores, | |
| "preferences": user_preferences if user_preferences else "No specific preferences provided", | |
| "user_movies": ", ".join(user_movies) if user_movies else "None", | |
| "alpha": alpha | |
| }): | |
| if chunk.content: | |
| accumulated_text += chunk.content | |
| yield accumulated_text | |
| except Exception as e: | |
| print(f"ERROR in rank_with_ai: {str(e)}") | |
| import traceback | |
| traceback.print_exc() | |
| # Fallback to simple format | |
| result = "## 🎬 Your Recommendations\n\n" | |
| for i, (title, score) in enumerate(recommendations[:10], 1): | |
| result += f"**{i}. {title}**\n" | |
| result += f"*Similarity: {score:.3f}*\n\n" | |
| yield result |