recommender.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import google.generativeai as genai
  2. from typing import List, Dict, Any
  3. import json
  4. from app.config import get_settings
  5. class BookRecommender:
  6. """AI-powered book recommendation engine using Gemini."""
  7. def __init__(self):
  8. settings = get_settings()
  9. if not settings.gemini_api_key:
  10. raise ValueError("GEMINI_API_KEY not configured")
  11. genai.configure(api_key=settings.gemini_api_key)
  12. # Use the latest flash model available
  13. self.model = genai.GenerativeModel('models/gemini-2.5-flash')
  14. async def generate_recommendations(
  15. self,
  16. reading_history: List[Dict[str, Any]],
  17. num_recommendations: int = 5
  18. ) -> List[Dict[str, Any]]:
  19. """
  20. Generate book recommendations based on reading history.
  21. Args:
  22. reading_history: List of books the user has read/listened to
  23. num_recommendations: Number of recommendations to generate
  24. Returns:
  25. List of recommended books with title, author, description, and reason
  26. """
  27. # Build context from reading history
  28. history_text = self._format_reading_history(reading_history)
  29. # Create prompt for Gemini
  30. prompt = f"""Based on this reading history, recommend {num_recommendations} audiobooks that this person would enjoy.
  31. Reading History:
  32. {history_text}
  33. For each recommendation, provide:
  34. 1. Title
  35. 2. Author
  36. 3. Brief description (2-3 sentences)
  37. 4. Why you're recommending it based on their reading history (1-2 sentences)
  38. 5. Genres (as a list)
  39. Format your response as a JSON array with objects containing: title, author, description, reason, genres.
  40. Only respond with the JSON array, no additional text."""
  41. # Call Gemini API
  42. response = self.model.generate_content(prompt)
  43. # Parse response
  44. response_text = response.text
  45. try:
  46. recommendations = json.loads(response_text)
  47. return recommendations
  48. except json.JSONDecodeError:
  49. # If Claude didn't return valid JSON, try to extract it
  50. # Look for JSON array in the response
  51. start = response_text.find("[")
  52. end = response_text.rfind("]") + 1
  53. if start >= 0 and end > start:
  54. recommendations = json.loads(response_text[start:end])
  55. return recommendations
  56. else:
  57. raise ValueError("Failed to parse recommendations from AI response")
  58. def _format_reading_history(self, history: List[Dict[str, Any]]) -> str:
  59. """Format reading history for the AI prompt."""
  60. formatted = []
  61. for i, book in enumerate(history, 1):
  62. title = book.get("title", "Unknown")
  63. author = book.get("author", "Unknown")
  64. genres = book.get("genres", [])
  65. progress = book.get("progress", 0)
  66. is_finished = book.get("is_finished", False)
  67. status = "Finished" if is_finished else f"In Progress ({int(progress * 100)}%)"
  68. genre_str = ", ".join(genres) if genres else "Unknown"
  69. formatted.append(
  70. f"{i}. {title} by {author}\n"
  71. f" Status: {status}\n"
  72. f" Genres: {genre_str}"
  73. )
  74. return "\n\n".join(formatted)
  75. async def explain_recommendation(
  76. self,
  77. book_title: str,
  78. book_author: str,
  79. reading_history: List[Dict[str, Any]]
  80. ) -> str:
  81. """
  82. Get a detailed explanation for why a specific book is recommended.
  83. Args:
  84. book_title: Title of the recommended book
  85. book_author: Author of the recommended book
  86. reading_history: User's reading history
  87. Returns:
  88. Detailed explanation text
  89. """
  90. history_text = self._format_reading_history(reading_history)
  91. prompt = f"""Explain in detail why "{book_title}" by {book_author} would be a good recommendation for someone with this reading history:
  92. {history_text}
  93. Provide a thoughtful 2-3 paragraph explanation connecting specific aspects of the recommended book to their reading preferences."""
  94. response = self.model.generate_content(prompt)
  95. return response.text