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: # Instrucciones para parsear: 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.from_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: return {} def comparar_preguntas_respuestas(dict_docente: dict, dict_alumno: dict) -> str: 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: configurar_credenciales(json_cred.name) vertexai.init(project="deploygpt", location="us-central1") texto_docente = extraer_texto(pdf_docente.name) texto_alumno = extraer_texto(pdf_alumno.name) 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) feedback = comparar_preguntas_respuestas(dict_docente, dict_alumno) if len(feedback.strip()) < 5: return "No se encontraron preguntas o respuestas válidas." 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.from_text(summary_prompt) summary_resp = model.generate_content( [summary_part], generation_config=generation_config, safety_settings=safety_settings, stream=False ) return f"{feedback}\n\n**Resumen**\n{summary_resp.text.strip()}" except Exception as e: return f"Error al procesar: {str(e)}" import gradio as gr 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)