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) | |