File size: 4,978 Bytes
00832a8
b54a6bc
 
19c3ca0
7cba34a
 
19c3ca0
 
7cba34a
19c3ca0
d329e85
19c3ca0
7cba34a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b54a6bc
e341366
4d995a1
b54a6bc
 
 
 
 
 
 
 
19c3ca0
 
 
 
 
 
 
 
 
 
 
 
 
1b22788
19c3ca0
 
 
 
 
 
 
 
1b22788
 
19c3ca0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1b22788
19c3ca0
 
 
 
1b22788
19c3ca0
 
 
 
 
 
 
b54a6bc
 
7cba34a
b54a6bc
e341366
d35d511
19c3ca0
 
 
1b22788
 
 
 
 
19c3ca0
 
 
 
 
 
1b22788
19c3ca0
 
 
 
 
 
 
1b22788
19c3ca0
 
1e129b0
 
19c3ca0
1e129b0
1b22788
7cba34a
 
 
 
1b22788
19c3ca0
7cba34a
 
 
b54a6bc
 
 
7cba34a
19c3ca0
 
 
7cba34a
 
f61af4a
1b22788
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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)