
Proyecto IA Monterroso (XX): el primer paso del “modo repaso” — PINSAPA pregunta, no solo responde
Hasta ahora PINSAPA solo hacía una cosa, por muy bien que la hiciera: recibir una pregunta y responder. Con el corpus de Computación y Robótica ya bastante más grande (apuntes propios + programaciones + las páginas de Moodle rescatadas del fallo de httrack), tocaba dar el paso que se había dejado a medias hace unas entradas: que PINSAPA también pueda preguntar a él.
Por qué no antes
“Modo repaso” se propuso, se aceptó con ganas, y se aparcó en el mismo momento: no había suficiente material real de clase para que las preguntas salieran de algo de verdad en vez de inventado. Ese problema ya está resuelto (ver las dos entradas anteriores). Quedaba la parte de diseño, que es distinta a todo lo hecho hasta ahora: la calculadora y el tiempo son herramientas de un solo turno (preguntas, calculo, respondo); esto necesita que el asistente recuerde, de un mensaje a otro, que ha hecho una pregunta y cuál era.
La decisión de arquitectura: cero estado nuevo
PINSAPA no tiene memoria propia — la conversación completa vive en el navegador y viaja entera en cada petición, tal y como se decidió desde el principio del proyecto. La tentación fácil habría sido montar algo para guardar “hay una pregunta pendiente, y la respuesta correcta es esta” en el servidor. Se ha descartado a propósito: el historial que ya viaja en cada petición ya contiene la propia pregunta que hizo PINSAPA, así que no hace falta duplicar esa información en ningún sitio nuevo. Si con el uso real esto no basta, será la señal de que hace falta estado de verdad — no antes de comprobarlo.
Con esa decisión, el trabajo se reduce a dos piezas, en el mismo estilo que la calculadora y el tiempo:
1. Detectar la petición y elegir un fragmento real. Mismo patrón de siempre: un paso determinista en Python, antes de hablar con el modelo.
_PATRON_PETICION_REPASO = re.compile(
r"(modo repaso|quiero repasar|hazme una pregunta|p[oó]n(?:me)? a prueba|"
r"preg[uú]ntame|exam[ií]name|hagamos un repaso|quiero que me preguntes)",
re.IGNORECASE,
)
def _intentar_repaso(pregunta, corpus):
if not _PATRON_PETICION_REPASO.search(pregunta):
return None
fragmento = fragmento_aleatorio(corpus)
if fragmento is None:
return None
return (
"El alumno ha pedido una pregunta de repaso. Aquí tienes un fragmento "
f"real de sus propios materiales (fuente: {fragmento['fuente']}):\n\n"
f"{fragmento['texto']}\n\n"
"Instrucciones: construye UNA sola pregunta clara sobre el contenido "
"de este fragmento (no sobre otra cosa). No reveles la respuesta "
"correcta todavía. No hagas más de una pregunta. Espera a que el "
"alumno responda."
)
El fragmento no sale de una búsqueda por palabras clave (buscar_contexto, la función de siempre, no serviría aquí: “hazme una pregunta” no comparte vocabulario con nada del corpus). Hacía falta una función nueva y muy pequeña en rag.py:
def fragmento_aleatorio(corpus=CORPUS_POR_DEFECTO):
"""Un fragmento real, elegido al azar, de un corpus concreto."""
indice = _INDICES.get(corpus)
if indice is None or not indice["fragmentos"]:
return None
return random.choice(indice["fragmentos"])
2. Decirle al modelo cómo corregir, una sola vez, en la propia personalidad. En vez de complicar la lógica de Python para “reconocer” cuándo el alumno está respondiendo a una pregunta de repaso, se añadió una frase fija a las dos personalidades (formal e informal):
Si el alumno te pidió antes una pregunta de repaso y ahora te está respondiendo a ella, evalúa su respuesta con honestidad usando el fragmento real que tú mismo citaste al preguntar: dile si es correcta, si es parcialmente correcta, o corrígele con el dato real si no lo es. No inventes ni cambies el criterio de corrección.
Esa instrucción está siempre presente, pero solo “se activa” en la práctica cuando hace falta — el resto del tiempo, el modelo simplemente no tiene nada de qué corregir y la ignora.
Cómo se comprobó
Antes de tocar ROCKY: una batería de quince frases (nueve que debían activar el modo repaso, seis que no) confirmó cero falsos positivos con preguntas normales (“qué tiempo hace”, “234 por 17”, “explícame qué es un sensor”). Y, a diferencia de otras veces, no se probó con una copia simplificada: se importó el propio main.py real, con sus dependencias reales instaladas aparte, para confirmar que _construir_system_prompt genera la pista correctamente con un fragmento auténtico del corpus de Computación y Robótica — incluyendo ya contenido de las páginas de Moodle rescatadas. Los cambios se aplicaron directamente sobre los archivos de ROCKY, como ya es costumbre, y compilan sin errores.
Confirmado por José Luis en ROCKY: funciona.
Instalación
Cambios en rag.py (una función nueva, fragmento_aleatorio) y en main.py (_intentar_repaso, su patrón de detección, y una frase añadida a cada personalidad). Sin dependencias nuevas. Reiniciar la API para que cargue el código actualizado.
Cómo comprobarlo
Pedir “hazme una pregunta de repaso” con el corpus de Computación y Robótica seleccionado, responder bien una vez y mal otra, y comprobar que PINSAPA corrige apoyándose en el fragmento real, no en lo que se invente.
Qué queda pendiente
Esto es el primer paso, no el modo repaso completo. Lo que hay que observar con uso real, no adelantar por si acaso: si el modelo de 3B parámetros evalúa bien la respuesta apoyándose solo en su propio historial, o si hace falta repetirle el fragmento correcto explícitamente en el turno de corrección. Tampoco hay todavía control de qué pasa si el alumno cambia de tema a mitad de una pregunta de repaso, ni una forma de pedir “otra pregunta distinta” sin repetir fragmento. Se decidirá qué de esto hace falta según lo que se vea usándolo, no antes.


Etiqueta:AIDARAC, arquitectura sin estado, corpus cyr1eso, ies monterroso, modo repaso, montesteam, PINSAPA, RAG



