""" Gateway de IA de Qualidot - Módulo de Adaptadores de Evaluación de Imágenes Propósito: Este módulo contiene funciones de adaptadores que permiten evaluar imágenes usando diferentes proveedores de IA. Cada función de adaptador se encarga de interactuar con un proveedor específico (como OpenAI, AssemblyAI, Deepgram, etc.) y de convertir la respuesta del proveedor al formato estándar de evaluación de imágenes de Qualidot. """ import json import mimetypes import mimetypes import tempfile import os from fastapi import HTTPException from matplotlib import image from openai import OpenAI, AsyncOpenAI import assemblyai as aai from pyparsing.common import Any from app.core.config import settings from app.schemas.image_standard import ImageRequestFile, StandardImageAnalysisResult, ImageEvaluationRubric from app.core import config from app.utilities.image_utilities import json_to_rubric, encode_image_from_bytes from app.services.image.prompt_builder import build_image_evaluation_prompt from anthropic import Anthropic import re # Importaciones de Clarifai from clarifai_grpc.channel.clarifai_channel import ClarifaiChannel from clarifai_grpc.grpc.api import resources_pb2, service_pb2, service_pb2_grpc from clarifai_grpc.grpc.api.status import status_code_pb2 # Función de adaptador principal que infiere el proveedor y llama al adaptador específico async def evaluate_image_with_provider(image_request: ImageRequestFile) -> StandardImageAnalysisResult: """ Función de adaptador para evaluar imágenes usando el proveedor de IA configurado. """ provider = image_request.provider.lower() content = await image_request.rubric.read() rubric_dict = json.loads(content) rubric = json_to_rubric(rubric_dict) prompt = build_image_evaluation_prompt(rubric) match provider: case "openai": return await evaluate_with_openai(image_request, prompt) case "clarifai": return await evaluate_with_clarifai(image_request, prompt) case "claude": return await evaluate_with_claude(image_request, prompt) case _: raise ValueError(f"Proveedor de IA no soportado: {image_request.provider}") # Función de adaptador para evaluar imágenes usando OpenAI async def evaluate_with_openai(image_request: ImageRequestFile, prompt: str) -> StandardImageAnalysisResult: """ Función de adaptador para evaluar imágenes usando OpenAI. (Plantilla para futuras implementaciones) """ client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) image_bytes = await image_request.file.read() base64_image = encode_image_from_bytes(image_bytes) try: response = await client.chat.completions.create( model=image_request.model, messages=[ {"role": "user", "content": [ {"type": "text", "text": prompt}, { "type": "image_url", "image_url": { "url": f"data:image/jpeg;base64,{base64_image}" } } ]} ], response_format={"type": "json_object"} ) resultado = json.loads(response.choices[0].message.content) return StandardImageAnalysisResult( status="success", original_filename=image_request.file.filename, provider_used="OpenAI", model_used=image_request.model, **resultado ) except Exception as e: # Capturamos cualquier error de OpenAI o de lectura de archivos raise HTTPException( status_code=500, detail=f"Error evaluando la imagen: {str(e)}" ) async def evaluate_with_clarifai(image_request: ImageRequestFile, prompt: str) -> StandardImageAnalysisResult: """ Función de adaptador para evaluar imágenes usando Clarifai. """ CLARIFAI_API_KEY = settings.CLARIFAI_API_KEY if not CLARIFAI_API_KEY: raise HTTPException( status_code=500, detail="No se encontró CLARIFAI_API_KEY en las variables de entorno" ) USER_ID = "openai" APP_ID = "chat-completion" try: image_bytes = await image_request.file.read() # 2. Preparamos y ejecutamos la llamada a Clarifai (gRPC) channel = ClarifaiChannel.get_grpc_channel() stub = service_pb2_grpc.V2Stub(channel) metadata = (('authorization', 'Key ' + CLARIFAI_API_KEY),) userDataObject = resources_pb2.UserAppIDSet(user_id=USER_ID, app_id=APP_ID) request = service_pb2.PostModelOutputsRequest( user_app_id=userDataObject, model_id=image_request.model, inputs=[ resources_pb2.Input( data=resources_pb2.Data( image=resources_pb2.Image(base64=image_bytes), text=resources_pb2.Text(raw=prompt) ) ) ] ) response = stub.PostModelOutputs(request, metadata=metadata) if response.status.code != status_code_pb2.SUCCESS: raise Exception(f"Clarifai Error: {response.status.description}") # 3. Extraemos la respuesta cruda raw_output = response.outputs[0].data.text.raw # 4. Limpiamos y parseamos el JSON devuelto json_match = re.search(r'\{.*\}', raw_output, re.DOTALL) if json_match: clean_json = json_match.group(0) parsed_data = json.loads(clean_json) else: # Respaldo en caso de que el modelo devuelva texto plano en lugar de JSON parsed_data = {"raw_response": raw_output} # 5. Retornamos el modelo estandarizado return StandardImageAnalysisResult( status="success", original_filename=image_request.file.filename, provider_used="Clarifai", model_used=image_request.model, **parsed_data ) except Exception as e: raise HTTPException( status_code=500, detail=f"Error evaluando la imagen con Clarifai: {str(e)}" ) async def evaluate_with_claude(image_request: ImageRequestFile, prompt: str) -> StandardImageAnalysisResult: """ Función de adaptador para evaluar imágenes usando Claude. """ client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) image_bytes = await image_request.file.read() base64_image = encode_image_from_bytes(image_bytes) media_type = image_request.file.content_type if media_type not in ["image/jpeg", "image/png", "image/gif", "image/webp"]: raise ValueError(f"Tipo de imagen no soportado por Anthropic: {media_type}") try: messages = [ { "role": "user", "content": [ { "type": "image", "source": { "type": "base64", "media_type": media_type, "data": base64_image }, }, {"type": "text", "text": prompt} ], } ] response = client.messages.create( model=image_request.model, max_tokens=1024, messages=messages, ) json_string = response.content[0].text parsed_data = json.loads(json_string) return StandardImageAnalysisResult( status="success", original_filename=image_request.file.filename, provider_used="Claude", model_used=image_request.model, **parsed_data ) except Exception as e: raise HTTPException( status_code=500, detail=f"Error evaluando la imagen: {str(e)}" )