cesar commited on
Commit
19c3ca0
·
verified ·
1 Parent(s): 25d2af3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -100
app.py CHANGED
@@ -1,27 +1,16 @@
1
  import gradio as gr
2
  import PyPDF2
3
  import os
 
4
  import vertexai
5
  from vertexai.generative_models import GenerativeModel, Part, SafetySetting
6
- import base64
7
-
8
- """
9
- Este código se encarga de:
10
- 1. Leer un archivo de credenciales JSON para configurar Google Cloud.
11
- 2. Inicializar Vertex AI en la región us-central1.
12
- 3. Extraer preguntas y respuestas de dos PDFs: uno del docente y otro del alumno.
13
- 4. Filtrar únicamente las preguntas realmente respondidas por el alumno.
14
- 5. Enviar ese contenido filtrado al modelo generativo (Gemini 1.5), con instrucciones para que
15
- NO mencione preguntas no respondidas.
16
- """
17
-
18
- # Configuración del modelo y parámetros globales
19
  generation_config = {
20
- "max_output_tokens": 8192,
21
  "temperature": 0,
22
- "top_p": 0.75,
23
  }
24
-
25
  safety_settings = [
26
  SafetySetting(
27
  category=SafetySetting.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
@@ -45,7 +34,6 @@ def configurar_credenciales(json_path: str):
45
  os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = json_path
46
 
47
  def extraer_texto(pdf_path: str) -> str:
48
- """Extraer texto de todas las páginas de un PDF."""
49
  texto_total = ""
50
  with open(pdf_path, "rb") as f:
51
  lector = PyPDF2.PdfReader(f)
@@ -53,101 +41,121 @@ def extraer_texto(pdf_path: str) -> str:
53
  texto_total += page.extract_text() or ""
54
  return texto_total
55
 
56
- def parsear_preguntas_respuestas(texto: str) -> dict:
57
- """Dado un texto con formato, retorna un dict {pregunta: respuesta}."""
58
- # Buscamos líneas que inicien con "Pregunta" y "Respuesta"
59
- lineas = texto.split("\n")
60
- resultado = {}
61
- pregunta_actual = None
62
-
63
- for linea in lineas:
64
- linea_str = linea.strip()
65
- if linea_str.lower().startswith("pregunta"):
66
- pregunta_actual = linea_str
67
- resultado[pregunta_actual] = ""
68
- elif linea_str.lower().startswith("respuesta") and pregunta_actual:
69
- # No mezclamos en la misma línea "Pregunta X:"
70
- # sino que esperamos "Pregunta X" en una línea y "Respuesta X" en la siguiente
71
- # si el formateo es distinto, ajusta aquí.
72
- # Tomamos lo que está después de ':'
73
- partes = linea_str.split(":", 1)
74
- if len(partes) > 1:
75
- respuesta = partes[1].strip()
76
- resultado[pregunta_actual] = respuesta
77
- return resultado
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  def revisar_examen(json_cred, pdf_docente, pdf_alumno):
80
  try:
81
- # Configurar credenciales
82
  configurar_credenciales(json_cred.name)
83
 
84
- # Inicializar Vertex AI
85
  vertexai.init(project="deploygpt", location="us-central1")
86
 
87
- # Extraer texto de ambos PDFs
88
- docente_texto = extraer_texto(pdf_docente.name)
89
- alumno_texto = extraer_texto(pdf_alumno.name)
90
-
91
- # Parsear preguntas y respuestas
92
- preguntas_docente = parsear_preguntas_respuestas(docente_texto)
93
- respuestas_alumno = parsear_preguntas_respuestas(alumno_texto)
94
-
95
- # Filtrar solo preguntas respondidas
96
- preguntas_filtradas = {}
97
- for pregunta_doc, resp_doc in preguntas_docente.items():
98
- if pregunta_doc in respuestas_alumno:
99
- # El alumno respondió esta pregunta
100
- preguntas_filtradas[pregunta_doc] = {
101
- "respuesta_doc": resp_doc,
102
- "respuesta_alumno": respuestas_alumno[pregunta_doc]
103
- }
104
-
105
- if not preguntas_filtradas:
106
- return "El alumno no respondió ninguna de las preguntas del docente."
107
-
108
- # Construir un texto que contenga únicamente las preguntas respondidas
109
- # e instrucciones claras para no alucinar preguntas.
110
- # Vamos a pasarlo en 1 solo Part, para forzar a que la LLM no confunda.
111
- contenido_final = """Instrucciones: Solo hay estas preguntas respondidas por el alumno.
112
- No menciones preguntas que no estén en esta lista. Para cada pregunta, analiza la respuesta.
113
- Al final, da un resumen.
114
- """
115
- for i, (p, data) in enumerate(preguntas_filtradas.items(), 1):
116
- contenido_final += f"\nPregunta {i}: {p}\n" \
117
- f"Respuesta del alumno: {data['respuesta_alumno']}\n" \
118
- f"Respuesta correcta (docente): {data['respuesta_doc']}\n"
119
-
120
- # Creamos un Part con el contenido filtrado
121
- part_filtrado = Part(
122
- mime_type="text/plain",
123
- text=contenido_final,
124
- )
125
-
126
- # System instruction, for clarity
127
- textsi_1 = """Actúa como un asistente de docente experto en Bioquímica.
128
- No menciones preguntas que el alumno no respondió.
129
- Analiza únicamente las preguntas provistas en el texto.
130
- Calcula un porcentaje de precisión basado en las respuestas incluidas.
131
- """
132
-
133
- model = GenerativeModel(
134
- "gemini-1.5-pro-001",
135
- system_instruction=[textsi_1]
136
- )
137
-
138
- # Llamada al modelo con las partes.
139
- response = model.generate_content(
140
- [part_filtrado],
141
  generation_config=generation_config,
142
  safety_settings=safety_settings,
143
- stream=False,
144
  )
 
145
 
146
- return response.text
147
 
148
  except Exception as e:
149
  return f"Error al procesar: {str(e)}"
150
 
 
151
  # Interfaz Gradio
152
  interface = gr.Interface(
153
  fn=revisar_examen,
@@ -156,9 +164,9 @@ interface = gr.Interface(
156
  gr.File(label="PDF Docente"),
157
  gr.File(label="PDF Alumno")
158
  ],
159
- outputs=gr.Textbox(label="Resultado"),
160
- title="Revisión de Exámenes",
161
- description="Sube tus credenciales, el PDF del docente y el del alumno para revisar las respuestas sin alucinaciones."
162
  )
163
 
164
  interface.launch(debug=True)
 
1
  import gradio as gr
2
  import PyPDF2
3
  import os
4
+ import json
5
  import vertexai
6
  from vertexai.generative_models import GenerativeModel, Part, SafetySetting
7
+
8
+ # Configuración global
 
 
 
 
 
 
 
 
 
 
 
9
  generation_config = {
10
+ "max_output_tokens": 4096,
11
  "temperature": 0,
12
+ "top_p": 0.8,
13
  }
 
14
  safety_settings = [
15
  SafetySetting(
16
  category=SafetySetting.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
 
34
  os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = json_path
35
 
36
  def extraer_texto(pdf_path: str) -> str:
 
37
  texto_total = ""
38
  with open(pdf_path, "rb") as f:
39
  lector = PyPDF2.PdfReader(f)
 
41
  texto_total += page.extract_text() or ""
42
  return texto_total
43
 
44
+ def parsear_con_llm(texto_pdf: str, model: GenerativeModel) -> dict:
45
+ """
46
+ Usa el LLM para extraer preguntas y respuestas.
47
+ Devuelve un dict {"Pregunta X": "Respuesta X", ...}.
48
+ """
49
+ # Instrucciones para parsear:
50
+ # - Buscar variaciones de "Pregunta" y "Respuesta" (mayúsculas, minúsculas, plural...)
51
+ # - Devolver un JSON limpio, sin texto extra.
52
+ prompt = f"""
53
+ Eres un parser de texto.
54
+ A continuación tienes el contenido de un PDF con un examen (o respuestas).
55
+ Debes extraer todas las preguntas y sus respuestas.
56
+ Considera que las palabras podrían estar en mayúsculas, minúsculas o plural
57
+ (por ejemplo 'Pregunta', 'PREGUNTA', 'Preguntas', 'RESPUESTA', 'RESPUESTAS', etc.).
58
+ Devuélvelas en formato JSON puro, sin explicación adicional.
59
+ Usa este formato de salida:
60
+
61
+ {{
62
+ "Pregunta 1": "Texto de la respuesta",
63
+ "Pregunta 2": "Texto de la respuesta",
64
+ ...
65
+ }}
66
+
67
+ Si hay preguntas sin respuesta, pon la respuesta como cadena vacía.
68
+ Si no hay ninguna pregunta, devuelve un JSON vacío: {{}}
69
+
70
+ Texto PDF:
71
+ {texto_pdf}
72
+ """
73
+ part_text = Part(mime_type="text/plain", text=prompt)
74
+ response = model.generate_content(
75
+ [part_text],
76
+ generation_config=generation_config,
77
+ safety_settings=safety_settings,
78
+ stream=False
79
+ )
80
+ try:
81
+ data = json.loads(response.text.strip())
82
+ if isinstance(data, dict):
83
+ return data
84
+ else:
85
+ return {}
86
+ except:
87
+ # Si no se pudo parsear como JSON, devolvemos dict vacío
88
+ return {}
89
+
90
+ def comparar_preguntas_respuestas(
91
+ dict_docente: dict, dict_alumno: dict
92
+ ) -> str:
93
+ """
94
+ Recorre las preguntas del dict_docente y
95
+ compara con las respuestas del dict_alumno.
96
+ """
97
+ retroalimentacion = []
98
+ for pregunta, resp_correcta in dict_docente.items():
99
+ resp_alumno = dict_alumno.get(pregunta, None)
100
+ if resp_alumno is None:
101
+ retroalimentacion.append(
102
+ f"**{pregunta}**\nNo fue asignada al alumno.\n"
103
+ )
104
+ else:
105
+ retroalimentacion.append(
106
+ f"**{pregunta}**\n"
107
+ f"Respuesta del alumno: {resp_alumno}\n"
108
+ f"Respuesta correcta: {resp_correcta}\n"
109
+ )
110
+ return "\n".join(retroalimentacion)
111
 
112
  def revisar_examen(json_cred, pdf_docente, pdf_alumno):
113
  try:
114
+ # 1. Configurar credenciales
115
  configurar_credenciales(json_cred.name)
116
 
117
+ # 2. Inicializar Vertex AI
118
  vertexai.init(project="deploygpt", location="us-central1")
119
 
120
+ # 3. Extraer texto de PDFs
121
+ texto_docente = extraer_texto(pdf_docente.name)
122
+ texto_alumno = extraer_texto(pdf_alumno.name)
123
+
124
+ # 4. Parsear con LLM para obtener dict de preguntas y respuestas
125
+ model = GenerativeModel("gemini-1.5-pro-001", system_instruction=["Eres un parser estricto."])
126
+ dict_docente = parsear_con_llm(texto_docente, model)
127
+ dict_alumno = parsear_con_llm(texto_alumno, model)
128
+
129
+ # 5. Comparar y generar retroalimentación
130
+ feedback = comparar_preguntas_respuestas(dict_docente, dict_alumno)
131
+
132
+ # 6. Generar un summary final con LLM (opcional)
133
+ # Queda a tu criterio si lo deseas:
134
+ if len(feedback.strip()) < 5:
135
+ return "No se encontraron preguntas/ respuestas válidas."
136
+
137
+ # Llamada final al modelo para un summary:
138
+ summary_prompt = f"""
139
+ Eres un profesor experto. Te muestro la comparación de preguntas y respuestas:
140
+ {feedback}
141
+ Por favor, genera un breve resumen del desempeño del alumno
142
+ sin inventar preguntas adicionales.
143
+ """
144
+ summary_part = Part(mime_type="text/plain", text=summary_prompt)
145
+ summary_resp = model.generate_content(
146
+ [summary_part],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  generation_config=generation_config,
148
  safety_settings=safety_settings,
149
+ stream=False
150
  )
151
+ summary_text = summary_resp.text.strip()
152
 
153
+ return f"{feedback}\n\n**Resumen**\n{summary_text}"
154
 
155
  except Exception as e:
156
  return f"Error al procesar: {str(e)}"
157
 
158
+
159
  # Interfaz Gradio
160
  interface = gr.Interface(
161
  fn=revisar_examen,
 
164
  gr.File(label="PDF Docente"),
165
  gr.File(label="PDF Alumno")
166
  ],
167
+ outputs=gr.Markdown(),
168
+ title="Revisión de Exámenes con LLM (Permisivo)",
169
+ description="Sube credenciales, el PDF del docente y del alumno; se emplea un LLM para encontrar 'Pregunta/Respuesta' aun con variaciones."
170
  )
171
 
172
  interface.launch(debug=True)