Construye un Profesor de Inglés Personal con Python - Fase 4

Integra IA local con GPT4All en un chatbot con Python y FastAPI. Fase 4: respuestas inteligentes, feedback educativo y personalización usando historial del usuario.

Construye un Profesor de Inglés Personal con Python - Fase 4

En esta fase llevaremos nuestro chatbot al siguiente nivel: IA local con GPT4All.

Hasta ahora, el bot podía:

  • Corregir frases con LanguageTool
  • Guardar historial de usuario

Pero todavía no aprendía de nuestros errores ni podía dar feedback personalizado.

Ahora vamos a integrar GPT4All y preparar la base para que el bot pueda aprender de tu historial y actuar como un profesor de inglés real.

Todo esto seguirá siendo 100% local, sin depender de servicios externos de pago.


📚 Tabla de Contenidos



1️⃣ Objetivo de esta fase

  • Integrar GPT4All localmente con Python
  • Usar python-dotenv para parametrizar el modelo
  • Conectar el historial de usuario con la IA para personalización
  • Preparar la base para memoria avanzada (vector storage)
  • Seguir buenas prácticas de estructura de proyecto en Python


2️⃣ Herramientas nuevas de esta fase

HerramientaUso
GPT4AllModelo de IA local para generar respuestas inteligentes
python-dotenvParametrizar variables críticas como el modelo

Añadimos la librería de GPT4All a requirements.txt:

fastapi==0.127.1
uvicorn==0.39.0
language-tool-python==2.9.5
sqlalchemy==2.0.4
gpt4all==2.8.2
python-dotenv==1.2.1

Instalamos todo con:

pip install -r requirements.txt

3️⃣ Configuración de entorno y variables

Crear archivo .env en la raíz del proyecto:

# Nombre del modelo GPT4All que quieres usar
MODEL_NAME=Meta-Llama-3-8B-Instruct-Q4_0.gguf

Para que tu tutor de inglés sea de alta calidad y esté preparado para el uso de bases de datos vectoriales (RAG) en el futuro (fase 4.1), recomiendo usar Meta Llama 3 8B Instruct. Es el equilibrio perfecto: es lo suficientemente inteligente para dar explicaciones gramaticales precisas y tiene una ventana de contexto amplia para manejar los datos que le envíes desde una base vectorial más adelante.

Para que nuestro bot actúe como profesor educativo, necesitamos un modelo instructivo que entienda bien las preguntas y explique conceptos:

Notas importantes:

  • Descarga inicial: La primera vez que ejecutes el servidor, verás que tarda en responder o verás actividad en la consola. Esto es porque el sistema está descargando el modelo (pesa unos 4.7 GB). No canceles el proceso.
  • Rendimiento: Llama 3 requiere más memoria RAM que el modelo antiguo (se recomiendan al menos 8GB de RAM libres). Si notas que tu computadora se ralentiza demasiado, puedes usar una alternativa más ligera como Nous-Hermes-2-緊湊型-Mistral-7B-DPO.Q4_0.gguf, pero para educación, Llama 3 sigue siendo la mejor opción.
  • Localización: El modelo se guardará automáticamente en tu carpeta de usuario (normalmente en ~/.cache/gpt4all/), por lo que no ocupará espacio dentro de la carpeta de tu proyecto

📦 Ejemplos de modelos disponibles

Estos son algunos de los modelos que puedes usar con GPT4All:

ModeloTamañoEnfoque
Meta-Llama-3-8B-Instruct-Q4_0.gguf4.7 GBInteligencia y razonamiento superior. Tutoría avanzada y RAG. Es el más “listo”.
mistral-7b-openorca.gguf2.1.1.gguf4.1 GBMuy rápido y sigue instrucciones al pie de la letra. Conversación fluida y corrección directa.
Nous-Hermes-2-Mistral-7B-DPO.Q4_0.gguf4.1 GBCreatividad y respuestas más humanas/naturales. Práctica de conversación informal (Slang).
orca-mini-3b-gguf2-q4_0.gguf2.0 GBLigero y muy rápido en computadoras viejas. Pruebas rápidas de desarrollo.
all-MiniLM-L6-v2.gguf45 MBEspecializado en vectores (no para chatear). Base de Datos Vectorial (Embeddings).

¿Cuál elegir según tu situación?

  • Si tienes una PC potente (16GB+ RAM): Usa Llama-3-8B. Es el que mejor te va a explicar por qué un alumno cometió un error gramatical y el que mejor entenderá los documentos que pongamos en la base vectorial después.
  • Si buscas velocidad: Usa Mistral OpenOrca. Es el estándar de la industria para modelos de 7 mil millones de parámetros por su equilibrio entre velocidad y calidad.
  • Si vas a implementar lo Vectorial (Próxima fase): Para generar la respuesta: Llama-3-8B.

Modelos más grandes suelen responder de forma más coherente y detallada, pero requieren más memoria.

4️⃣ Servicio de IA generativa

Creamos un servicio modular para encapsular GPT4All en:

app/services/gpt_service.py

import os
import logging
from dotenv import load_dotenv
from gpt4all import GPT4All
from app.database.crud import messages_crud
from sqlalchemy.orm import Session
from fastapi.concurrency import run_in_threadpool

load_dotenv()
logger = logging.getLogger("uvicorn.error")

MODEL_NAME = os.getenv("MODEL_NAME", "Meta-Llama-3-8B-Instruct.Q4_0.gguf")


class GPTService:
    def __init__(self):
        self.model = None

    def initialize(self):
        """
        Verifica la existencia del modelo y lo carga/descarga.
        """
        if self.model is not None:
            return

        logger.info(f"GPTService: Verificando/Descargando modelo {MODEL_NAME}...")
        try:
            # Esto iniciará la descarga si no existe. 
            # El progreso de gpt4all debería salir en consola ahora.
            self.model = GPT4All(MODEL_NAME)
            logger.info("GPTService: Modelo cargado y listo.")
        except Exception as e:
            logger.error(f"GPTService: Error al cargar el modelo: {e}")
            raise e

    async def generate_reply(self, user_id: str, db: Session, user_message: str) -> str:
        """
        Genera la respuesta del chatbot basada en el historial del usuario y su mensaje actual.
        """
        # Recuperar historial del usuario
        history = messages_crud.get_history_by_user(db, user_id)
        context = ""

        # Últimos 5 mensajes (o menos si no hay tantos)
        for h in history[-5:]:
            context += f"Original: {h.original_text}\nCorrected: {h.corrected_text}\n"

        # Preparar prompt
        prompt = f"""
Eres un profesor de inglés. Tu alumno ha enviado el siguiente mensaje:
{user_message}

Historial reciente del usuario:
{context}

Corrige y explica de forma amable si es necesario. Responde en inglés.
"""

        # Ejecutar la generación en un thread pool para no bloquear el event loop
        response = await run_in_threadpool(self.model.generate, prompt)
        return response

Qué hace:

  • Recupera el historial de un usuario
  • GPT4All recibe los últimos 10 mensajes del usuario para contextualizar su respuesta. Esto hace que pueda corregir errores recurrentes y dar feedback educativo personalizado.
  • Pasa todo al modelo GPT4All
  • Devuelve la respuesta generada

Por ahora, GPT4All solo utiliza el historial disponible en la base de datos para generar respuestas; no modifica ni actualiza sus parámetros internos. Esto hace que el bot recuerde errores pasados y pueda dar feedback más personalizado, sentando la base para la Fase 4.1 (memoria vectorial).

5️⃣ Actualizar Router: combinar corrección + IA

app/routers/messages_router.py

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from typing import List
from app.schemas.message_schemas import Message, MessageResponse, MessageHistoryResponse
from app.database.crud import messages_crud
from app.database.session import SessionLocal
from app.services.grammar_service import LanguageToolService
from app.services.gpt_service import GPTService

router = APIRouter()
grammar_service = LanguageToolService()
gpt_service = GPTService()  # Instancia única del GPTService

# Dependency
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


@router.post("/sendMessage", response_model=MessageResponse)
async def send_message(msg: Message, db: Session = Depends(get_db)):
    # Lógica de corrección de texto
    corrected, feedback = await grammar_service.check_text(msg.text)

    # Guardar historial
    messages_crud.create_message(db, msg.user_id, msg.text, corrected)

    # Generar respuesta GPT de forma asíncrona
    try:
        # Antes:
        gpt_reply = await gpt_service.generate_reply(msg.user_id, db, corrected)

        # Ahora:
        gpt_reply = await gpt_service.generate_reply(msg.user_id, db, msg.text)
    except Exception as e:
        gpt_reply = "Lo siento, no puedo responder a tu mensaje ahora."

    return MessageResponse(original=msg.text, corrected=corrected, message=gpt_reply)


@router.get("/history/{user_id}", response_model=List[MessageHistoryResponse])
def get_history(user_id: str, db: Session = Depends(get_db)):
    messages = messages_crud.get_history_by_user(db, user_id)
    return [
        MessageHistoryResponse(original=m.original_text, corrected=m.corrected_text)
        for m in messages
    ]

Qué cambia respecto a la Fase 3:

  • Ahora /sendMessage devuelve la respuesta generativa de GPT4All.
  • La corrección gramatical sigue activa.
  • La persistencia del historial sigue intacta.
  • Se separa claramente la lógica de corrección y generación de respuestas en servicios distintos.
  • En el router send_message, en lugar de enviar corrected enviamos el texto original y dará la corrección educativa correcta.
  • Corrected sigue guardandose en la base de datos para referencia histórica

6️⃣ Cambiar el archivo main.py

from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.database.base import Base
from app.database.session import engine
from app.routers import messages_router

@asynccontextmanager
async def lifespan(app: FastAPI):
    print("\nAplicación iniciando...", flush=True)
    # Startup: Base de Datos
    Base.metadata.create_all(bind=engine)

    # Startup: GPT
    # Importante: Esto descargará el modelo si no existe y PUEDE TARDAR MUCHO.
    messages_router.gpt_service.initialize()

    yield
    print("[LIFESPAN] Aplicación cerrándose...", flush=True)

app = FastAPI(lifespan=lifespan)
app.include_router(messages_router.router)

7️⃣ Probar el endpoint

  • La primera vez que ejecutes GPT4All, el modelo se descargará automáticamente y puede tardar varios minutos.
  • Se recomienda tener al menos 8GB de RAM libres para Llama-3-8B.
  • Para PCs con menos recursos, existen modelos más pequeños y rápidos que podemos usar.
  • La generación de respuesta se ejecuta de forma asíncrona para no bloquear la API.
  • El historial del usuario se toma en cuenta para personalizar respuestas, pero el modelo no aprende permanentemente todavía.
{
  "user_id": "amoelcodigo",
  "text": "Hello, ow are you?"
}

Posible respuesta:

{
  "original": "Hello, ow are you?",
  "corrected": "Hello, ow, are you?",
  "message": "¡Hola! Me alegra verte aquí.\n \n Lo siento mucho, pero parece que hay un pequeño problema con tu escritura en inglés. Quiero ayudarte a mejorarla. Vamos a revisar juntos el mensaje que enviaste: \"Hello, ow, are you?\"\n\nLa corrección es la siguiente:\n\"Hello, how are you?\"\n\n¿Por qué? Bueno, cuando hablamos o escribimos en inglés, hay algunas reglas importantes para seguir. En este caso, se trata de la puntuación y el orden de las palabras.\n\nEn español, \"ow\" no tiene un significado específico, pero en inglés es una interjección que se utiliza para expresar dolor o sorpresa (como cuando alguien te pincha con algo). Sin embargo, en este contexto, parece que estás tratando de preguntar por la salud del otro. En ese caso, debes usar \"how\" (¿cómo?)"
}

💡 Nota importante: La corrección automática (corrected) de LanguageTool detecta errores simples de gramática y ortografía, pero puede fallar en errores semánticos o contextuales. Por ejemplo, si el usuario escribe “I am your fater”, corrected podría devolver “I am your fatter”, lo cual cambia completamente el significado. Gracias a GPT4All, la IA ve el mensaje original, interpreta la intención del usuario y genera una corrección educativa correcta junto con una explicación clara. Esto convierte al bot en un profesor de inglés real, que no solo corrige sino que enseña.

8️⃣ Resultado de la fase 4

  • El bot integra GPT4All para generar respuestas inteligentes
  • Modelo parametrizable mediante .env
  • Usa historial de usuario para contextualizar sus respuestas
  • Sienta las bases para la memoria vectorial en Fase 4.1
  • Mantiene una estructura profesional y escalable en Python

🚀 ¿Qué sigue?

En la Fase 4.1 implementaremos memoria avanzada con embeddings y almacenamiento vectorial, de manera que el bot aprenda de tus errores pasados, recuerde tu progreso y genere feedback cada vez más personalizado.

Si quieres ir a la fase 4.1 haz click aquí