diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a1e5ac6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..4c0d52d --- /dev/null +++ b/app/__init__.py @@ -0,0 +1 @@ + diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..f3f153b Binary files /dev/null and b/app/__pycache__/__init__.cpython-312.pyc differ diff --git a/app/__pycache__/main.cpython-312.pyc b/app/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000..37ab906 Binary files /dev/null and b/app/__pycache__/main.cpython-312.pyc differ diff --git a/app/api/v1/__init__.py b/app/api/v1/__init__.py new file mode 100644 index 0000000..8f862e9 --- /dev/null +++ b/app/api/v1/__init__.py @@ -0,0 +1 @@ +"""API v1 package""" diff --git a/app/api/v1/__pycache__/__init__.cpython-312.pyc b/app/api/v1/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..a9b3aad Binary files /dev/null and b/app/api/v1/__pycache__/__init__.cpython-312.pyc differ diff --git a/app/api/v1/endpoints/__pycache__/router.cpython-312.pyc b/app/api/v1/endpoints/__pycache__/router.cpython-312.pyc new file mode 100644 index 0000000..f270342 Binary files /dev/null and b/app/api/v1/endpoints/__pycache__/router.cpython-312.pyc differ diff --git a/app/api/v1/endpoints/audio/__pycache__/transcription.cpython-312.pyc b/app/api/v1/endpoints/audio/__pycache__/transcription.cpython-312.pyc new file mode 100644 index 0000000..695a164 Binary files /dev/null and b/app/api/v1/endpoints/audio/__pycache__/transcription.cpython-312.pyc differ diff --git a/app/api/v1/endpoints/audio/transcription.py b/app/api/v1/endpoints/audio/transcription.py new file mode 100644 index 0000000..572c84e --- /dev/null +++ b/app/api/v1/endpoints/audio/transcription.py @@ -0,0 +1,38 @@ +""" +Gateway de IA de Qualidot - Módulo de Procesamiento de Audio + +Propósito: + Este endpoint recibe un audio y lo convierte a texto usando inteligencia artificial. + +Homologación: + Sin importar qué proveedor de IA se utilice, el resultado siempre se entrega en el mismo formato estándar para Qualidot. +""" + +from dotenv import load_dotenv +from fastapi import APIRouter, Depends, UploadFile, File, HTTPException +from fastapi.security import APIKeyHeader +from app.api.v1.endpoints import router +from app.schemas.audio_standard import StandardTranscriptionResult, AudioRequestFile +from app.services.audio.transcription_adapters import transcribe_audio_with_provider + +# Inicializar el router de FastAPI para este módulo +audio_router_transcription = APIRouter() + +@audio_router_transcription.post("/transcripts/", response_model=StandardTranscriptionResult) +async def transcribe_audio(audio_request: AudioRequestFile = Depends()) -> StandardTranscriptionResult: + """ + Endpoint para transcribir audio simple infiriendo el proveedor de IA + + Args: + audio_request: Objeto AudioRequestFile que contiene el archivo de audio, + el proveedor, el modelo y las opciones de diarización, marcas de tiempo y análisis de sentimiento. + + Returns: + StandardTranscriptionResult: Resultado de la transcripción en formato estándar de Qualidot + """ + try: + # Transcribir usando el adaptador de transcripción que infiere el proveedor + transcription_result = await transcribe_audio_with_provider(audio_request) + return transcription_result + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) \ No newline at end of file diff --git a/app/api/v1/endpoints/image/__pycache__/rubricated_analysis.cpython-312.pyc b/app/api/v1/endpoints/image/__pycache__/rubricated_analysis.cpython-312.pyc new file mode 100644 index 0000000..db6c217 Binary files /dev/null and b/app/api/v1/endpoints/image/__pycache__/rubricated_analysis.cpython-312.pyc differ diff --git a/app/api/v1/endpoints/image/rubricated_analysis.py b/app/api/v1/endpoints/image/rubricated_analysis.py new file mode 100644 index 0000000..cafc0ab --- /dev/null +++ b/app/api/v1/endpoints/image/rubricated_analysis.py @@ -0,0 +1,39 @@ +""" +Gateway de IA de Qualidot - Módulo de Procesamiento de Imágenes con una rubrica de análisis + +Propósito: + Este endpoint recibe una imagen y la analiza usando una rúbrica de evaluación basada en inteligencia artificial. + +Homologación: + Sin importar qué proveedor de IA se utilice, el resultado siempre se entrega en el mismo formato estándar para Qualidot. +""" + +from dotenv import load_dotenv +from fastapi import APIRouter, Depends, UploadFile, File, HTTPException +from fastapi.security import APIKeyHeader +from app.api.v1.endpoints import router +from app.schemas.image_standard import StandardImageAnalysisResult, ImageRequestFile + +from app.services.image.evaluations_adapters import evaluate_image_with_provider + +# Inicializar el router de FastAPI para este módulo +image_router_analysis = APIRouter() + +@image_router_analysis.post("/evaluations/", response_model=StandardImageAnalysisResult) +async def evaluate_image(image_request: ImageRequestFile = Depends()) -> StandardImageAnalysisResult: + """ + Endpoint para analizar imágenes usando una rúbrica de evaluación infiriendo el proveedor de IA + + Args: + image_request: Objeto ImageRequestFile que contiene la imagen, + el proveedor, el modelo y las opciones de análisis. + + Returns: + StandardImageAnalysisResult: Resultado del análisis de imágenes en formato estándar de Qualidot + """ + try: + # Analizar imagen usando el adaptador de análisis de imagen que infiere el proveedor + analysis_result = await evaluate_image_with_provider(image_request) + return analysis_result + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) \ No newline at end of file diff --git a/app/api/v1/endpoints/router.py b/app/api/v1/endpoints/router.py new file mode 100644 index 0000000..a1163f8 --- /dev/null +++ b/app/api/v1/endpoints/router.py @@ -0,0 +1,61 @@ +""" +Gateway de IA de Qualidot - Módulo maestro de endpoints de procesamiento. + +Propósito: + Este módulo actúa como el punto central de enrutamiento para todos los + endpoints relacionados con el procesamiento de audio, imágenes, + documentos, video, etc. + +Homologación: + Sin importar qué proveedor de IA se utilice, el resultado siempre se + entrega en el mismo formato estándar para Qualidot. + +""" + +from dotenv import load_dotenv +from fastapi import APIRouter, UploadFile, File, HTTPException +from fastapi.security import APIKeyHeader + +# Importar los routers específicos de cada módulo +from app.api.v1.endpoints.audio.transcription import audio_router_transcription # Endpoint de transcripción de audio +from app.api.v1.endpoints.texto.resume import text_router_summary # Endpoint de resumen de texto +from app.api.v1.endpoints.texto.rubricated_analysis import text_router_analysis # Endpoint de análisis rubricado de texto +from app.api.v1.endpoints.video.transcription import video_router_transcription # Endpoint de transcripción de video +from app.api.v1.endpoints.image.rubricated_analysis import image_router_analysis # Endpoint de análisis rubricado de imágenes + +# Inicializar el router de FastAPI para los módulos de procesamiento +api_router_audio = APIRouter() +api_router_text = APIRouter() +api_router_video = APIRouter() +api_router_image = APIRouter() + + +api_router_audio.include_router( + audio_router_transcription, + prefix="/audio", + tags=["Procesamiento de Audio"] +) + +api_router_text.include_router( + text_router_summary, + prefix="/text", + tags=["Procesamiento de Texto"] +) + +api_router_text.include_router( + text_router_analysis, + prefix="/text", + tags=["Procesamiento de Texto"] +) + +api_router_video.include_router( + video_router_transcription, + prefix="/video", + tags=["Procesamiento de Video"] +) + +api_router_image.include_router( + image_router_analysis, + prefix="/image", + tags=["Procesamiento de Imágenes"] +) \ No newline at end of file diff --git a/app/api/v1/endpoints/texto/__pycache__/resume.cpython-312.pyc b/app/api/v1/endpoints/texto/__pycache__/resume.cpython-312.pyc new file mode 100644 index 0000000..2329166 Binary files /dev/null and b/app/api/v1/endpoints/texto/__pycache__/resume.cpython-312.pyc differ diff --git a/app/api/v1/endpoints/texto/__pycache__/rubricated_analysis.cpython-312.pyc b/app/api/v1/endpoints/texto/__pycache__/rubricated_analysis.cpython-312.pyc new file mode 100644 index 0000000..8564fc5 Binary files /dev/null and b/app/api/v1/endpoints/texto/__pycache__/rubricated_analysis.cpython-312.pyc differ diff --git a/app/api/v1/endpoints/texto/resume.py b/app/api/v1/endpoints/texto/resume.py new file mode 100644 index 0000000..8b7f4c7 --- /dev/null +++ b/app/api/v1/endpoints/texto/resume.py @@ -0,0 +1,39 @@ +""" +Gateway de IA de Qualidot - Módulo de Procesamiento de Texto para Resumen + +Propósito: + Este endpoint recibe un texto y lo resume usando inteligencia artificial. + +Homologación: + Sin importar qué proveedor de IA se utilice, el resultado siempre se entrega en el mismo formato estándar para Qualidot. +""" + +from dotenv import load_dotenv +from fastapi import APIRouter, Depends, UploadFile, File, HTTPException +from fastapi.security import APIKeyHeader +from app.api.v1.endpoints import router +from app.schemas.text_standard import StandardTextAnalysisResult, TextRequestFile +#from app.services.transcription_adapters import transcribe_audio_with_provider +from app.services.text.resume_adapters import summarize_text_with_provider + +# Inicializar el router de FastAPI para este módulo +text_router_summary = APIRouter() + +@text_router_summary.post("/summaries/", response_model=StandardTextAnalysisResult) +async def summarize_text(text_request: TextRequestFile = Depends()) -> StandardTextAnalysisResult: + """ + Endpoint para resumir texto simple infiriendo el proveedor de IA + + Args: + text_request: Objeto TextRequestFile que contiene el archivo de texto, + el proveedor, el modelo y las opciones de diarización, marcas de tiempo y análisis de sentimiento. + + Returns: + StandardTextAnalysisResult: Resultado del análisis de texto en formato estándar de Qualidot + """ + try: + # Resumir texto usando el adaptador de resumen de texto que infiere el proveedor + analysis_result = await summarize_text_with_provider(text_request) + return analysis_result + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) \ No newline at end of file diff --git a/app/api/v1/endpoints/texto/rubricated_analysis.py b/app/api/v1/endpoints/texto/rubricated_analysis.py new file mode 100644 index 0000000..a0200ef --- /dev/null +++ b/app/api/v1/endpoints/texto/rubricated_analysis.py @@ -0,0 +1,39 @@ +""" +Gateway de IA de Qualidot - Módulo de Procesamiento de Texto para Análisis Rubricado + +Propósito: + Este endpoint recibe un texto y lo analiza usando una rúbrica de evaluación basada en inteligencia artificial. + +Homologación: + Sin importar qué proveedor de IA se utilice, el resultado siempre se entrega en el mismo formato estándar para Qualidot. +""" + +from dotenv import load_dotenv +from fastapi import APIRouter, Depends, UploadFile, File, HTTPException +from fastapi.security import APIKeyHeader +from app.api.v1.endpoints import router +from app.schemas.text_standard import StandardTextAnalysisResult, TextRequestFile +#from app.services.transcription_adapters import transcribe_audio_with_provider +from app.services.text.evaluations_adapters import evaluate_text_with_provider + +# Inicializar el router de FastAPI para este módulo +text_router_analysis = APIRouter() + +@text_router_analysis.post("/evaluations/", response_model=StandardTextAnalysisResult) +async def evaluate_text(text_request: TextRequestFile = Depends()) -> StandardTextAnalysisResult: + """ + Endpoint para analizar texto usando una rúbrica de evaluación infiriendo el proveedor de IA + + Args: + text_request: Objeto TextRequestFile que contiene el archivo de texto, + el proveedor, el modelo y las opciones de diarización, marcas de tiempo y análisis de sentimiento. + + Returns: + StandardTextAnalysisResult: Resultado del análisis de texto en formato estándar de Qualidot + """ + try: + # Analizar texto usando el adaptador de análisis de texto que infiere el proveedor + analysis_result = await evaluate_text_with_provider(text_request) + return analysis_result + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) \ No newline at end of file diff --git a/app/api/v1/endpoints/video/__pycache__/transcription.cpython-312.pyc b/app/api/v1/endpoints/video/__pycache__/transcription.cpython-312.pyc new file mode 100644 index 0000000..1c008f0 Binary files /dev/null and b/app/api/v1/endpoints/video/__pycache__/transcription.cpython-312.pyc differ diff --git a/app/api/v1/endpoints/video/transcription.py b/app/api/v1/endpoints/video/transcription.py new file mode 100644 index 0000000..e7b60b0 --- /dev/null +++ b/app/api/v1/endpoints/video/transcription.py @@ -0,0 +1,36 @@ +""" +Gateway de IA de Qualidot - Módulo de Procesamiento de Videos + +Propósito: + Este endpoint recibe un video y lo convierte a texto usando inteligencia artificial. + +Homologación: + Sin importar qué proveedor de IA se utilice, el resultado siempre se entrega en el mismo formato estándar para Qualidot. +""" + +from dotenv import load_dotenv +from fastapi import APIRouter, Depends, UploadFile, File, HTTPException +from app.schemas.video_standard import StandardTranscriptionResult, VideoRequestFile +from app.services.video.transcription_adapters import transcribe_video_with_provider + +# Inicializar el router de FastAPI para este módulo +video_router_transcription = APIRouter() + +@video_router_transcription.post("/transcripts/", response_model=StandardTranscriptionResult) +async def transcribe_video(video_request: VideoRequestFile = Depends()) -> StandardTranscriptionResult: + """ + Endpoint para transcribir video simple infiriendo el proveedor de IA + + Args: + video_request: Objeto VideoRequestFile que contiene el archivo de video, + el proveedor, el modelo y las opciones de diarización, marcas de tiempo y análisis de sentimiento. + + Returns: + StandardTranscriptionResult: Resultado de la transcripción en formato estándar de Qualidot + """ + try: + # Transcribir usando el adaptador de transcripción que infiere el proveedor + transcription_result = await transcribe_video_with_provider(video_request) + return transcription_result + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) \ No newline at end of file diff --git a/app/core/__pycache__/config.cpython-312.pyc b/app/core/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000..19234a7 Binary files /dev/null and b/app/core/__pycache__/config.cpython-312.pyc differ diff --git a/app/core/config.py b/app/core/config.py new file mode 100644 index 0000000..1f7d4c1 --- /dev/null +++ b/app/core/config.py @@ -0,0 +1,26 @@ +import os +from dotenv import load_dotenv + +load_dotenv() + +class Settings: + # Configuración para las claves de API de los proveedores de IA + + # --------------------------------------------------------------- + # Proveedores de Audio + # --------------------------------------------------------------- + # OpenAI + OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "") + + #AssemblyAI + ASSEMBLYAI_API_KEY: str = os.getenv("ASSEMBLYAI_API_KEY", "") + + # Deepgram + DEEPGRAM_API_KEY: str = os.getenv("DEEPGRAM_API_KEY", "") + + # --------------------------------------------------------------- + # Proveedores de Imagen + # --------------------------------------------------------------- + + +settings = Settings() \ No newline at end of file diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..ba7c11d --- /dev/null +++ b/app/main.py @@ -0,0 +1,60 @@ +from fastapi import FastAPI +from app.api.v1.endpoints.router import api_router_audio, api_router_text, api_router_image, api_router_video + +app = FastAPI( + title="Template de API de Procesamiento general", + description="Template de API para procesamiento de audio, imagenes, documento, video, etc", + version="1.0.0" +) + +app.include_router(api_router_audio, prefix="/api/v1", tags=["Procesamiento de Audio"]) +app.include_router(api_router_text, prefix="/api/v1", tags=["Procesamiento de Texto"]) +app.include_router(api_router_image, prefix="/api/v1", tags=["Procesamiento de Imágenes"]) +app.include_router(api_router_video, prefix="/api/v1", tags=["Procesamiento de Video"]) + +@app.get("/") +def root(): + """ + Endpoint raíz para verificar que la API está corriendo y proporcionar información básica sobre los endpoints disponibles. + + Returns: + dict: Un mensaje de bienvenida y un resumen de los endpoints disponibles en la API. + + """ + return { + "message": "Template de API de Procesamiento general está corriendo con normalidad", + "docs": "/docs", + "endpoints": { + "audio": { + "transcripción de audio": "/api/v1/audio/transcripts/", + }, + "texto": { + "resumen de texto": "/api/v1/text/summaries/", + "análisis rubricado": "/api/v1/text/evaluations/" + }, + "imágenes": { + "análisis rubricado": "/api/v1/image/evaluations/", + }, + "video": { + "transcripción de video": "/api/v1/video/transcripts/" + } + }, + "modelos_disponibles": { + "audio": { + "openai": [ + "gpt-4o-transcribe", + "whisper-1" + ], + "assemblyai": ["universal-3-pro", "universal-2"] + }, + "texto": { + "pendiente de agregar proveedores y modelos específicos para procesamiento de texto" + }, + "imágenes": { + "pendiente de agregar proveedores y modelos específicos para procesamiento de imágenes" + }, + "video": { + "pendiente de agregar proveedores y modelos específicos para procesamiento de video" + } + } + } \ No newline at end of file diff --git a/app/schemas/__pycache__/audio_standar.cpython-312.pyc b/app/schemas/__pycache__/audio_standar.cpython-312.pyc new file mode 100644 index 0000000..81a07ee Binary files /dev/null and b/app/schemas/__pycache__/audio_standar.cpython-312.pyc differ diff --git a/app/schemas/__pycache__/audio_standard.cpython-312.pyc b/app/schemas/__pycache__/audio_standard.cpython-312.pyc new file mode 100644 index 0000000..b95adda Binary files /dev/null and b/app/schemas/__pycache__/audio_standard.cpython-312.pyc differ diff --git a/app/schemas/__pycache__/image_standard.cpython-312.pyc b/app/schemas/__pycache__/image_standard.cpython-312.pyc new file mode 100644 index 0000000..ffcf4c3 Binary files /dev/null and b/app/schemas/__pycache__/image_standard.cpython-312.pyc differ diff --git a/app/schemas/__pycache__/text_standard.cpython-312.pyc b/app/schemas/__pycache__/text_standard.cpython-312.pyc new file mode 100644 index 0000000..f38f664 Binary files /dev/null and b/app/schemas/__pycache__/text_standard.cpython-312.pyc differ diff --git a/app/schemas/__pycache__/video_standard.cpython-312.pyc b/app/schemas/__pycache__/video_standard.cpython-312.pyc new file mode 100644 index 0000000..b814004 Binary files /dev/null and b/app/schemas/__pycache__/video_standard.cpython-312.pyc differ diff --git a/app/schemas/audio_standard.py b/app/schemas/audio_standard.py new file mode 100644 index 0000000..9e45a62 --- /dev/null +++ b/app/schemas/audio_standard.py @@ -0,0 +1,92 @@ +""" +Esquema de resultado esperado de modelos de transcripción. + +Propósito: + Define el formato estándar de los resultados devueltos por los modelos de transcripción + de audio, independientemente del proveedor de IA utilizado. + +Homologación: + Garantiza que el resultado de la transcripción siempre se entregue en el mismo + formato estándar para Qualidot. + +Pendiente (recomendación personal): + - Definir mejor la estructura de cada segmento según las opciones avanzadas (diarization, timestamps, sentiment) + - Delimitar los proveedores de IA soportados inicialmente +""" + +from fastapi import UploadFile +from fastapi.params import File, Form +from pydantic import BaseModel, Field +from typing import List, Optional + +class AudioRequestFile: + """Modelo de solicitud para transcripción de audio.""" + def __init__( + self, + file: UploadFile = File(..., description="Archivo de audio a procesar (ej. mp3, wav)"), + provider: str = Form(..., description="Proveedor de IA a utilizar (ej. openai, assemblyai)"), + model: str = Form(..., description="Modelo de IA a utilizar (ej. whisper-1)"), + diarization: Optional[bool] = Form(False, description="Activa la separación e identificación de múltiples hablantes"), + timestamps: Optional[bool] = Form(False, description="Activa las marcas de tiempo exactas por cada segmento hablado"), + sentiment: Optional[bool] = Form(False, description="Activa el análisis de sentimiento (POSITIVO/NEGATIVO) por segmento") + ): + self.file = file + self.provider = provider + self.model = model + self.diarization = diarization + self.timestamps = timestamps + self.sentiment = sentiment + +class TranscriptionSegment(BaseModel): + """Modelo que representa un segmento de transcripción de audio detallado.""" + text: str = Field( + ..., + description="Texto transcrito en este fragmento específico" + ) + speaker: Optional[str] = Field( + None, + description="Identificador del hablante (ej. 'Speaker A') si diarization está activo" + ) + start_time: Optional[float] = Field( + None, + description="Marca de tiempo en segundos donde inicia el segmento" + ) + end_time: Optional[float] = Field( + None, + description="Marca de tiempo en segundos donde termina el segmento" + ) + sentiment: Optional[str] = Field( + None, + description="Sentimiento detectado en el segmento (ej. 'POSITIVE', 'NEGATIVE')" + ) + +class StandardTranscriptionResult(BaseModel): + """Modelo que representa el resultado estándar de una transcripción de audio para Qualidot.""" + status: str = Field( + ..., + description="Estado final de la petición ('success' o 'error')" + ) + original_filename: str = Field( + ..., + description="Nombre original del archivo de audio procesado" + ) + provider_used: str = Field( + ..., + description="Proveedor de IA que ejecutó la transcripción (ej. OpenAI)" + ) + model_used: str = Field( + ..., + description="Modelo específico utilizado para el proceso" + ) + full_transcript: Optional[str] = Field( + None, + description="Texto completo y continuo de toda la transcripción" + ) + segments: Optional[List[TranscriptionSegment]] = Field( + None, + description="Lista detallada de segmentos si se solicitaron opciones avanzadas (diarization, timestamps, sentiment)" + ) + confidence_score: Optional[float] = Field( + None, + description="Nivel de certeza global del modelo (valor entre 0.0 y 1.0)" + ) \ No newline at end of file diff --git a/app/schemas/image_standard.py b/app/schemas/image_standard.py new file mode 100644 index 0000000..bfc529d --- /dev/null +++ b/app/schemas/image_standard.py @@ -0,0 +1,44 @@ +""" +Esquema de resultado esperado de modelos de análisis de imágenes. + +Propósito: + Define el formato estándar de los resultados devueltos por los modelos de análisis + de imágenes, independientemente del proveedor de IA utilizado. + +Homologación: + Garantiza que el resultado del análisis de imágenes siempre se entregue en el mismo + formato estándar para Qualidot. +""" +from fastapi import UploadFile +from fastapi.params import File, Form +from pydantic import BaseModel, Field +from typing import List, Optional + +class ImageRequestFile: + """Modelo de solicitud para análisis de imágenes.""" + def __init__( + self, + file: UploadFile = File(..., description="Archivo de imagen a procesar (ej. jpg, png)"), + provider: str = Form(..., description="Proveedor de IA a utilizar (ej. openai, google)"), + model: str = Form(..., description="Modelo de IA a utilizar (ej. vision-1)"), + rubric: Optional[str] = Form(None, description="Rúbrica de evaluación personalizada para el análisis") + ): + self.file = file + self.provider = provider + self.model = model + self.rubric = rubric + +class StandardImageAnalysisResult(BaseModel): + """Modelo que representa el resultado estándar de un análisis de imágenes para Qualidot.""" + status: str = Field( + ..., + description="Estado del análisis (ej. 'success', 'error')" + ) + analysis: Optional[dict] = Field( + None, + description="Resultados detallados del análisis de la imagen, estructurados según la rúbrica si se proporcionó" + ) + error: Optional[str] = Field( + None, + description="Mensaje de error en caso de que el análisis haya fallado" + ) \ No newline at end of file diff --git a/app/schemas/text_standard.py b/app/schemas/text_standard.py new file mode 100644 index 0000000..131fe5a --- /dev/null +++ b/app/schemas/text_standard.py @@ -0,0 +1,44 @@ +""" +Esquema de resultado esperado de modelos de análisis de texto (JSON). + +Propósito: + Define el formato estándar de los resultados devueltos por los modelos de análisis + de texto, independientemente del proveedor de IA utilizado. + +Homologación: + Garantiza que el resultado del análisis de texto siempre se entregue en el mismo + formato estándar para Qualidot. +""" +from fastapi import UploadFile +from fastapi.params import File, Form +from pydantic import BaseModel, Field +from typing import List, Optional + +class TextRequestFile: + """Modelo de solicitud para análisis de texto.""" + def __init__( + self, + file: UploadFile = File(..., description="Archivo de texto a procesar (ej. txt, json)"), + provider: str = Form(..., description="Proveedor de IA a utilizar (ej. openai, google)"), + model: str = Form(..., description="Modelo de IA a utilizar (ej. text-1)"), + rubric: Optional[str] = Form(None, description="Rúbrica de evaluación personalizada para el análisis") + ): + self.file = file + self.provider = provider + self.model = model + self.rubric = rubric + +class StandardTextAnalysisResult(BaseModel): + """Modelo que representa el resultado estándar de un análisis de texto para Qualidot.""" + status: str = Field( + ..., + description="Estado del análisis (ej. 'success', 'error')" + ) + analysis: Optional[dict] = Field( + None, + description="Resultados detallados del análisis de texto, estructurados según la rúbrica si se proporcionó" + ) + error: Optional[str] = Field( + None, + description="Mensaje de error en caso de que el análisis haya fallado" + ) \ No newline at end of file diff --git a/app/schemas/video_standard.py b/app/schemas/video_standard.py new file mode 100644 index 0000000..65c31fd --- /dev/null +++ b/app/schemas/video_standard.py @@ -0,0 +1,65 @@ +""" +Esquema de resultado esperado de modelos de transcripción de video. + +Propósito: + Define el formato estándar de los resultados devueltos por los modelos de transcripción + de video, independientemente del proveedor de IA utilizado. + +Homologación: + Garantiza que el resultado de la transcripción siempre se entregue en el mismo + formato estándar para Qualidot. +""" +from fastapi import UploadFile +from fastapi.params import File, Form +from pydantic import BaseModel, Field +from typing import List, Optional + +class VideoRequestFile: + """Modelo de solicitud para transcripción de video.""" + def __init__( + self, + file: UploadFile = File(..., description="Archivo de video a procesar (ej. mp4, mov)"), + provider: str = Form(..., description="Proveedor de IA a utilizar (ej. openai, assemblyai)"), + model: str = Form(..., description="Modelo de IA a utilizar (ej. whisper-1)"), + diarization: Optional[bool] = Form(False, description="Activa la separación e identificación de múltiples hablantes"), + timestamps: Optional[bool] = Form(False, description="Activa las marcas de tiempo exactas por cada segmento hablado"), + sentiment: Optional[bool] = Form(False, description="Activa el análisis de sentimiento (POSITIVO/NEGATIVO) por segmento") + ): + self.file = file + self.provider = provider + self.model = model + self.diarization = diarization + self.timestamps = timestamps + self.sentiment = sentiment + + +class StandardTranscriptionResult(BaseModel): + """Modelo que representa el resultado estándar de una transcripción de video para Qualidot.""" + status: str = Field( + ..., + description="Estado final de la petición ('success' o 'error')" + ) + original_filename: str = Field( + ..., + description="Nombre original del archivo de video procesado" + ) + provider_used: str = Field( + ..., + description="Proveedor de IA que ejecutó la transcripción (ej. OpenAI)" + ) + model_used: str = Field( + ..., + description="Modelo específico utilizado para el proceso" + ) + full_transcript: Optional[str] = Field( + None, + description="Texto completo y continuo de toda la transcripción" + ) + segments: Optional[List[dict]] = Field( ##Queda pendiente definir mejor la estructura de cada segmento según las opciones avanzadas + None, + description="Lista detallada de segmentos si se solicitaron opciones avanzadas (diarization, timestamps, sentiment)" + ) + confidence_score: Optional[float] = Field( + None, + description="Nivel de certeza global del modelo (valor entre 0.0 y 1.0)" + ) \ No newline at end of file diff --git a/app/services/__pycache__/transcription_adapters.cpython-312.pyc b/app/services/__pycache__/transcription_adapters.cpython-312.pyc new file mode 100644 index 0000000..85ad205 Binary files /dev/null and b/app/services/__pycache__/transcription_adapters.cpython-312.pyc differ diff --git a/app/services/audio/__pycache__/transcription_adapters.cpython-312.pyc b/app/services/audio/__pycache__/transcription_adapters.cpython-312.pyc new file mode 100644 index 0000000..999534b Binary files /dev/null and b/app/services/audio/__pycache__/transcription_adapters.cpython-312.pyc differ diff --git a/app/services/audio/transcription_adapters.py b/app/services/audio/transcription_adapters.py new file mode 100644 index 0000000..1455a5d --- /dev/null +++ b/app/services/audio/transcription_adapters.py @@ -0,0 +1,163 @@ +""" +Gateway de IA de Qualidot - Módulo de Adaptadores de Transcripción + +Propósito: + Este módulo contiene funciones de adaptadores que permiten transcribir audio 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 transcripción de Qualidot. + +""" + +import tempfile +import os +from fastapi import HTTPException +from openai import OpenAI, AsyncOpenAI +import assemblyai as aai +from app.core.config import settings +from app.schemas.audio_standard import AudioRequestFile +from app.schemas.audio_standard import StandardTranscriptionResult +from app.utilities.audio_utilities import validate_audio_file, validate_audio_size, validate_audio_request +from app.core import config + +# Función de adaptador principal que infiere el proveedor y llama al adaptador específico +async def transcribe_audio_with_provider(audio_request: AudioRequestFile) -> StandardTranscriptionResult: + """ + Función de adaptador para transcribir audio usando el proveedor de IA configurado. + """ + provider = audio_request.provider.lower() + + match provider: + case "openai": + return await transcribe_with_openai(audio_request) + case "assemblyai": + return await transcribe_with_assemblyai(audio_request) + case _: + raise ValueError(f"Proveedor de IA no soportado: {audio_request.provider}") + +# Función de adaptador para transcribir audio usando OpenAI +async def transcribe_with_openai(audio_request: AudioRequestFile) -> StandardTranscriptionResult: + """ + Función de adaptador para transcribir audio usando OpenAI. + """ + client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) + + audio_content = await audio_request.file.read() + + temp_audio_path = None # Inicializamos la variable fuera del try + + # Validar el audio antes de continuar + validate_audio_request(audio_request, audio_content) + + try: + # Crear archivo temporal para el audio + with tempfile.NamedTemporaryFile( + delete=False, + suffix=os.path.splitext(audio_request.file.filename)[1] + ) as temp_audio: + temp_audio.write(audio_content) + temp_audio_path = temp_audio.name + + with open(temp_audio_path, "rb") as audio_file_obj: + transcription = await client.audio.transcriptions.create( + model=audio_request.model, + file=audio_file_obj, + response_format="text" + ) + + result = StandardTranscriptionResult( + status="success", + original_filename=audio_request.file.filename, + full_transcript=transcription, + model_used=audio_request.model, + provider_used="OpenAI", + confidence_score=None + ) + + return result + + except Exception as e: + # Capturamos cualquier error de OpenAI o de lectura de archivos + raise HTTPException( + status_code=500, + detail=f"Error transcribiendo el audio: {str(e)}" + ) + + finally: + if temp_audio_path and os.path.exists(temp_audio_path): + try: + os.unlink(temp_audio_path) + except Exception: + pass + +# Función de adaptador para transcribir audio usando AssemblyAI +async def transcribe_with_assemblyai(audio_request: AudioRequestFile) -> StandardTranscriptionResult: + """ + Función de adaptador para transcribir audio usando AssemblyAI. + """ + aai.settings.api_key = settings.ASSEMBLYAI_API_KEY + + audio_content = await audio_request.file.read() + temp_audio_path = None + + # Validar el audio antes de continuar + validate_audio_request(audio_request, audio_content) + + try: + # Crear archivo temporal para el audio + with tempfile.NamedTemporaryFile( + delete=False, + suffix=os.path.splitext(audio_request.file.filename)[1] + ) as temp_audio: + temp_audio.write(audio_content) + temp_audio_path = temp_audio.name + + #Definimos el modelo a usar + config = aai.TranscriptionConfig(language_code="es", speaker_labels=audio_request.diarization, + sentiment_analysis=audio_request.sentiment) + + transcription_obj = aai.Transcriber(config=config).transcribe(temp_audio_path) + + iterable = transcription_obj.sentiment_analysis if audio_request.sentiment else transcription_obj.utterances + + + if transcription_obj.status == aai.TranscriptStatus.error: + raise Exception(f"AssemblyAI Error: {transcription_obj.error}") + + assemblyaiSegment = "utterances" if not audio_request.sentiment else "sentiment_analysis" + + result = StandardTranscriptionResult( + status="success", + original_filename=audio_request.file.filename, + full_transcript=transcription_obj.text, + model_used=audio_request.model, + provider_used="AssemblyAI", + confidence_score=None, + segments=[ + { + "text": segment.text, + "speaker": segment.speaker if audio_request.diarization else None, + "start_time": segment.start if audio_request.timestamps else None, + "end_time": segment.end if audio_request.timestamps else None, + "sentiment": segment.sentiment if audio_request.sentiment else None + } + for segment in iterable + ] if (audio_request.diarization or audio_request.timestamps or audio_request.sentiment) else None + ) + + return result + + except Exception as e: + # Capturamos cualquier error de OpenAI o de lectura de archivos + raise HTTPException( + status_code=500, + detail=f"Error transcribiendo el audio: {str(e)}" + ) + + finally: + if temp_audio_path and os.path.exists(temp_audio_path): + try: + os.unlink(temp_audio_path) + except Exception: + pass + # Aquí iría la implementación específica para AssemblyAI, similar a la de OpenAI pero usando su SDK y formato de respuesta + pass \ No newline at end of file diff --git a/app/services/image/__pycache__/evaluations_adapters.cpython-312.pyc b/app/services/image/__pycache__/evaluations_adapters.cpython-312.pyc new file mode 100644 index 0000000..2971a13 Binary files /dev/null and b/app/services/image/__pycache__/evaluations_adapters.cpython-312.pyc differ diff --git a/app/services/image/evaluations_adapters.py b/app/services/image/evaluations_adapters.py new file mode 100644 index 0000000..f9f286e --- /dev/null +++ b/app/services/image/evaluations_adapters.py @@ -0,0 +1,68 @@ +""" +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 tempfile +import os +from fastapi import HTTPException +from openai import OpenAI, AsyncOpenAI +import assemblyai as aai +from app.core.config import settings +from app.schemas.image_standard import ImageRequestFile, StandardImageAnalysisResult +from app.core import config + +# 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() + + match provider: + case "openai": + return await evaluate_with_openai(image_request) + case "inserte nombre de otra ia aqui": + return await evaluate_with_ai_model2(image_request) + + # AGREGAR OTROS CASOS PARA DIFERENTES PROVEEDORES DE IA AQUÍ + + 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) -> StandardImageAnalysisResult: + """ + Función de adaptador para evaluar imágenes usando OpenAI. + (Plantilla para futuras implementaciones) + """ + client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) + + # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE EVALUACIÓN CON OPENAI: + # 1. Validar la imagen de entrada (tamaño, formato, etc.) + # 2. Configurar el cliente de OpenAI con la clave API + # 3. Llamar a la API de OpenAI para evaluar la imagen + # 4. Convertir la respuesta de OpenAI al formato estándar de evaluación de imágenes de Qualidot + # 5. Manejar errores y excepciones adecuadamente + raise NotImplementedError("La función evaluate_with_openai aún no está implementada.") + +async def evaluate_with_ai_model2(image_request: ImageRequestFile) -> StandardImageAnalysisResult: + """ + Función de adaptador para transcribir video usando otra AI. + (Plantilla para futuras implementaciones) + """ + + # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE TRANSCRIPCIÓN CON IA (ELEGIR MODELO): + # 1. Validar el video de entrada (tamaño, formato, etc.) + # 2. Configurar el cliente de OpenAI con la clave API + # 3. Llamar a la API de OpenAI para transcribir el video + # 4. Convertir la respuesta de OpenAI al formato estándar de resumen de texto de Qualidot + # 5. Manejar errores y excepciones adecuadamente + raise NotImplementedError("La función transcribe_with_ai_model2 aún no está implementada.") + +# Otros modelos de IA \ No newline at end of file diff --git a/app/services/text/__pycache__/evaluations_adapters.cpython-312.pyc b/app/services/text/__pycache__/evaluations_adapters.cpython-312.pyc new file mode 100644 index 0000000..c4b6d91 Binary files /dev/null and b/app/services/text/__pycache__/evaluations_adapters.cpython-312.pyc differ diff --git a/app/services/text/__pycache__/resume_adapters.cpython-312.pyc b/app/services/text/__pycache__/resume_adapters.cpython-312.pyc new file mode 100644 index 0000000..cbed22d Binary files /dev/null and b/app/services/text/__pycache__/resume_adapters.cpython-312.pyc differ diff --git a/app/services/text/evaluations_adapters.py b/app/services/text/evaluations_adapters.py new file mode 100644 index 0000000..b136d8c --- /dev/null +++ b/app/services/text/evaluations_adapters.py @@ -0,0 +1,50 @@ +""" +Gateway de IA de Qualidot - Módulo de Adaptadores de Resumen de Texto + +Propósito: + Este módulo contiene funciones de adaptadores que permiten resumir texto 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 resumen de texto de Qualidot. + +""" + +import tempfile +import os +from fastapi import HTTPException +from openai import OpenAI, AsyncOpenAI +import assemblyai as aai +from app.core.config import settings +from app.schemas.text_standard import TextRequestFile, StandardTextAnalysisResult +from app.core import config + +# Función de adaptador principal que infiere el proveedor y llama al adaptador específico +async def evaluate_text_with_provider(text_request: TextRequestFile) -> StandardTextAnalysisResult: + """ + Función de adaptador para evaluar texto usando el proveedor de IA configurado. + """ + provider = text_request.provider.lower() + + match provider: + case "openai": + return await evaluate_with_openai(text_request) + # AGREGAR OTROS CASOS PARA DIFERENTES PROVEEDORES DE IA AQUÍ + case _: + raise ValueError(f"Proveedor de IA no soportado: {text_request.provider}") + +# Función de adaptador para evaluar texto usando OpenAI +async def evaluate_with_openai(text_request: TextRequestFile) -> StandardTextAnalysisResult: + """ + Función de adaptador para evaluar texto usando OpenAI. + (Plantilla para futuras implementaciones) + """ + client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) + + # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE EVALUACIÓN CON OPENAI: + # 1. Validar el texto de entrada (tamaño, formato, etc.) + # 2. Configurar el cliente de OpenAI con la clave API + # 3. Llamar a la API de OpenAI para evaluar el texto + # 4. Convertir la respuesta de OpenAI al formato estándar de evaluación de texto de Qualidot + # 5. Manejar errores y excepciones adecuadamente + raise NotImplementedError("La función evaluate_with_openai aún no está implementada.") + +# Otros modelos de IA \ No newline at end of file diff --git a/app/services/text/resume_adapters.py b/app/services/text/resume_adapters.py new file mode 100644 index 0000000..31def8b --- /dev/null +++ b/app/services/text/resume_adapters.py @@ -0,0 +1,50 @@ +""" +Gateway de IA de Qualidot - Módulo de Adaptadores de Resumen de Texto + +Propósito: + Este módulo contiene funciones de adaptadores que permiten resumir texto 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 resumen de texto de Qualidot. + +""" + +import tempfile +import os +from fastapi import HTTPException +from openai import OpenAI, AsyncOpenAI +import assemblyai as aai +from app.core.config import settings +from app.schemas.text_standard import TextRequestFile, StandardTextAnalysisResult +from app.core import config + +# Función de adaptador principal que infiere el proveedor y llama al adaptador específico +async def summarize_text_with_provider(text_request: TextRequestFile) -> StandardTextAnalysisResult: + """ + Función de adaptador para resumir texto usando el proveedor de IA configurado. + """ + provider = text_request.provider.lower() + + match provider: + case "openai": + return await summarize_with_openai(text_request) + # AGREGAR OTROS CASOS PARA DIFERENTES PROVEEDORES DE IA AQUÍ + case _: + raise ValueError(f"Proveedor de IA no soportado: {text_request.provider}") + +# Función de adaptador para resumir texto usando OpenAI +async def summarize_with_openai(text_request: TextRequestFile) -> StandardTextAnalysisResult: + """ + Función de adaptador para resumir texto usando OpenAI. + (Plantilla para futuras implementaciones) + """ + client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) + + # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE RESUMEN CON OPENAI: + # 1. Validar el texto de entrada (tamaño, formato, etc.) + # 2. Configurar el cliente de OpenAI con la clave API + # 3. Llamar a la API de OpenAI para resumir el texto + # 4. Convertir la respuesta de OpenAI al formato estándar de resumen de texto de Qualidot + # 5. Manejar errores y excepciones adecuadamente + raise NotImplementedError("La función summarize_with_openai aún no está implementada.") + +# Otros modelos de IA \ No newline at end of file diff --git a/app/services/video/__pycache__/transcription_adapters.cpython-312.pyc b/app/services/video/__pycache__/transcription_adapters.cpython-312.pyc new file mode 100644 index 0000000..ca3e8be Binary files /dev/null and b/app/services/video/__pycache__/transcription_adapters.cpython-312.pyc differ diff --git a/app/services/video/transcription_adapters.py b/app/services/video/transcription_adapters.py new file mode 100644 index 0000000..aa8fb41 --- /dev/null +++ b/app/services/video/transcription_adapters.py @@ -0,0 +1,67 @@ +""" +Gateway de IA de Qualidot - Módulo de Adaptadores de Transcripción de Video + +Propósito: + Este módulo contiene funciones de adaptadores que permiten resumir texto 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 resumen de texto de Qualidot. + +""" + +import tempfile +import os +from fastapi import HTTPException +from openai import OpenAI, AsyncOpenAI +import assemblyai as aai +from app.core.config import settings +from app.schemas.video_standard import VideoRequestFile, StandardTranscriptionResult +from app.core import config + +# Función de adaptador principal que infiere el proveedor y llama al adaptador específico +async def transcribe_video_with_provider(video_request: VideoRequestFile) -> StandardTranscriptionResult: + """ + Función de adaptador para transcribir video usando el proveedor de IA configurado. + """ + provider = video_request.provider.lower() + + match provider: + case "nombre de la ia 1 aqui": + return await transcribe_with_ai_model1(video_request) + case "nombre de la ia 2 aqui": + return await transcribe_with_ai_model2(video_request) + + # AGREGAR OTROS CASOS PARA DIFERENTES PROVEEDORES DE IA AQUÍ + + case _: + raise ValueError(f"Proveedor de IA no soportado: {video_request.provider}") + +# Función de adaptador para transcribir video usando OpenAI +async def transcribe_with_ai_model1(video_request: VideoRequestFile) -> StandardTranscriptionResult: + """ + Función de adaptador para transcribir video usando OpenAI. + (Plantilla para futuras implementaciones) + """ + + # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE TRANSCRIPCIÓN CON IA (ELEGIR MODELO): + # 1. Validar el video de entrada (tamaño, formato, etc.) + # 2. Configurar el cliente de OpenAI con la clave API + # 3. Llamar a la API de OpenAI para transcribir el video + # 4. Convertir la respuesta de OpenAI al formato estándar de resumen de texto de Qualidot + # 5. Manejar errores y excepciones adecuadamente + raise NotImplementedError("La función transcribe_with_ai_model1 aún no está implementada.") + +async def transcribe_with_ai_model2(video_request: VideoRequestFile) -> StandardTranscriptionResult: + """ + Función de adaptador para transcribir video usando OpenAI. + (Plantilla para futuras implementaciones) + """ + + # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE TRANSCRIPCIÓN CON IA (ELEGIR MODELO): + # 1. Validar el video de entrada (tamaño, formato, etc.) + # 2. Configurar el cliente de OpenAI con la clave API + # 3. Llamar a la API de OpenAI para transcribir el video + # 4. Convertir la respuesta de OpenAI al formato estándar de resumen de texto de Qualidot + # 5. Manejar errores y excepciones adecuadamente + raise NotImplementedError("La función transcribe_with_ai_model2 aún no está implementada.") + +# Otros modelos de IA \ No newline at end of file diff --git a/app/utilities/__pycache__/audio_utilities.cpython-312.pyc b/app/utilities/__pycache__/audio_utilities.cpython-312.pyc new file mode 100644 index 0000000..fa521a5 Binary files /dev/null and b/app/utilities/__pycache__/audio_utilities.cpython-312.pyc differ diff --git a/app/utilities/audio_utilities.py b/app/utilities/audio_utilities.py new file mode 100644 index 0000000..8592a32 --- /dev/null +++ b/app/utilities/audio_utilities.py @@ -0,0 +1,62 @@ +""" +Validadores comunes para los endpoints de audio. +""" +import os +from fastapi import UploadFile, HTTPException + +from app.schemas.audio_standard import AudioRequestFile + +# Configuración de validaciones +ALLOWED_AUDIO_FORMATS = ["audio/mpeg", "audio/wav", "audio/x-wav", "audio/mp4", "audio/x-m4a"] +ALLOWED_EXTENSIONS = [".mp3", ".wav", ".m4a"] +MAX_AUDIO_SIZE_MB = 25 # Tamaño máximo en MB +MAX_AUDIO_SIZE_BYTES = MAX_AUDIO_SIZE_MB * 1024 * 1024 + + +def validate_audio_file(audio_file: UploadFile) -> None: + """ + Valida el formato y tamaño del archivo de audio. + + Args: + audio_file: Archivo de audio a validar + + Raises: + HTTPException: Si el archivo no cumple con los requisitos + """ + # Validar formato (content-type) + if audio_file.content_type not in ALLOWED_AUDIO_FORMATS: + raise HTTPException( + status_code=400, + detail=f"Formato de audio no válido. Solo se permiten: MP3, WAV, M4A" + ) + + # Validar extensión del archivo + file_extension = os.path.splitext(audio_file.filename)[1].lower() + if file_extension not in ALLOWED_EXTENSIONS: + raise HTTPException( + status_code=400, + detail=f"Extensión de archivo no válida. Solo se permiten: .mp3, .wav, .m4a" + ) + + +def validate_audio_size(audio_content: bytes) -> None: + """ + Valida el tamaño del contenido del audio. + + Args: + audio_content: Contenido del archivo de audio en bytes + + Raises: + HTTPException: Si el archivo excede el tamaño máximo + """ + if len(audio_content) > MAX_AUDIO_SIZE_BYTES: + raise HTTPException( + status_code=400, + detail=f"El archivo excede el tamaño máximo permitido de {MAX_AUDIO_SIZE_MB}MB" + ) + +def validate_audio_request(audio_request: AudioRequestFile, audio_content: bytes): + # Validar el archivo de audio + validate_audio_file(audio_request.file) + # Validar tamaño del archivo + validate_audio_size(audio_content) \ No newline at end of file diff --git a/docs.md b/docs.md new file mode 100644 index 0000000..ff67d25 --- /dev/null +++ b/docs.md @@ -0,0 +1,77 @@ +# Qualidot AI Gateway - FastAPI Template + +## Metadatos + +- **Autor:** Alan Ivan Sánchez Gómez +- **Revisor:** Francisco Pineda +- **Proyecto:** Qualidot +- **Versión:** 1.0 +- **Fecha de creación:** 12/03/2026 +- **Última actualización:** 14/03/2026 +- **Lenguaje:** Python 3.12+ +- **Tipo:** Microservicio +- **Dominio:** IA / NLP / Audio / Visión / Multimodal +- **Estado:** Desarrollo (MVP Funcional) + +--- + +## Descripción General + +Este microservicio es una **Template de FastAPI** diseñada para el consumo de modelos de IA (OpenAI, AssemblyAI, etc.). Su característica principal es la **homologación de respuestas**: sin importar el proveedor utilizado, el cliente siempre recibe los resultados en un formato estándar de Qualidot. + +--- + +## Objetivo + +- **Estandarización:** Proveer una base sólida para integrar nuevos modelos de IA rápidamente. +- **Evaluación:** Facilitar el testing de modelos mediante Postman con una estructura de datos predecible. +- **Escalabilidad:** Separar la lógica de negocio de los proveedores externos mediante el uso de adaptadores. + +--- + +## Cambios a Implementar + +- Agregar endpoints para procesamiento de texto, imagen y video. +- Mejorar la gestión de errores y mensajes de validación. +- Implementar autenticación y autorización (JWT o API Key). +- Añadir pruebas unitarias y de integración. +- Optimizar el manejo de archivos temporales y recursos. +- Agregar ejemplos de uso y scripts de automatización para testing. + +--- + +## Arquitectura / Flujo + +Entrada (Postman/Client) + ↓ +Validación (Check de extensión y peso del archivo) + ↓ +Preprocesamiento (Creación de archivos temporales / Seek(0)) + ↓ +Modelo(s) IA (Adaptadores de OpenAI, AssemblyAI, etc.) + ↓ +Postprocesamiento (Mapeo a esquema homologado StandardTranscriptionResult) + ↓ +Salida (Respuesta JSON estandarizada) + +## Estructura del proyecto +Para mantener el orden y evitar errores de importación circular, el proyecto se organiza de la siguiente manera: + +fastApiTemplate/ +├── app/ +│ ├── api/ +│ │ └── v1/ +│ │ └── endpoints/ # Definición de rutas y routers +│ │ ├── audio/ # Endpoints específicos de procesamiento de audio +│ │ ├── image/ # (Pendiente) Procesamiento de visión +│ │ ├── video/ # (Pendiente) Procesamiento de video +│ │ └── router.py # Router maestro que unifica los módulos +│ ├── core/ +│ │ └── config.py # Configuración central (Settings y Pydantic-settings) +│ ├── schemas/ # Contratos de datos (Pydantic Models) +│ ├── services/ # Adaptadores y lógica con proveedores de IA +│ ├── utilities/ # Helpers (Validaciones, conversiones de audio) +│ └── main.py # Punto de entrada de la aplicación +├── .env # Credenciales y API Keys (No trackeado en Git) +├── requirements.txt # Dependencias del proyecto +└── docs.md # Documentación técnica \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ecc246d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,28 @@ +# Dependencias principales +annotated-doc==0.0.4 +annotated-types==0.7.0 +anyio==4.11.0 +fastapi[standard]==0.121.3 +idna==3.11 +pydantic==2.12.4 +pydantic_core==2.41.5 +sniffio==1.3.1 +starlette==0.50.0 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +uvicorn[standard] +python-multipart +python-dotenv + +# Procesamiento de audio +noisereduce +librosa +soundfile +praat-parselmouth +numpy + +# Modelos de IA e integraciones +openai +langchain +langchain-openai +assemblyai \ No newline at end of file