| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- from anthropic import Anthropic
- from typing import List, Dict, Any
- import json
- from app.config import get_settings
- class BookRecommender:
- """AI-powered book recommendation engine using Claude."""
- def __init__(self):
- settings = get_settings()
- if not settings.anthropic_api_key:
- raise ValueError("ANTHROPIC_API_KEY not configured")
- self.client = Anthropic(api_key=settings.anthropic_api_key)
- async def generate_recommendations(
- self,
- reading_history: List[Dict[str, Any]],
- num_recommendations: int = 5
- ) -> List[Dict[str, Any]]:
- """
- Generate book recommendations based on reading history.
- Args:
- reading_history: List of books the user has read/listened to
- num_recommendations: Number of recommendations to generate
- Returns:
- List of recommended books with title, author, description, and reason
- """
- # Build context from reading history
- history_text = self._format_reading_history(reading_history)
- # Create prompt for Claude
- prompt = f"""Based on this reading history, recommend {num_recommendations} audiobooks that this person would enjoy.
- Reading History:
- {history_text}
- For each recommendation, provide:
- 1. Title
- 2. Author
- 3. Brief description (2-3 sentences)
- 4. Why you're recommending it based on their reading history (1-2 sentences)
- 5. Genres (as a list)
- Format your response as a JSON array with objects containing: title, author, description, reason, genres.
- Only respond with the JSON array, no additional text."""
- # Call Claude API
- message = self.client.messages.create(
- model="claude-3-5-sonnet-20241022",
- max_tokens=2048,
- messages=[{"role": "user", "content": prompt}]
- )
- # Parse response
- response_text = message.content[0].text
- try:
- recommendations = json.loads(response_text)
- return recommendations
- except json.JSONDecodeError:
- # If Claude didn't return valid JSON, try to extract it
- # Look for JSON array in the response
- start = response_text.find("[")
- end = response_text.rfind("]") + 1
- if start >= 0 and end > start:
- recommendations = json.loads(response_text[start:end])
- return recommendations
- else:
- raise ValueError("Failed to parse recommendations from AI response")
- def _format_reading_history(self, history: List[Dict[str, Any]]) -> str:
- """Format reading history for the AI prompt."""
- formatted = []
- for i, book in enumerate(history, 1):
- title = book.get("title", "Unknown")
- author = book.get("author", "Unknown")
- genres = book.get("genres", [])
- progress = book.get("progress", 0)
- is_finished = book.get("is_finished", False)
- status = "Finished" if is_finished else f"In Progress ({int(progress * 100)}%)"
- genre_str = ", ".join(genres) if genres else "Unknown"
- formatted.append(
- f"{i}. {title} by {author}\n"
- f" Status: {status}\n"
- f" Genres: {genre_str}"
- )
- return "\n\n".join(formatted)
- async def explain_recommendation(
- self,
- book_title: str,
- book_author: str,
- reading_history: List[Dict[str, Any]]
- ) -> str:
- """
- Get a detailed explanation for why a specific book is recommended.
- Args:
- book_title: Title of the recommended book
- book_author: Author of the recommended book
- reading_history: User's reading history
- Returns:
- Detailed explanation text
- """
- history_text = self._format_reading_history(reading_history)
- prompt = f"""Explain in detail why "{book_title}" by {book_author} would be a good recommendation for someone with this reading history:
- {history_text}
- Provide a thoughtful 2-3 paragraph explanation connecting specific aspects of the recommended book to their reading preferences."""
- message = self.client.messages.create(
- model="claude-3-5-sonnet-20241022",
- max_tokens=512,
- messages=[{"role": "user", "content": prompt}]
- )
- return message.content[0].text
|