
- Setup project structure with FastAPI - Create database models for users, gifts, preferences, and recommendations - Configure SQLite database with SQLAlchemy ORM - Setup Alembic for database migrations - Implement user authentication with JWT - Create API endpoints for users, gifts, preferences, and recommendations - Integrate OpenAI API for gift recommendations - Add comprehensive documentation
137 lines
6.0 KiB
Python
137 lines
6.0 KiB
Python
import json
|
|
from typing import Dict, Optional
|
|
|
|
import openai
|
|
from fastapi import HTTPException, status
|
|
|
|
from app.core.config import settings
|
|
from app.schemas.preference import Preference
|
|
|
|
|
|
class AIRecommendationService:
|
|
def __init__(self):
|
|
self.api_key = settings.OPENAI_API_KEY
|
|
self.model = settings.OPENAI_MODEL
|
|
|
|
if not self.api_key:
|
|
print("Warning: OPENAI_API_KEY not set. AI recommendations will not work.")
|
|
|
|
async def generate_recommendations(
|
|
self,
|
|
recipient_name: str,
|
|
occasion: Optional[str] = None,
|
|
preferences: Optional[Preference] = None,
|
|
budget_min: Optional[float] = None,
|
|
budget_max: Optional[float] = None,
|
|
additional_info: Optional[str] = None,
|
|
) -> Dict:
|
|
"""Generate gift recommendations based on preferences and constraints."""
|
|
if not self.api_key:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail="AI service not available. OPENAI_API_KEY not configured.",
|
|
)
|
|
|
|
# Construct the prompt
|
|
budget_text = ""
|
|
if budget_min is not None and budget_max is not None:
|
|
budget_text = f"Budget range: ${budget_min} - ${budget_max}"
|
|
elif budget_min is not None:
|
|
budget_text = f"Minimum budget: ${budget_min}"
|
|
elif budget_max is not None:
|
|
budget_text = f"Maximum budget: ${budget_max}"
|
|
|
|
preferences_text = ""
|
|
if preferences:
|
|
preferences_text = f"""
|
|
Recipient interests: {preferences.interests if preferences.interests else 'Not specified'}
|
|
Recipient hobbies: {preferences.hobbies if preferences.hobbies else 'Not specified'}
|
|
Favorite colors: {preferences.favorite_colors if preferences.favorite_colors else 'Not specified'}
|
|
Clothing size: {preferences.clothing_size if preferences.clothing_size else 'Not specified'}
|
|
Dislikes: {preferences.dislikes if preferences.dislikes else 'Not specified'}
|
|
"""
|
|
|
|
occasion_text = f"Occasion: {occasion}" if occasion else "Occasion: Not specified"
|
|
additional_text = f"Additional information: {additional_info}" if additional_info else ""
|
|
|
|
prompt = f"""
|
|
You are a gift recommendation expert. Please suggest a thoughtful gift for {recipient_name}.
|
|
{occasion_text}
|
|
{budget_text}
|
|
{preferences_text}
|
|
{additional_text}
|
|
|
|
Please provide your gift recommendation in JSON format with the following fields:
|
|
1. recommendation_text: A thoughtful explanation of your recommendation
|
|
2. item_name: The name of the recommended gift
|
|
3. description: A detailed description of the gift
|
|
4. price_estimate: Estimated price (as a number only, no currency symbol)
|
|
5. purchase_url: A suggested place to purchase the gift (can be a generic store name if specific URL not available)
|
|
"""
|
|
|
|
try:
|
|
openai.api_key = self.api_key
|
|
|
|
response = openai.ChatCompletion.create(
|
|
model=self.model,
|
|
messages=[
|
|
{"role": "system", "content": "You are a helpful gift recommendation assistant."},
|
|
{"role": "user", "content": prompt}
|
|
],
|
|
temperature=0.7,
|
|
max_tokens=800,
|
|
)
|
|
|
|
# Extract the recommendation from the response
|
|
recommendation_text = response.choices[0].message.content.strip()
|
|
|
|
# Try to parse the JSON response
|
|
try:
|
|
# Find the JSON part in the response
|
|
json_start = recommendation_text.find('{')
|
|
json_end = recommendation_text.rfind('}') + 1
|
|
|
|
if json_start >= 0 and json_end > json_start:
|
|
json_content = recommendation_text[json_start:json_end]
|
|
recommendation_dict = json.loads(json_content)
|
|
else:
|
|
# If we can't find proper JSON, create a basic response
|
|
recommendation_dict = {
|
|
"recommendation_text": recommendation_text,
|
|
"item_name": "Custom Gift",
|
|
"description": "Based on the preferences provided",
|
|
"price_estimate": budget_min if budget_min else 50.0,
|
|
"purchase_url": "https://www.amazon.com"
|
|
}
|
|
|
|
# Ensure all required fields are present
|
|
required_fields = ["recommendation_text", "item_name", "description", "price_estimate", "purchase_url"]
|
|
for field in required_fields:
|
|
if field not in recommendation_dict:
|
|
recommendation_dict[field] = "Not specified"
|
|
|
|
# Convert price to float if it's a string
|
|
if isinstance(recommendation_dict["price_estimate"], str):
|
|
price_str = recommendation_dict["price_estimate"].replace("$", "").replace(",", "")
|
|
try:
|
|
recommendation_dict["price_estimate"] = float(price_str)
|
|
except ValueError:
|
|
recommendation_dict["price_estimate"] = budget_min if budget_min else 50.0
|
|
|
|
return recommendation_dict
|
|
|
|
except json.JSONDecodeError:
|
|
# If JSON parsing fails, create a structured response from the text
|
|
return {
|
|
"recommendation_text": recommendation_text,
|
|
"item_name": "Custom Gift",
|
|
"description": "Based on the preferences provided",
|
|
"price_estimate": budget_min if budget_min else 50.0,
|
|
"purchase_url": "https://www.amazon.com"
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail=f"Error generating recommendation: {str(e)}",
|
|
) |