recommender.py 4.3 KB

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