import gradio as gr import PyPDF2 import os import json import vertexai from vertexai.generative_models import GenerativeModel, Part, SafetySetting # Configuración global generation_config = { "max_output_tokens": 4096, "temperature": 0, "top_p": 0.8, } 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: 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_con_llm(texto_pdf: str, model: GenerativeModel) -> dict: """ Usa el LLM para extraer preguntas y respuestas. Devuelve un dict {"Pregunta X": "Respuesta X", ...}. """ # Instrucciones para parsear: # - Buscar variaciones de "Pregunta" y "Respuesta" (mayúsculas, minúsculas, plural...) # - Devolver un JSON limpio, sin texto extra. prompt = f""" Eres un parser de texto. A continuación tienes el contenido de un PDF con un examen (o respuestas). Debes extraer todas las preguntas y sus respuestas. Considera que las palabras podrían estar en mayúsculas, minúsculas o plural (por ejemplo 'Pregunta', 'PREGUNTA', 'Preguntas', 'RESPUESTA', 'RESPUESTAS', etc.). Devuélvelas en formato JSON puro, sin explicación adicional. Usa este formato de salida: {{ "Pregunta 1": "Texto de la respuesta", "Pregunta 2": "Texto de la respuesta", ... }} Si hay preguntas sin respuesta, pon la respuesta como cadena vacía. Si no hay ninguna pregunta, devuelve un JSON vacío: {{}} Texto PDF: {texto_pdf} """ part_text = Part(mime_type="text/plain", text=prompt) response = model.generate_content( [part_text], generation_config=generation_config, safety_settings=safety_settings, stream=False ) try: data = json.loads(response.text.strip()) if isinstance(data, dict): return data else: return {} except: # Si no se pudo parsear como JSON, devolvemos dict vacío return {} def comparar_preguntas_respuestas( dict_docente: dict, dict_alumno: dict ) -> str: """ Recorre las preguntas del dict_docente y compara con las respuestas del dict_alumno. """ retroalimentacion = [] for pregunta, resp_correcta in dict_docente.items(): resp_alumno = dict_alumno.get(pregunta, None) if resp_alumno is None: retroalimentacion.append( f"**{pregunta}**\nNo fue asignada al alumno.\n" ) else: retroalimentacion.append( f"**{pregunta}**\n" f"Respuesta del alumno: {resp_alumno}\n" f"Respuesta correcta: {resp_correcta}\n" ) return "\n".join(retroalimentacion) def revisar_examen(json_cred, pdf_docente, pdf_alumno): try: # 1. Configurar credenciales configurar_credenciales(json_cred.name) # 2. Inicializar Vertex AI vertexai.init(project="deploygpt", location="us-central1") # 3. Extraer texto de PDFs texto_docente = extraer_texto(pdf_docente.name) texto_alumno = extraer_texto(pdf_alumno.name) # 4. Parsear con LLM para obtener dict de preguntas y respuestas model = GenerativeModel("gemini-1.5-pro-001", system_instruction=["Eres un parser estricto."]) dict_docente = parsear_con_llm(texto_docente, model) dict_alumno = parsear_con_llm(texto_alumno, model) # 5. Comparar y generar retroalimentación feedback = comparar_preguntas_respuestas(dict_docente, dict_alumno) # 6. Generar un summary final con LLM (opcional) # Queda a tu criterio si lo deseas: if len(feedback.strip()) < 5: return "No se encontraron preguntas/ respuestas válidas." # Llamada final al modelo para un summary: summary_prompt = f""" Eres un profesor experto. Te muestro la comparación de preguntas y respuestas: {feedback} Por favor, genera un breve resumen del desempeño del alumno sin inventar preguntas adicionales. """ summary_part = Part(mime_type="text/plain", text=summary_prompt) summary_resp = model.generate_content( [summary_part], generation_config=generation_config, safety_settings=safety_settings, stream=False ) summary_text = summary_resp.text.strip() return f"{feedback}\n\n**Resumen**\n{summary_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.Markdown(), title="Revisión de Exámenes con LLM (Permisivo)", description="Sube credenciales, el PDF del docente y del alumno; se emplea un LLM para encontrar 'Pregunta/Respuesta' aun con variaciones." ) interface.launch(debug=True)