DemoProfeIA / app.py
cesar's picture
Update app.py
63918e4 verified
raw
history blame
6.11 kB
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": 8192,
"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:
"""
Prompt más flexible:
- Reconoce enumeraciones en secciones 'Preguntas' y 'RESPUESTAS', p. ej. '1.', '2)', '3-'.
- Permite que las preguntas tengan texto como "Teniendo en cuenta que..." sin la palabra "Pregunta".
- Devuelve un JSON que asocia la pregunta X con la respuesta X.
"""
prompt = f"""
Eres un parser de texto que recibe el contenido de un PDF con:
- Una sección de \"Preguntas\" enumeradas (1., 2., 3..., etc.).
- Una sección de \"RESPUESTAS\" enumeradas de la misma forma.
Para cada número (1, 2, 3, 4, 5, 6...), empareja la pregunta con la respuesta.
Devuélvelo en un JSON con el siguiente formato:
{{
"Pregunta 1": "texto de la respuesta 1",
"Pregunta 2": "texto de la respuesta 2",
...
}}
Reglas:
1. Si una pregunta dice \"1. Teniendo en cuenta...\", eso es \"Pregunta 1\".
2. Si en la sección RESPUESTAS dice \"1. Metabolismo...\", esa es la Respuesta 1.
3. Si no hay correspondencia entre pregunta y respuesta, deja la respuesta como cadena vacía.
4. Si no hay nada, devuelve un JSON vacío: {{}}.
Texto PDF:
{texto_pdf}
Devuelve solo el JSON, sin explicaciones adicionales.
"""
part_text = Part.from_text(prompt)
response = model.generate_content(
[part_text],
generation_config=generation_config,
safety_settings=safety_settings,
stream=False
)
# Intentamos parsear el contenido como JSON
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:
"""Compara dict_docente vs dict_alumno y retorna retroalimentación."""
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):
"""Función generadora que muestra progreso en Gradio con yield."""
yield "Cargando credenciales..."
try:
configurar_credenciales(json_cred.name)
yield "Inicializando Vertex AI..."
vertexai.init(project="deploygpt", location="us-central1")
yield "Extrayendo texto del PDF del docente..."
texto_docente = extraer_texto(pdf_docente.name)
yield "Extrayendo texto del PDF del alumno..."
texto_alumno = extraer_texto(pdf_alumno.name)
yield "Parseando preguntas/respuestas del docente..."
model = GenerativeModel(
"gemini-1.5-pro-001",
system_instruction=["Eres un parser estricto."]
)
dict_docente = parsear_con_llm(texto_docente, model)
yield "Parseando preguntas/respuestas del alumno..."
dict_alumno = parsear_con_llm(texto_alumno, model)
yield "Comparando..."
feedback = comparar_preguntas_respuestas(dict_docente, dict_alumno)
if len(feedback.strip()) < 5:
yield "No se encontraron preguntas o respuestas válidas."
return
yield "Generando resumen final..."
summary_prompt = f"""
Eres un profesor experto de bioquímica. 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
)
final_result = f"{feedback}\n\n**Resumen**\n{summary_resp.text.strip()}"
yield final_result
except Exception as e:
yield 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 del Docente"),
gr.File(label="PDF Alumno")
],
outputs="text",
title="Revisión de Exámenes (Preguntas enumeradas + RESPUESTAS enumeradas)",
description=(
"Sube tus credenciales, el PDF del docente y el PDF del alumno. El LLM "
"buscará enumeraciones (1., 2., 3., etc.) en PREGUNTAS y RESPUESTAS y "
"mostrará el avance paso a paso."
)
)
interface.launch(debug=True)