Compare commits

1 Commits
main ... main

Author SHA256 Message Date
lansan69
e882442f8b Versión 1.0 2026-03-31 20:00:04 -06:00
30 changed files with 778 additions and 89 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
.env
.env
.venv

Binary file not shown.

View File

@@ -18,7 +18,7 @@ from app.services.audio.transcription_adapters import transcribe_audio_with_prov
# Inicializar el router de FastAPI para este módulo
audio_router_transcription = APIRouter()
@audio_router_transcription.post("/transcripts/", response_model=StandardTranscriptionResult)
@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

View File

@@ -0,0 +1,37 @@
"""
Gateway de IA de Qualidot - Módulo de Procesamiento de Documentos
Propósito:
Este endpoint recibe un documento y lo analiza usando inteligencia artificial,
extrayendo información relevante y estructurada.
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.document_standard import DocumentRequestFile, StandardDocumentAnalysisResult
from app.services.document.evaluations_adapters import evaluate_document_with_provider
# Inicializar el router de FastAPI para este módulo
document_router_analysis = APIRouter()
@document_router_analysis.post("/evaluations", response_model=StandardDocumentAnalysisResult)
async def evaluate_document(document_request: DocumentRequestFile = Depends()) -> StandardDocumentAnalysisResult:
"""
Endpoint para analizar documentos usando inteligencia artificial
Args:
document_request: Objeto DocumentRequestFile que contiene el archivo de documento,
el proveedor, el modelo y la rúbrica de evaluación.
Returns:
StandardDocumentAnalysisResult: Resultado del análisis de documentos en formato estándar de Qualidot
"""
try:
# Analizar el documento usando el adaptador de análisis que infiere el proveedor
analysis_result = await evaluate_document_with_provider(document_request)
return analysis_result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -17,18 +17,19 @@ 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
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
from app.api.v1.endpoints.document.rubricated_analysis import document_router_analysis # Endpoint de análisis rubricado de documentos
# 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_document = APIRouter()
api_router_audio.include_router(
audio_router_transcription,
@@ -58,4 +59,10 @@ api_router_image.include_router(
image_router_analysis,
prefix="/image",
tags=["Procesamiento de Imágenes"]
)
)
api_router_document.include_router(
document_router_analysis,
prefix="/document",
tags=["Procesamiento de Documentos"]
)

View File

@@ -19,7 +19,7 @@ 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)
@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

View File

@@ -19,7 +19,7 @@ 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)
@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

View File

@@ -16,7 +16,7 @@ from app.services.video.transcription_adapters import transcribe_video_with_prov
# Inicializar el router de FastAPI para este módulo
video_router_transcription = APIRouter()
@video_router_transcription.post("/transcripts/", response_model=StandardTranscriptionResult)
@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

View File

@@ -1,5 +1,5 @@
from fastapi import FastAPI
from app.api.v1.endpoints.router import api_router_audio, api_router_text, api_router_image, api_router_video
from app.api.v1.endpoints.router import api_router_audio, api_router_text, api_router_image, api_router_video, api_router_document
app = FastAPI(
title="Template de API de Procesamiento general",
@@ -11,6 +11,8 @@ app.include_router(api_router_audio, prefix="/api/v1", tags=["Procesamiento de A
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.include_router(api_router_document, prefix="/api/v1", tags=["Procesamiento de Documentos"])
@app.get("/")
def root():
@@ -26,18 +28,22 @@ def root():
"docs": "/docs",
"endpoints": {
"audio": {
"transcripción de audio": "/api/v1/audio/transcripts/",
"transcripción de audio": "/api/v1/audio/transcripts",
},
"texto": {
"resumen de texto": "/api/v1/text/summaries/",
"análisis rubricado": "/api/v1/text/evaluations/"
"resumen de texto": "/api/v1/text/summaries",
"análisis rubricado": "/api/v1/text/evaluations"
},
"imágenes": {
"análisis rubricado": "/api/v1/image/evaluations/",
"análisis rubricado": "/api/v1/image/evaluations",
},
"video": {
"transcripción de video": "/api/v1/video/transcripts/"
"transcripción de video": "/api/v1/video/transcripts"
},
"documentos": {
"análisis rubricado": "/api/v1/document/evaluations"
}
},
"modelos_disponibles": {
"audio": {

View File

@@ -0,0 +1,60 @@
"""
Esquema de resultado esperado de modelos de análisis de documentos.
Propósito:
Define el formato estándar de los resultados devueltos por los modelos de análisis
de documentos, independientemente del proveedor de IA utilizado.
Homologación:
Garantiza que el resultado del análisis de documentos siempre se entregue en el mismo
formato estándar para Qualidot.
"""
from dataclasses import dataclass
from fastapi import UploadFile
from fastapi.params import File, Form
from pydantic import BaseModel, Field
from typing import List, Optional
class DocumentEvaluationCriteria(BaseModel):
"""Modelo que representa un criterio de evaluación específico dentro de una rúbrica de análisis de imágenes."""
name: str = Field(..., description="Nombre del criterio de evaluación")
description: str = Field(None, description="Descripción detallada del criterio")
score: float = Field(None, description="Puntuación asignada al criterio después del análisis")
subcriteria: Optional[List["DocumentEvaluationCriteria"]] = Field(None, description="Lista de subcriterios de evaluación")
class DocumentEvaluationRubric(BaseModel):
"""Modelo que representa una rúbrica de evaluación personalizada para análisis de documentos."""
name: str = Field(..., description="Nombre de la rúbrica de evaluación")
description: str = Field(None, description="Descripción detallada de la rúbrica")
category: Optional[str] = Field(
None,
description="Contexto jerárquico de la categoría para especializar a la IA (ej. Audio > Education > English > B2)"
)
criteria: List[DocumentEvaluationCriteria] = Field(None, description="Lista de criterios de evaluación específicos")
@dataclass
class DocumentRequestFile:
"""Modelo de solicitud para análisis de documentos."""
file: UploadFile = File(..., description="Archivo de documento a procesar (ej. pdf, docx)")
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: UploadFile = File(..., description="Archivo JSON de evaluación")
class StandardDocumentAnalysisResult(BaseModel):
"""Modelo que representa el resultado estándar de un análisis de documentos para Qualidot."""
status: str = Field(..., description="Estado del análisis (ej. 'success', 'error')")
original_filename: str = Field(..., description="Nombre original del archivo de documento procesado")
provider_used: str = Field(..., description="Proveedor de IA utilizado para el análisis (ej. 'OpenAI')")
model_used: str = Field(..., description="Modelo específico utilizado para el análisis (ej. 'vision-1')")
score: float = Field(None, description="Puntuación general asignada al documento después del análisis")
feedback: str = Field(None, description="Comentarios o retroalimentación generada por el modelo de IA sobre el documento")
detailed_criteria: List[DocumentEvaluationCriteria] = Field(None, description="Lista de criterios de evaluación detallados con sus respectivas puntuaciones y descripciones")
class StandardDocumentAnalysis(BaseModel):
"""Modelo que representa el resultado estándar de un análisis de documentos para Qualidot."""
score: float = Field(None, description="Puntuación general asignada al documento después del análisis")
feedback: str = Field(None, description="Comentarios o retroalimentación generada por el modelo de IA sobre la imagen")
detailed_criteria: List[DocumentEvaluationCriteria] = Field(None, description="Lista de criterios de evaluación detallados con sus respectivas puntuaciones y descripciones")

View File

@@ -0,0 +1,162 @@
"""
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.document_standard import DocumentRequestFile, StandardDocumentAnalysisResult
from app.core import config
from app.utilities.document_utilities import json_to_rubric, encode_document_from_bytes
from app.services.document.prompt_builder import build_document_evaluation_prompt
from anthropic import Anthropic
from app.utilities.image_utilities import encode_image_from_bytes
# Función de adaptador principal que infiere el proveedor y llama al adaptador específico
async def evaluate_document_with_provider(document_request: DocumentRequestFile) -> StandardDocumentAnalysisResult:
"""
Función de adaptador para evaluar documentos usando el proveedor de IA configurado.
"""
provider = document_request.provider.lower()
content = await document_request.rubric.read()
rubric_dict = json.loads(content)
rubric = json_to_rubric(rubric_dict)
prompt = build_document_evaluation_prompt(rubric)
match provider:
case "openai":
return await evaluate_with_openai(document_request, prompt)
case "claude":
return await evaluate_with_claude(document_request, prompt)
case "gemini":
return await evaluate_with_gemini(document_request, prompt)
case _:
raise ValueError(f"Proveedor de IA no soportado: {document_request.provider}")
# Función de adaptador para evaluar documentos usando OpenAI
async def evaluate_with_openai(document_request: DocumentRequestFile, prompt: str) -> StandardDocumentAnalysisResult:
"""
Función de adaptador para evaluar documentos usando OpenAI.
"""
client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY)
document_bytes = await document_request.file.read()
base64_document = encode_document_from_bytes(document_bytes)
media_type = document_request.file.content_type
try:
response = await client.chat.completions.create(
model=document_request.model,
messages=[
{"role": "user", "content": [
{"type": "text", "text": prompt},
{
"type": "file",
"file": {
"file_data": f"data:{media_type};base64,{base64_document}",
"filename": document_request.file.filename
}
}
]}
],
response_format={"type": "json_object"}
)
resultado = json.loads(response.choices[0].message.content)
return StandardDocumentAnalysisResult(
status="success",
original_filename=document_request.file.filename,
provider_used="openai",
model_used=document_request.model,
**resultado
)
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Error evaluando el documento: {str(e)}"
)
# Función de adaptador para evaluar documentos usando Claude
async def evaluate_with_claude(document_request: DocumentRequestFile, prompt: str) -> StandardDocumentAnalysisResult:
"""
Función de adaptador para evaluar documentos usando Claude.
"""
client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
document_bytes = await document_request.file.read()
base64_document = encode_document_from_bytes(document_bytes)
media_type = document_request.file.content_type
if media_type not in ["application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"]:
raise ValueError(f"Tipo de documento no soportado por Anthropic: {media_type}")
try:
messages = [
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "base64",
"media_type": media_type,
"data": base64_document
},
},
{"type": "text", "text": prompt}
],
}
]
response = client.messages.create(
model=document_request.model,
max_tokens=4096,
messages=messages,
)
json_string = response.content[0].text
parsed_data = json.loads(json_string)
return StandardDocumentAnalysisResult(
status="success",
original_filename=document_request.file.filename,
provider_used="Claude",
model_used=document_request.model,
**parsed_data
)
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Error evaluando el documento: {str(e)}"
)
# Función de adaptador para evaluar documentos usando Gemini
async def evaluate_with_gemini(document_request: DocumentRequestFile, prompt: str) -> StandardDocumentAnalysisResult:
"""
Función de adaptador para evaluar documentos usando Gemini.
(Plantilla para futuras implementaciones)
"""
# Aquí iría la implementación específica para Gemini
pass

View File

@@ -0,0 +1,58 @@
"""
Módulo encargado de construir el prompt estandarizado para la evaluación de imágenes,
inyectando la rúbrica, el contexto de especialidad y el esquema JSON esperado.
Este módulo es crucial para asegurar que los evaluadores expertos reciban instrucciones
claras y consistentes, y que sus respuestas se ajusten al formato requerido para su posterior
procesamiento y análisis.
"""
import json
from app.schemas.document_standard import DocumentEvaluationRubric, StandardDocumentAnalysis
def build_document_evaluation_prompt(rubric: DocumentEvaluationRubric) -> str:
"""
Construye el prompt estandarizado inyectando la rúbrica,
el contexto de especialidad y el esquema JSON esperado.
"""
# Extraemos el JSON de la rúbrica de forma limpia (ignorando nulos)
rubric_json = rubric.model_dump_json(exclude_none=True, indent=2)
# Extraemos el esquema dinámico de salida basado en Pydantic
expected_output_schema = json.dumps(StandardDocumentAnalysis.model_json_schema(), indent=2)
# Obtenemos el path de especialización
# Si por alguna razón viene vacío, le damos un rol genérico por defecto
specialization_path = rubric.category if rubric.category else "General Document Analysis"
# Ensamblamos el prompt con f-strings
prompt = f"""
# ROLE
You are a highly specialized Expert Evaluator with deep domain expertise in the following area: **{specialization_path}**. Your objective is to perform a highly accurate, objective, and domain-calibrated analysis of the provided input based STRICTLY on your specialization and the provided evaluation rubric.
# TASK
I will provide you with an input and a JSON object representing an `EvaluationRubric`.
Your task is to analyze the input and score it against every criterion and subcriterion defined in the rubric. You must justify your scores with objective feedback based on evidence.
# DOMAIN CONTEXT
Specialization Path: {specialization_path}
# RUBRIC
{rubric_json}
# EVALUATION RULES
1. **Domain Calibration (CRITICAL):** Calibrate your expectations, strictness, and feedback entirely according to the **{specialization_path}** context. Do not evaluate using generalized standards; apply the specific standards expected at this exact level and category.
2. **Strict Adherence:** Evaluate ONLY the criteria and subcriteria listed in the rubric. Do not invent new metrics.
3. **Scoring:** Assign a numeric `score` to each criterion and subcriterion. The score must reflect how well the input meets the description of that specific metric.
4. **Objective Feedback:** Generate constructive, evidence-grounded `feedback` for the overall evaluation. Mention specific elements or patterns observed in the input that justify the scores within the context of the specialization.
5. **Subcriteria Handling:** If a criterion has `subcriteria`, evaluate and score each subcriterion individually. The parent criterion's `score` MUST be the exact mathematical **sum** of its subcriteria scores.
6. **Final Score Calculation:** The overall final `score` of the evaluation MUST be the exact mathematical **sum** of all the main criteria scores.
# OUTPUT FORMAT
You MUST return your response EXCLUSIVELY as a raw, valid JSON object that strictly adheres to the following JSON Schema definition. Do NOT include markdown blocks (```json), explanations, or any text outside the JSON object.
# EXPECTED JSON SCHEMA
{expected_output_schema}
"""
return prompt

View File

@@ -24,6 +24,12 @@ 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:
@@ -91,62 +97,77 @@ async def evaluate_with_openai(image_request: ImageRequestFile, prompt: str) ->
detail=f"Error evaluando la imagen: {str(e)}"
)
async def evaluate_with_clarifai(image_request: ImageRequestFile, rubric: ImageEvaluationRubric, prompt: str) -> StandardImageAnalysisResult:
async def evaluate_with_clarifai(image_request: ImageRequestFile, prompt: str) -> StandardImageAnalysisResult:
"""
Función de adaptador para evaluar imágenes usando Clarifai con un modelo Multimodal.
Función de adaptador para evaluar imágenes usando Clarifai.
"""
try:
# 1. Obtener el token de configuración (PAT)
pat = settings.CLARIFAI_API_KEY
if not pat:
raise ValueError("La clave CLARIFAI_API_KEY no está configurada en el entorno.")
# 2. Obtener la URL del modelo enviada en la petición
model_url = image_request.model
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"
)
# Inicializar el modelo de Clarifai
model = Model(url=model_url, pat=pat)
# 3. Leer los bytes de la imagen subida
USER_ID = "openai"
APP_ID = "chat-completion"
try:
image_bytes = await image_request.file.read()
if not image_bytes:
raise ValueError("El archivo de imagen recibido está vacío.")
# 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)
# 4. Preparar el input multimodal para Clarifai combinando la imagen y el prompt de evaluación
multimodal_input = Inputs.get_multimodal_input(
input_id="image_evaluation",
image_bytes=image_bytes,
raw_text=prompt
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)
)
)
]
)
# 5. Llamar a la API de Clarifai para evaluar la imagen
predict_response = model.predict([multimodal_input])
# Extraer el texto crudo de la respuesta del modelo
raw_output = predict_response.outputs[0].data.text.raw
# 6. Limpiar la respuesta y convertirla a JSON
# Los LLMs suelen devolver el JSON envuelto en bloques de markdown (```json ... ```)
clean_json = raw_output.replace("```json", "").replace("```", "").strip()
response = stub.PostModelOutputs(request, metadata=metadata)
# Convertir el string limpio a un diccionario de Python
parsed_data = json.loads(clean_json)
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
# 7. Retornar el resultado validado contra tu esquema estándar de Pydantic
return StandardImageAnalysisResult(**parsed_data)
except json.JSONDecodeError as e:
# Error específico si el modelo alucinó texto extra y no devolvió un JSON válido
raise HTTPException(
status_code=500,
detail=f"El modelo de Clarifai no devolvió un JSON válido. Error: {str(e)} | Respuesta cruda: {raw_output if 'raw_output' in locals() else 'N/A'}"
# 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:
# Captura cualquier otro error (problemas de red, token inválido, URL incorrecta, etc.)
raise HTTPException(
status_code=500,
detail=f"Error interno al evaluar con Clarifai: {str(e)}"
status_code=500,
detail=f"Error evaluando la imagen con Clarifai: {str(e)}"
)
async def evaluate_with_claude(image_request: ImageRequestFile, prompt: str) -> StandardImageAnalysisResult:

View File

@@ -44,9 +44,10 @@ Specialization Path: {specialization_path}
# EVALUATION RULES
1. **Domain Calibration (CRITICAL):** Calibrate your expectations, strictness, and feedback entirely according to the **{specialization_path}** context. Do not evaluate using generalized standards; apply the specific standards expected at this exact level and category.
2. **Strict Adherence:** Evaluate ONLY the criteria and subcriteria listed in the rubric. Do not invent new metrics.
3. **Scoring:** Assign a numeric `score` to each criterion and subcriterion. The score must reflect how well the input meets the description of that specific metric.
3. **Scoring:** Assign a numeric `score` to each criterion and subcriterion. The score must reflect how well the input meets the description of that specific metric.
4. **Objective Feedback:** Generate constructive, evidence-grounded `feedback` for the overall evaluation. Mention specific elements or patterns observed in the input that justify the scores within the context of the specialization.
5. **Subcriteria Handling:** If a criterion has `subcriteria`, evaluate each subcriterion individually. The parent criterion's score should be a logical aggregate (e.g., average) of its subcriteria scores.
5. **Subcriteria Handling:** If a criterion has `subcriteria`, evaluate and score each subcriterion individually. The parent criterion's `score` MUST be the exact mathematical **sum** of its subcriteria scores.
6. **Final Score Calculation:** The overall final `score` of the evaluation MUST be the exact mathematical **sum** of all the main criteria scores.
# OUTPUT FORMAT
You MUST return your response EXCLUSIVELY as a raw, valid JSON object that strictly adheres to the following JSON Schema definition. Do NOT include markdown blocks (```json), explanations, or any text outside the JSON object.

View File

@@ -0,0 +1,171 @@
{
"rubric": {
"id": "e7b9c1d2-4f5a-6b7c-8d9e-0a1b2c3d4e5f",
"name": "Academic Essay Evaluation Rubric",
"description": "Rúbrica detallada para evaluar la redacción de ensayos. Mide la calidad de la argumentación, la cohesión estructural y el dominio de la gramática y el vocabulario.",
"user_id": "112b3fda-e380-4919-9f9d-ff941a3b1938",
"status": true,
"visibility": "private",
"verified": true
},
"category": {
"id": 14,
"name": "Essay Evaluation",
"status": true,
"hierarchy": [
{
"id": 11,
"name": "Education",
"level": 0,
"parent_id": null
},
{
"id": 12,
"name": "Language Arts",
"level": 1,
"parent_id": 11
},
{
"id": 13,
"name": "Academic Writing",
"level": 2,
"parent_id": 12
},
{
"id": 14,
"name": "Essay Evaluation",
"level": 3,
"parent_id": 13
}
]
},
"criteria": [
{
"id": 100,
"name": "Content & Argumentation",
"description": "Evalúa la profundidad del contenido, la claridad de la tesis y la solidez de los argumentos presentados.",
"type_criteria": "primary",
"type_value": "numeric",
"value": "10",
"status": true,
"criteria_id": null,
"sub_criteria": [
{
"id": 101,
"name": "Thesis Clarity",
"description": "El ensayo presenta una tesis central clara, específica y debatible en la introducción.",
"type_criteria": "primary",
"type_value": "numeric",
"value": "10",
"status": true,
"criteria_id": 100
},
{
"id": 102,
"name": "Evidence & Support",
"description": "Los argumentos están respaldados por evidencia sólida, ejemplos relevantes o citas bien integradas.",
"type_criteria": "primary",
"type_value": "numeric",
"value": "10",
"status": true,
"criteria_id": 100
},
{
"id": 103,
"name": "Critical Thinking",
"description": "El texto demuestra un análisis profundo del tema, evitando generalidades superficiales o afirmaciones sin fundamento.",
"type_criteria": "primary",
"type_value": "numeric",
"value": "10",
"status": true,
"criteria_id": 100
}
]
},
{
"id": 200,
"name": "Structure & Organization",
"description": "Evalúa el flujo lógico del texto, la estructuración de párrafos y el uso de conectores.",
"type_criteria": "primary",
"type_value": "numeric",
"value": "10",
"status": true,
"criteria_id": null,
"sub_criteria": [
{
"id": 201,
"name": "Logical Flow",
"description": "Las ideas progresan de manera lógica. La introducción, el desarrollo y la conclusión están claramente definidos y equilibrados.",
"type_criteria": "primary",
"type_value": "numeric",
"value": "10",
"status": true,
"criteria_id": 200
},
{
"id": 202,
"name": "Paragraph Cohesion",
"description": "Cada párrafo se centra en una idea principal única. Las transiciones entre párrafos y oraciones son fluidas y naturales.",
"type_criteria": "primary",
"type_value": "numeric",
"value": "10",
"status": true,
"criteria_id": 200
}
]
},
{
"id": 300,
"name": "Language Mechanics & Style",
"description": "Evalúa las convenciones ortográficas, gramaticales y la riqueza del vocabulario.",
"type_criteria": "primary",
"type_value": "numeric",
"value": "10",
"status": true,
"criteria_id": null,
"sub_criteria": [
{
"id": 301,
"name": "Grammar & Syntax",
"description": "Las oraciones están construidas correctamente. No hay errores de concordancia, tiempos verbales o sintaxis.",
"type_criteria": "primary",
"type_value": "numeric",
"value": "10",
"status": true,
"criteria_id": 300
},
{
"id": 302,
"name": "Spelling & Punctuation",
"description": "Uso impecable de la ortografía y los signos de puntuación (comas, puntos, comillas, etc.).",
"type_criteria": "primary",
"type_value": "numeric",
"value": "10",
"status": true,
"criteria_id": 300
},
{
"id": 303,
"name": "Vocabulary & Tone",
"description": "El vocabulario es variado, preciso y adecuado para un contexto académico. El tono se mantiene formal y objetivo.",
"type_criteria": "primary",
"type_value": "numeric",
"value": "10",
"status": true,
"criteria_id": 300
}
]
}
],
"references": [
{
"id": 1,
"title": "APA Style Guidelines for Academic Writing",
"url": "https://apastyle.apa.org/style-grammar-guidelines",
"reference_type": "official_doc",
"is_primary": true,
"description": "Guía oficial de estilo APA para redacción, gramática y estructuración de textos académicos.",
"status": true
}
]
}

View File

@@ -0,0 +1,51 @@
from app.schemas.document_standard import DocumentEvaluationRubric, StandardDocumentAnalysis, DocumentEvaluationCriteria
def encode_document_from_bytes(document_bytes: bytes) -> str:
"""
Codifica un documento a base64 a partir de sus bytes.
"""
import base64
return base64.b64encode(document_bytes).decode('utf-8')
def json_to_rubric(json_data: dict) -> DocumentEvaluationRubric:
"""Convierte un diccionario JSON en un objeto DocumentEvaluationRubric mapeando los campos correctos."""
def parse_criteria(criteria_list: list) -> list:
parsed_criteria = []
for item in criteria_list:
# Convertir el valor a float si existe
score_value = float(item["value"]) if item.get("value") is not None else None
description = str(item["description"]) if item.get("description") is not None else None
# Procesar subcriterios recursivamente si existen
sub_raw = item.get("sub_criteria")
sub_parsed = parse_criteria(sub_raw) if sub_raw else None
parsed_criteria.append(DocumentEvaluationCriteria(
name=item["name"],
description=description,
score=score_value,
subcriteria=sub_parsed
))
return parsed_criteria
# Jerarquía de la categoría
category_info = json_data.get("category", {})
hierarchy_list = category_info.get("hierarchy", [])
# Ordenamos por nivel para asegurar la lógica: Nivel 0 -> Nivel 1 -> Nivel 2
sorted_hierarchy = sorted(hierarchy_list, key=lambda x: x.get("level", 0))
# Unimos los nombres con ' > '
specialization_path = " > ".join([item["name"] for item in sorted_hierarchy]) if sorted_hierarchy else None
# Extraer la información principal de la rúbrica
rubric_info = json_data.get("rubric", {})
return DocumentEvaluationRubric(
name=rubric_info.get("name", "Sin nombre"),
description=str(rubric_info["description"]) if rubric_info.get("description") is not None else None,
category=specialization_path,
criteria=parse_criteria(json_data.get("criteria", []))
)

View File

@@ -1,29 +1,143 @@
# 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
# ==========================================
# Web & API Framework (FastAPI)
# ==========================================
fastapi==0.121.3 # Framework principal para crear la API
fastapi-cli==0.0.24 # Interfaz de línea de comandos para FastAPI
fastapi-cloud-cli==0.15.0 # Herramientas de despliegue para FastAPI
starlette==0.50.0 # Toolkit ASGI base de FastAPI
uvicorn==0.41.0 # Servidor ASGI para ejecutar la aplicación
uvloop==0.22.1 # Ciclo de eventos rápido para Uvicorn
anyio==4.11.0 # Soporte asíncrono para concurrencia
sniffio==1.3.1 # Detección de la librería asíncrona en uso
websockets==16.0 # Soporte para conexiones WebSockets
watchfiles==1.1.1 # Recarga automática del servidor al detectar cambios
python-multipart==0.0.22 # Manejo de datos de formularios y subida de archivos
# Procesamiento de audio
noisereduce
librosa
soundfile
praat-parselmouth
numpy
# ==========================================
# Validación de Datos & Tipado
# ==========================================
pydantic==2.12.4 # Validación de datos y gestión de esquemas
pydantic_core==2.41.5 # Núcleo en Rust para Pydantic (rendimiento)
pydantic-extra-types==2.11.0 # Tipos adicionales para Pydantic
pydantic-settings==2.13.1 # Manejo avanzado de variables de entorno
annotated-types==0.7.0 # Metadatos para el tipado de variables
typing_extensions==4.15.0 # Funciones de tipado para versiones antiguas de Python
typing-inspection==0.4.2 # Inspección de tipos en tiempo de ejecución
email-validator==2.3.0 # Validación de correos electrónicos
# Modelos de IA e integraciones
openai
langchain
langchain-openai
assemblyai
anthropic
# ==========================================
# Modelos de IA, LLMs & Agentes (LangChain)
# ==========================================
openai==2.28.0 # Cliente oficial para la API de OpenAI
langchain==1.2.12 # Framework para aplicaciones con LLMs
langchain-core==1.2.19 # Componentes y abstracciones base de LangChain
langchain-openai==1.1.11 # Integración específica de OpenAI para LangChain
langgraph==1.1.2 # Creación de agentes y flujos cíclicos con LLMs
langgraph-checkpoint==4.0.1 # Gestión de estados y memoria para LangGraph
langgraph-prebuilt==1.0.8 # Componentes preconstruidos para LangGraph
langgraph-sdk==0.3.11 # SDK oficial de LangGraph
langsmith==0.7.17 # Monitoreo, trazas y depuración para LangChain
tiktoken==0.12.0 # Tokenizador rápido usado por OpenAI
# ==========================================
# Procesamiento de Audio & Voz (Speech-to-Text)
# ==========================================
assemblyai==0.56.0 # SDK de AssemblyAI para transcripción de audio
deepgram-sdk==6.0.1 # SDK de Deepgram para transcripción de audio
librosa==0.11.0 # Análisis de música y señales de audio
noisereduce==3.0.3 # Algoritmos para reducción de ruido en audio
praat-parselmouth==0.4.7 # Interfaz de Python para Praat (análisis fonético)
soundfile==0.13.1 # Lectura y escritura de archivos de audio
audioread==3.1.0 # Decodificación de audio multiplataforma
soxr==1.0.0 # Conversión de frecuencia de muestreo (resampling) de alta calidad
# ==========================================
# Ciencia de Datos, Matemáticas & Machine Learning
# ==========================================
numpy==2.4.3 # Computación numérica y manejo de arreglos (matrices)
scipy==1.17.1 # Funciones matemáticas, científicas y de ingeniería
scikit-learn==1.8.0 # Herramientas de Machine Learning y análisis de datos
numba==0.64.0 # Compilador JIT (Just-In-Time) para acelerar código matemático
llvmlite==0.46.0 # Motor subyacente para compilar con Numba
joblib==1.5.3 # Procesamiento en paralelo y caché (usado por scikit-learn)
threadpoolctl==3.6.0 # Control de hilos en librerías nativas (C/C++)
# ==========================================
# Redes & Clientes HTTP
# ==========================================
requests==2.32.5 # Cliente HTTP síncrono estándar
requests-toolbelt==1.0.0 # Utilidades adicionales para la librería requests
httpx==0.28.1 # Cliente HTTP asíncrono (alternativa moderna a requests)
httpcore==1.0.9 # Motor subyacente de red para HTTPX
httptools==0.7.1 # Analizador (parser) de peticiones HTTP ultrarrápido
h11==0.16.0 # Implementación pura de HTTP/1.1
urllib3==2.6.3 # Cliente HTTP base con gestión de conexiones y reintentos
certifi==2026.2.25 # Colección de certificados SSL/TLS raíz
idna==3.11 # Soporte para nombres de dominio internacionalizados
dnspython==2.8.0 # Herramientas para consultas y manipulación de DNS
# ==========================================
# Gráficos & Visualización
# ==========================================
matplotlib==3.10.8 # Creación de gráficas y visualizaciones de datos
contourpy==1.3.3 # Cálculo de contornos 2D (dependencia de matplotlib)
cycler==0.12.1 # Creación de iteradores complejos (dependencia de matplotlib)
fonttools==4.62.1 # Manipulación de fuentes tipográficas
kiwisolver==1.5.0 # Solucionador matemático rápido (dependencia de matplotlib)
pillow==12.1.1 # Procesamiento y manipulación de imágenes (PIL)
pyparsing==3.3.2 # Herramienta para crear analizadores de texto sintácticos
# ==========================================
# CLI (Terminal) & Utilidades de Salida
# ==========================================
click==8.3.1 # Creación rápida de interfaces de línea de comandos (CLI)
typer==0.24.1 # Creación de CLIs basado en Pydantic y tipado
rich==14.3.3 # Texto enriquecido, tablas y colores en la terminal
rich-toolkit==0.19.7 # Componentes adicionales para Rich
tqdm==4.67.3 # Barras de progreso visuales en consola
shellingham==1.5.4 # Herramienta para detectar qué shell se está utilizando
# ==========================================
# Serialización, Parsing & Utilidades Generales
# ==========================================
python-dotenv==1.2.2 # Carga de variables de entorno desde archivos .env
orjson==3.11.7 # Analizador (parser) de JSON ultrarrápido
jiter==0.13.0 # Parser de JSON eficiente (usado internamente por Pydantic)
ormsgpack==1.12.2 # Serialización de datos en formato MessagePack (rápida)
msgpack==1.1.2 # Serialización de datos en formato MessagePack (estándar)
PyYAML==6.0.3 # Procesamiento de archivos YAML
jsonpatch==1.33 # Aplicación de parches a documentos JSON
jsonpointer==3.0.0 # Identificación de nodos dentro de un JSON
python-dateutil==2.9.0.post0 # Extensiones robustas para el manejo de fechas (datetime)
regex==2026.2.28 # Motor de expresiones regulares alternativo y más potente
uuid_utils==0.14.1 # Utilidades para la generación rápida de UUIDs
charset-normalizer==3.4.5 # Detección automática de codificación de texto
six==1.17.0 # Librería de compatibilidad entre Python 2 y 3
tenacity==9.1.4 # Reintentos automáticos para código propenso a fallos
decorator==5.2.1 # Simplificación en la creación de decoradores
xxhash==3.6.0 # Algoritmo de hash no criptográfico extremadamente rápido
zstandard==0.25.0 # Compresión de datos rápida (algoritmo zstd)
packaging==26.0 # Manejo y parseo de versiones de paquetes de Python
platformdirs==4.9.4 # Identificación de rutas de directorios estándar del SO
pooch==1.9.0 # Descarga y almacenamiento en caché de archivos de datos
lazy-loader==0.5 # Carga perezosa (lazy) de módulos pesados
cffi==2.0.0 # Interfaz para llamar código en C desde Python (FFI)
pycparser==3.0 # Analizador sintáctico de lenguaje C en Python
rignore==0.7.6 # Herramienta para analizar archivos ignorados (ej. .gitignore)
annotated-doc==0.0.4 # Utilidades para extraer documentación de tipos anotados
fastar==0.8.0 # Utilidad secundaria (generalmente vinculada al framework web)
# ==========================================
# Plantillas & Procesamiento de Markdown
# ==========================================
Jinja2==3.1.6 # Motor de plantillas (usado comúnmente para renderizar HTML)
MarkupSafe==3.0.3 # Escapado seguro de strings para evitar inyecciones en HTML
markdown-it-py==4.0.0 # Analizador y renderizador de Markdown extensible
mdurl==0.1.2 # Utilidad para parsear URLs dentro de Markdown
Pygments==2.19.2 # Resaltador de sintaxis genérico para código fuente
# ==========================================
# Monitoreo & Diagnóstico del Sistema
# ==========================================
sentry-sdk==2.54.0 # Integración con Sentry para monitoreo y rastreo de errores
distro==1.9.0 # Extracción de información específica del sistema operativo Linux