recipe-api / app.py
Hammad712's picture
Update app.py
b2c3d9b verified
raw
history blame
11.8 kB
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from pymongo import MongoClient
from urllib.parse import quote_plus
import uuid
from typing import List, Optional
import json
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import HTMLResponse
import os
import base64
from groq import Groq
# Initialize Groq client
client = Groq(api_key='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')
# MongoDB connection setup
def get_mongo_client():
password = quote_plus("momimaad@123") # Change this to your MongoDB password
mongo_uri = f"mongodb+srv://hammad:{password}@cluster0.2a9yu.mongodb.net/"
return MongoClient(mongo_uri)
db_client = get_mongo_client()
db = db_client["recipe"]
user_collection = db["user_info"]
# Pydantic models for user data
class User(BaseModel):
first_name: str
last_name: str
email: str
password: str
class UserData(BaseModel):
email: str
password: str
class UserToken(BaseModel):
token: str
class RecipeData(BaseModel):
name: str
class AltrecipeData(BaseModel):
recipe_name: str
dietary_restrictions: str
allergies: List
class Ingredient(BaseModel):
name: str
quantity: str
class Recipe(BaseModel):
recipe_name: str
ingredients: List[Ingredient]
directions: List[str]
# Data model for LLM to generate
class Alternative_Ingredient(BaseModel):
name: str
quantity: str
class Alternative_Recipe(BaseModel):
recipe_name: str
alternative_ingredients: List[Alternative_Ingredient]
alternative_directions: List[str]
def get_recipe(recipe_name: str) -> Recipe:
chat_completion = client.chat.completions.create(
messages=[
{
"role": "system",
"content": f"""Your are an expert agent to generate a recipes with proper and corrected ingredients and direction. Your directions should be concise and to the point and dont explain any irrelevant text.
You are a recipe database that outputs recipes in JSON.\n
The JSON object must use the schema: {json.dumps(Recipe.model_json_schema(), indent=2)}""",
},
{
"role": "user",
"content": f"Fetch a recipe for {recipe_name}",
},
],
model="llama-3.2-90b-text-preview",
temperature=0,
# Streaming is not supported in JSON mode
stream=False,
# Enable JSON mode by setting the response format
response_format={"type": "json_object"},
)
return Recipe.model_validate_json(chat_completion.choices[0].message.content)
def Suggest_ingredient_alternatives(recipe_name: str, dietary_restrictions: str, allergies: List) -> Alternative_Recipe:
chat_completion = client.chat.completions.create(
messages=[
{
"role": "system",
"content": f"""
You are an expert agent to suggest alternatives for specific allergies ingredients for the provided recipe {recipe_name}.
Please take the following into account:
- If the user has dietary restrictions, suggest substitutes that align with their needs (e.g., vegan, gluten-free, etc.) in alternative_directions and your alternative_directions should be concise and to the point.
-In ingredient you will recommend the safe ingredient for avoid any allergy and dietary restriction.
- Consider the following allergies {allergies} and recommend the safe ingredient to avoid this allergies.
recipe_name: {recipe_name}
Dietary Restrictions: {dietary_restrictions}
Allergies: {', '.join(allergies)}
You are a recipe database that outputs alternative recipes to avoid allergy and dietary_restrictions in JSON.\n
The JSON object must use the schema: {json.dumps(Alternative_Recipe.model_json_schema(), indent=2)}""",
},
{
"role": "user",
"content": f"""Fetch a alternative recipe for recipe_name: {recipe_name}
Dietary Restrictions: {dietary_restrictions}
Allergies: {', '.join(allergies)}""",
},
],
model="llama-3.2-90b-text-preview",
temperature=0,
# Streaming is not supported in JSON mode
stream=False,
# Enable JSON mode by setting the response format
response_format={"type": "json_object"},
)
return Alternative_Recipe.model_validate_json(chat_completion.choices[0].message.content)
def get_status(content):
chat_completion = client.chat.completions.create(
messages=[
{
"role": "system",
"content": """Your are an expert agent to status yes if any kind of recipe dish present in explanation other no
Json output format:
{'status':return'yes' if any dish present in expalantion return 'no' if not dish present in image}
""",
},
{
"role": "user",
"content": f"Image Explanation {content}",
},
],
model="llama3-groq-70b-8192-tool-use-preview",
temperature=0,
# Streaming is not supported in JSON mode
stream=False,
# Enable JSON mode by setting the response format
response_format={"type": "json_object"},
)
return chat_completion.choices[0].message.content
# Function to encode the image
def encode_image(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
def explain_image(base64_image):
text_query = '''
explain the image.
'''
chat_completion = client.chat.completions.create(
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": text_query},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}",
},
},
],
}
],
model="llama-3.2-90b-vision-preview")
return chat_completion.choices[0].message.content
class get_recipe_name(BaseModel):
recipe_name: List[str]
ingredients: List[List[str]]
def generate_recipe_name(base64_image):
# Example of how the JSON should look to make it clearer
example_json_structure = {
"recipe_name": "Chicken Karhai",
"ingredients": [
"chicken",
"tomatoes",
"onions",
"ginger",
"garlic",
"green chilies",
"yogurt",
"cumin seeds",
"coriander powder",
"red chili powder",
"turmeric powder",
"garam masala",
"fresh coriander leaves",
"oil",
"salt"
]
}
# Generating the query prompt to ask for ingredients
text_query = f'''What are the ingredients used in these dishes? Do not add any explanation, just write the names of the ingredients in proper JSON according to the following format:
The JSON object must follow this schema:
{json.dumps(get_recipe_name.model_json_schema(), indent=2)}
Example format:
{json.dumps(example_json_structure, indent=2)}
Write the name of the dish and then list the ingredients used for each recipe, focusing on traditional Pakistani ingredients and terminology.
'''
chat_completion = client.chat.completions.create(
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": text_query},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}",
},
},
],
}
],
response_format={"type": "json_object"},
model="llama-3.2-90b-vision-preview")
return json.loads(chat_completion.choices[0].message.content)
app = FastAPI()
@app.post("/get_recipe/{token}")
async def get_recipe_response(token: str, recipe_user: RecipeData):
user = user_collection.find_one({"token": token})
if not user:
raise HTTPException(status_code=401, detail="Invalid token")
# Find user by email
recipe_name = recipe_user.name
response = get_recipe(recipe_name)
return {
"Response": response
}
@app.post("/get_recipe_alternative/{token}")
async def get_alternative_recipe_response(token: str, altrecipe_user: AltrecipeData):
user = user_collection.find_one({"token": token})
if not user:
raise HTTPException(status_code=401, detail="Invalid token")
response = Suggest_ingredient_alternatives(altrecipe_user.recipe_name, altrecipe_user.dietary_restrictions, altrecipe_user.allergies)
return {
"Response": response
}
# Directory to save uploaded images
UPLOAD_DIR = "uploads"
# Ensure the upload directory exists
os.makedirs(UPLOAD_DIR, exist_ok=True)
# Endpoint to upload an image
@app.post("/upload-image/{token}")
async def upload_image(token: str, file: UploadFile = File(...)):
user = user_collection.find_one({"token": token})
if not user:
raise HTTPException(status_code=401, detail="Invalid token")
# Validate the file type
if not file.filename.lower().endswith(('.png', '.jpg', '.jpeg')):
raise HTTPException(status_code=400, detail="Invalid file type. Only PNG, JPG, and JPEG are allowed.")
# Create a file path for saving the uploaded file
file_path = os.path.join(UPLOAD_DIR, file.filename)
# Save the file
with open(file_path, "wb") as buffer:
buffer.write(await file.read())
# Getting the base64 string
base64_image = encode_image(file_path)
status = get_status(explain_image(base64_image))
status_json = json.loads(status)
if status_json['status'].lower() == 'no':
response = {"recipe_name": [], 'ingredients': []}
else:
response = generate_recipe_name(base64_image)
return {
"Response": response
}
# Endpoint to register a new user
@app.post("/register")
async def register_user(user: User):
# Check if user already exists
existing_user = user_collection.find_one({"email": user.email})
if existing_user:
raise HTTPException(status_code=400, detail="Email already registered")
# Create user data
user_data = {
"first_name": user.first_name,
"last_name": user.last_name,
"email": user.email,
"password": user.password, # Store plaintext password (not recommended in production)
}
# Insert the user data into the user_info collection
result = user_collection.insert_one(user_data)
return {"msg": "User registered successfully", "user_id": str(result.inserted_id)}
# Endpoint to check user credentials and generate a token
@app.post("/get_token")
async def check_credentials(user: UserData):
# Find user by email
existing_user = user_collection.find_one({"email": user.email})
# Check if user exists and password matches
if not existing_user or existing_user["password"] != user.password:
raise HTTPException(status_code=401, detail="Invalid email or password")
# Generate a UUID token
token = str(uuid.uuid4())
# Update the user document with the token
user_collection.update_one({"email": user.email}, {"$set": {"token": token}})
return {
"first_name": existing_user["first_name"],
"last_name": existing_user["last_name"],
"token": token,
}