Spaces:
Sleeping
Sleeping
import gradio as gr | |
import PyPDF2 | |
import os | |
import vertexai | |
from vertexai.generative_models import GenerativeModel, Part, SafetySetting | |
import base64 | |
""" | |
Este código se encarga de: | |
1. Leer un archivo de credenciales JSON para configurar Google Cloud. | |
2. Inicializar Vertex AI en la región us-central1. | |
3. Extraer preguntas y respuestas de dos PDFs: uno del docente y otro del alumno. | |
4. Filtrar únicamente las preguntas realmente respondidas por el alumno. | |
5. Enviar ese contenido filtrado al modelo generativo (Gemini 1.5), con instrucciones para que | |
NO mencione preguntas no respondidas. | |
""" | |
# Configuración del modelo y parámetros globales | |
generation_config = { | |
"max_output_tokens": 8192, | |
"temperature": 0, | |
"top_p": 0.75, | |
} | |
safety_settings = [ | |
SafetySetting( | |
category=SafetySetting.HarmCategory.HARM_CATEGORY_HATE_SPEECH, | |
threshold=SafetySetting.HarmBlockThreshold.OFF | |
), | |
SafetySetting( | |
category=SafetySetting.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, | |
threshold=SafetySetting.HarmBlockThreshold.OFF | |
), | |
SafetySetting( | |
category=SafetySetting.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, | |
threshold=SafetySetting.HarmBlockThreshold.OFF | |
), | |
SafetySetting( | |
category=SafetySetting.HarmCategory.HARM_CATEGORY_HARASSMENT, | |
threshold=SafetySetting.HarmBlockThreshold.OFF | |
), | |
] | |
def configurar_credenciales(json_path: str): | |
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = json_path | |
def extraer_texto(pdf_path: str) -> str: | |
"""Extraer texto de todas las páginas de un PDF.""" | |
texto_total = "" | |
with open(pdf_path, "rb") as f: | |
lector = PyPDF2.PdfReader(f) | |
for page in lector.pages: | |
texto_total += page.extract_text() or "" | |
return texto_total | |
def parsear_preguntas_respuestas(texto: str) -> dict: | |
"""Dado un texto con formato, retorna un dict {pregunta: respuesta}.""" | |
# Buscamos líneas que inicien con "Pregunta" y "Respuesta" | |
lineas = texto.split("\n") | |
resultado = {} | |
pregunta_actual = None | |
for linea in lineas: | |
linea_str = linea.strip() | |
if linea_str.lower().startswith("pregunta"): | |
pregunta_actual = linea_str | |
resultado[pregunta_actual] = "" | |
elif linea_str.lower().startswith("respuesta") and pregunta_actual: | |
# No mezclamos en la misma línea "Pregunta X:" | |
# sino que esperamos "Pregunta X" en una línea y "Respuesta X" en la siguiente | |
# si el formateo es distinto, ajusta aquí. | |
# Tomamos lo que está después de ':' | |
partes = linea_str.split(":", 1) | |
if len(partes) > 1: | |
respuesta = partes[1].strip() | |
resultado[pregunta_actual] = respuesta | |
return resultado | |
def revisar_examen(json_cred, pdf_docente, pdf_alumno): | |
try: | |
# Configurar credenciales | |
configurar_credenciales(json_cred.name) | |
# Inicializar Vertex AI | |
vertexai.init(project="deploygpt", location="us-central1") | |
# Extraer texto de ambos PDFs | |
docente_texto = extraer_texto(pdf_docente.name) | |
alumno_texto = extraer_texto(pdf_alumno.name) | |
# Parsear preguntas y respuestas | |
preguntas_docente = parsear_preguntas_respuestas(docente_texto) | |
respuestas_alumno = parsear_preguntas_respuestas(alumno_texto) | |
# Filtrar solo preguntas respondidas | |
preguntas_filtradas = {} | |
for pregunta_doc, resp_doc in preguntas_docente.items(): | |
if pregunta_doc in respuestas_alumno: | |
# El alumno respondió esta pregunta | |
preguntas_filtradas[pregunta_doc] = { | |
"respuesta_doc": resp_doc, | |
"respuesta_alumno": respuestas_alumno[pregunta_doc] | |
} | |
if not preguntas_filtradas: | |
return "El alumno no respondió ninguna de las preguntas del docente." | |
# Construir un texto que contenga únicamente las preguntas respondidas | |
# e instrucciones claras para no alucinar preguntas. | |
# Vamos a pasarlo en 1 solo Part, para forzar a que la LLM no confunda. | |
contenido_final = """Instrucciones: Solo hay estas preguntas respondidas por el alumno. | |
No menciones preguntas que no estén en esta lista. Para cada pregunta, analiza la respuesta. | |
Al final, da un resumen. | |
""" | |
for i, (p, data) in enumerate(preguntas_filtradas.items(), 1): | |
contenido_final += f"\nPregunta {i}: {p}\n" \ | |
f"Respuesta del alumno: {data['respuesta_alumno']}\n" \ | |
f"Respuesta correcta (docente): {data['respuesta_doc']}\n" | |
# Creamos un Part con el contenido filtrado | |
part_filtrado = Part( | |
mime_type="text/plain", | |
text=contenido_final, | |
) | |
# System instruction, for clarity | |
textsi_1 = """Actúa como un asistente de docente experto en Bioquímica. | |
No menciones preguntas que el alumno no respondió. | |
Analiza únicamente las preguntas provistas en el texto. | |
Calcula un porcentaje de precisión basado en las respuestas incluidas. | |
""" | |
model = GenerativeModel( | |
"gemini-1.5-pro-001", | |
system_instruction=[textsi_1] | |
) | |
# Llamada al modelo con las partes. | |
response = model.generate_content( | |
[part_filtrado], | |
generation_config=generation_config, | |
safety_settings=safety_settings, | |
stream=False, | |
) | |
return response.text | |
except Exception as e: | |
return f"Error al procesar: {str(e)}" | |
# Interfaz Gradio | |
interface = gr.Interface( | |
fn=revisar_examen, | |
inputs=[ | |
gr.File(label="Credenciales JSON"), | |
gr.File(label="PDF Docente"), | |
gr.File(label="PDF Alumno") | |
], | |
outputs=gr.Textbox(label="Resultado"), | |
title="Revisión de Exámenes", | |
description="Sube tus credenciales, el PDF del docente y el del alumno para revisar las respuestas sin alucinaciones." | |
) | |
interface.launch(debug=True) | |