from io import BytesIO import requests from fastapi import HTTPException from PIL import Image from app.config import get_settings from app.core.errors import BadRequestError, VendorError from app.schemas.requests import ExtractionRequest from app.schemas.responses import APIResponse from app.services.factory import AIServiceFactory from app.utils.logger import exception_to_str, setup_logger logger = setup_logger(__name__) settings = get_settings() async def handle_extract(request: ExtractionRequest): request.max_attempts = max(request.max_attempts, 1) request.max_attempts = min(request.max_attempts, 5) for attempt in range(1, request.max_attempts + 1): try: logger.info(f"Attempt: {attempt}") if request.ai_model in settings.OPENAI_MODELS: ai_vendor = "openai" elif request.ai_model in settings.ANTHROPIC_MODELS: ai_vendor = "anthropic" else: raise ValueError( f"Invalid AI model: {request.ai_model}, only support {settings.SUPPORTED_MODELS}" ) service = AIServiceFactory.get_service(ai_vendor) # pil_images = [] pil_images = None # temporarily removed to save cost for url in request.img_urls: try: # response = requests.get(url) # response.raise_for_status() # image = Image.open(BytesIO(response.content)) # pil_images.append(image) pass except Exception as e: # logger.error(f"Failed to download or process image from {url}: {exception_to_str(e)}") raise HTTPException( status_code=400, detail=f"Failed to process image from {url}", headers={"attempt": attempt}, ) json_attributes = await service.extract_attributes_with_validation( request.attributes, request.ai_model, request.img_urls, request.product_taxonomy, request.product_data, pil_images=pil_images, ) break except BadRequestError as e: logger.error( f"Bad request error: {exception_to_str(e)}", ) raise HTTPException( status_code=400, detail=exception_to_str(e), headers={"attempt": attempt}, ) except ValueError as e: logger.error(f"Value error: {exception_to_str(e)}") raise HTTPException( status_code=400, detail=exception_to_str(e), headers={"attempt": attempt}, ) except VendorError as e: logger.error(f"Vendor error: {exception_to_str(e)}") if attempt == request.max_attempts: raise HTTPException( status_code=500, detail=exception_to_str(e), headers={"attempt": attempt}, ) else: if request.ai_model in settings.ANTHROPIC_MODELS: request.ai_model = settings.OPENAI_MODELS[ 0 ] # switch to OpenAI, and try again if max_attempts not reached logger.info( f"Switching from anthropic to {request.ai_model} for attempt {attempt + 1}" ) elif request.ai_model in settings.OPENAI_MODELS: request.ai_model = settings.ANTHROPIC_MODELS[ 0 ] # switch to anthropic, and try again if max_attempts not reached logger.info( f"Switching from OpenAI to {request.ai_model} for attempt {attempt + 1}" ) except HTTPException as e: logger.error(f"HTTP exception: {exception_to_str(e)}") raise e except Exception as e: logger.error("Exception: ", exception_to_str(e)) if ( "overload" in str(e).lower() and request.ai_model in settings.ANTHROPIC_MODELS ): request.ai_model = settings.OPENAI_MODELS[ 0 ] # switch to OpenAI, and try again if max_attempts not reached if attempt == request.max_attempts: raise HTTPException( status_code=500, detail="Internal server error", headers={"attempt": attempt}, ) return json_attributes, attempt