Menu iconMenu icon
OpenAI API Biblia Volumen 2

Capítulo 4: Construyendo un Chatbot Simple con Memoria

4.3 Implementación de Memoria de Chat Basada en Sesiones

La memoria basada en sesiones es una característica crucial que transforma un chatbot simple en un agente conversacional verdaderamente interactivo y consciente del contexto. Sin memoria, cada interacción se vuelve aislada y desconectada, forzando a los usuarios a proporcionar contexto repetidamente y limitando el flujo natural de la conversación. Esta sección explora cómo implementar una gestión robusta de memoria en tu chatbot, permitiéndole mantener discusiones coherentes y contextuales a través de múltiples intercambios.

Examinaremos dos enfoques distintos para implementar memoria de sesión - usando la gestión de estado incorporada de Streamlit y las sesiones del lado del servidor de Flask. Ambos métodos ofrecen sus propias ventajas y pueden adaptarse para satisfacer requisitos específicos del proyecto. Al final de esta sección, comprenderás cómo crear un chatbot que puede mantener el contexto, recordar interacciones previas y proporcionar respuestas más significativas y conectadas.

La implementación que cubriremos asegura que tu chatbot pueda:

  • Mantener la consciencia contextual durante conversaciones completas
  • Manejar diálogos complejos de múltiples turnos de manera efectiva
  • Proporcionar respuestas más relevantes y personalizadas basadas en el historial de conversación
  • Gestionar la memoria eficientemente sin exceder los límites de tokens
✅ Dotar a tu asistente de memoria — para que no olvide el flujo de la conversación cada vez que la página se actualiza o la sesión se reinicia.

4.3.1 ¿Por qué es importante la memoria de sesión?

La memoria de sesión es un aspecto fundamental para crear chatbots inteligentes que puedan participar en conversaciones significativas y conscientes del contexto. Al igual que los humanos dependen de la memoria durante las discusiones, los chatbots necesitan una manera de recordar y procesar las interacciones previas. Cuando hablamos con otros, naturalmente hacemos referencia a puntos anteriores, construimos sobre el entendimiento compartido y mantenemos un flujo coherente de ideas. Este patrón natural de comunicación es lo que hace que las conversaciones se sientan orgánicas y significativas. Sin capacidades de memoria, los chatbots esencialmente comienzan de cero con cada respuesta, lo que lleva a interacciones desconectadas y a menudo frustrantes que se sienten más como hablar con una máquina que tener una conversación real.

El concepto de memoria de sesión transforma fundamentalmente las interacciones del chatbot de varias maneras críticas:

  • Recordar y hacer referencia a partes previas de la conversación con precisión
    • Hacer seguimiento de detalles específicos mencionados anteriormente en el chat, como preferencias del usuario, especificaciones técnicas o información personal compartida
    • Hacer referencia a acuerdos o decisiones pasadas con precisión, asegurando la continuidad en discusiones o negociaciones complejas
    • Mantener el contexto histórico para una mejor resolución de problemas y soporte
  • Construir contexto incrementalmente durante una interacción
    • Comprender temas complejos que se desarrollan a lo largo de múltiples mensajes, permitiendo una exploración más profunda de los temas
    • Desarrollar respuestas más sofisticadas a medida que avanza la conversación, construyendo sobre conceptos previamente establecidos
    • Crear un hilo narrativo coherente a través de múltiples intercambios
  • Proporcionar respuestas más matizadas y relevantes basadas en el historial de conversación
    • Adaptar las respuestas al nivel de conocimiento demostrado por el usuario, ajustando la terminología y complejidad según corresponda
    • Evitar repetir información ya discutida, haciendo las conversaciones más eficientes
    • Usar interacciones pasadas para proporcionar respuestas más personalizadas y contextualmente apropiadas
  • Crear una experiencia conversacional más natural y humana
    • Mantener una personalidad y tono consistentes durante todo el chat, mejorando la participación del usuario
    • Adaptar las respuestas basadas en las preferencias del usuario e interacciones pasadas, creando una experiencia más personalizada
    • Aprender de intercambios anteriores para mejorar la calidad de interacciones futuras

En conversaciones reales, el contexto se construye con el tiempo de múltiples formas sofisticadas que reflejan patrones naturales de diálogo humano:

  • El usuario se refiere a mensajes anteriores
    • Las preguntas naturalmente se construyen sobre respuestas previas, creando un hilo continuo de comprensión
    • Los temas evolucionan orgánicamente mientras los usuarios hacen referencia y expanden puntos anteriores en la conversación
    • El contexto previo moldea cómo se interpreta y entiende la nueva información
  • El asistente necesita recordar respuestas previas
    • Mantiene consistencia a través de las respuestas para construir confianza y fiabilidad
    • Utiliza el contexto establecido para proporcionar información más matizada y relevante
    • Construye una comprensión integral de las necesidades del usuario a lo largo del tiempo
  • Las preguntas de seguimiento dependen del conocimiento previo
    • Cada pregunta se construye sobre la base de intercambios anteriores
    • Los temas complejos pueden explorarse gradualmente, con profundidad creciente
    • La conversación progresa naturalmente desde conceptos básicos hacia una comprensión más avanzada
    • Crea cadenas de diálogo más significativas conectando ideas relacionadas
    • Permite un flujo de conversación natural que se siente más humano y atractivo

Sin implementación de memoria, los chatbots tratan cada interacción como un evento aislado, completamente desconectado de intercambios previos. Esta limitación fundamental afecta incluso a modelos sofisticados como GPT-4o de varias maneras críticas:

  1. Pérdida de Contexto: Cada respuesta se genera sin ninguna conciencia de conversaciones previas, haciendo imposible mantener discusiones coherentes y extendidas.
  2. Interacciones Repetitivas: El chatbot puede proporcionar la misma información múltiples veces o pedir detalles que ya fueron compartidos, creando una experiencia de usuario frustrante.
  3. Respuestas Inconsistentes: Sin acceso a intercambios previos, el chatbot podría dar respuestas contradictorias a preguntas relacionadas, socavando la confianza del usuario.
  4. Comprensión Limitada: La incapacidad de hacer referencia al contexto pasado significa que el chatbot no puede construir sobre el conocimiento previamente establecido ni adaptar sus respuestas basándose en la comprensión demostrada por el usuario.

Al final de esta sección, sabrás cómo:

  • Almacenar y recuperar mensajes para una sesión dada
    • Implementar mecanismos de almacenamiento seguro usando encriptación y protección estándar de la industria
    • Manejar diferentes tipos de datos de mensajes efectivamente, incluyendo texto, datos estructurados y metadatos
  • Mantener y actualizar memoria de múltiples turnos
    • Procesar cadenas de conversación eficientemente usando estructuras de datos optimizadas
    • Gestionar el contexto a través de múltiples intercambios mientras se mantiene la coherencia de la conversación
  • Evitar el uso excesivo de tokens mediante limitación de la longitud del historial
    • Implementar estrategias inteligentes de gestión de memoria que prioricen la información relevante
    • Equilibrar la retención de contexto con el rendimiento a través de algoritmos inteligentes de poda

4.3.2 Streamlit: Memoria de Estado de Sesión

Streamlit hace que la gestión del historial de conversación sea super simple con st.session_state, que persiste los datos durante toda la sesión del navegador.

Aquí te mostramos cómo puedes crear un chatbot con memoria de sesión usando Streamlit:

Paso 1: Importar Bibliotecas

import streamlit as st
import openai
import os
from dotenv import load_dotenv
  • streamlit: Usado para crear la interfaz de usuario del chatbot.
  • openai: La biblioteca de Python de OpenAI, usada para interactuar con el modelo GPT-4o.
  • os: Proporciona una manera de interactuar con el sistema operativo, por ejemplo para acceder a variables de entorno.
  • dotenv: Usado para cargar variables de entorno desde un archivo .env.

Paso 2: Cargar la Clave API y Configurar la Página

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

st.set_page_config(page_title="GPT-4o Chat with Memory", page_icon="🧠")
st.title("🧠 GPT-4o Chatbot with Session Memory")
  • load_dotenv(): Carga la clave API de OpenAI desde un archivo .env. Este archivo debe estar en el mismo directorio que tu script de Python y contener la línea OPENAI_API_KEY=TU_CLAVE_API.
  • openai.api_key = os.getenv("OPENAI_API_KEY"): Establece la clave API de OpenAI.
  • st.set_page_config(...): Configura el título y el ícono de la página que aparecen en la pestaña del navegador.
  • st.title(...): Establece el título de la aplicación Streamlit, que se muestra en la parte superior de la página.

Paso 3: Inicializar el Estado de la Sesión

if "messages" not in st.session_state:
    st.session_state.messages = [{"role": "system", "content": "You are a helpful assistant that remembers this session."}]
  • st.session_state: La forma en que Streamlit almacena variables entre interacciones del usuario. Los datos en st.session_state persisten mientras la pestaña del navegador permanezca abierta.
  • Este código verifica si la clave "messages" existe en st.session_state. Si no existe (lo cual ocurre cuando el usuario carga la aplicación por primera vez), inicializa "messages" como una lista que contiene un único diccionario.
  • Este diccionario representa el "mensaje del sistema", que se utiliza para establecer el comportamiento del asistente. En este caso, el mensaje del sistema le indica al asistente que sea útil y recuerde la conversación.

Paso 4: Mostrar el Historial de Chat

for msg in st.session_state.messages[1:]:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])
  • Este bucle for itera a través de los mensajes en la lista "messages", comenzando desde el segundo mensaje (índice 1) para omitir el mensaje del sistema.
  • st.chat_message(msg["role"]): Crea una burbuja de chat en la aplicación Streamlit para mostrar el mensaje. El role ("user" o "assistant") determina la apariencia de la burbuja.
  • st.markdown(msg["content"]): Muestra el contenido del mensaje dentro de la burbuja de chat. Se utiliza st.markdown para renderizar el texto.

Paso 5: Entrada del Usuario y Generación de Respuesta

user_input = st.chat_input("Say something...")
if user_input:
    st.chat_message("user").markdown(user_input)
    st.session_state.messages.append({"role": "user", "content": user_input})

    with st.chat_message("assistant"):
        with st.spinner("Thinking..."):
            response = openai.ChatCompletion.create(
                model="gpt-4o",
                messages=st.session_state.messages,
                temperature=0.6
            )
            reply = response["choices"][0]["message"]["content"]
            st.markdown(reply)
            st.session_state.messages.append({"role": "assistant", "content": reply})
  • user_input = st.chat_input("Di algo..."): Crea un campo de entrada de texto en la parte inferior de la aplicación donde el usuario puede escribir su mensaje. La etiqueta "Di algo..." se muestra junto al campo de entrada.
  • El bloque if user_input: se ejecuta cuando el usuario ingresa texto y presiona Enter.
  • st.chat_message("user").markdown(user_input): Muestra el mensaje del usuario en la interfaz de chat.
  • st.session_state.messages.append({"role": "user", "content": user_input}): Agrega el mensaje del usuario a la lista "messages" en st.session_state, para que se almacene en el historial de conversación.
  • with st.chat_message("assistant"):: Crea una burbuja de chat para la respuesta del asistente.
  • with st.spinner("Pensando..."): Muestra una animación de carga mientras la aplicación espera una respuesta de la API de OpenAI.
  • response = openai.ChatCompletion.create(...): Llama a la API de OpenAI para obtener una respuesta del modelo GPT-4o.
    • model: Especifica el modelo de lenguaje a usar ("gpt-4o").
    • messages: Pasa todo el historial de conversación (almacenado en st.session_state.messages) a la API. Así es como el modelo "recuerda" la conversación.
    • temperature: Un valor entre 0 y 1 que controla la aleatoriedad de la salida del modelo. Un valor más bajo (por ejemplo, 0.2) hace que la salida sea más determinista, mientras que un valor más alto (por ejemplo, 0.8) la hace más aleatoria.
  • reply = response["choices"][0]["message"]["content"]: Extrae la respuesta del asistente de la respuesta de la API.
  • st.markdown(reply): Muestra la respuesta del asistente en la interfaz de chat.
  • st.session_state.messages.append({"role": "assistant", "content": reply}): Agrega la respuesta del asistente a la lista "messages" en st.session_state.

Este ejemplo crea un chatbot simple con memoria usando Streamlit. La lista st.session_state.messages almacena el historial de conversación, permitiendo que el chatbot mantenga el contexto a través de múltiples interacciones. El historial del chat se muestra en la aplicación, y el usuario puede ingresar mensajes usando el campo st.chat_input. Las respuestas del asistente son generadas por el modelo OpenAI GPT-4o.

pip install flask openai flask-session python-dotenv
  • flask: Un framework web.
  • openai: La biblioteca de Python de OpenAI.
  • flask-session: Extensión de Flask para manejar sesiones del lado del servidor.
  • python-dotenv: Para cargar variables de entorno desde un archivo .env.

Paso 2: Actualizar app.py

from flask import Flask, request, render_template, session
import openai
import os
from dotenv import load_dotenv
from flask_session import Session

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

app = Flask(__name__)
app.secret_key = os.urandom(24)
app.config["SESSION_TYPE"] = "filesystem"
Session(app)

@app.route("/", methods=["GET", "POST"])
def chat():
    if "history" not in session:
        session["history"] = [
            {"role": "system", "content": "You are a helpful assistant."}
        ]

    if request.method == "POST":
        user_input = request.form["user_input"]
        session["history"].append({"role": "user", "content": user_input})

        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=session["history"],
            temperature=0.6,
        )

        assistant_reply = response["choices"][0]["message"]["content"]
        session["history"].append({"role": "assistant", "content": assistant_reply})

        return render_template("chat.html", history=session.get("history")[1:])  # Skip system prompt

    return render_template("chat.html", history=session.get("history")[1:])  # Skip system prompt
  • from flask import ...: Importa los componentes necesarios de Flask, incluyendo session para gestionar las sesiones de usuario.
  • import openai: Importa la biblioteca OpenAI.
  • import os: Importa el módulo os para interactuar con el sistema operativo, particularmente para acceder a variables de entorno.
  • from dotenv import load_dotenv: Importa la función load_dotenv de la biblioteca python-dotenv.
  • from flask_session import Session: Importa la clase Session de flask_session.
  • load_dotenv(): Carga las variables de entorno (como la clave API de OpenAI) desde un archivo .env.
  • openai.api_key = os.getenv("OPENAI_API_KEY"): Obtiene la clave API de OpenAI del entorno y la configura para la biblioteca OpenAI.
  • app = Flask(__name__): Crea una instancia de la aplicación Flask.
  • app.secret_key = os.urandom(24): Establece una clave secreta para la aplicación Flask. Esto es esencial para usar sesiones de Flask. os.urandom(24) genera una clave aleatoria y criptográficamente segura.
  • app.config["SESSION_TYPE"] = "filesystem": Configura Flask-Session para almacenar los datos de sesión en el sistema de archivos del servidor. Otras opciones como "redis" o "mongodb" están disponibles para uso en producción.
  • Session(app): Inicializa la extensión Flask-Session, vinculándola a la aplicación Flask.
  • @app.route("/", methods=["GET", "POST"]): Define la ruta para la página principal de la aplicación ("/"). La función chat() maneja tanto solicitudes GET como POST.
  • def chat()::
    • if "history" not in session:: Verifica si la sesión del usuario ya tiene un historial de conversación. Si no, inicializa la sesión con un mensaje del sistema. El mensaje del sistema ayuda a establecer el comportamiento del asistente.
    • if request.method == "POST":: Maneja las solicitudes POST, que ocurren cuando el usuario envía un mensaje a través del formulario de chat.
      • user_input = request.form["user_input"]: Obtiene la entrada del usuario del formulario.
      • session["history"].append({"role": "user", "content": user_input}): Añade el mensaje del usuario al historial de conversación almacenado en la sesión.
      • response = openai.ChatCompletion.create(...): Llama a la API de OpenAI para obtener una respuesta del modelo GPT-4o, pasando el historial de conversación.
      • assistant_reply = response["choices"][0]["message"]["content"]: Extrae la respuesta del asistente de la respuesta de la API.
      • session["history"].append({"role": "assistant", "content": assistant_reply}): Añade la respuesta del asistente al historial de conversación en la sesión.
      • return render_template("chat.html", history=session.get("history")[1:]): Renderiza la plantilla chat.html, pasando el historial de conversación (excluyendo el mensaje inicial del sistema) para ser mostrado.
    • return render_template("chat.html", history=session.get("history")[1:]): Maneja las solicitudes GET (cuando el usuario carga la página por primera vez). Renderiza la plantilla chat.html, pasando el historial de conversación (excluyendo el mensaje del sistema).

Paso 3: Tu Plantilla HTML (templates/chat.html)

<!DOCTYPE html>
<html>
<head>
  <title>GPT-4o Assistant</title>
  <style>
    body { font-family: Arial; background: #f7f7f7; padding: 40px; }
    .container { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 10px; }
    .user, .assistant { margin-bottom: 15px; }
    .user p { background: #d4f0ff; padding: 10px; border-radius: 10px; }
    .assistant p { background: #e8ffe8; padding: 10px; border-radius: 10px; }
    textarea { width: 100%; height: 80px; }
    input[type="submit"] { margin-top: 10px; padding: 10px 20px; }
  </style>
</head>
<body>
  <div class="container">
    <h2>GPT-4o Chatbot</h2>
    {% for msg in history %}
      <div class="{{ msg.role }}">
        <p><strong>{{ msg.role.capitalize() }}:</strong> {{ msg.content }}</p>
      </div>
    {% endfor %}
    <form method="post">
      <textarea name="user_input" placeholder="Type your message..."></textarea><br>
      <input type="submit" value="Send">
    </form>
  </div>
</body>
</html>
  • <!DOCTYPE html>: Declara el tipo de documento como HTML5.
  • <html>: El elemento raíz del documento HTML.
  • <head>: Contiene los metadatos del documento HTML.
    • <title>: Especifica el título de la página HTML.
    • <style>: Incluye CSS para el estilo básico de la interfaz del chat.
  • <body>: Contiene el contenido visible de la página HTML.
    • <div class="container">: Un contenedor para la aplicación de chat.
    • <h2>: Un encabezado para la aplicación de chat.
    • {% for msg in history %}: Un bucle de plantilla Jinja2 que itera a través de la variable history (pasada desde el código de Flask) para mostrar los mensajes del chat.
      • <div class="{{ msg.role }}">: Crea un elemento div para cada mensaje. La clase se asigna según el rol del mensaje ("user" o "assistant") para fines de estilo.
      • <p>: Muestra el contenido del mensaje.
      • <strong>: Muestra el rol.
    • <form method="post">: Un formulario para que el usuario envíe sus mensajes.
      • <textarea>: Un campo de entrada de texto multilínea para que el usuario escriba su mensaje.
      • <input type="submit" value="Send">: Un botón para enviar el mensaje.
  • templates: Flask, por defecto, busca las plantillas HTML en una carpeta llamada "templates" en el mismo directorio que tu archivo app.py. Por lo tanto, este archivo debe guardarse como templates/chat.html.

Este código crea un chatbot usando Flask y OpenAI, con el historial de la conversación almacenado en sesiones del lado del servidor. El servidor conserva la memoria del chat durante la sesión del usuario, eliminándola cuando el usuario cierra la pestaña del navegador.

4.3.4 Opcional: Limitar el Historial de Mensajes

Los modelos de lenguaje grandes tienen una ventana de contexto limitada (por ejemplo, 128k tokens en GPT-4o), lo que determina cuánta conversación el modelo puede "recordar" al mismo tiempo. Para evitar superar este límite y generar errores, deberías limitar la cantidad de mensajes almacenados en el historial de la conversación.

Aquí te mostramos cómo recortar las entradas más antiguas tanto en Streamlit como en Flask:

Streamlit

import streamlit as st

MAX_HISTORY = 20  # Maximum number of messages to keep

if SESSION_MESSAGES_KEY in st.session_state:
    if len(st.session_state[SESSION_MESSAGES_KEY]) > MAX_HISTORY:
        # Keep the system message and the last MAX_HISTORY messages
        st.session_state[SESSION_MESSAGES_KEY] = [st.session_state[SESSION_MESSAGES_KEY][0]] + st.session_state[SESSION_MESSAGES_KEY][-MAX_HISTORY + 1:]

Explicación:

  • MAX_HISTORY: Una constante que define la cantidad máxima de mensajes a conservar. Este ejemplo mantiene los últimos 20 mensajes.
  • if SESSION_MESSAGES_KEY in st.session_state: Verifica si la clave existe
  • El código luego recorta la lista st.session_state.messages, conservando el primer mensaje (el mensaje del sistema) y los últimos MAX_HISTORY - 1 mensajes.

Flask

from flask import session

MAX_HISTORY = 20  # Maximum number of messages to keep

if SESSION_MESSAGES_KEY in session:
    if len(session[SESSION_MESSAGES_KEY]) > MAX_HISTORY:
        # Keep the system message and the last MAX_HISTORY messages
        session[SESSION_MESSAGES_KEY] = [session[SESSION_MESSAGES_KEY][0]] + session[SESSION_MESSAGES_KEY][-MAX_HISTORY + 1:]

Explicación:

  • MAX_HISTORY: Una constante que define el número máximo de mensajes a conservar.
  • if SESSION_MESSAGES_KEY in session: Verifica si la clave existe
  • El código recorta la lista session["history"], manteniendo el primer mensaje (el mensaje del sistema) y los últimos MAX_HISTORY - 1 mensajes.

Consideraciones Importantes de Implementación:

  • Gestión del Mensaje del Sistema: El mensaje del sistema juega un papel crucial en establecer el comportamiento y contexto del chatbot. Siempre debe preservarse como el primer mensaje en tu historial de conversación. Al implementar el recorte de mensajes, asegúrate de que tu código específicamente mantenga este mensaje mediante:
    • Mantenerlo separado del flujo regular de conversación
    • Incluir un manejo especial en tu lógica de recorte
    • Verificar su presencia antes de cada interacción
  • Protocolo Integral de Pruebas: Para asegurar un rendimiento confiable del chatbot:
    • Probar con diferentes longitudes de conversación, desde intercambios cortos hasta diálogos extensos
    • Verificar que el contexto se mantenga incluso después del recorte
    • Comprobar casos límite donde la coherencia podría romperse
    • Monitorear el uso de recursos del sistema durante conversaciones prolongadas
  • Estrategias Avanzadas de Recorte: Considera estos enfoques sofisticados:
    • Recorte basado en tokens: Calcular el uso real de tokens usando un tokenizador
    • Recorte basado en importancia: Mantener mensajes según su relevancia
    • Enfoque híbrido: Combinar conteo de tokens con relevancia de mensajes
    • Ajuste dinámico: Modificar el umbral de recorte según la complejidad de la conversación

4.3.5 Consejo Adicional: Guardar en Archivo o Base de Datos

¿Quieres mantener la memoria incluso después de que termine la sesión? Exploremos varios métodos efectivos para el almacenamiento de memoria a largo plazo:

1. Exportar historial de conversación a un archivo JSON

Proceso: Los datos de st.session_state.messages (en Streamlit) o session["history"] (en Flask) pueden guardarse en un archivo JSON. Esto implica convertir la lista de diccionarios de mensajes en una cadena JSON y escribirla en un archivo.

Ejemplo de Código (Streamlit):

import json
import streamlit as st

def save_chat_log(filename="chat_log.json"):
    """Saves the chat log from st.session_state to a JSON file."""
    if SESSION_MESSAGES_KEY in st.session_state:
        try:
            with open(filename, "w") as f:
                json.dump(st.session_state[SESSION_MESSAGES_KEY], f, indent=2)
            print(f"Chat log saved to {filename}")
        except Exception as e:
            st.error(f"Error saving chat log: {e}")

# Example usage:  Call this function when the user ends the session or when appropriate
if st.button("Save Chat Log"):
    save_chat_log()

Ejemplo de Código (Flask):

import json
from flask import session, Flask

def save_chat_log(filename="chat_log.json"):
    """Saves the chat log from the Flask session to a JSON file."""
    if SESSION_MESSAGES_KEY in session:
        try:
            with open(filename, "w") as f:
                json.dump(session[SESSION_MESSAGES_KEY], f, indent=2)
            print(f"Chat log saved to {filename}")
        except Exception as e:
            #  Use the app context to display a message
            with app.app_context():
                print(f"Error saving chat log: {e}")

# Example Usage
app = Flask(__name__)
@app.route('/save_log')
def save_log():
    save_chat_log()
    return "Chat log saved!"

Explicación:

  • La función json.dump() se utiliza para serializar la lista de mensajes a una cadena con formato JSON. El parámetro indent=2 hace que el archivo JSON sea más legible para humanos.
  • El código maneja posibles errores durante la escritura del archivo.
  • El ejemplo de Streamlit utiliza un botón para activar el guardado, y el ejemplo de Flask crea una ruta /save_log para guardar el archivo.

Ventajas:

  • Fácil de implementar usando el módulo json incorporado en Python.
  • Adecuado para aplicaciones pequeñas y prototipos rápidos.
  • Fácil de respaldar y controlar versiones.

Desventajas:

  • No es ideal para aplicaciones a gran escala con múltiples usuarios.
  • Sin capacidad eficiente de consulta o indexación.

2. Almacenar conversaciones en una base de datos SQLite, PostgreSQL o NoSQL

Proceso: Almacenar el historial de conversación en una base de datos. Cada mensaje puede ser una fila en una tabla (para bases de datos SQL) o un documento en una colección (para bases de datos NoSQL).

Ejemplo de Código (SQLite - Streamlit):

import streamlit as st
import sqlite3
import datetime

def get_connection():
    """Gets or creates a SQLite connection."""
    conn = getattr(st.session_state, "sqlite_conn", None)
    if conn is None:
        conn = sqlite3.connect("chat_log.db")
        st.session_state.sqlite_conn = conn
    return conn

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS messages (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            role TEXT NOT NULL,
            content TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    conn.commit()

def store_message(role, content):
    """Stores a message in the database."""
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute(
        "INSERT INTO messages (role, content) VALUES (?, ?)",
        (role, content),
    )
    conn.commit()

create_table()  # Ensure table exists

# Store messages
if st.session_state.user_input:
    store_message("user", st.session_state.user_input)
if st.session_state.get("reply"):  # replace reply with a key you are using
    store_message("assistant", st.session_state.reply)

# Example of retrieving messages (optional, for demonstration)
conn = get_connection()
cursor = conn.cursor()
cursor.execute("SELECT role, content, created_at FROM messages ORDER BY created_at DESC LIMIT 5")
recent_messages = cursor.fetchall()
st.write("Last 5 Messages from DB:")
for row in recent_messages:
    st.write(f"{row[0]}: {row[1]} (at {row[2]})")

Ejemplo de Código (PostgreSQL - Flask):

from flask import Flask, request, render_template, session
import openai
import os
from dotenv import load_dotenv
import psycopg2  # PostgreSQL library
import datetime
from typing import List, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL") #Make sure to set this in .env

app = Flask(__name__)
app.secret_key = os.urandom(24)

def get_db_connection():
    """Gets a PostgreSQL connection."""
    try:
        conn = psycopg2.connect(DATABASE_URL)
        return conn
    except psycopg2.Error as e:
        print(f"Database connection error: {e}")
        return None

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id SERIAL PRIMARY KEY,
                    session_id TEXT NOT NULL,
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            """)
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error creating table: {e}")
        finally:
            conn.close()

def store_message(session_id: str, role: str, content: str):
    """Stores a message in the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
                (session_id, role, content),
            )
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error storing message: {e}")
        finally:
            conn.close()

create_table()  # Ensure the table exists

@app.route("/", methods=["GET", "POST"])
def chat():
    if "session_id" not in session:
        session["session_id"] = os.urandom(16).hex()  # Unique session ID

    if "history" not in session:
        session["history"] = [{"role": "system", "content": "You are a helpful assistant."}]

    if request.method == "POST":
        user_input = request.form["user_input"]
        session_id = session["session_id"]
        store_message(session_id, "user", user_input)  # Store in DB
        session["history"].append({"role": "user", "content": user_input})

        conn = get_db_connection() #
        if conn: #
            try: #
                cursor = conn.cursor() #
                cursor.execute("SELECT role, content, created_at FROM messages WHERE session_id = %s ORDER BY created_at", (session_id,)) #
                messages_from_db = cursor.fetchall() #
            except psycopg2.Error as e: #
                print(f"Error fetching messages: {e}") #
            finally: #
                conn.close() #

        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=session["history"],
            temperature=0.6,
        )

        assistant_reply = response["choices"][0]["message"]["content"]
        store_message(session_id, "assistant", assistant_reply)  # Store in DB
        session["history"].append({"role": "assistant", "content": assistant_reply})
        session.modified = True

        return render_template("chat.html", history=session.get("history")[1:])
    return render_template("chat.html", history=session.get("history")[1:])

Explicación:

  • El ejemplo de Streamlit usa SQLite, una base de datos ligera que no requiere un servidor separado. El ejemplo de Flask usa PostgreSQL, una base de datos más robusta que es adecuada para aplicaciones multiusuario.
  • Ambos ejemplos crean una tabla llamada "messages" para almacenar el historial de conversación. La tabla incluye columnas para ID del mensaje, rol, contenido y marca de tiempo.
  • La función store_message() inserta un nuevo mensaje en la base de datos.
  • El ejemplo de Flask recupera mensajes de la base de datos y los pasa a la plantilla para su visualización. El ejemplo de Streamlit también muestra cómo recuperar datos.

Ventajas:

  • SQLite: Perfecto para aplicaciones de un solo usuario con datos estructurados. No se necesita un servidor de base de datos separado.
  • PostgreSQL: Ideal para sistemas multiusuario que requieren acceso concurrente. Más robusto y escalable que SQLite.
  • NoSQL (No mostrado en detalle): Óptimo para esquemas flexibles y datos de conversación no estructurados. Bases de datos como MongoDB o CouchDB serían adecuadas.
  • Desventajas:
    • Requiere más configuración que usar un archivo JSON.
    • Necesidad de gestionar conexiones y esquemas de base de datos.

3. Reutilizar memoria en sesiones futuras mediante etiquetado con ID de usuario

Proceso: Para permitir que los usuarios tengan conversaciones persistentes y personalizadas, puedes etiquetar cada mensaje con un ID de usuario y almacenar esta información en la base de datos. Cuando un usuario regresa, puedes recuperar su historial específico de conversación de la base de datos.

Ejemplo de Código (PostgreSQL - Flask):

from flask import Flask, request, render_template, session, redirect, url_for
import openai
import os
from dotenv import load_dotenv
import psycopg2
import datetime
from typing import List, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")

app = Flask(__name__)
app.secret_key = os.urandom(24)

def get_db_connection():
    """Gets a PostgreSQL connection."""
    try:
        conn = psycopg2.connect(DATABASE_URL)
        return conn
    except psycopg2.Error as e:
        print(f"Database connection error: {e}")
        return None

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id SERIAL PRIMARY KEY,
                    user_id TEXT NOT NULL,  -- Added user_id
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            """)
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error creating table: {e}")
        finally:
            conn.close()

def store_message(user_id: str, role: str, content: str):
    """Stores a message in the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO messages (user_id, role, content) VALUES (%s, %s, %s)",
                (user_id, role, content),
            )
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error storing message: {e}")
        finally:
            conn.close()

create_table()

def get_user_history(user_id: str) -> List[Dict[str, str]]:
    """Retrieves a user's conversation history from the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT role, content FROM messages WHERE user_id = %s ORDER BY created_at",
                (user_id,),
            )
            history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
            return history
        except psycopg2.Error as e:
            print(f"Error retrieving user history: {e}")
            return []
        finally:
            conn.close()
    return []

@app.route("/", methods=["GET", "POST"])
def chat():
    if "user_id" not in session:
        session["user_id"] = os.urandom(16).hex()  # Unique user ID
    user_id = session["user_id"]

    history = get_user_history(user_id)  # Get user's history

    if request.method == "POST":
        user_input = request.form["user_input"]
        store_message(user_id, "user", user_input)  # Store with user ID
        history.append({"role": "user", "content": user_input})

        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=history,
            temperature=0.6,
        )

        assistant_reply = response["choices"][0]["message"]["content"]
        store_message(user_id, "assistant", assistant_reply)  # Store with user ID
        history.append({"role": "assistant", "content": assistant_reply})
        session["history"] = history #update

        return render_template("chat.html", history=history[1:])

    return render_template("chat.html", history=history[1:])

@app.route("/clear", methods=["POST"])
def clear_chat():
    session.pop("user_id", None)  #remove user id
    session.pop("history", None)
    return redirect(url_for("chat"))

Explicación:

  • La tabla de base de datos "messages" ahora incluye una columna "user_id".
  • Cuando un usuario inicia una sesión, se genera un "user_id" único y se almacena en la session de Flask.
  • La función store_message() ahora requiere un "user_id" y lo almacena junto con el mensaje.
  • La función get_user_history() recupera el historial de conversación para un usuario específico de la base de datos.
  • La ruta de chat recupera el historial del usuario y lo utiliza para construir los mensajes enviados a OpenAI, manteniendo así el historial de conversación a través de múltiples visitas del mismo usuario.
  • Ventajas:
    • Permite un historial de conversación personalizado para cada usuario.
    • Permite un contexto y preferencias específicos para cada usuario.
    • Facilita el análisis de patrones de conversación a lo largo del tiempo.
  • Desventajas:
    • Requiere una base de datos.
    • Más complejo de implementar que el almacenamiento simple basado en sesiones.

En esta sección, aprendiste varios aspectos cruciales para construir un chatbot con capacidades de memoria:

  • Agregaste memoria basada en sesiones a tu chatbot usando st.session_state (Streamlit) y flask.session (Flask)
    • Implementaste almacenamiento temporal para conversaciones en curso
    • Aprendiste a gestionar variables de sesión de manera efectiva
  • Preservaste el historial de chat a través de interacciones
    • Creaste esquemas de base de datos para almacenar datos de conversación
    • Implementaste métodos para guardar y recuperar mensajes anteriores
  • Mejoraste el contexto y la coherencia para conversaciones de múltiples turnos
    • Desarrollaste sistemas para mantener el contexto de la conversación
    • Mejoraste la comprensión del lenguaje natural a través del contexto histórico
  • Aprendiste a limitar el uso de tokens mediante el recorte del historial de mensajes
    • Implementaste estrategias eficientes de poda de mensajes
    • Equilibraste la retención de memoria con las limitaciones de tokens de la API

Esto le da a tu chatbot la capacidad de mantener conversaciones naturales y fluidas — un hito clave hacia la construcción de un asistente inteligente. Con estas características, tu chatbot ahora puede recordar interacciones previas, mantener el contexto durante las conversaciones y gestionar la memoria de manera eficiente mientras se mantiene dentro de las restricciones técnicas.

4.3 Implementación de Memoria de Chat Basada en Sesiones

La memoria basada en sesiones es una característica crucial que transforma un chatbot simple en un agente conversacional verdaderamente interactivo y consciente del contexto. Sin memoria, cada interacción se vuelve aislada y desconectada, forzando a los usuarios a proporcionar contexto repetidamente y limitando el flujo natural de la conversación. Esta sección explora cómo implementar una gestión robusta de memoria en tu chatbot, permitiéndole mantener discusiones coherentes y contextuales a través de múltiples intercambios.

Examinaremos dos enfoques distintos para implementar memoria de sesión - usando la gestión de estado incorporada de Streamlit y las sesiones del lado del servidor de Flask. Ambos métodos ofrecen sus propias ventajas y pueden adaptarse para satisfacer requisitos específicos del proyecto. Al final de esta sección, comprenderás cómo crear un chatbot que puede mantener el contexto, recordar interacciones previas y proporcionar respuestas más significativas y conectadas.

La implementación que cubriremos asegura que tu chatbot pueda:

  • Mantener la consciencia contextual durante conversaciones completas
  • Manejar diálogos complejos de múltiples turnos de manera efectiva
  • Proporcionar respuestas más relevantes y personalizadas basadas en el historial de conversación
  • Gestionar la memoria eficientemente sin exceder los límites de tokens
✅ Dotar a tu asistente de memoria — para que no olvide el flujo de la conversación cada vez que la página se actualiza o la sesión se reinicia.

4.3.1 ¿Por qué es importante la memoria de sesión?

La memoria de sesión es un aspecto fundamental para crear chatbots inteligentes que puedan participar en conversaciones significativas y conscientes del contexto. Al igual que los humanos dependen de la memoria durante las discusiones, los chatbots necesitan una manera de recordar y procesar las interacciones previas. Cuando hablamos con otros, naturalmente hacemos referencia a puntos anteriores, construimos sobre el entendimiento compartido y mantenemos un flujo coherente de ideas. Este patrón natural de comunicación es lo que hace que las conversaciones se sientan orgánicas y significativas. Sin capacidades de memoria, los chatbots esencialmente comienzan de cero con cada respuesta, lo que lleva a interacciones desconectadas y a menudo frustrantes que se sienten más como hablar con una máquina que tener una conversación real.

El concepto de memoria de sesión transforma fundamentalmente las interacciones del chatbot de varias maneras críticas:

  • Recordar y hacer referencia a partes previas de la conversación con precisión
    • Hacer seguimiento de detalles específicos mencionados anteriormente en el chat, como preferencias del usuario, especificaciones técnicas o información personal compartida
    • Hacer referencia a acuerdos o decisiones pasadas con precisión, asegurando la continuidad en discusiones o negociaciones complejas
    • Mantener el contexto histórico para una mejor resolución de problemas y soporte
  • Construir contexto incrementalmente durante una interacción
    • Comprender temas complejos que se desarrollan a lo largo de múltiples mensajes, permitiendo una exploración más profunda de los temas
    • Desarrollar respuestas más sofisticadas a medida que avanza la conversación, construyendo sobre conceptos previamente establecidos
    • Crear un hilo narrativo coherente a través de múltiples intercambios
  • Proporcionar respuestas más matizadas y relevantes basadas en el historial de conversación
    • Adaptar las respuestas al nivel de conocimiento demostrado por el usuario, ajustando la terminología y complejidad según corresponda
    • Evitar repetir información ya discutida, haciendo las conversaciones más eficientes
    • Usar interacciones pasadas para proporcionar respuestas más personalizadas y contextualmente apropiadas
  • Crear una experiencia conversacional más natural y humana
    • Mantener una personalidad y tono consistentes durante todo el chat, mejorando la participación del usuario
    • Adaptar las respuestas basadas en las preferencias del usuario e interacciones pasadas, creando una experiencia más personalizada
    • Aprender de intercambios anteriores para mejorar la calidad de interacciones futuras

En conversaciones reales, el contexto se construye con el tiempo de múltiples formas sofisticadas que reflejan patrones naturales de diálogo humano:

  • El usuario se refiere a mensajes anteriores
    • Las preguntas naturalmente se construyen sobre respuestas previas, creando un hilo continuo de comprensión
    • Los temas evolucionan orgánicamente mientras los usuarios hacen referencia y expanden puntos anteriores en la conversación
    • El contexto previo moldea cómo se interpreta y entiende la nueva información
  • El asistente necesita recordar respuestas previas
    • Mantiene consistencia a través de las respuestas para construir confianza y fiabilidad
    • Utiliza el contexto establecido para proporcionar información más matizada y relevante
    • Construye una comprensión integral de las necesidades del usuario a lo largo del tiempo
  • Las preguntas de seguimiento dependen del conocimiento previo
    • Cada pregunta se construye sobre la base de intercambios anteriores
    • Los temas complejos pueden explorarse gradualmente, con profundidad creciente
    • La conversación progresa naturalmente desde conceptos básicos hacia una comprensión más avanzada
    • Crea cadenas de diálogo más significativas conectando ideas relacionadas
    • Permite un flujo de conversación natural que se siente más humano y atractivo

Sin implementación de memoria, los chatbots tratan cada interacción como un evento aislado, completamente desconectado de intercambios previos. Esta limitación fundamental afecta incluso a modelos sofisticados como GPT-4o de varias maneras críticas:

  1. Pérdida de Contexto: Cada respuesta se genera sin ninguna conciencia de conversaciones previas, haciendo imposible mantener discusiones coherentes y extendidas.
  2. Interacciones Repetitivas: El chatbot puede proporcionar la misma información múltiples veces o pedir detalles que ya fueron compartidos, creando una experiencia de usuario frustrante.
  3. Respuestas Inconsistentes: Sin acceso a intercambios previos, el chatbot podría dar respuestas contradictorias a preguntas relacionadas, socavando la confianza del usuario.
  4. Comprensión Limitada: La incapacidad de hacer referencia al contexto pasado significa que el chatbot no puede construir sobre el conocimiento previamente establecido ni adaptar sus respuestas basándose en la comprensión demostrada por el usuario.

Al final de esta sección, sabrás cómo:

  • Almacenar y recuperar mensajes para una sesión dada
    • Implementar mecanismos de almacenamiento seguro usando encriptación y protección estándar de la industria
    • Manejar diferentes tipos de datos de mensajes efectivamente, incluyendo texto, datos estructurados y metadatos
  • Mantener y actualizar memoria de múltiples turnos
    • Procesar cadenas de conversación eficientemente usando estructuras de datos optimizadas
    • Gestionar el contexto a través de múltiples intercambios mientras se mantiene la coherencia de la conversación
  • Evitar el uso excesivo de tokens mediante limitación de la longitud del historial
    • Implementar estrategias inteligentes de gestión de memoria que prioricen la información relevante
    • Equilibrar la retención de contexto con el rendimiento a través de algoritmos inteligentes de poda

4.3.2 Streamlit: Memoria de Estado de Sesión

Streamlit hace que la gestión del historial de conversación sea super simple con st.session_state, que persiste los datos durante toda la sesión del navegador.

Aquí te mostramos cómo puedes crear un chatbot con memoria de sesión usando Streamlit:

Paso 1: Importar Bibliotecas

import streamlit as st
import openai
import os
from dotenv import load_dotenv
  • streamlit: Usado para crear la interfaz de usuario del chatbot.
  • openai: La biblioteca de Python de OpenAI, usada para interactuar con el modelo GPT-4o.
  • os: Proporciona una manera de interactuar con el sistema operativo, por ejemplo para acceder a variables de entorno.
  • dotenv: Usado para cargar variables de entorno desde un archivo .env.

Paso 2: Cargar la Clave API y Configurar la Página

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

st.set_page_config(page_title="GPT-4o Chat with Memory", page_icon="🧠")
st.title("🧠 GPT-4o Chatbot with Session Memory")
  • load_dotenv(): Carga la clave API de OpenAI desde un archivo .env. Este archivo debe estar en el mismo directorio que tu script de Python y contener la línea OPENAI_API_KEY=TU_CLAVE_API.
  • openai.api_key = os.getenv("OPENAI_API_KEY"): Establece la clave API de OpenAI.
  • st.set_page_config(...): Configura el título y el ícono de la página que aparecen en la pestaña del navegador.
  • st.title(...): Establece el título de la aplicación Streamlit, que se muestra en la parte superior de la página.

Paso 3: Inicializar el Estado de la Sesión

if "messages" not in st.session_state:
    st.session_state.messages = [{"role": "system", "content": "You are a helpful assistant that remembers this session."}]
  • st.session_state: La forma en que Streamlit almacena variables entre interacciones del usuario. Los datos en st.session_state persisten mientras la pestaña del navegador permanezca abierta.
  • Este código verifica si la clave "messages" existe en st.session_state. Si no existe (lo cual ocurre cuando el usuario carga la aplicación por primera vez), inicializa "messages" como una lista que contiene un único diccionario.
  • Este diccionario representa el "mensaje del sistema", que se utiliza para establecer el comportamiento del asistente. En este caso, el mensaje del sistema le indica al asistente que sea útil y recuerde la conversación.

Paso 4: Mostrar el Historial de Chat

for msg in st.session_state.messages[1:]:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])
  • Este bucle for itera a través de los mensajes en la lista "messages", comenzando desde el segundo mensaje (índice 1) para omitir el mensaje del sistema.
  • st.chat_message(msg["role"]): Crea una burbuja de chat en la aplicación Streamlit para mostrar el mensaje. El role ("user" o "assistant") determina la apariencia de la burbuja.
  • st.markdown(msg["content"]): Muestra el contenido del mensaje dentro de la burbuja de chat. Se utiliza st.markdown para renderizar el texto.

Paso 5: Entrada del Usuario y Generación de Respuesta

user_input = st.chat_input("Say something...")
if user_input:
    st.chat_message("user").markdown(user_input)
    st.session_state.messages.append({"role": "user", "content": user_input})

    with st.chat_message("assistant"):
        with st.spinner("Thinking..."):
            response = openai.ChatCompletion.create(
                model="gpt-4o",
                messages=st.session_state.messages,
                temperature=0.6
            )
            reply = response["choices"][0]["message"]["content"]
            st.markdown(reply)
            st.session_state.messages.append({"role": "assistant", "content": reply})
  • user_input = st.chat_input("Di algo..."): Crea un campo de entrada de texto en la parte inferior de la aplicación donde el usuario puede escribir su mensaje. La etiqueta "Di algo..." se muestra junto al campo de entrada.
  • El bloque if user_input: se ejecuta cuando el usuario ingresa texto y presiona Enter.
  • st.chat_message("user").markdown(user_input): Muestra el mensaje del usuario en la interfaz de chat.
  • st.session_state.messages.append({"role": "user", "content": user_input}): Agrega el mensaje del usuario a la lista "messages" en st.session_state, para que se almacene en el historial de conversación.
  • with st.chat_message("assistant"):: Crea una burbuja de chat para la respuesta del asistente.
  • with st.spinner("Pensando..."): Muestra una animación de carga mientras la aplicación espera una respuesta de la API de OpenAI.
  • response = openai.ChatCompletion.create(...): Llama a la API de OpenAI para obtener una respuesta del modelo GPT-4o.
    • model: Especifica el modelo de lenguaje a usar ("gpt-4o").
    • messages: Pasa todo el historial de conversación (almacenado en st.session_state.messages) a la API. Así es como el modelo "recuerda" la conversación.
    • temperature: Un valor entre 0 y 1 que controla la aleatoriedad de la salida del modelo. Un valor más bajo (por ejemplo, 0.2) hace que la salida sea más determinista, mientras que un valor más alto (por ejemplo, 0.8) la hace más aleatoria.
  • reply = response["choices"][0]["message"]["content"]: Extrae la respuesta del asistente de la respuesta de la API.
  • st.markdown(reply): Muestra la respuesta del asistente en la interfaz de chat.
  • st.session_state.messages.append({"role": "assistant", "content": reply}): Agrega la respuesta del asistente a la lista "messages" en st.session_state.

Este ejemplo crea un chatbot simple con memoria usando Streamlit. La lista st.session_state.messages almacena el historial de conversación, permitiendo que el chatbot mantenga el contexto a través de múltiples interacciones. El historial del chat se muestra en la aplicación, y el usuario puede ingresar mensajes usando el campo st.chat_input. Las respuestas del asistente son generadas por el modelo OpenAI GPT-4o.

pip install flask openai flask-session python-dotenv
  • flask: Un framework web.
  • openai: La biblioteca de Python de OpenAI.
  • flask-session: Extensión de Flask para manejar sesiones del lado del servidor.
  • python-dotenv: Para cargar variables de entorno desde un archivo .env.

Paso 2: Actualizar app.py

from flask import Flask, request, render_template, session
import openai
import os
from dotenv import load_dotenv
from flask_session import Session

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

app = Flask(__name__)
app.secret_key = os.urandom(24)
app.config["SESSION_TYPE"] = "filesystem"
Session(app)

@app.route("/", methods=["GET", "POST"])
def chat():
    if "history" not in session:
        session["history"] = [
            {"role": "system", "content": "You are a helpful assistant."}
        ]

    if request.method == "POST":
        user_input = request.form["user_input"]
        session["history"].append({"role": "user", "content": user_input})

        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=session["history"],
            temperature=0.6,
        )

        assistant_reply = response["choices"][0]["message"]["content"]
        session["history"].append({"role": "assistant", "content": assistant_reply})

        return render_template("chat.html", history=session.get("history")[1:])  # Skip system prompt

    return render_template("chat.html", history=session.get("history")[1:])  # Skip system prompt
  • from flask import ...: Importa los componentes necesarios de Flask, incluyendo session para gestionar las sesiones de usuario.
  • import openai: Importa la biblioteca OpenAI.
  • import os: Importa el módulo os para interactuar con el sistema operativo, particularmente para acceder a variables de entorno.
  • from dotenv import load_dotenv: Importa la función load_dotenv de la biblioteca python-dotenv.
  • from flask_session import Session: Importa la clase Session de flask_session.
  • load_dotenv(): Carga las variables de entorno (como la clave API de OpenAI) desde un archivo .env.
  • openai.api_key = os.getenv("OPENAI_API_KEY"): Obtiene la clave API de OpenAI del entorno y la configura para la biblioteca OpenAI.
  • app = Flask(__name__): Crea una instancia de la aplicación Flask.
  • app.secret_key = os.urandom(24): Establece una clave secreta para la aplicación Flask. Esto es esencial para usar sesiones de Flask. os.urandom(24) genera una clave aleatoria y criptográficamente segura.
  • app.config["SESSION_TYPE"] = "filesystem": Configura Flask-Session para almacenar los datos de sesión en el sistema de archivos del servidor. Otras opciones como "redis" o "mongodb" están disponibles para uso en producción.
  • Session(app): Inicializa la extensión Flask-Session, vinculándola a la aplicación Flask.
  • @app.route("/", methods=["GET", "POST"]): Define la ruta para la página principal de la aplicación ("/"). La función chat() maneja tanto solicitudes GET como POST.
  • def chat()::
    • if "history" not in session:: Verifica si la sesión del usuario ya tiene un historial de conversación. Si no, inicializa la sesión con un mensaje del sistema. El mensaje del sistema ayuda a establecer el comportamiento del asistente.
    • if request.method == "POST":: Maneja las solicitudes POST, que ocurren cuando el usuario envía un mensaje a través del formulario de chat.
      • user_input = request.form["user_input"]: Obtiene la entrada del usuario del formulario.
      • session["history"].append({"role": "user", "content": user_input}): Añade el mensaje del usuario al historial de conversación almacenado en la sesión.
      • response = openai.ChatCompletion.create(...): Llama a la API de OpenAI para obtener una respuesta del modelo GPT-4o, pasando el historial de conversación.
      • assistant_reply = response["choices"][0]["message"]["content"]: Extrae la respuesta del asistente de la respuesta de la API.
      • session["history"].append({"role": "assistant", "content": assistant_reply}): Añade la respuesta del asistente al historial de conversación en la sesión.
      • return render_template("chat.html", history=session.get("history")[1:]): Renderiza la plantilla chat.html, pasando el historial de conversación (excluyendo el mensaje inicial del sistema) para ser mostrado.
    • return render_template("chat.html", history=session.get("history")[1:]): Maneja las solicitudes GET (cuando el usuario carga la página por primera vez). Renderiza la plantilla chat.html, pasando el historial de conversación (excluyendo el mensaje del sistema).

Paso 3: Tu Plantilla HTML (templates/chat.html)

<!DOCTYPE html>
<html>
<head>
  <title>GPT-4o Assistant</title>
  <style>
    body { font-family: Arial; background: #f7f7f7; padding: 40px; }
    .container { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 10px; }
    .user, .assistant { margin-bottom: 15px; }
    .user p { background: #d4f0ff; padding: 10px; border-radius: 10px; }
    .assistant p { background: #e8ffe8; padding: 10px; border-radius: 10px; }
    textarea { width: 100%; height: 80px; }
    input[type="submit"] { margin-top: 10px; padding: 10px 20px; }
  </style>
</head>
<body>
  <div class="container">
    <h2>GPT-4o Chatbot</h2>
    {% for msg in history %}
      <div class="{{ msg.role }}">
        <p><strong>{{ msg.role.capitalize() }}:</strong> {{ msg.content }}</p>
      </div>
    {% endfor %}
    <form method="post">
      <textarea name="user_input" placeholder="Type your message..."></textarea><br>
      <input type="submit" value="Send">
    </form>
  </div>
</body>
</html>
  • <!DOCTYPE html>: Declara el tipo de documento como HTML5.
  • <html>: El elemento raíz del documento HTML.
  • <head>: Contiene los metadatos del documento HTML.
    • <title>: Especifica el título de la página HTML.
    • <style>: Incluye CSS para el estilo básico de la interfaz del chat.
  • <body>: Contiene el contenido visible de la página HTML.
    • <div class="container">: Un contenedor para la aplicación de chat.
    • <h2>: Un encabezado para la aplicación de chat.
    • {% for msg in history %}: Un bucle de plantilla Jinja2 que itera a través de la variable history (pasada desde el código de Flask) para mostrar los mensajes del chat.
      • <div class="{{ msg.role }}">: Crea un elemento div para cada mensaje. La clase se asigna según el rol del mensaje ("user" o "assistant") para fines de estilo.
      • <p>: Muestra el contenido del mensaje.
      • <strong>: Muestra el rol.
    • <form method="post">: Un formulario para que el usuario envíe sus mensajes.
      • <textarea>: Un campo de entrada de texto multilínea para que el usuario escriba su mensaje.
      • <input type="submit" value="Send">: Un botón para enviar el mensaje.
  • templates: Flask, por defecto, busca las plantillas HTML en una carpeta llamada "templates" en el mismo directorio que tu archivo app.py. Por lo tanto, este archivo debe guardarse como templates/chat.html.

Este código crea un chatbot usando Flask y OpenAI, con el historial de la conversación almacenado en sesiones del lado del servidor. El servidor conserva la memoria del chat durante la sesión del usuario, eliminándola cuando el usuario cierra la pestaña del navegador.

4.3.4 Opcional: Limitar el Historial de Mensajes

Los modelos de lenguaje grandes tienen una ventana de contexto limitada (por ejemplo, 128k tokens en GPT-4o), lo que determina cuánta conversación el modelo puede "recordar" al mismo tiempo. Para evitar superar este límite y generar errores, deberías limitar la cantidad de mensajes almacenados en el historial de la conversación.

Aquí te mostramos cómo recortar las entradas más antiguas tanto en Streamlit como en Flask:

Streamlit

import streamlit as st

MAX_HISTORY = 20  # Maximum number of messages to keep

if SESSION_MESSAGES_KEY in st.session_state:
    if len(st.session_state[SESSION_MESSAGES_KEY]) > MAX_HISTORY:
        # Keep the system message and the last MAX_HISTORY messages
        st.session_state[SESSION_MESSAGES_KEY] = [st.session_state[SESSION_MESSAGES_KEY][0]] + st.session_state[SESSION_MESSAGES_KEY][-MAX_HISTORY + 1:]

Explicación:

  • MAX_HISTORY: Una constante que define la cantidad máxima de mensajes a conservar. Este ejemplo mantiene los últimos 20 mensajes.
  • if SESSION_MESSAGES_KEY in st.session_state: Verifica si la clave existe
  • El código luego recorta la lista st.session_state.messages, conservando el primer mensaje (el mensaje del sistema) y los últimos MAX_HISTORY - 1 mensajes.

Flask

from flask import session

MAX_HISTORY = 20  # Maximum number of messages to keep

if SESSION_MESSAGES_KEY in session:
    if len(session[SESSION_MESSAGES_KEY]) > MAX_HISTORY:
        # Keep the system message and the last MAX_HISTORY messages
        session[SESSION_MESSAGES_KEY] = [session[SESSION_MESSAGES_KEY][0]] + session[SESSION_MESSAGES_KEY][-MAX_HISTORY + 1:]

Explicación:

  • MAX_HISTORY: Una constante que define el número máximo de mensajes a conservar.
  • if SESSION_MESSAGES_KEY in session: Verifica si la clave existe
  • El código recorta la lista session["history"], manteniendo el primer mensaje (el mensaje del sistema) y los últimos MAX_HISTORY - 1 mensajes.

Consideraciones Importantes de Implementación:

  • Gestión del Mensaje del Sistema: El mensaje del sistema juega un papel crucial en establecer el comportamiento y contexto del chatbot. Siempre debe preservarse como el primer mensaje en tu historial de conversación. Al implementar el recorte de mensajes, asegúrate de que tu código específicamente mantenga este mensaje mediante:
    • Mantenerlo separado del flujo regular de conversación
    • Incluir un manejo especial en tu lógica de recorte
    • Verificar su presencia antes de cada interacción
  • Protocolo Integral de Pruebas: Para asegurar un rendimiento confiable del chatbot:
    • Probar con diferentes longitudes de conversación, desde intercambios cortos hasta diálogos extensos
    • Verificar que el contexto se mantenga incluso después del recorte
    • Comprobar casos límite donde la coherencia podría romperse
    • Monitorear el uso de recursos del sistema durante conversaciones prolongadas
  • Estrategias Avanzadas de Recorte: Considera estos enfoques sofisticados:
    • Recorte basado en tokens: Calcular el uso real de tokens usando un tokenizador
    • Recorte basado en importancia: Mantener mensajes según su relevancia
    • Enfoque híbrido: Combinar conteo de tokens con relevancia de mensajes
    • Ajuste dinámico: Modificar el umbral de recorte según la complejidad de la conversación

4.3.5 Consejo Adicional: Guardar en Archivo o Base de Datos

¿Quieres mantener la memoria incluso después de que termine la sesión? Exploremos varios métodos efectivos para el almacenamiento de memoria a largo plazo:

1. Exportar historial de conversación a un archivo JSON

Proceso: Los datos de st.session_state.messages (en Streamlit) o session["history"] (en Flask) pueden guardarse en un archivo JSON. Esto implica convertir la lista de diccionarios de mensajes en una cadena JSON y escribirla en un archivo.

Ejemplo de Código (Streamlit):

import json
import streamlit as st

def save_chat_log(filename="chat_log.json"):
    """Saves the chat log from st.session_state to a JSON file."""
    if SESSION_MESSAGES_KEY in st.session_state:
        try:
            with open(filename, "w") as f:
                json.dump(st.session_state[SESSION_MESSAGES_KEY], f, indent=2)
            print(f"Chat log saved to {filename}")
        except Exception as e:
            st.error(f"Error saving chat log: {e}")

# Example usage:  Call this function when the user ends the session or when appropriate
if st.button("Save Chat Log"):
    save_chat_log()

Ejemplo de Código (Flask):

import json
from flask import session, Flask

def save_chat_log(filename="chat_log.json"):
    """Saves the chat log from the Flask session to a JSON file."""
    if SESSION_MESSAGES_KEY in session:
        try:
            with open(filename, "w") as f:
                json.dump(session[SESSION_MESSAGES_KEY], f, indent=2)
            print(f"Chat log saved to {filename}")
        except Exception as e:
            #  Use the app context to display a message
            with app.app_context():
                print(f"Error saving chat log: {e}")

# Example Usage
app = Flask(__name__)
@app.route('/save_log')
def save_log():
    save_chat_log()
    return "Chat log saved!"

Explicación:

  • La función json.dump() se utiliza para serializar la lista de mensajes a una cadena con formato JSON. El parámetro indent=2 hace que el archivo JSON sea más legible para humanos.
  • El código maneja posibles errores durante la escritura del archivo.
  • El ejemplo de Streamlit utiliza un botón para activar el guardado, y el ejemplo de Flask crea una ruta /save_log para guardar el archivo.

Ventajas:

  • Fácil de implementar usando el módulo json incorporado en Python.
  • Adecuado para aplicaciones pequeñas y prototipos rápidos.
  • Fácil de respaldar y controlar versiones.

Desventajas:

  • No es ideal para aplicaciones a gran escala con múltiples usuarios.
  • Sin capacidad eficiente de consulta o indexación.

2. Almacenar conversaciones en una base de datos SQLite, PostgreSQL o NoSQL

Proceso: Almacenar el historial de conversación en una base de datos. Cada mensaje puede ser una fila en una tabla (para bases de datos SQL) o un documento en una colección (para bases de datos NoSQL).

Ejemplo de Código (SQLite - Streamlit):

import streamlit as st
import sqlite3
import datetime

def get_connection():
    """Gets or creates a SQLite connection."""
    conn = getattr(st.session_state, "sqlite_conn", None)
    if conn is None:
        conn = sqlite3.connect("chat_log.db")
        st.session_state.sqlite_conn = conn
    return conn

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS messages (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            role TEXT NOT NULL,
            content TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    conn.commit()

def store_message(role, content):
    """Stores a message in the database."""
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute(
        "INSERT INTO messages (role, content) VALUES (?, ?)",
        (role, content),
    )
    conn.commit()

create_table()  # Ensure table exists

# Store messages
if st.session_state.user_input:
    store_message("user", st.session_state.user_input)
if st.session_state.get("reply"):  # replace reply with a key you are using
    store_message("assistant", st.session_state.reply)

# Example of retrieving messages (optional, for demonstration)
conn = get_connection()
cursor = conn.cursor()
cursor.execute("SELECT role, content, created_at FROM messages ORDER BY created_at DESC LIMIT 5")
recent_messages = cursor.fetchall()
st.write("Last 5 Messages from DB:")
for row in recent_messages:
    st.write(f"{row[0]}: {row[1]} (at {row[2]})")

Ejemplo de Código (PostgreSQL - Flask):

from flask import Flask, request, render_template, session
import openai
import os
from dotenv import load_dotenv
import psycopg2  # PostgreSQL library
import datetime
from typing import List, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL") #Make sure to set this in .env

app = Flask(__name__)
app.secret_key = os.urandom(24)

def get_db_connection():
    """Gets a PostgreSQL connection."""
    try:
        conn = psycopg2.connect(DATABASE_URL)
        return conn
    except psycopg2.Error as e:
        print(f"Database connection error: {e}")
        return None

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id SERIAL PRIMARY KEY,
                    session_id TEXT NOT NULL,
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            """)
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error creating table: {e}")
        finally:
            conn.close()

def store_message(session_id: str, role: str, content: str):
    """Stores a message in the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
                (session_id, role, content),
            )
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error storing message: {e}")
        finally:
            conn.close()

create_table()  # Ensure the table exists

@app.route("/", methods=["GET", "POST"])
def chat():
    if "session_id" not in session:
        session["session_id"] = os.urandom(16).hex()  # Unique session ID

    if "history" not in session:
        session["history"] = [{"role": "system", "content": "You are a helpful assistant."}]

    if request.method == "POST":
        user_input = request.form["user_input"]
        session_id = session["session_id"]
        store_message(session_id, "user", user_input)  # Store in DB
        session["history"].append({"role": "user", "content": user_input})

        conn = get_db_connection() #
        if conn: #
            try: #
                cursor = conn.cursor() #
                cursor.execute("SELECT role, content, created_at FROM messages WHERE session_id = %s ORDER BY created_at", (session_id,)) #
                messages_from_db = cursor.fetchall() #
            except psycopg2.Error as e: #
                print(f"Error fetching messages: {e}") #
            finally: #
                conn.close() #

        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=session["history"],
            temperature=0.6,
        )

        assistant_reply = response["choices"][0]["message"]["content"]
        store_message(session_id, "assistant", assistant_reply)  # Store in DB
        session["history"].append({"role": "assistant", "content": assistant_reply})
        session.modified = True

        return render_template("chat.html", history=session.get("history")[1:])
    return render_template("chat.html", history=session.get("history")[1:])

Explicación:

  • El ejemplo de Streamlit usa SQLite, una base de datos ligera que no requiere un servidor separado. El ejemplo de Flask usa PostgreSQL, una base de datos más robusta que es adecuada para aplicaciones multiusuario.
  • Ambos ejemplos crean una tabla llamada "messages" para almacenar el historial de conversación. La tabla incluye columnas para ID del mensaje, rol, contenido y marca de tiempo.
  • La función store_message() inserta un nuevo mensaje en la base de datos.
  • El ejemplo de Flask recupera mensajes de la base de datos y los pasa a la plantilla para su visualización. El ejemplo de Streamlit también muestra cómo recuperar datos.

Ventajas:

  • SQLite: Perfecto para aplicaciones de un solo usuario con datos estructurados. No se necesita un servidor de base de datos separado.
  • PostgreSQL: Ideal para sistemas multiusuario que requieren acceso concurrente. Más robusto y escalable que SQLite.
  • NoSQL (No mostrado en detalle): Óptimo para esquemas flexibles y datos de conversación no estructurados. Bases de datos como MongoDB o CouchDB serían adecuadas.
  • Desventajas:
    • Requiere más configuración que usar un archivo JSON.
    • Necesidad de gestionar conexiones y esquemas de base de datos.

3. Reutilizar memoria en sesiones futuras mediante etiquetado con ID de usuario

Proceso: Para permitir que los usuarios tengan conversaciones persistentes y personalizadas, puedes etiquetar cada mensaje con un ID de usuario y almacenar esta información en la base de datos. Cuando un usuario regresa, puedes recuperar su historial específico de conversación de la base de datos.

Ejemplo de Código (PostgreSQL - Flask):

from flask import Flask, request, render_template, session, redirect, url_for
import openai
import os
from dotenv import load_dotenv
import psycopg2
import datetime
from typing import List, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")

app = Flask(__name__)
app.secret_key = os.urandom(24)

def get_db_connection():
    """Gets a PostgreSQL connection."""
    try:
        conn = psycopg2.connect(DATABASE_URL)
        return conn
    except psycopg2.Error as e:
        print(f"Database connection error: {e}")
        return None

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id SERIAL PRIMARY KEY,
                    user_id TEXT NOT NULL,  -- Added user_id
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            """)
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error creating table: {e}")
        finally:
            conn.close()

def store_message(user_id: str, role: str, content: str):
    """Stores a message in the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO messages (user_id, role, content) VALUES (%s, %s, %s)",
                (user_id, role, content),
            )
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error storing message: {e}")
        finally:
            conn.close()

create_table()

def get_user_history(user_id: str) -> List[Dict[str, str]]:
    """Retrieves a user's conversation history from the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT role, content FROM messages WHERE user_id = %s ORDER BY created_at",
                (user_id,),
            )
            history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
            return history
        except psycopg2.Error as e:
            print(f"Error retrieving user history: {e}")
            return []
        finally:
            conn.close()
    return []

@app.route("/", methods=["GET", "POST"])
def chat():
    if "user_id" not in session:
        session["user_id"] = os.urandom(16).hex()  # Unique user ID
    user_id = session["user_id"]

    history = get_user_history(user_id)  # Get user's history

    if request.method == "POST":
        user_input = request.form["user_input"]
        store_message(user_id, "user", user_input)  # Store with user ID
        history.append({"role": "user", "content": user_input})

        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=history,
            temperature=0.6,
        )

        assistant_reply = response["choices"][0]["message"]["content"]
        store_message(user_id, "assistant", assistant_reply)  # Store with user ID
        history.append({"role": "assistant", "content": assistant_reply})
        session["history"] = history #update

        return render_template("chat.html", history=history[1:])

    return render_template("chat.html", history=history[1:])

@app.route("/clear", methods=["POST"])
def clear_chat():
    session.pop("user_id", None)  #remove user id
    session.pop("history", None)
    return redirect(url_for("chat"))

Explicación:

  • La tabla de base de datos "messages" ahora incluye una columna "user_id".
  • Cuando un usuario inicia una sesión, se genera un "user_id" único y se almacena en la session de Flask.
  • La función store_message() ahora requiere un "user_id" y lo almacena junto con el mensaje.
  • La función get_user_history() recupera el historial de conversación para un usuario específico de la base de datos.
  • La ruta de chat recupera el historial del usuario y lo utiliza para construir los mensajes enviados a OpenAI, manteniendo así el historial de conversación a través de múltiples visitas del mismo usuario.
  • Ventajas:
    • Permite un historial de conversación personalizado para cada usuario.
    • Permite un contexto y preferencias específicos para cada usuario.
    • Facilita el análisis de patrones de conversación a lo largo del tiempo.
  • Desventajas:
    • Requiere una base de datos.
    • Más complejo de implementar que el almacenamiento simple basado en sesiones.

En esta sección, aprendiste varios aspectos cruciales para construir un chatbot con capacidades de memoria:

  • Agregaste memoria basada en sesiones a tu chatbot usando st.session_state (Streamlit) y flask.session (Flask)
    • Implementaste almacenamiento temporal para conversaciones en curso
    • Aprendiste a gestionar variables de sesión de manera efectiva
  • Preservaste el historial de chat a través de interacciones
    • Creaste esquemas de base de datos para almacenar datos de conversación
    • Implementaste métodos para guardar y recuperar mensajes anteriores
  • Mejoraste el contexto y la coherencia para conversaciones de múltiples turnos
    • Desarrollaste sistemas para mantener el contexto de la conversación
    • Mejoraste la comprensión del lenguaje natural a través del contexto histórico
  • Aprendiste a limitar el uso de tokens mediante el recorte del historial de mensajes
    • Implementaste estrategias eficientes de poda de mensajes
    • Equilibraste la retención de memoria con las limitaciones de tokens de la API

Esto le da a tu chatbot la capacidad de mantener conversaciones naturales y fluidas — un hito clave hacia la construcción de un asistente inteligente. Con estas características, tu chatbot ahora puede recordar interacciones previas, mantener el contexto durante las conversaciones y gestionar la memoria de manera eficiente mientras se mantiene dentro de las restricciones técnicas.

4.3 Implementación de Memoria de Chat Basada en Sesiones

La memoria basada en sesiones es una característica crucial que transforma un chatbot simple en un agente conversacional verdaderamente interactivo y consciente del contexto. Sin memoria, cada interacción se vuelve aislada y desconectada, forzando a los usuarios a proporcionar contexto repetidamente y limitando el flujo natural de la conversación. Esta sección explora cómo implementar una gestión robusta de memoria en tu chatbot, permitiéndole mantener discusiones coherentes y contextuales a través de múltiples intercambios.

Examinaremos dos enfoques distintos para implementar memoria de sesión - usando la gestión de estado incorporada de Streamlit y las sesiones del lado del servidor de Flask. Ambos métodos ofrecen sus propias ventajas y pueden adaptarse para satisfacer requisitos específicos del proyecto. Al final de esta sección, comprenderás cómo crear un chatbot que puede mantener el contexto, recordar interacciones previas y proporcionar respuestas más significativas y conectadas.

La implementación que cubriremos asegura que tu chatbot pueda:

  • Mantener la consciencia contextual durante conversaciones completas
  • Manejar diálogos complejos de múltiples turnos de manera efectiva
  • Proporcionar respuestas más relevantes y personalizadas basadas en el historial de conversación
  • Gestionar la memoria eficientemente sin exceder los límites de tokens
✅ Dotar a tu asistente de memoria — para que no olvide el flujo de la conversación cada vez que la página se actualiza o la sesión se reinicia.

4.3.1 ¿Por qué es importante la memoria de sesión?

La memoria de sesión es un aspecto fundamental para crear chatbots inteligentes que puedan participar en conversaciones significativas y conscientes del contexto. Al igual que los humanos dependen de la memoria durante las discusiones, los chatbots necesitan una manera de recordar y procesar las interacciones previas. Cuando hablamos con otros, naturalmente hacemos referencia a puntos anteriores, construimos sobre el entendimiento compartido y mantenemos un flujo coherente de ideas. Este patrón natural de comunicación es lo que hace que las conversaciones se sientan orgánicas y significativas. Sin capacidades de memoria, los chatbots esencialmente comienzan de cero con cada respuesta, lo que lleva a interacciones desconectadas y a menudo frustrantes que se sienten más como hablar con una máquina que tener una conversación real.

El concepto de memoria de sesión transforma fundamentalmente las interacciones del chatbot de varias maneras críticas:

  • Recordar y hacer referencia a partes previas de la conversación con precisión
    • Hacer seguimiento de detalles específicos mencionados anteriormente en el chat, como preferencias del usuario, especificaciones técnicas o información personal compartida
    • Hacer referencia a acuerdos o decisiones pasadas con precisión, asegurando la continuidad en discusiones o negociaciones complejas
    • Mantener el contexto histórico para una mejor resolución de problemas y soporte
  • Construir contexto incrementalmente durante una interacción
    • Comprender temas complejos que se desarrollan a lo largo de múltiples mensajes, permitiendo una exploración más profunda de los temas
    • Desarrollar respuestas más sofisticadas a medida que avanza la conversación, construyendo sobre conceptos previamente establecidos
    • Crear un hilo narrativo coherente a través de múltiples intercambios
  • Proporcionar respuestas más matizadas y relevantes basadas en el historial de conversación
    • Adaptar las respuestas al nivel de conocimiento demostrado por el usuario, ajustando la terminología y complejidad según corresponda
    • Evitar repetir información ya discutida, haciendo las conversaciones más eficientes
    • Usar interacciones pasadas para proporcionar respuestas más personalizadas y contextualmente apropiadas
  • Crear una experiencia conversacional más natural y humana
    • Mantener una personalidad y tono consistentes durante todo el chat, mejorando la participación del usuario
    • Adaptar las respuestas basadas en las preferencias del usuario e interacciones pasadas, creando una experiencia más personalizada
    • Aprender de intercambios anteriores para mejorar la calidad de interacciones futuras

En conversaciones reales, el contexto se construye con el tiempo de múltiples formas sofisticadas que reflejan patrones naturales de diálogo humano:

  • El usuario se refiere a mensajes anteriores
    • Las preguntas naturalmente se construyen sobre respuestas previas, creando un hilo continuo de comprensión
    • Los temas evolucionan orgánicamente mientras los usuarios hacen referencia y expanden puntos anteriores en la conversación
    • El contexto previo moldea cómo se interpreta y entiende la nueva información
  • El asistente necesita recordar respuestas previas
    • Mantiene consistencia a través de las respuestas para construir confianza y fiabilidad
    • Utiliza el contexto establecido para proporcionar información más matizada y relevante
    • Construye una comprensión integral de las necesidades del usuario a lo largo del tiempo
  • Las preguntas de seguimiento dependen del conocimiento previo
    • Cada pregunta se construye sobre la base de intercambios anteriores
    • Los temas complejos pueden explorarse gradualmente, con profundidad creciente
    • La conversación progresa naturalmente desde conceptos básicos hacia una comprensión más avanzada
    • Crea cadenas de diálogo más significativas conectando ideas relacionadas
    • Permite un flujo de conversación natural que se siente más humano y atractivo

Sin implementación de memoria, los chatbots tratan cada interacción como un evento aislado, completamente desconectado de intercambios previos. Esta limitación fundamental afecta incluso a modelos sofisticados como GPT-4o de varias maneras críticas:

  1. Pérdida de Contexto: Cada respuesta se genera sin ninguna conciencia de conversaciones previas, haciendo imposible mantener discusiones coherentes y extendidas.
  2. Interacciones Repetitivas: El chatbot puede proporcionar la misma información múltiples veces o pedir detalles que ya fueron compartidos, creando una experiencia de usuario frustrante.
  3. Respuestas Inconsistentes: Sin acceso a intercambios previos, el chatbot podría dar respuestas contradictorias a preguntas relacionadas, socavando la confianza del usuario.
  4. Comprensión Limitada: La incapacidad de hacer referencia al contexto pasado significa que el chatbot no puede construir sobre el conocimiento previamente establecido ni adaptar sus respuestas basándose en la comprensión demostrada por el usuario.

Al final de esta sección, sabrás cómo:

  • Almacenar y recuperar mensajes para una sesión dada
    • Implementar mecanismos de almacenamiento seguro usando encriptación y protección estándar de la industria
    • Manejar diferentes tipos de datos de mensajes efectivamente, incluyendo texto, datos estructurados y metadatos
  • Mantener y actualizar memoria de múltiples turnos
    • Procesar cadenas de conversación eficientemente usando estructuras de datos optimizadas
    • Gestionar el contexto a través de múltiples intercambios mientras se mantiene la coherencia de la conversación
  • Evitar el uso excesivo de tokens mediante limitación de la longitud del historial
    • Implementar estrategias inteligentes de gestión de memoria que prioricen la información relevante
    • Equilibrar la retención de contexto con el rendimiento a través de algoritmos inteligentes de poda

4.3.2 Streamlit: Memoria de Estado de Sesión

Streamlit hace que la gestión del historial de conversación sea super simple con st.session_state, que persiste los datos durante toda la sesión del navegador.

Aquí te mostramos cómo puedes crear un chatbot con memoria de sesión usando Streamlit:

Paso 1: Importar Bibliotecas

import streamlit as st
import openai
import os
from dotenv import load_dotenv
  • streamlit: Usado para crear la interfaz de usuario del chatbot.
  • openai: La biblioteca de Python de OpenAI, usada para interactuar con el modelo GPT-4o.
  • os: Proporciona una manera de interactuar con el sistema operativo, por ejemplo para acceder a variables de entorno.
  • dotenv: Usado para cargar variables de entorno desde un archivo .env.

Paso 2: Cargar la Clave API y Configurar la Página

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

st.set_page_config(page_title="GPT-4o Chat with Memory", page_icon="🧠")
st.title("🧠 GPT-4o Chatbot with Session Memory")
  • load_dotenv(): Carga la clave API de OpenAI desde un archivo .env. Este archivo debe estar en el mismo directorio que tu script de Python y contener la línea OPENAI_API_KEY=TU_CLAVE_API.
  • openai.api_key = os.getenv("OPENAI_API_KEY"): Establece la clave API de OpenAI.
  • st.set_page_config(...): Configura el título y el ícono de la página que aparecen en la pestaña del navegador.
  • st.title(...): Establece el título de la aplicación Streamlit, que se muestra en la parte superior de la página.

Paso 3: Inicializar el Estado de la Sesión

if "messages" not in st.session_state:
    st.session_state.messages = [{"role": "system", "content": "You are a helpful assistant that remembers this session."}]
  • st.session_state: La forma en que Streamlit almacena variables entre interacciones del usuario. Los datos en st.session_state persisten mientras la pestaña del navegador permanezca abierta.
  • Este código verifica si la clave "messages" existe en st.session_state. Si no existe (lo cual ocurre cuando el usuario carga la aplicación por primera vez), inicializa "messages" como una lista que contiene un único diccionario.
  • Este diccionario representa el "mensaje del sistema", que se utiliza para establecer el comportamiento del asistente. En este caso, el mensaje del sistema le indica al asistente que sea útil y recuerde la conversación.

Paso 4: Mostrar el Historial de Chat

for msg in st.session_state.messages[1:]:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])
  • Este bucle for itera a través de los mensajes en la lista "messages", comenzando desde el segundo mensaje (índice 1) para omitir el mensaje del sistema.
  • st.chat_message(msg["role"]): Crea una burbuja de chat en la aplicación Streamlit para mostrar el mensaje. El role ("user" o "assistant") determina la apariencia de la burbuja.
  • st.markdown(msg["content"]): Muestra el contenido del mensaje dentro de la burbuja de chat. Se utiliza st.markdown para renderizar el texto.

Paso 5: Entrada del Usuario y Generación de Respuesta

user_input = st.chat_input("Say something...")
if user_input:
    st.chat_message("user").markdown(user_input)
    st.session_state.messages.append({"role": "user", "content": user_input})

    with st.chat_message("assistant"):
        with st.spinner("Thinking..."):
            response = openai.ChatCompletion.create(
                model="gpt-4o",
                messages=st.session_state.messages,
                temperature=0.6
            )
            reply = response["choices"][0]["message"]["content"]
            st.markdown(reply)
            st.session_state.messages.append({"role": "assistant", "content": reply})
  • user_input = st.chat_input("Di algo..."): Crea un campo de entrada de texto en la parte inferior de la aplicación donde el usuario puede escribir su mensaje. La etiqueta "Di algo..." se muestra junto al campo de entrada.
  • El bloque if user_input: se ejecuta cuando el usuario ingresa texto y presiona Enter.
  • st.chat_message("user").markdown(user_input): Muestra el mensaje del usuario en la interfaz de chat.
  • st.session_state.messages.append({"role": "user", "content": user_input}): Agrega el mensaje del usuario a la lista "messages" en st.session_state, para que se almacene en el historial de conversación.
  • with st.chat_message("assistant"):: Crea una burbuja de chat para la respuesta del asistente.
  • with st.spinner("Pensando..."): Muestra una animación de carga mientras la aplicación espera una respuesta de la API de OpenAI.
  • response = openai.ChatCompletion.create(...): Llama a la API de OpenAI para obtener una respuesta del modelo GPT-4o.
    • model: Especifica el modelo de lenguaje a usar ("gpt-4o").
    • messages: Pasa todo el historial de conversación (almacenado en st.session_state.messages) a la API. Así es como el modelo "recuerda" la conversación.
    • temperature: Un valor entre 0 y 1 que controla la aleatoriedad de la salida del modelo. Un valor más bajo (por ejemplo, 0.2) hace que la salida sea más determinista, mientras que un valor más alto (por ejemplo, 0.8) la hace más aleatoria.
  • reply = response["choices"][0]["message"]["content"]: Extrae la respuesta del asistente de la respuesta de la API.
  • st.markdown(reply): Muestra la respuesta del asistente en la interfaz de chat.
  • st.session_state.messages.append({"role": "assistant", "content": reply}): Agrega la respuesta del asistente a la lista "messages" en st.session_state.

Este ejemplo crea un chatbot simple con memoria usando Streamlit. La lista st.session_state.messages almacena el historial de conversación, permitiendo que el chatbot mantenga el contexto a través de múltiples interacciones. El historial del chat se muestra en la aplicación, y el usuario puede ingresar mensajes usando el campo st.chat_input. Las respuestas del asistente son generadas por el modelo OpenAI GPT-4o.

pip install flask openai flask-session python-dotenv
  • flask: Un framework web.
  • openai: La biblioteca de Python de OpenAI.
  • flask-session: Extensión de Flask para manejar sesiones del lado del servidor.
  • python-dotenv: Para cargar variables de entorno desde un archivo .env.

Paso 2: Actualizar app.py

from flask import Flask, request, render_template, session
import openai
import os
from dotenv import load_dotenv
from flask_session import Session

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

app = Flask(__name__)
app.secret_key = os.urandom(24)
app.config["SESSION_TYPE"] = "filesystem"
Session(app)

@app.route("/", methods=["GET", "POST"])
def chat():
    if "history" not in session:
        session["history"] = [
            {"role": "system", "content": "You are a helpful assistant."}
        ]

    if request.method == "POST":
        user_input = request.form["user_input"]
        session["history"].append({"role": "user", "content": user_input})

        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=session["history"],
            temperature=0.6,
        )

        assistant_reply = response["choices"][0]["message"]["content"]
        session["history"].append({"role": "assistant", "content": assistant_reply})

        return render_template("chat.html", history=session.get("history")[1:])  # Skip system prompt

    return render_template("chat.html", history=session.get("history")[1:])  # Skip system prompt
  • from flask import ...: Importa los componentes necesarios de Flask, incluyendo session para gestionar las sesiones de usuario.
  • import openai: Importa la biblioteca OpenAI.
  • import os: Importa el módulo os para interactuar con el sistema operativo, particularmente para acceder a variables de entorno.
  • from dotenv import load_dotenv: Importa la función load_dotenv de la biblioteca python-dotenv.
  • from flask_session import Session: Importa la clase Session de flask_session.
  • load_dotenv(): Carga las variables de entorno (como la clave API de OpenAI) desde un archivo .env.
  • openai.api_key = os.getenv("OPENAI_API_KEY"): Obtiene la clave API de OpenAI del entorno y la configura para la biblioteca OpenAI.
  • app = Flask(__name__): Crea una instancia de la aplicación Flask.
  • app.secret_key = os.urandom(24): Establece una clave secreta para la aplicación Flask. Esto es esencial para usar sesiones de Flask. os.urandom(24) genera una clave aleatoria y criptográficamente segura.
  • app.config["SESSION_TYPE"] = "filesystem": Configura Flask-Session para almacenar los datos de sesión en el sistema de archivos del servidor. Otras opciones como "redis" o "mongodb" están disponibles para uso en producción.
  • Session(app): Inicializa la extensión Flask-Session, vinculándola a la aplicación Flask.
  • @app.route("/", methods=["GET", "POST"]): Define la ruta para la página principal de la aplicación ("/"). La función chat() maneja tanto solicitudes GET como POST.
  • def chat()::
    • if "history" not in session:: Verifica si la sesión del usuario ya tiene un historial de conversación. Si no, inicializa la sesión con un mensaje del sistema. El mensaje del sistema ayuda a establecer el comportamiento del asistente.
    • if request.method == "POST":: Maneja las solicitudes POST, que ocurren cuando el usuario envía un mensaje a través del formulario de chat.
      • user_input = request.form["user_input"]: Obtiene la entrada del usuario del formulario.
      • session["history"].append({"role": "user", "content": user_input}): Añade el mensaje del usuario al historial de conversación almacenado en la sesión.
      • response = openai.ChatCompletion.create(...): Llama a la API de OpenAI para obtener una respuesta del modelo GPT-4o, pasando el historial de conversación.
      • assistant_reply = response["choices"][0]["message"]["content"]: Extrae la respuesta del asistente de la respuesta de la API.
      • session["history"].append({"role": "assistant", "content": assistant_reply}): Añade la respuesta del asistente al historial de conversación en la sesión.
      • return render_template("chat.html", history=session.get("history")[1:]): Renderiza la plantilla chat.html, pasando el historial de conversación (excluyendo el mensaje inicial del sistema) para ser mostrado.
    • return render_template("chat.html", history=session.get("history")[1:]): Maneja las solicitudes GET (cuando el usuario carga la página por primera vez). Renderiza la plantilla chat.html, pasando el historial de conversación (excluyendo el mensaje del sistema).

Paso 3: Tu Plantilla HTML (templates/chat.html)

<!DOCTYPE html>
<html>
<head>
  <title>GPT-4o Assistant</title>
  <style>
    body { font-family: Arial; background: #f7f7f7; padding: 40px; }
    .container { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 10px; }
    .user, .assistant { margin-bottom: 15px; }
    .user p { background: #d4f0ff; padding: 10px; border-radius: 10px; }
    .assistant p { background: #e8ffe8; padding: 10px; border-radius: 10px; }
    textarea { width: 100%; height: 80px; }
    input[type="submit"] { margin-top: 10px; padding: 10px 20px; }
  </style>
</head>
<body>
  <div class="container">
    <h2>GPT-4o Chatbot</h2>
    {% for msg in history %}
      <div class="{{ msg.role }}">
        <p><strong>{{ msg.role.capitalize() }}:</strong> {{ msg.content }}</p>
      </div>
    {% endfor %}
    <form method="post">
      <textarea name="user_input" placeholder="Type your message..."></textarea><br>
      <input type="submit" value="Send">
    </form>
  </div>
</body>
</html>
  • <!DOCTYPE html>: Declara el tipo de documento como HTML5.
  • <html>: El elemento raíz del documento HTML.
  • <head>: Contiene los metadatos del documento HTML.
    • <title>: Especifica el título de la página HTML.
    • <style>: Incluye CSS para el estilo básico de la interfaz del chat.
  • <body>: Contiene el contenido visible de la página HTML.
    • <div class="container">: Un contenedor para la aplicación de chat.
    • <h2>: Un encabezado para la aplicación de chat.
    • {% for msg in history %}: Un bucle de plantilla Jinja2 que itera a través de la variable history (pasada desde el código de Flask) para mostrar los mensajes del chat.
      • <div class="{{ msg.role }}">: Crea un elemento div para cada mensaje. La clase se asigna según el rol del mensaje ("user" o "assistant") para fines de estilo.
      • <p>: Muestra el contenido del mensaje.
      • <strong>: Muestra el rol.
    • <form method="post">: Un formulario para que el usuario envíe sus mensajes.
      • <textarea>: Un campo de entrada de texto multilínea para que el usuario escriba su mensaje.
      • <input type="submit" value="Send">: Un botón para enviar el mensaje.
  • templates: Flask, por defecto, busca las plantillas HTML en una carpeta llamada "templates" en el mismo directorio que tu archivo app.py. Por lo tanto, este archivo debe guardarse como templates/chat.html.

Este código crea un chatbot usando Flask y OpenAI, con el historial de la conversación almacenado en sesiones del lado del servidor. El servidor conserva la memoria del chat durante la sesión del usuario, eliminándola cuando el usuario cierra la pestaña del navegador.

4.3.4 Opcional: Limitar el Historial de Mensajes

Los modelos de lenguaje grandes tienen una ventana de contexto limitada (por ejemplo, 128k tokens en GPT-4o), lo que determina cuánta conversación el modelo puede "recordar" al mismo tiempo. Para evitar superar este límite y generar errores, deberías limitar la cantidad de mensajes almacenados en el historial de la conversación.

Aquí te mostramos cómo recortar las entradas más antiguas tanto en Streamlit como en Flask:

Streamlit

import streamlit as st

MAX_HISTORY = 20  # Maximum number of messages to keep

if SESSION_MESSAGES_KEY in st.session_state:
    if len(st.session_state[SESSION_MESSAGES_KEY]) > MAX_HISTORY:
        # Keep the system message and the last MAX_HISTORY messages
        st.session_state[SESSION_MESSAGES_KEY] = [st.session_state[SESSION_MESSAGES_KEY][0]] + st.session_state[SESSION_MESSAGES_KEY][-MAX_HISTORY + 1:]

Explicación:

  • MAX_HISTORY: Una constante que define la cantidad máxima de mensajes a conservar. Este ejemplo mantiene los últimos 20 mensajes.
  • if SESSION_MESSAGES_KEY in st.session_state: Verifica si la clave existe
  • El código luego recorta la lista st.session_state.messages, conservando el primer mensaje (el mensaje del sistema) y los últimos MAX_HISTORY - 1 mensajes.

Flask

from flask import session

MAX_HISTORY = 20  # Maximum number of messages to keep

if SESSION_MESSAGES_KEY in session:
    if len(session[SESSION_MESSAGES_KEY]) > MAX_HISTORY:
        # Keep the system message and the last MAX_HISTORY messages
        session[SESSION_MESSAGES_KEY] = [session[SESSION_MESSAGES_KEY][0]] + session[SESSION_MESSAGES_KEY][-MAX_HISTORY + 1:]

Explicación:

  • MAX_HISTORY: Una constante que define el número máximo de mensajes a conservar.
  • if SESSION_MESSAGES_KEY in session: Verifica si la clave existe
  • El código recorta la lista session["history"], manteniendo el primer mensaje (el mensaje del sistema) y los últimos MAX_HISTORY - 1 mensajes.

Consideraciones Importantes de Implementación:

  • Gestión del Mensaje del Sistema: El mensaje del sistema juega un papel crucial en establecer el comportamiento y contexto del chatbot. Siempre debe preservarse como el primer mensaje en tu historial de conversación. Al implementar el recorte de mensajes, asegúrate de que tu código específicamente mantenga este mensaje mediante:
    • Mantenerlo separado del flujo regular de conversación
    • Incluir un manejo especial en tu lógica de recorte
    • Verificar su presencia antes de cada interacción
  • Protocolo Integral de Pruebas: Para asegurar un rendimiento confiable del chatbot:
    • Probar con diferentes longitudes de conversación, desde intercambios cortos hasta diálogos extensos
    • Verificar que el contexto se mantenga incluso después del recorte
    • Comprobar casos límite donde la coherencia podría romperse
    • Monitorear el uso de recursos del sistema durante conversaciones prolongadas
  • Estrategias Avanzadas de Recorte: Considera estos enfoques sofisticados:
    • Recorte basado en tokens: Calcular el uso real de tokens usando un tokenizador
    • Recorte basado en importancia: Mantener mensajes según su relevancia
    • Enfoque híbrido: Combinar conteo de tokens con relevancia de mensajes
    • Ajuste dinámico: Modificar el umbral de recorte según la complejidad de la conversación

4.3.5 Consejo Adicional: Guardar en Archivo o Base de Datos

¿Quieres mantener la memoria incluso después de que termine la sesión? Exploremos varios métodos efectivos para el almacenamiento de memoria a largo plazo:

1. Exportar historial de conversación a un archivo JSON

Proceso: Los datos de st.session_state.messages (en Streamlit) o session["history"] (en Flask) pueden guardarse en un archivo JSON. Esto implica convertir la lista de diccionarios de mensajes en una cadena JSON y escribirla en un archivo.

Ejemplo de Código (Streamlit):

import json
import streamlit as st

def save_chat_log(filename="chat_log.json"):
    """Saves the chat log from st.session_state to a JSON file."""
    if SESSION_MESSAGES_KEY in st.session_state:
        try:
            with open(filename, "w") as f:
                json.dump(st.session_state[SESSION_MESSAGES_KEY], f, indent=2)
            print(f"Chat log saved to {filename}")
        except Exception as e:
            st.error(f"Error saving chat log: {e}")

# Example usage:  Call this function when the user ends the session or when appropriate
if st.button("Save Chat Log"):
    save_chat_log()

Ejemplo de Código (Flask):

import json
from flask import session, Flask

def save_chat_log(filename="chat_log.json"):
    """Saves the chat log from the Flask session to a JSON file."""
    if SESSION_MESSAGES_KEY in session:
        try:
            with open(filename, "w") as f:
                json.dump(session[SESSION_MESSAGES_KEY], f, indent=2)
            print(f"Chat log saved to {filename}")
        except Exception as e:
            #  Use the app context to display a message
            with app.app_context():
                print(f"Error saving chat log: {e}")

# Example Usage
app = Flask(__name__)
@app.route('/save_log')
def save_log():
    save_chat_log()
    return "Chat log saved!"

Explicación:

  • La función json.dump() se utiliza para serializar la lista de mensajes a una cadena con formato JSON. El parámetro indent=2 hace que el archivo JSON sea más legible para humanos.
  • El código maneja posibles errores durante la escritura del archivo.
  • El ejemplo de Streamlit utiliza un botón para activar el guardado, y el ejemplo de Flask crea una ruta /save_log para guardar el archivo.

Ventajas:

  • Fácil de implementar usando el módulo json incorporado en Python.
  • Adecuado para aplicaciones pequeñas y prototipos rápidos.
  • Fácil de respaldar y controlar versiones.

Desventajas:

  • No es ideal para aplicaciones a gran escala con múltiples usuarios.
  • Sin capacidad eficiente de consulta o indexación.

2. Almacenar conversaciones en una base de datos SQLite, PostgreSQL o NoSQL

Proceso: Almacenar el historial de conversación en una base de datos. Cada mensaje puede ser una fila en una tabla (para bases de datos SQL) o un documento en una colección (para bases de datos NoSQL).

Ejemplo de Código (SQLite - Streamlit):

import streamlit as st
import sqlite3
import datetime

def get_connection():
    """Gets or creates a SQLite connection."""
    conn = getattr(st.session_state, "sqlite_conn", None)
    if conn is None:
        conn = sqlite3.connect("chat_log.db")
        st.session_state.sqlite_conn = conn
    return conn

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS messages (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            role TEXT NOT NULL,
            content TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    conn.commit()

def store_message(role, content):
    """Stores a message in the database."""
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute(
        "INSERT INTO messages (role, content) VALUES (?, ?)",
        (role, content),
    )
    conn.commit()

create_table()  # Ensure table exists

# Store messages
if st.session_state.user_input:
    store_message("user", st.session_state.user_input)
if st.session_state.get("reply"):  # replace reply with a key you are using
    store_message("assistant", st.session_state.reply)

# Example of retrieving messages (optional, for demonstration)
conn = get_connection()
cursor = conn.cursor()
cursor.execute("SELECT role, content, created_at FROM messages ORDER BY created_at DESC LIMIT 5")
recent_messages = cursor.fetchall()
st.write("Last 5 Messages from DB:")
for row in recent_messages:
    st.write(f"{row[0]}: {row[1]} (at {row[2]})")

Ejemplo de Código (PostgreSQL - Flask):

from flask import Flask, request, render_template, session
import openai
import os
from dotenv import load_dotenv
import psycopg2  # PostgreSQL library
import datetime
from typing import List, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL") #Make sure to set this in .env

app = Flask(__name__)
app.secret_key = os.urandom(24)

def get_db_connection():
    """Gets a PostgreSQL connection."""
    try:
        conn = psycopg2.connect(DATABASE_URL)
        return conn
    except psycopg2.Error as e:
        print(f"Database connection error: {e}")
        return None

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id SERIAL PRIMARY KEY,
                    session_id TEXT NOT NULL,
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            """)
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error creating table: {e}")
        finally:
            conn.close()

def store_message(session_id: str, role: str, content: str):
    """Stores a message in the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
                (session_id, role, content),
            )
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error storing message: {e}")
        finally:
            conn.close()

create_table()  # Ensure the table exists

@app.route("/", methods=["GET", "POST"])
def chat():
    if "session_id" not in session:
        session["session_id"] = os.urandom(16).hex()  # Unique session ID

    if "history" not in session:
        session["history"] = [{"role": "system", "content": "You are a helpful assistant."}]

    if request.method == "POST":
        user_input = request.form["user_input"]
        session_id = session["session_id"]
        store_message(session_id, "user", user_input)  # Store in DB
        session["history"].append({"role": "user", "content": user_input})

        conn = get_db_connection() #
        if conn: #
            try: #
                cursor = conn.cursor() #
                cursor.execute("SELECT role, content, created_at FROM messages WHERE session_id = %s ORDER BY created_at", (session_id,)) #
                messages_from_db = cursor.fetchall() #
            except psycopg2.Error as e: #
                print(f"Error fetching messages: {e}") #
            finally: #
                conn.close() #

        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=session["history"],
            temperature=0.6,
        )

        assistant_reply = response["choices"][0]["message"]["content"]
        store_message(session_id, "assistant", assistant_reply)  # Store in DB
        session["history"].append({"role": "assistant", "content": assistant_reply})
        session.modified = True

        return render_template("chat.html", history=session.get("history")[1:])
    return render_template("chat.html", history=session.get("history")[1:])

Explicación:

  • El ejemplo de Streamlit usa SQLite, una base de datos ligera que no requiere un servidor separado. El ejemplo de Flask usa PostgreSQL, una base de datos más robusta que es adecuada para aplicaciones multiusuario.
  • Ambos ejemplos crean una tabla llamada "messages" para almacenar el historial de conversación. La tabla incluye columnas para ID del mensaje, rol, contenido y marca de tiempo.
  • La función store_message() inserta un nuevo mensaje en la base de datos.
  • El ejemplo de Flask recupera mensajes de la base de datos y los pasa a la plantilla para su visualización. El ejemplo de Streamlit también muestra cómo recuperar datos.

Ventajas:

  • SQLite: Perfecto para aplicaciones de un solo usuario con datos estructurados. No se necesita un servidor de base de datos separado.
  • PostgreSQL: Ideal para sistemas multiusuario que requieren acceso concurrente. Más robusto y escalable que SQLite.
  • NoSQL (No mostrado en detalle): Óptimo para esquemas flexibles y datos de conversación no estructurados. Bases de datos como MongoDB o CouchDB serían adecuadas.
  • Desventajas:
    • Requiere más configuración que usar un archivo JSON.
    • Necesidad de gestionar conexiones y esquemas de base de datos.

3. Reutilizar memoria en sesiones futuras mediante etiquetado con ID de usuario

Proceso: Para permitir que los usuarios tengan conversaciones persistentes y personalizadas, puedes etiquetar cada mensaje con un ID de usuario y almacenar esta información en la base de datos. Cuando un usuario regresa, puedes recuperar su historial específico de conversación de la base de datos.

Ejemplo de Código (PostgreSQL - Flask):

from flask import Flask, request, render_template, session, redirect, url_for
import openai
import os
from dotenv import load_dotenv
import psycopg2
import datetime
from typing import List, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")

app = Flask(__name__)
app.secret_key = os.urandom(24)

def get_db_connection():
    """Gets a PostgreSQL connection."""
    try:
        conn = psycopg2.connect(DATABASE_URL)
        return conn
    except psycopg2.Error as e:
        print(f"Database connection error: {e}")
        return None

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id SERIAL PRIMARY KEY,
                    user_id TEXT NOT NULL,  -- Added user_id
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            """)
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error creating table: {e}")
        finally:
            conn.close()

def store_message(user_id: str, role: str, content: str):
    """Stores a message in the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO messages (user_id, role, content) VALUES (%s, %s, %s)",
                (user_id, role, content),
            )
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error storing message: {e}")
        finally:
            conn.close()

create_table()

def get_user_history(user_id: str) -> List[Dict[str, str]]:
    """Retrieves a user's conversation history from the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT role, content FROM messages WHERE user_id = %s ORDER BY created_at",
                (user_id,),
            )
            history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
            return history
        except psycopg2.Error as e:
            print(f"Error retrieving user history: {e}")
            return []
        finally:
            conn.close()
    return []

@app.route("/", methods=["GET", "POST"])
def chat():
    if "user_id" not in session:
        session["user_id"] = os.urandom(16).hex()  # Unique user ID
    user_id = session["user_id"]

    history = get_user_history(user_id)  # Get user's history

    if request.method == "POST":
        user_input = request.form["user_input"]
        store_message(user_id, "user", user_input)  # Store with user ID
        history.append({"role": "user", "content": user_input})

        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=history,
            temperature=0.6,
        )

        assistant_reply = response["choices"][0]["message"]["content"]
        store_message(user_id, "assistant", assistant_reply)  # Store with user ID
        history.append({"role": "assistant", "content": assistant_reply})
        session["history"] = history #update

        return render_template("chat.html", history=history[1:])

    return render_template("chat.html", history=history[1:])

@app.route("/clear", methods=["POST"])
def clear_chat():
    session.pop("user_id", None)  #remove user id
    session.pop("history", None)
    return redirect(url_for("chat"))

Explicación:

  • La tabla de base de datos "messages" ahora incluye una columna "user_id".
  • Cuando un usuario inicia una sesión, se genera un "user_id" único y se almacena en la session de Flask.
  • La función store_message() ahora requiere un "user_id" y lo almacena junto con el mensaje.
  • La función get_user_history() recupera el historial de conversación para un usuario específico de la base de datos.
  • La ruta de chat recupera el historial del usuario y lo utiliza para construir los mensajes enviados a OpenAI, manteniendo así el historial de conversación a través de múltiples visitas del mismo usuario.
  • Ventajas:
    • Permite un historial de conversación personalizado para cada usuario.
    • Permite un contexto y preferencias específicos para cada usuario.
    • Facilita el análisis de patrones de conversación a lo largo del tiempo.
  • Desventajas:
    • Requiere una base de datos.
    • Más complejo de implementar que el almacenamiento simple basado en sesiones.

En esta sección, aprendiste varios aspectos cruciales para construir un chatbot con capacidades de memoria:

  • Agregaste memoria basada en sesiones a tu chatbot usando st.session_state (Streamlit) y flask.session (Flask)
    • Implementaste almacenamiento temporal para conversaciones en curso
    • Aprendiste a gestionar variables de sesión de manera efectiva
  • Preservaste el historial de chat a través de interacciones
    • Creaste esquemas de base de datos para almacenar datos de conversación
    • Implementaste métodos para guardar y recuperar mensajes anteriores
  • Mejoraste el contexto y la coherencia para conversaciones de múltiples turnos
    • Desarrollaste sistemas para mantener el contexto de la conversación
    • Mejoraste la comprensión del lenguaje natural a través del contexto histórico
  • Aprendiste a limitar el uso de tokens mediante el recorte del historial de mensajes
    • Implementaste estrategias eficientes de poda de mensajes
    • Equilibraste la retención de memoria con las limitaciones de tokens de la API

Esto le da a tu chatbot la capacidad de mantener conversaciones naturales y fluidas — un hito clave hacia la construcción de un asistente inteligente. Con estas características, tu chatbot ahora puede recordar interacciones previas, mantener el contexto durante las conversaciones y gestionar la memoria de manera eficiente mientras se mantiene dentro de las restricciones técnicas.

4.3 Implementación de Memoria de Chat Basada en Sesiones

La memoria basada en sesiones es una característica crucial que transforma un chatbot simple en un agente conversacional verdaderamente interactivo y consciente del contexto. Sin memoria, cada interacción se vuelve aislada y desconectada, forzando a los usuarios a proporcionar contexto repetidamente y limitando el flujo natural de la conversación. Esta sección explora cómo implementar una gestión robusta de memoria en tu chatbot, permitiéndole mantener discusiones coherentes y contextuales a través de múltiples intercambios.

Examinaremos dos enfoques distintos para implementar memoria de sesión - usando la gestión de estado incorporada de Streamlit y las sesiones del lado del servidor de Flask. Ambos métodos ofrecen sus propias ventajas y pueden adaptarse para satisfacer requisitos específicos del proyecto. Al final de esta sección, comprenderás cómo crear un chatbot que puede mantener el contexto, recordar interacciones previas y proporcionar respuestas más significativas y conectadas.

La implementación que cubriremos asegura que tu chatbot pueda:

  • Mantener la consciencia contextual durante conversaciones completas
  • Manejar diálogos complejos de múltiples turnos de manera efectiva
  • Proporcionar respuestas más relevantes y personalizadas basadas en el historial de conversación
  • Gestionar la memoria eficientemente sin exceder los límites de tokens
✅ Dotar a tu asistente de memoria — para que no olvide el flujo de la conversación cada vez que la página se actualiza o la sesión se reinicia.

4.3.1 ¿Por qué es importante la memoria de sesión?

La memoria de sesión es un aspecto fundamental para crear chatbots inteligentes que puedan participar en conversaciones significativas y conscientes del contexto. Al igual que los humanos dependen de la memoria durante las discusiones, los chatbots necesitan una manera de recordar y procesar las interacciones previas. Cuando hablamos con otros, naturalmente hacemos referencia a puntos anteriores, construimos sobre el entendimiento compartido y mantenemos un flujo coherente de ideas. Este patrón natural de comunicación es lo que hace que las conversaciones se sientan orgánicas y significativas. Sin capacidades de memoria, los chatbots esencialmente comienzan de cero con cada respuesta, lo que lleva a interacciones desconectadas y a menudo frustrantes que se sienten más como hablar con una máquina que tener una conversación real.

El concepto de memoria de sesión transforma fundamentalmente las interacciones del chatbot de varias maneras críticas:

  • Recordar y hacer referencia a partes previas de la conversación con precisión
    • Hacer seguimiento de detalles específicos mencionados anteriormente en el chat, como preferencias del usuario, especificaciones técnicas o información personal compartida
    • Hacer referencia a acuerdos o decisiones pasadas con precisión, asegurando la continuidad en discusiones o negociaciones complejas
    • Mantener el contexto histórico para una mejor resolución de problemas y soporte
  • Construir contexto incrementalmente durante una interacción
    • Comprender temas complejos que se desarrollan a lo largo de múltiples mensajes, permitiendo una exploración más profunda de los temas
    • Desarrollar respuestas más sofisticadas a medida que avanza la conversación, construyendo sobre conceptos previamente establecidos
    • Crear un hilo narrativo coherente a través de múltiples intercambios
  • Proporcionar respuestas más matizadas y relevantes basadas en el historial de conversación
    • Adaptar las respuestas al nivel de conocimiento demostrado por el usuario, ajustando la terminología y complejidad según corresponda
    • Evitar repetir información ya discutida, haciendo las conversaciones más eficientes
    • Usar interacciones pasadas para proporcionar respuestas más personalizadas y contextualmente apropiadas
  • Crear una experiencia conversacional más natural y humana
    • Mantener una personalidad y tono consistentes durante todo el chat, mejorando la participación del usuario
    • Adaptar las respuestas basadas en las preferencias del usuario e interacciones pasadas, creando una experiencia más personalizada
    • Aprender de intercambios anteriores para mejorar la calidad de interacciones futuras

En conversaciones reales, el contexto se construye con el tiempo de múltiples formas sofisticadas que reflejan patrones naturales de diálogo humano:

  • El usuario se refiere a mensajes anteriores
    • Las preguntas naturalmente se construyen sobre respuestas previas, creando un hilo continuo de comprensión
    • Los temas evolucionan orgánicamente mientras los usuarios hacen referencia y expanden puntos anteriores en la conversación
    • El contexto previo moldea cómo se interpreta y entiende la nueva información
  • El asistente necesita recordar respuestas previas
    • Mantiene consistencia a través de las respuestas para construir confianza y fiabilidad
    • Utiliza el contexto establecido para proporcionar información más matizada y relevante
    • Construye una comprensión integral de las necesidades del usuario a lo largo del tiempo
  • Las preguntas de seguimiento dependen del conocimiento previo
    • Cada pregunta se construye sobre la base de intercambios anteriores
    • Los temas complejos pueden explorarse gradualmente, con profundidad creciente
    • La conversación progresa naturalmente desde conceptos básicos hacia una comprensión más avanzada
    • Crea cadenas de diálogo más significativas conectando ideas relacionadas
    • Permite un flujo de conversación natural que se siente más humano y atractivo

Sin implementación de memoria, los chatbots tratan cada interacción como un evento aislado, completamente desconectado de intercambios previos. Esta limitación fundamental afecta incluso a modelos sofisticados como GPT-4o de varias maneras críticas:

  1. Pérdida de Contexto: Cada respuesta se genera sin ninguna conciencia de conversaciones previas, haciendo imposible mantener discusiones coherentes y extendidas.
  2. Interacciones Repetitivas: El chatbot puede proporcionar la misma información múltiples veces o pedir detalles que ya fueron compartidos, creando una experiencia de usuario frustrante.
  3. Respuestas Inconsistentes: Sin acceso a intercambios previos, el chatbot podría dar respuestas contradictorias a preguntas relacionadas, socavando la confianza del usuario.
  4. Comprensión Limitada: La incapacidad de hacer referencia al contexto pasado significa que el chatbot no puede construir sobre el conocimiento previamente establecido ni adaptar sus respuestas basándose en la comprensión demostrada por el usuario.

Al final de esta sección, sabrás cómo:

  • Almacenar y recuperar mensajes para una sesión dada
    • Implementar mecanismos de almacenamiento seguro usando encriptación y protección estándar de la industria
    • Manejar diferentes tipos de datos de mensajes efectivamente, incluyendo texto, datos estructurados y metadatos
  • Mantener y actualizar memoria de múltiples turnos
    • Procesar cadenas de conversación eficientemente usando estructuras de datos optimizadas
    • Gestionar el contexto a través de múltiples intercambios mientras se mantiene la coherencia de la conversación
  • Evitar el uso excesivo de tokens mediante limitación de la longitud del historial
    • Implementar estrategias inteligentes de gestión de memoria que prioricen la información relevante
    • Equilibrar la retención de contexto con el rendimiento a través de algoritmos inteligentes de poda

4.3.2 Streamlit: Memoria de Estado de Sesión

Streamlit hace que la gestión del historial de conversación sea super simple con st.session_state, que persiste los datos durante toda la sesión del navegador.

Aquí te mostramos cómo puedes crear un chatbot con memoria de sesión usando Streamlit:

Paso 1: Importar Bibliotecas

import streamlit as st
import openai
import os
from dotenv import load_dotenv
  • streamlit: Usado para crear la interfaz de usuario del chatbot.
  • openai: La biblioteca de Python de OpenAI, usada para interactuar con el modelo GPT-4o.
  • os: Proporciona una manera de interactuar con el sistema operativo, por ejemplo para acceder a variables de entorno.
  • dotenv: Usado para cargar variables de entorno desde un archivo .env.

Paso 2: Cargar la Clave API y Configurar la Página

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

st.set_page_config(page_title="GPT-4o Chat with Memory", page_icon="🧠")
st.title("🧠 GPT-4o Chatbot with Session Memory")
  • load_dotenv(): Carga la clave API de OpenAI desde un archivo .env. Este archivo debe estar en el mismo directorio que tu script de Python y contener la línea OPENAI_API_KEY=TU_CLAVE_API.
  • openai.api_key = os.getenv("OPENAI_API_KEY"): Establece la clave API de OpenAI.
  • st.set_page_config(...): Configura el título y el ícono de la página que aparecen en la pestaña del navegador.
  • st.title(...): Establece el título de la aplicación Streamlit, que se muestra en la parte superior de la página.

Paso 3: Inicializar el Estado de la Sesión

if "messages" not in st.session_state:
    st.session_state.messages = [{"role": "system", "content": "You are a helpful assistant that remembers this session."}]
  • st.session_state: La forma en que Streamlit almacena variables entre interacciones del usuario. Los datos en st.session_state persisten mientras la pestaña del navegador permanezca abierta.
  • Este código verifica si la clave "messages" existe en st.session_state. Si no existe (lo cual ocurre cuando el usuario carga la aplicación por primera vez), inicializa "messages" como una lista que contiene un único diccionario.
  • Este diccionario representa el "mensaje del sistema", que se utiliza para establecer el comportamiento del asistente. En este caso, el mensaje del sistema le indica al asistente que sea útil y recuerde la conversación.

Paso 4: Mostrar el Historial de Chat

for msg in st.session_state.messages[1:]:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])
  • Este bucle for itera a través de los mensajes en la lista "messages", comenzando desde el segundo mensaje (índice 1) para omitir el mensaje del sistema.
  • st.chat_message(msg["role"]): Crea una burbuja de chat en la aplicación Streamlit para mostrar el mensaje. El role ("user" o "assistant") determina la apariencia de la burbuja.
  • st.markdown(msg["content"]): Muestra el contenido del mensaje dentro de la burbuja de chat. Se utiliza st.markdown para renderizar el texto.

Paso 5: Entrada del Usuario y Generación de Respuesta

user_input = st.chat_input("Say something...")
if user_input:
    st.chat_message("user").markdown(user_input)
    st.session_state.messages.append({"role": "user", "content": user_input})

    with st.chat_message("assistant"):
        with st.spinner("Thinking..."):
            response = openai.ChatCompletion.create(
                model="gpt-4o",
                messages=st.session_state.messages,
                temperature=0.6
            )
            reply = response["choices"][0]["message"]["content"]
            st.markdown(reply)
            st.session_state.messages.append({"role": "assistant", "content": reply})
  • user_input = st.chat_input("Di algo..."): Crea un campo de entrada de texto en la parte inferior de la aplicación donde el usuario puede escribir su mensaje. La etiqueta "Di algo..." se muestra junto al campo de entrada.
  • El bloque if user_input: se ejecuta cuando el usuario ingresa texto y presiona Enter.
  • st.chat_message("user").markdown(user_input): Muestra el mensaje del usuario en la interfaz de chat.
  • st.session_state.messages.append({"role": "user", "content": user_input}): Agrega el mensaje del usuario a la lista "messages" en st.session_state, para que se almacene en el historial de conversación.
  • with st.chat_message("assistant"):: Crea una burbuja de chat para la respuesta del asistente.
  • with st.spinner("Pensando..."): Muestra una animación de carga mientras la aplicación espera una respuesta de la API de OpenAI.
  • response = openai.ChatCompletion.create(...): Llama a la API de OpenAI para obtener una respuesta del modelo GPT-4o.
    • model: Especifica el modelo de lenguaje a usar ("gpt-4o").
    • messages: Pasa todo el historial de conversación (almacenado en st.session_state.messages) a la API. Así es como el modelo "recuerda" la conversación.
    • temperature: Un valor entre 0 y 1 que controla la aleatoriedad de la salida del modelo. Un valor más bajo (por ejemplo, 0.2) hace que la salida sea más determinista, mientras que un valor más alto (por ejemplo, 0.8) la hace más aleatoria.
  • reply = response["choices"][0]["message"]["content"]: Extrae la respuesta del asistente de la respuesta de la API.
  • st.markdown(reply): Muestra la respuesta del asistente en la interfaz de chat.
  • st.session_state.messages.append({"role": "assistant", "content": reply}): Agrega la respuesta del asistente a la lista "messages" en st.session_state.

Este ejemplo crea un chatbot simple con memoria usando Streamlit. La lista st.session_state.messages almacena el historial de conversación, permitiendo que el chatbot mantenga el contexto a través de múltiples interacciones. El historial del chat se muestra en la aplicación, y el usuario puede ingresar mensajes usando el campo st.chat_input. Las respuestas del asistente son generadas por el modelo OpenAI GPT-4o.

pip install flask openai flask-session python-dotenv
  • flask: Un framework web.
  • openai: La biblioteca de Python de OpenAI.
  • flask-session: Extensión de Flask para manejar sesiones del lado del servidor.
  • python-dotenv: Para cargar variables de entorno desde un archivo .env.

Paso 2: Actualizar app.py

from flask import Flask, request, render_template, session
import openai
import os
from dotenv import load_dotenv
from flask_session import Session

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

app = Flask(__name__)
app.secret_key = os.urandom(24)
app.config["SESSION_TYPE"] = "filesystem"
Session(app)

@app.route("/", methods=["GET", "POST"])
def chat():
    if "history" not in session:
        session["history"] = [
            {"role": "system", "content": "You are a helpful assistant."}
        ]

    if request.method == "POST":
        user_input = request.form["user_input"]
        session["history"].append({"role": "user", "content": user_input})

        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=session["history"],
            temperature=0.6,
        )

        assistant_reply = response["choices"][0]["message"]["content"]
        session["history"].append({"role": "assistant", "content": assistant_reply})

        return render_template("chat.html", history=session.get("history")[1:])  # Skip system prompt

    return render_template("chat.html", history=session.get("history")[1:])  # Skip system prompt
  • from flask import ...: Importa los componentes necesarios de Flask, incluyendo session para gestionar las sesiones de usuario.
  • import openai: Importa la biblioteca OpenAI.
  • import os: Importa el módulo os para interactuar con el sistema operativo, particularmente para acceder a variables de entorno.
  • from dotenv import load_dotenv: Importa la función load_dotenv de la biblioteca python-dotenv.
  • from flask_session import Session: Importa la clase Session de flask_session.
  • load_dotenv(): Carga las variables de entorno (como la clave API de OpenAI) desde un archivo .env.
  • openai.api_key = os.getenv("OPENAI_API_KEY"): Obtiene la clave API de OpenAI del entorno y la configura para la biblioteca OpenAI.
  • app = Flask(__name__): Crea una instancia de la aplicación Flask.
  • app.secret_key = os.urandom(24): Establece una clave secreta para la aplicación Flask. Esto es esencial para usar sesiones de Flask. os.urandom(24) genera una clave aleatoria y criptográficamente segura.
  • app.config["SESSION_TYPE"] = "filesystem": Configura Flask-Session para almacenar los datos de sesión en el sistema de archivos del servidor. Otras opciones como "redis" o "mongodb" están disponibles para uso en producción.
  • Session(app): Inicializa la extensión Flask-Session, vinculándola a la aplicación Flask.
  • @app.route("/", methods=["GET", "POST"]): Define la ruta para la página principal de la aplicación ("/"). La función chat() maneja tanto solicitudes GET como POST.
  • def chat()::
    • if "history" not in session:: Verifica si la sesión del usuario ya tiene un historial de conversación. Si no, inicializa la sesión con un mensaje del sistema. El mensaje del sistema ayuda a establecer el comportamiento del asistente.
    • if request.method == "POST":: Maneja las solicitudes POST, que ocurren cuando el usuario envía un mensaje a través del formulario de chat.
      • user_input = request.form["user_input"]: Obtiene la entrada del usuario del formulario.
      • session["history"].append({"role": "user", "content": user_input}): Añade el mensaje del usuario al historial de conversación almacenado en la sesión.
      • response = openai.ChatCompletion.create(...): Llama a la API de OpenAI para obtener una respuesta del modelo GPT-4o, pasando el historial de conversación.
      • assistant_reply = response["choices"][0]["message"]["content"]: Extrae la respuesta del asistente de la respuesta de la API.
      • session["history"].append({"role": "assistant", "content": assistant_reply}): Añade la respuesta del asistente al historial de conversación en la sesión.
      • return render_template("chat.html", history=session.get("history")[1:]): Renderiza la plantilla chat.html, pasando el historial de conversación (excluyendo el mensaje inicial del sistema) para ser mostrado.
    • return render_template("chat.html", history=session.get("history")[1:]): Maneja las solicitudes GET (cuando el usuario carga la página por primera vez). Renderiza la plantilla chat.html, pasando el historial de conversación (excluyendo el mensaje del sistema).

Paso 3: Tu Plantilla HTML (templates/chat.html)

<!DOCTYPE html>
<html>
<head>
  <title>GPT-4o Assistant</title>
  <style>
    body { font-family: Arial; background: #f7f7f7; padding: 40px; }
    .container { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 10px; }
    .user, .assistant { margin-bottom: 15px; }
    .user p { background: #d4f0ff; padding: 10px; border-radius: 10px; }
    .assistant p { background: #e8ffe8; padding: 10px; border-radius: 10px; }
    textarea { width: 100%; height: 80px; }
    input[type="submit"] { margin-top: 10px; padding: 10px 20px; }
  </style>
</head>
<body>
  <div class="container">
    <h2>GPT-4o Chatbot</h2>
    {% for msg in history %}
      <div class="{{ msg.role }}">
        <p><strong>{{ msg.role.capitalize() }}:</strong> {{ msg.content }}</p>
      </div>
    {% endfor %}
    <form method="post">
      <textarea name="user_input" placeholder="Type your message..."></textarea><br>
      <input type="submit" value="Send">
    </form>
  </div>
</body>
</html>
  • <!DOCTYPE html>: Declara el tipo de documento como HTML5.
  • <html>: El elemento raíz del documento HTML.
  • <head>: Contiene los metadatos del documento HTML.
    • <title>: Especifica el título de la página HTML.
    • <style>: Incluye CSS para el estilo básico de la interfaz del chat.
  • <body>: Contiene el contenido visible de la página HTML.
    • <div class="container">: Un contenedor para la aplicación de chat.
    • <h2>: Un encabezado para la aplicación de chat.
    • {% for msg in history %}: Un bucle de plantilla Jinja2 que itera a través de la variable history (pasada desde el código de Flask) para mostrar los mensajes del chat.
      • <div class="{{ msg.role }}">: Crea un elemento div para cada mensaje. La clase se asigna según el rol del mensaje ("user" o "assistant") para fines de estilo.
      • <p>: Muestra el contenido del mensaje.
      • <strong>: Muestra el rol.
    • <form method="post">: Un formulario para que el usuario envíe sus mensajes.
      • <textarea>: Un campo de entrada de texto multilínea para que el usuario escriba su mensaje.
      • <input type="submit" value="Send">: Un botón para enviar el mensaje.
  • templates: Flask, por defecto, busca las plantillas HTML en una carpeta llamada "templates" en el mismo directorio que tu archivo app.py. Por lo tanto, este archivo debe guardarse como templates/chat.html.

Este código crea un chatbot usando Flask y OpenAI, con el historial de la conversación almacenado en sesiones del lado del servidor. El servidor conserva la memoria del chat durante la sesión del usuario, eliminándola cuando el usuario cierra la pestaña del navegador.

4.3.4 Opcional: Limitar el Historial de Mensajes

Los modelos de lenguaje grandes tienen una ventana de contexto limitada (por ejemplo, 128k tokens en GPT-4o), lo que determina cuánta conversación el modelo puede "recordar" al mismo tiempo. Para evitar superar este límite y generar errores, deberías limitar la cantidad de mensajes almacenados en el historial de la conversación.

Aquí te mostramos cómo recortar las entradas más antiguas tanto en Streamlit como en Flask:

Streamlit

import streamlit as st

MAX_HISTORY = 20  # Maximum number of messages to keep

if SESSION_MESSAGES_KEY in st.session_state:
    if len(st.session_state[SESSION_MESSAGES_KEY]) > MAX_HISTORY:
        # Keep the system message and the last MAX_HISTORY messages
        st.session_state[SESSION_MESSAGES_KEY] = [st.session_state[SESSION_MESSAGES_KEY][0]] + st.session_state[SESSION_MESSAGES_KEY][-MAX_HISTORY + 1:]

Explicación:

  • MAX_HISTORY: Una constante que define la cantidad máxima de mensajes a conservar. Este ejemplo mantiene los últimos 20 mensajes.
  • if SESSION_MESSAGES_KEY in st.session_state: Verifica si la clave existe
  • El código luego recorta la lista st.session_state.messages, conservando el primer mensaje (el mensaje del sistema) y los últimos MAX_HISTORY - 1 mensajes.

Flask

from flask import session

MAX_HISTORY = 20  # Maximum number of messages to keep

if SESSION_MESSAGES_KEY in session:
    if len(session[SESSION_MESSAGES_KEY]) > MAX_HISTORY:
        # Keep the system message and the last MAX_HISTORY messages
        session[SESSION_MESSAGES_KEY] = [session[SESSION_MESSAGES_KEY][0]] + session[SESSION_MESSAGES_KEY][-MAX_HISTORY + 1:]

Explicación:

  • MAX_HISTORY: Una constante que define el número máximo de mensajes a conservar.
  • if SESSION_MESSAGES_KEY in session: Verifica si la clave existe
  • El código recorta la lista session["history"], manteniendo el primer mensaje (el mensaje del sistema) y los últimos MAX_HISTORY - 1 mensajes.

Consideraciones Importantes de Implementación:

  • Gestión del Mensaje del Sistema: El mensaje del sistema juega un papel crucial en establecer el comportamiento y contexto del chatbot. Siempre debe preservarse como el primer mensaje en tu historial de conversación. Al implementar el recorte de mensajes, asegúrate de que tu código específicamente mantenga este mensaje mediante:
    • Mantenerlo separado del flujo regular de conversación
    • Incluir un manejo especial en tu lógica de recorte
    • Verificar su presencia antes de cada interacción
  • Protocolo Integral de Pruebas: Para asegurar un rendimiento confiable del chatbot:
    • Probar con diferentes longitudes de conversación, desde intercambios cortos hasta diálogos extensos
    • Verificar que el contexto se mantenga incluso después del recorte
    • Comprobar casos límite donde la coherencia podría romperse
    • Monitorear el uso de recursos del sistema durante conversaciones prolongadas
  • Estrategias Avanzadas de Recorte: Considera estos enfoques sofisticados:
    • Recorte basado en tokens: Calcular el uso real de tokens usando un tokenizador
    • Recorte basado en importancia: Mantener mensajes según su relevancia
    • Enfoque híbrido: Combinar conteo de tokens con relevancia de mensajes
    • Ajuste dinámico: Modificar el umbral de recorte según la complejidad de la conversación

4.3.5 Consejo Adicional: Guardar en Archivo o Base de Datos

¿Quieres mantener la memoria incluso después de que termine la sesión? Exploremos varios métodos efectivos para el almacenamiento de memoria a largo plazo:

1. Exportar historial de conversación a un archivo JSON

Proceso: Los datos de st.session_state.messages (en Streamlit) o session["history"] (en Flask) pueden guardarse en un archivo JSON. Esto implica convertir la lista de diccionarios de mensajes en una cadena JSON y escribirla en un archivo.

Ejemplo de Código (Streamlit):

import json
import streamlit as st

def save_chat_log(filename="chat_log.json"):
    """Saves the chat log from st.session_state to a JSON file."""
    if SESSION_MESSAGES_KEY in st.session_state:
        try:
            with open(filename, "w") as f:
                json.dump(st.session_state[SESSION_MESSAGES_KEY], f, indent=2)
            print(f"Chat log saved to {filename}")
        except Exception as e:
            st.error(f"Error saving chat log: {e}")

# Example usage:  Call this function when the user ends the session or when appropriate
if st.button("Save Chat Log"):
    save_chat_log()

Ejemplo de Código (Flask):

import json
from flask import session, Flask

def save_chat_log(filename="chat_log.json"):
    """Saves the chat log from the Flask session to a JSON file."""
    if SESSION_MESSAGES_KEY in session:
        try:
            with open(filename, "w") as f:
                json.dump(session[SESSION_MESSAGES_KEY], f, indent=2)
            print(f"Chat log saved to {filename}")
        except Exception as e:
            #  Use the app context to display a message
            with app.app_context():
                print(f"Error saving chat log: {e}")

# Example Usage
app = Flask(__name__)
@app.route('/save_log')
def save_log():
    save_chat_log()
    return "Chat log saved!"

Explicación:

  • La función json.dump() se utiliza para serializar la lista de mensajes a una cadena con formato JSON. El parámetro indent=2 hace que el archivo JSON sea más legible para humanos.
  • El código maneja posibles errores durante la escritura del archivo.
  • El ejemplo de Streamlit utiliza un botón para activar el guardado, y el ejemplo de Flask crea una ruta /save_log para guardar el archivo.

Ventajas:

  • Fácil de implementar usando el módulo json incorporado en Python.
  • Adecuado para aplicaciones pequeñas y prototipos rápidos.
  • Fácil de respaldar y controlar versiones.

Desventajas:

  • No es ideal para aplicaciones a gran escala con múltiples usuarios.
  • Sin capacidad eficiente de consulta o indexación.

2. Almacenar conversaciones en una base de datos SQLite, PostgreSQL o NoSQL

Proceso: Almacenar el historial de conversación en una base de datos. Cada mensaje puede ser una fila en una tabla (para bases de datos SQL) o un documento en una colección (para bases de datos NoSQL).

Ejemplo de Código (SQLite - Streamlit):

import streamlit as st
import sqlite3
import datetime

def get_connection():
    """Gets or creates a SQLite connection."""
    conn = getattr(st.session_state, "sqlite_conn", None)
    if conn is None:
        conn = sqlite3.connect("chat_log.db")
        st.session_state.sqlite_conn = conn
    return conn

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS messages (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            role TEXT NOT NULL,
            content TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    conn.commit()

def store_message(role, content):
    """Stores a message in the database."""
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute(
        "INSERT INTO messages (role, content) VALUES (?, ?)",
        (role, content),
    )
    conn.commit()

create_table()  # Ensure table exists

# Store messages
if st.session_state.user_input:
    store_message("user", st.session_state.user_input)
if st.session_state.get("reply"):  # replace reply with a key you are using
    store_message("assistant", st.session_state.reply)

# Example of retrieving messages (optional, for demonstration)
conn = get_connection()
cursor = conn.cursor()
cursor.execute("SELECT role, content, created_at FROM messages ORDER BY created_at DESC LIMIT 5")
recent_messages = cursor.fetchall()
st.write("Last 5 Messages from DB:")
for row in recent_messages:
    st.write(f"{row[0]}: {row[1]} (at {row[2]})")

Ejemplo de Código (PostgreSQL - Flask):

from flask import Flask, request, render_template, session
import openai
import os
from dotenv import load_dotenv
import psycopg2  # PostgreSQL library
import datetime
from typing import List, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL") #Make sure to set this in .env

app = Flask(__name__)
app.secret_key = os.urandom(24)

def get_db_connection():
    """Gets a PostgreSQL connection."""
    try:
        conn = psycopg2.connect(DATABASE_URL)
        return conn
    except psycopg2.Error as e:
        print(f"Database connection error: {e}")
        return None

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id SERIAL PRIMARY KEY,
                    session_id TEXT NOT NULL,
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            """)
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error creating table: {e}")
        finally:
            conn.close()

def store_message(session_id: str, role: str, content: str):
    """Stores a message in the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
                (session_id, role, content),
            )
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error storing message: {e}")
        finally:
            conn.close()

create_table()  # Ensure the table exists

@app.route("/", methods=["GET", "POST"])
def chat():
    if "session_id" not in session:
        session["session_id"] = os.urandom(16).hex()  # Unique session ID

    if "history" not in session:
        session["history"] = [{"role": "system", "content": "You are a helpful assistant."}]

    if request.method == "POST":
        user_input = request.form["user_input"]
        session_id = session["session_id"]
        store_message(session_id, "user", user_input)  # Store in DB
        session["history"].append({"role": "user", "content": user_input})

        conn = get_db_connection() #
        if conn: #
            try: #
                cursor = conn.cursor() #
                cursor.execute("SELECT role, content, created_at FROM messages WHERE session_id = %s ORDER BY created_at", (session_id,)) #
                messages_from_db = cursor.fetchall() #
            except psycopg2.Error as e: #
                print(f"Error fetching messages: {e}") #
            finally: #
                conn.close() #

        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=session["history"],
            temperature=0.6,
        )

        assistant_reply = response["choices"][0]["message"]["content"]
        store_message(session_id, "assistant", assistant_reply)  # Store in DB
        session["history"].append({"role": "assistant", "content": assistant_reply})
        session.modified = True

        return render_template("chat.html", history=session.get("history")[1:])
    return render_template("chat.html", history=session.get("history")[1:])

Explicación:

  • El ejemplo de Streamlit usa SQLite, una base de datos ligera que no requiere un servidor separado. El ejemplo de Flask usa PostgreSQL, una base de datos más robusta que es adecuada para aplicaciones multiusuario.
  • Ambos ejemplos crean una tabla llamada "messages" para almacenar el historial de conversación. La tabla incluye columnas para ID del mensaje, rol, contenido y marca de tiempo.
  • La función store_message() inserta un nuevo mensaje en la base de datos.
  • El ejemplo de Flask recupera mensajes de la base de datos y los pasa a la plantilla para su visualización. El ejemplo de Streamlit también muestra cómo recuperar datos.

Ventajas:

  • SQLite: Perfecto para aplicaciones de un solo usuario con datos estructurados. No se necesita un servidor de base de datos separado.
  • PostgreSQL: Ideal para sistemas multiusuario que requieren acceso concurrente. Más robusto y escalable que SQLite.
  • NoSQL (No mostrado en detalle): Óptimo para esquemas flexibles y datos de conversación no estructurados. Bases de datos como MongoDB o CouchDB serían adecuadas.
  • Desventajas:
    • Requiere más configuración que usar un archivo JSON.
    • Necesidad de gestionar conexiones y esquemas de base de datos.

3. Reutilizar memoria en sesiones futuras mediante etiquetado con ID de usuario

Proceso: Para permitir que los usuarios tengan conversaciones persistentes y personalizadas, puedes etiquetar cada mensaje con un ID de usuario y almacenar esta información en la base de datos. Cuando un usuario regresa, puedes recuperar su historial específico de conversación de la base de datos.

Ejemplo de Código (PostgreSQL - Flask):

from flask import Flask, request, render_template, session, redirect, url_for
import openai
import os
from dotenv import load_dotenv
import psycopg2
import datetime
from typing import List, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")

app = Flask(__name__)
app.secret_key = os.urandom(24)

def get_db_connection():
    """Gets a PostgreSQL connection."""
    try:
        conn = psycopg2.connect(DATABASE_URL)
        return conn
    except psycopg2.Error as e:
        print(f"Database connection error: {e}")
        return None

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id SERIAL PRIMARY KEY,
                    user_id TEXT NOT NULL,  -- Added user_id
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            """)
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error creating table: {e}")
        finally:
            conn.close()

def store_message(user_id: str, role: str, content: str):
    """Stores a message in the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO messages (user_id, role, content) VALUES (%s, %s, %s)",
                (user_id, role, content),
            )
            conn.commit()
        except psycopg2.Error as e:
            print(f"Error storing message: {e}")
        finally:
            conn.close()

create_table()

def get_user_history(user_id: str) -> List[Dict[str, str]]:
    """Retrieves a user's conversation history from the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT role, content FROM messages WHERE user_id = %s ORDER BY created_at",
                (user_id,),
            )
            history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
            return history
        except psycopg2.Error as e:
            print(f"Error retrieving user history: {e}")
            return []
        finally:
            conn.close()
    return []

@app.route("/", methods=["GET", "POST"])
def chat():
    if "user_id" not in session:
        session["user_id"] = os.urandom(16).hex()  # Unique user ID
    user_id = session["user_id"]

    history = get_user_history(user_id)  # Get user's history

    if request.method == "POST":
        user_input = request.form["user_input"]
        store_message(user_id, "user", user_input)  # Store with user ID
        history.append({"role": "user", "content": user_input})

        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=history,
            temperature=0.6,
        )

        assistant_reply = response["choices"][0]["message"]["content"]
        store_message(user_id, "assistant", assistant_reply)  # Store with user ID
        history.append({"role": "assistant", "content": assistant_reply})
        session["history"] = history #update

        return render_template("chat.html", history=history[1:])

    return render_template("chat.html", history=history[1:])

@app.route("/clear", methods=["POST"])
def clear_chat():
    session.pop("user_id", None)  #remove user id
    session.pop("history", None)
    return redirect(url_for("chat"))

Explicación:

  • La tabla de base de datos "messages" ahora incluye una columna "user_id".
  • Cuando un usuario inicia una sesión, se genera un "user_id" único y se almacena en la session de Flask.
  • La función store_message() ahora requiere un "user_id" y lo almacena junto con el mensaje.
  • La función get_user_history() recupera el historial de conversación para un usuario específico de la base de datos.
  • La ruta de chat recupera el historial del usuario y lo utiliza para construir los mensajes enviados a OpenAI, manteniendo así el historial de conversación a través de múltiples visitas del mismo usuario.
  • Ventajas:
    • Permite un historial de conversación personalizado para cada usuario.
    • Permite un contexto y preferencias específicos para cada usuario.
    • Facilita el análisis de patrones de conversación a lo largo del tiempo.
  • Desventajas:
    • Requiere una base de datos.
    • Más complejo de implementar que el almacenamiento simple basado en sesiones.

En esta sección, aprendiste varios aspectos cruciales para construir un chatbot con capacidades de memoria:

  • Agregaste memoria basada en sesiones a tu chatbot usando st.session_state (Streamlit) y flask.session (Flask)
    • Implementaste almacenamiento temporal para conversaciones en curso
    • Aprendiste a gestionar variables de sesión de manera efectiva
  • Preservaste el historial de chat a través de interacciones
    • Creaste esquemas de base de datos para almacenar datos de conversación
    • Implementaste métodos para guardar y recuperar mensajes anteriores
  • Mejoraste el contexto y la coherencia para conversaciones de múltiples turnos
    • Desarrollaste sistemas para mantener el contexto de la conversación
    • Mejoraste la comprensión del lenguaje natural a través del contexto histórico
  • Aprendiste a limitar el uso de tokens mediante el recorte del historial de mensajes
    • Implementaste estrategias eficientes de poda de mensajes
    • Equilibraste la retención de memoria con las limitaciones de tokens de la API

Esto le da a tu chatbot la capacidad de mantener conversaciones naturales y fluidas — un hito clave hacia la construcción de un asistente inteligente. Con estas características, tu chatbot ahora puede recordar interacciones previas, mantener el contexto durante las conversaciones y gestionar la memoria de manera eficiente mientras se mantiene dentro de las restricciones técnicas.