226 lines
8.0 KiB
Python
226 lines
8.0 KiB
Python
"""
|
|
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)}"
|
|
) |