Construyendo una aplicación de aprendizaje de idiomas impulsada por IA Aprendiendo de dos chats de IA
Construcción de una aplicación de aprendizaje de idiomas con IA basada en dos chats de IA.
Un tutorial paso a paso sobre cómo crear una aplicación de aprendizaje de idiomas de doble chatbot con Langchain, OpenAI, gTTS y Streamlit

Cuando comencé a aprender un nuevo idioma, me gustaba comprar esos libros de “diálogos conversacionales”. Encuentro esos libros muy útiles ya que me ayudan a entender cómo funcionaba el idioma, no solo la gramática y el vocabulario, sino también cómo la gente lo usaba realmente en la vida cotidiana.
Ahora, con el auge de los grandes modelos de idiomas (LLMs), se me ocurrió un pensamiento: ¿podría replicar estos libros de aprendizaje de idiomas en un formato más interactivo, dinámico y escalable? ¿Podría utilizar LLM para crear una herramienta que genere conversaciones frescas y bajo demanda para los estudiantes de idiomas?
Este pensamiento inspiró el proyecto que me gustaría compartir contigo hoy: una aplicación de aprendizaje de idiomas impulsada por IA, donde los estudiantes pueden observar y aprender de dos chatbots de IA que participan en una conversación o un debate definido por el usuario.
Con respecto a la tecnología empleada, he utilizado Langchain, la API de OpenAI, gTTS y Streamlit para crear la aplicación donde los usuarios pueden definir los roles, escenarios o temas de debate y dejar que la IA genere el contenido.
Demo de la aplicación de aprendizaje de idiomas desarrollada. (Imagen del autor)
- Transformadores de Visión (ViT) en Generación de Leyendas de Imágen...
- ¿Cómo construir una IA responsable con TensorFlow?
- Microsoft AI presenta una estrategia avanzada de optimización de co...
Si tienes curiosidad por saber cómo funciona todo, únete a mí mientras te guío en el viaje de construir este sistema de doble chatbot interactivo, paso a paso 🗺️📍🚶♀️.
Puedes encontrar el código fuente completo aquí 💻. En este blog, también repasaremos los fragmentos de código clave para explicar las ideas.
Con eso en mente, ¡empecemos!
Tabla de contenido · 1. Descripción general del proyecto · 2. Prerrequisitos ∘ 2.1 LangChain ∘ 2.2 ConversationChain · 3. Diseño del proyecto ∘ 3.1 Desarrollo de un solo chatbot ∘ 3.2 Desarrollo de un sistema de doble chatbot · 4. Diseño de la interfaz de la aplicación con Streamlit · 5. Aprendizajes y extensiones futuras · 6. Conclusión
1. Descripción general del proyecto
Como se mencionó anteriormente, nuestro objetivo es crear una aplicación de aprendizaje de idiomas única impulsada por dos chatbots conversacionales de IA. El aspecto innovador de esta aplicación radica en tener estos chatbots interactuando entre sí, creando diálogos realistas en el idioma objetivo. Los usuarios pueden observar estas conversaciones impulsadas por IA, utilizarlas como recursos de aprendizaje de idiomas y comprender el uso práctico de su idioma elegido.
En nuestra aplicación, los usuarios deberían tener la flexibilidad de personalizar su experiencia de aprendizaje según sus necesidades. Pueden ajustar varias configuraciones, incluido el idioma objetivo, el modo de aprendizaje, la duración de la sesión y el nivel de competencia.
Idioma objetivo 🔤
Los usuarios pueden elegir el idioma que desean aprender. Esta elección guía el idioma utilizado por los chatbots durante sus interacciones. Por el momento, he incluido soporte para inglés — ‘en’, alemán — ‘de’, español — ‘es’ y francés — ‘fr’, pero es trivial agregar más idiomas siempre y cuando el modelo GPT tenga suficiente conocimiento sobre ellos.
Modo de aprendizaje 📖
Esta configuración permite a los usuarios seleccionar el estilo de conversación entre los chatbots. En el modo “conversación”, los usuarios pueden definir los roles (por ejemplo, cliente y personal de espera) y las acciones (pedir comida y tomar un pedido) para cada bot y especificar un escenario (en un restaurante), sobre el cual los bots simularán una conversación realista. En el modo “debate”, se solicita a los usuarios que ingresen un tema de debate (¿Deberíamos adoptar la energía nuclear?). Los bots luego participan en un animado debate sobre el tema proporcionado.
La interfaz de la aplicación debe ser receptiva y ajustarse dinámicamente según el modo de aprendizaje seleccionado por el usuario, proporcionando una experiencia de usuario perfecta.
Duración de la Sesión ⏰
La configuración de duración de sesión le da control a los usuarios sobre la duración de cada conversación o debate del chatbot. Esto significa que pueden tener diálogos cortos y rápidos o discusiones más largas y detalladas, dependiendo de sus preferencias.
Nivel de Proficiencia 🏆
Esta configuración adapta la complejidad de la conversación del chatbot al nivel de habilidad lingüística del usuario. Los principiantes pueden preferir conversaciones más simples, mientras que los aprendices avanzados pueden manejar debates o discusiones más complejas.
Una vez que los usuarios especifican esas configuraciones, pueden iniciar la sesión y ver cómo los chatbots de inteligencia artificial entran en acción, llevando a cabo diálogos dinámicos e interactivos de acuerdo con las preferencias del usuario. Nuestro flujo de trabajo general se puede ilustrar de la siguiente manera:

2. Prerrequisitos
Antes de adentrarnos en el desarrollo de nuestra aplicación, familiaricémonos con las herramientas que vamos a utilizar. En esta sección, presentaremos brevemente la biblioteca LangChain, específicamente el módulo ConversationChain
, que sirve como columna vertebral de nuestra aplicación.
2.1 LangChain
Construir una aplicación impulsada por Modelos de Lenguaje Grande (LLMs) involucra muchas complejidades. Necesitas interactuar con proveedores de modelos de lenguaje a través de llamadas de API, conectar estos modelos a varias fuentes de datos, manejar el historial de interacciones de usuario y diseñar tuberías para ejecutar tareas complejas. Ahí es donde entra en juego la biblioteca LangChain.
LangChain es un framework dedicado a simplificar el desarrollo de aplicaciones impulsadas por LLMs. Ofrece una amplia variedad de componentes que abordan los puntos dolorosos comunes mencionados anteriormente. Ya sea la gestión de interacciones con los proveedores de modelos de lenguaje, orquestar conexiones de datos, mantener la memoria para interacciones históricas o definir tuberías de tarea intrincadas, LangChain lo tiene cubierto.
Un concepto clave introducido por LangChain es la ” Cadena “. En esencia, las cadenas nos permiten combinar múltiples componentes para crear una aplicación única y coherente. Por ejemplo, un tipo fundamental de cadena en LangChain es el LLMChain.
Crea una tubería que primero formatea la plantilla de solicitud usando los valores de clave de entrada proporcionados por el usuario, luego pasa las instrucciones formateadas a LLM, y finalmente devuelve la salida de LLM.
LangChain aloja una variedad de tipos de cadena, incluyendo RetrievalQAChain,
para preguntas y respuestas sobre documentos, SummarizationChain,
para resumir varios documentos, y por supuesto, nuestro enfoque para hoy, el ConversationChain.
2.2 ConversationChain
ConversationChain
se utiliza para facilitar conversaciones interactivas proporcionando un marco para intercambiar mensajes y almacenar el historial de conversaciones. Aquí hay un ejemplo de código para ilustrar su uso:
from langchain.chains import ConversationChain# Crear cadena de conversaciónconversation = ConversationChain(memory, prompt, llm)# Ejecutar cadena de conversaciónconversation.predict(input="¡Hola!")# Obtener la respuesta de LLM: "¡Hola! ¿Cómo puedo ayudarte hoy?"# Podemos seguir llamando a la cadena de conversaciónconversation.predict(input="¡Estoy haciendo bien! Sólo estoy teniendo una conversación con una IA.")# Obtener la respuesta de LLM: "¡Eso suena divertido! Me alegra conversar contigo. ¿Hay algo específico de lo que te gustaría hablar?"
En este ejemplo, ConversationChain
toma tres entradas, memoria, un componente de LangChain que mantiene el historial de interacciones; prompt, la entrada a LLM; y llm, el modelo de lenguaje grande central (por ejemplo, GPT-3.5-Turbo, etc.).
Una vez que se instancia el objeto ConversationChain
, simplemente podemos llamar a conversation.predict()
con la entrada del usuario para obtener la respuesta de LLM. La conveniencia con ConversationChain
es que podemos llamar a conversation.predict()
varias veces, y automáticamente registra el historial de mensajes bajo el capó.
En la próxima sección, aprovecharemos el poder de ConversationChain
para crear nuestros chatbots y profundizaremos en cómo se definen y utilizan la memoria, la plantilla de sugerencias y el LLM.
Si desea obtener más información sobre LangChain, consulte su documentación oficial. Además, esta lista de reproducción de YouTube también ofrece una introducción completa y práctica.
3. Diseño del proyecto
Ahora que tenemos una comprensión clara de lo que queremos construir y las herramientas para hacerlo, ¡es hora de poner manos a la obra y sumergirnos en el código! En esta sección, nos centraremos en los detalles para crear nuestra interacción de chatbot dual. Primero, exploraremos la definición de clase para un solo chatbot y luego ampliaremos esto para crear una clase de chatbot dual, lo que permitirá que nuestros dos chatbots interactúen. Guardaremos el diseño de la interfaz de la aplicación utilizando Streamlit para la Sección 4.
3.1 Desarrollo de un solo chatbot
En esta subsección, desarrollaremos juntos un solo chatbot, que luego se integrará en el sistema de chatbot dual. Comencemos con el diseño general de la clase, luego cambiaremos nuestra atención a la ingeniería de sugerencias.
🏗️ Diseño de la Clase
Nuestra clase de chatbot debería permitir la gestión de un chatbot individual. Esto implica instanciar un chatbot con un LLM especificado por el usuario como su columna vertebral, proporcionar instrucciones basadas en la intención del usuario y facilitar conversaciones interactivas de varias rondas. Con eso en mente, comencemos a codificar.
Primero, importe las bibliotecas necesarias:
import osimport openaifrom langchain.prompts import ( ChatPromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate, HumanMessagePromptTemplate)from langchain.prompts import PromptTemplatefrom langchain.chains import LLMChainfrom langchain.chains import ConversationChainfrom langchain.chat_models import ChatOpenAIfrom langchain.memory import ConversationBufferMemory
A continuación, definimos el constructor de la clase:
class Chatbot: """Definición de clase para un solo chatbot con memoria, creado con LangChain.""" def __init__(self, engine): """Seleccione el modelo de lenguaje grande de la columna vertebral, así como instanciar la memoria para crear una cadena de lenguaje en LangChain. """ # Instanciar LLM if engine == 'OpenAI': # Recordatorio: es necesario configurar la clave de API de OpenAI # (por ejemplo, a través de la variable de entorno OPENAI_API_KEY) self.llm = ChatOpenAI( model_name="gpt-3.5-turbo", temperature=0.7 ) else: raise KeyError("¡Tipo de modelo de chat no admitido actualmente!") # Instanciar memoria self.memory = ConversationBufferMemory(return_messages=True)
Actualmente, solo puede elegir utilizar la API nativa de OpenAI. Sin embargo, agregar más LLM de backend es sencillo ya que LangChain admite varios tipos (por ejemplo, punto final de Azure OpenAI, modelos de chat de Anthropic, API de PaLM en Google Vertex AI, etc.).
Además de LLM, otro componente importante que necesitamos instanciar es la memoria, que realiza un seguimiento del historial de conversaciones. Aquí, usamos ConversationBufferMemory
para este propósito, que simplemente agrega los últimos pocos datos de entrada/salida a la entrada actual del chatbot. Este es el tipo de memoria más simple que ofrece LangChain y es suficiente para nuestro propósito actual.
Para obtener una descripción completa de otros tipos de memoria, consulte la documentación oficial.
Continuando, necesitamos tener un método de clase que nos permita dar instrucciones al chatbot y tener conversaciones con él. Para esto está self.instruct()
:
def instruct(self, role, oppo_role, language, scenario, session_length, proficiency_level, learning_mode, starter=False): """Determinar el contexto de la interacción del chatbot. """ # Definir configuraciones de idioma self.role = role self.oppo_role = oppo_role self.language = language self.scenario = scenario self.session_length = session_length self.proficiency_level = proficiency_level self.learning_mode = learning_mode self.starter = starter # Definir plantilla de sugerencias prompt = ChatPromptTemplate.from_messages([ SystemMessagePromptTemplate.from_template(self._specify_system_message()), MessagesPlaceholder(variable_name="history"), HumanMessagePromptTemplate.from_template("{input}") ]) # Crear cadena de conversación self.conversation = ConversationChain(memory=self.memory, prompt=prompt, llm=self.llm, verbose=False)
- Definimos un par de configuraciones para permitir a los usuarios personalizar su experiencia de aprendizaje.
Además de lo que se ha mencionado en “Sección 1 Visión general del proyecto”, tenemos cuatro nuevos atributos:
self.role/self.oppo_role:
este atributo toma la forma de un diccionario que registra el nombre del rol y las acciones correspondientes. Por ejemplo:
self.role = {'nombre': 'Cliente', 'acción': 'ordenar comida'}
self.oppo_role
representa el papel que desempeña el otro chatbot que participa en la conversación con el chatbot actual. Es esencial porque el chatbot actual necesita entender con quién está comunicándose, proporcionando la información contextual necesaria.
self.scenario
establece el escenario para la conversación. Para el modo de aprendizaje “conversación”, self.scenario
representa el lugar donde ocurre la conversación; para el modo “debate”, self.scenario
representa el tema del debate.
Por último, self.starter
es solo una bandera booleana para indicar si el chatbot actual iniciará la conversación.
- Estructuramos la indicación para el chatbot.
En OpenAI, un modelo de chat generalmente toma una lista de mensajes como entrada y devuelve un mensaje generado por el modelo como salida. LangChain admite SystemMessage
, AIMessage
, HumanMessage
: SystemMessage
ayuda a establecer el comportamiento del chatbot, AIMessage
almacena las respuestas anteriores del chatbot y HumanMessage
proporciona solicitudes o comentarios a los que el chatbot debe responder.
LangChain ofrece convenientemente PromptTemplate
para simplificar la generación y la ingestión de la indicación. Para una aplicación de chatbot, necesitamos especificar la PromptTemplate
para los tres tipos de mensajes. La pieza más importante es establecer el SystemMessage
, que controla el comportamiento del chatbot. Tenemos un método separado, self._specify_system_message()
, para manejar esto, que discutiremos en detalle más adelante.
- Finalmente, unimos todas las piezas y construimos una
ConversationChain.
🖋️ Diseño de la Indicación
Nuestro enfoque ahora se centra en guiar al chatbot para que participe en la conversación como lo desea el usuario. Con este fin, tenemos el método self._specify_system_message()
. La firma de este método se muestra a continuación:
def _specify_system_message(self): """Especifica el comportamiento del chatbot, que consta de los siguientes aspectos: - contexto general: conducir una conversación/debate en el escenario dado - el idioma hablado - propósito de la conversación/debate simulado - requisito de complejidad del idioma - requisito de longitud del intercambio - otros matices Salidas: -------- indicación: instrucciones para el chatbot. """
Esencialmente, este método compila una cadena, que luego se alimentará en SystemMessagePromptTemplate.from_template()
para instruir al chatbot, como se muestra en la definición del método self.instruct()
anterior. Analizaremos esta “cadena larga” en lo siguiente para comprender cómo se incorpora cada requisito de aprendizaje de idiomas en la indicación.
1️⃣ Duración de la sesión
La duración de la sesión se controla especificando directamente el número máximo de intercambios que pueden ocurrir dentro de una sesión. Esos números están codificados por ahora.
# Determine el número de intercambios entre dos botsdict_exchange_counts = { 'Corta': {'Conversación': 8, 'Debate': 4}, 'Larga': {'Conversación': 16, 'Debate': 8}}intercambio_cuenta = dict_exchange_counts[self.session_length][self.learning_mode]
2️⃣ Número de oraciones que el chatbot puede decir en un intercambio
Además de limitar el número total de intercambios permitidos, también es beneficioso restringir cuánto puede decir un chatbot dentro de un intercambio, o equivalentemente, el número de oraciones.
En mis experimentos, generalmente no hay necesidad de limitar esto en el modo “conversación”, ya que el chatbot imita un diálogo de la vida real y tiende a hablar a una longitud razonable. Sin embargo, en el modo “debate”, es necesario imponer un límite. De lo contrario, el chatbot puede seguir hablando, generando eventualmente un “ensayo” 😆.
Similar a limitar la duración de la sesión, los números que restringen la duración del discurso también están codificados y corresponden con el nivel de competencia del usuario en el idioma objetivo:
# Determine el número de oraciones en una ronda de debateargument_num_dict = { 'Principiante': 4, 'Intermedio': 6, 'Avanzado': 8}
3️⃣ Determinar la complejidad del discurso
Aquí, regulamos el nivel de complejidad del lenguaje que el chatbot puede usar:
if self.proficiency_level == 'Principiante': lang_requirement = """usar vocabulario básico y estructuras de oraciones simples. Debe evitar modismos, jerga y construcciones gramaticales complejas."""elif self.proficiency_level == 'Intermedio': lang_requirement = """usar un rango más amplio de vocabulario y una variedad de estructuras de oraciones. Puede incluir algunos modismos y expresiones coloquiales, pero evite el lenguaje altamente técnico o expresiones literarias complejas."""elif self.proficiency_level == 'Avanzado': lang_requirement = """usar vocabulario sofisticado, estructuras de oraciones complejas, modismos, expresiones coloquiales y lenguaje técnico donde corresponda."""else: raise KeyError('¡Nivel de competencia no soportado actualmente!')
4️⃣ ¡Pongámoslo todo junto!
Esto es lo que parece la instrucción para diferentes modos de aprendizaje:
# Compilar instrucciones del botif self.learning_mode == 'Conversación': prompt = f"""Eres una IA que es buena en juegos de rol. Estás simulando una conversación típica sucedida {self.scenario}. En este escenario, estás interpretando el papel de un {self.role['name']} {self.role['action']}, hablando con un {self.oppo_role['name']} {self.oppo_role['action']}. Tu conversación solo debe ser conducida en {self.language}. No traduzcas. Esta conversación simulada está diseñada para que los estudiantes de idiomas {self.language} aprendan conversaciones de la vida real en {self.language}. Debe suponer que el nivel de competencia de los estudiantes en {self.language} es {self.proficiency_level}. Por lo tanto, debes {lang_requirement}. Debe terminar la conversación dentro de {exchange_counts} intercambios con el {self.oppo_role['name']}. Haz tu conversación con {self.oppo_role['name']} natural y típica en el escenario considerado en {self.language} cultural."""elif self.learning_mode == 'Debate': prompt = f"""Eres una IA que es buena en el debate. Ahora estás comprometido en un debate con el siguiente tema: {self.scenario}. En este debate, estás asumiendo el papel de un {self.role['name']}. Recuerda siempre tus posturas en el debate. Tu debate solo debe ser conducido en {self.language}. No traduzcas. Este debate simulado está diseñado para que los estudiantes de idiomas {self.language} aprendan {self.language}. Debe suponer que el nivel de competencia de los estudiantes en {self.language} es {self.proficiency_level}. Por lo tanto, debes {lang_requirement}. Intercambiarás opiniones con otra IA (que juega el papel de {self.oppo_role['name']}) {exchange_counts} veces. Cada vez que hables, solo puedes hablar no más de {argument_num_dict[self.proficiency_level]} oraciones."""else: raise KeyError('¡Modo de aprendizaje no soportado actualmente!')
5️⃣ ¿Quién habla primero?
Finalmente, instruimos al chatbot si debe hablar primero o esperar la respuesta del oponente IA:
# Dar instrucciones al botif self.starter: # En caso de que el bot actual sea el primero en hablar prompt += f"Eres el líder del {self.learning_mode}. \n"else: # En caso de que el bot actual sea el segundo en hablar prompt += f"Espera la declaración del {self.oppo_role['name']}'. "
Ahora hemos completado el diseño de la solicitud 🎉 Como resumen rápido, esto es lo que hemos desarrollado hasta ahora:

3.2 Desarrollo de un sistema de chatbots dual
¡Ahora llegamos a la parte emocionante! En esta subsección, desarrollaremos una clase de chatbot dual para permitir que dos chatbots interactúen entre sí 💬💬
🏗️ Diseño de Clases
Gracias a la clase de Chatbot única previamente desarrollada, podemos instanciar sin esfuerzo dos chatbots en el constructor de la clase:
class DualChatbot: """Definición de clase para un sistema de interacción entre dos chatbots, creado con LangChain.""" def __init__(self, motor, diccionario_de_roles, idioma, escenario, nivel_de_proficiencia, modo_de_aprendizaje, duracion_de_sesion): # Instanciar dos chatbots self.motor = motor self.nivel_de_proficiencia = nivel_de_proficiencia self.idioma = idioma self.chatbots = diccionario_de_roles for k in diccionario_de_roles.keys(): self.chatbots[k].update({'chatbot': Chatbot(motor)}) # Asignación de roles para los dos chatbots self.chatbots['rol1']['chatbot'].instruct(role=self.chatbots['rol1'], oppo_role=self.chatbots['rol2'], language=idioma, scenario=escenario, session_length=duracion_de_sesion, proficiency_level=nivel_de_proficiencia, learning_mode=modo_de_aprendizaje, starter=True) self.chatbots['rol2']['chatbot'].instruct(role=self.chatbots['rol2'], oppo_role=self.chatbots['rol1'], language=idioma, scenario=escenario, session_length=duracion_de_sesion, proficiency_level=nivel_de_proficiencia, learning_mode=modo_de_aprendizaje, starter=False) # Agregar duración de sesión self.duracion_de_sesion = duracion_de_sesion # Preparar conversación self._reset_conversation_history()
El self.chatbots
es un diccionario diseñado para almacenar información relacionada con ambos bots:
# Para los modos de "conversación"self.chatbots= { 'rol1': {'nombre': 'Cliente', 'acción': 'ordenar comida', 'chatbot': Chatbot()}, 'rol2': {'nombre': 'Camarero', 'acción': 'tomar el pedido', 'chatbot': Chatbot()} }# Para los modos de "debate"self.chatbots= { 'rol1': {'nombre': 'Partidario', 'chatbot': Chatbot()}, 'rol2': {'nombre': 'Oponente', 'chatbot': Chatbot()} }
El self._reset_conversation_history
sirve para iniciar un historial de conversación fresco y proporcionar las instrucciones iniciales a los chatbots:
def _reset_conversation_history(self): """Restablecer el historial de conversación. """ # Espacio reservado para el historial de conversación self.conversation_history = [] # Entradas para los dos chatbots self.input1 = "Comenzar la conversación." self.input2 = ""
Para facilitar la interacción entre los dos chatbots, empleamos el método self.step()
. Este método permite una ronda de interacción entre los dos bots:
def step(self): """Realizar una ronda de intercambio entre dos chatbots. """ # Chatbot1 habla output1 = self.chatbots['rol1']['chatbot'].conversation.predict(input=self.input1) self.conversation_history.append({"bot": self.chatbots['rol1']['nombre'], "texto": output1}) # Pasar la salida del chatbot1 como entrada al chatbot2 self.input2 = output1 # Chatbot2 habla output2 = self.chatbots['rol2']['chatbot'].conversation.predict(input=self.input2) self.conversation_history.append({"bot": self.chatbots['rol2']['nombre'], "texto": output2}) # Pasar la salida del chatbot2 como entrada al chatbot1 self.input1 = output2 # Traducir respuestas translate1 = self.translate(output1) translate2 = self.translate(output2) return output1, output2, translate1, translate2
Observe que hemos incorporado un método llamado self.translate()
. El propósito de este método es traducir el script al inglés. Esta funcionalidad podría ser útil para los estudiantes de idiomas, ya que pueden comprender el significado de la conversación generada en el idioma objetivo.
Para lograr la funcionalidad de traducción, podemos emplear el LLMChain
básico, que requiere un modelo LLM de backend y una instrucción:
def translate(self, mensaje): """Traducir el script generado al inglés. """ if self.idioma == 'Inglés': # No se realiza ninguna traducción traduccion = 'Traducción: ' + mensaje else: # Instanciar el traductor if self.motor == 'OpenAI': # Recordatorio: es necesario configurar la clave de la API de OpenAI # (por ejemplo, a través de la variable de entorno OPENAI_API_KEY) self.translator = ChatOpenAI( model_name="gpt-3.5-turbo", temperature=0.7 ) else: raise KeyError("¡Actualmente no se admite este tipo de modelo de traducción!") # Especificar instrucción instruccion = """Traducir la siguiente oración del {src_lang} (idioma de origen) al {trg_lang} (idioma de destino). Aquí está la oración en el idioma de origen: \n {src_input}.""" prompt = PromptTemplate( input_variables=["src_lang", "trg_lang", "src_input"], template=instruccion, ) # Crear una cadena de idiomas translator_chain = LLMChain(llm=self.translator, prompt=prompt) traduccion = translator_chain.predict(src_lang=self.idioma, trg_lang="Inglés", src_input=mensaje) return traduccion
Finalmente, podría ser beneficioso para los estudiantes de idiomas tener un resumen de los puntos clave de aprendizaje de idiomas del guión de conversación generado, ya sea vocabulario clave, puntos gramaticales o frases funcionales. Para ello, podemos incluir un método self.summary()
:
def summary(self, script): """Destila los puntos clave de aprendizaje de idiomas a partir de los guiones generados. """ # Instancia el bot de resumen if self.engine == 'OpenAI': # Recordatorio: es necesario configurar la clave de la API de OpenAI # (por ejemplo, a través de la variable de entorno OPENAI_API_KEY) self.summary_bot = ChatOpenAI( model_name="gpt-3.5-turbo", temperature=0.7 ) else: raise KeyError("¡Tipo de modelo de resumen no soportado actualmente!") # Especifique la instrucción instruction = """El siguiente texto es una conversación simulada en {src_lang}. El objetivo de este texto es ayudar a los estudiantes de {src_lang} a aprender el uso en la vida real de {src_lang}. Por lo tanto, su tarea es resumir los puntos clave de aprendizaje basados en el texto proporcionado. Específicamente, debería resumir el vocabulario clave, los puntos gramaticales y las frases funcionales que podrían ser importantes para los estudiantes que aprenden {src_lang}. Su resumen debe realizarse en inglés, pero usar ejemplos del texto en el idioma original cuando sea apropiado. Recuerde que sus estudiantes objetivo tienen un nivel de competencia de {proficiency} en {src_lang}. Su resumen debe coincidir con su nivel de competencia. La conversación es: \n {script}.""" prompt = PromptTemplate( input_variables=["src_lang", "proficiency", "script"], template=instruction, ) # Crea una cadena de idioma summary_chain = LLMChain(llm=self.summary_bot, prompt=prompt) summary = summary_chain.predict(src_lang=self.language, proficiency=self.proficiency_level, script=script) return summary
Al igual que con el método self.translate()
, utilizamos una cadena de idioma básica LLMChain
para realizar la tarea deseada. Tenga en cuenta que pedimos explícitamente al modelo de idioma que resuma los puntos clave de aprendizaje de idiomas en función del nivel de competencia del usuario.
Con esto, hemos completado el desarrollo de la clase de chatbot dual 🥂 Como resumen rápido, esto es lo que hemos desarrollado hasta ahora:

4. Diseño de la interfaz de la aplicación con Streamlit
Ahora estamos listos para desarrollar la interfaz de usuario 🖥️ Para este proyecto, usaremos la biblioteca Streamlit para construir el frontend.
Si no está familiarizado, Streamlit es una biblioteca de Python de código abierto para crear aplicaciones web interactivas centradas en la ciencia de datos y el aprendizaje automático. Simplifica el proceso de construcción y implementación de aplicaciones al proporcionar una API fácil de usar, recarga de código en vivo para actualizaciones instantáneas, widgets interactivos para entrada de usuario, soporte para bibliotecas de visualización de datos y la capacidad de incorporar medios ricos.
Comencemos con un nuevo script de Python app.py e importemos las bibliotecas necesarias:
import streamlit as stfrom streamlit_chat import messagefrom chatbot import DualChatbotimport timefrom gtts import gTTSfrom io import BytesIO
Junto con la biblioteca principal de streamlit
, también importamos la biblioteca streamlit_chat
, un componente de Streamlit desarrollado por la comunidad específicamente diseñado para crear interfaces de usuario de chatbot. Nuestra clase DualChatbot
previamente desarrollada se almacena en el archivo chatbot.py, por lo que también necesitamos importar eso. Por último, importamos gTTS
, que significa Text-to-Speech de Google, para agregar audio al guión de conversación generado por el bot en este proyecto.
Antes de configurar la interfaz de Streamlit, definamos primero las configuraciones de aprendizaje de idiomas:
# Define las configuraciones de aprendizaje de idiomasLANGUAGES = ['Inglés', 'Alemán', 'Español', 'Francés']SESSION_LENGTHS = ['Corto', 'Largo']PROFICIENCY_LEVELS = ['Principiante', 'Intermedio', 'Avanzado']MAX_EXCHANGE_COUNTS = { 'Corto': {'Conversación': 8, 'Debate': 4}, 'Largo': {'Conversación': 16, 'Debate': 8}}AUDIO_SPEECH = { 'Inglés': 'en', 'Alemán': 'de', 'Español': 'es', 'Francés': 'fr'}AVATAR_SEED = [123, 42]# Define el motor llmengine = 'OpenAI'
El AVATAR_SEED
se utiliza para generar diferentes iconos de avatar para diferentes chatbots.
Comenzamos estableciendo el diseño básico de la interfaz de usuario y estableciendo opciones para que el usuario seleccione:
# Establecer el título de la appst.title('Aplicación de aprendizaje de idiomas 🌍📖🎓')# Establecer la descripción de la appst.markdown("""Esta aplicación genera guiones de conversación o debate para ayudar en el aprendizaje de idiomas 🎯 Elija su configuración deseada y presione 'Generar' para comenzar 🚀""")# Agregar un selectbox para el modo de aprendizajelearning_mode = st.sidebar.selectbox('Modo de aprendizaje 📖', ('Conversación', 'Debate'))if learning_mode == 'Conversación': role1 = st.sidebar.text_input('Rol 1 🎭') action1 = st.sidebar.text_input('Acción 1 🗣️') role2 = st.sidebar.text_input('Rol 2 🎭') action2 = st.sidebar.text_input('Acción 2 🗣️') scenario = st.sidebar.text_input('Escenario 🎥') time_delay = 2 # Configurar el diccionario de roles role_dict = { 'rol1': {'nombre': role1, 'acción': action1}, 'rol2': {'nombre': role2, 'acción': action2} }else: scenario = st.sidebar.text_input('Tema de debate 💬') # Configurar el diccionario de roles role_dict = { 'rol1': {'nombre': 'Proponente'}, 'rol2': {'nombre': 'Oponente'} } time_delay = 5language = st.sidebar.selectbox('Idioma destino 🔤', LANGUAGES)session_length = st.sidebar.selectbox('Duración de la sesión ⏰', SESSION_LENGTHS)proficiency_level = st.sidebar.selectbox('Nivel de competencia 🏆', PROFICIENCY_LEVELS)
Observe la introducción de una variable time_delay
. Se utiliza para especificar el tiempo de espera entre la visualización de dos mensajes consecutivos. Si este retraso se establece en cero, los intercambios generados entre dos chatbots aparecerán en la aplicación rápidamente (limitado solo por el tiempo de respuesta de OpenAI). Sin embargo, para la experiencia del usuario, podría ser beneficioso permitir suficiente tiempo para que el usuario lea el mensaje generado antes de que aparezca el siguiente intercambio.
A continuación, inicializamos el estado de sesión Streamlit para almacenar datos de sesión específicos del usuario en la aplicación Streamlit:
if "bot1_mesg" not in st.session_state: st.session_state["bot1_mesg"] = []if "bot2_mesg" not in st.session_state: st.session_state["bot2_mesg"] = []if 'batch_flag' not in st.session_state: st.session_state["batch_flag"] = Falseif 'translate_flag' not in st.session_state: st.session_state["translate_flag"] = Falseif 'audio_flag' not in st.session_state: st.session_state["audio_flag"] = Falseif 'message_counter' not in st.session_state: st.session_state["message_counter"] = 0
Aquí respondemos dos preguntas:
1️⃣ En primer lugar, ¿por qué necesitamos “session_state”?
En Streamlit, cada vez que el usuario interactúa con la aplicación, Streamlit vuelve a ejecutar todo el script de arriba a abajo, actualizando la salida de la aplicación en consecuencia. Sin embargo, esta naturaleza reactiva de Streamlit puede plantear un desafío cuando desea mantener datos específicos del usuario o preservar el estado a través de diferentes interacciones o páginas dentro de la aplicación. Dado que Streamlit vuelve a cargar el script en cada interacción del usuario, las variables regulares de Python perderían sus valores y la aplicación se restablecería a su estado inicial.
Aquí es donde entra en juego el estado de sesión. El estado de sesión en Streamlit proporciona una forma de almacenar y recuperar datos que persisten durante la sesión del usuario, incluso cuando se vuelve a cargar la aplicación o el usuario navega entre diferentes componentes o páginas. Le permite mantener información estadística y preservar el contexto de la aplicación para cada usuario.
2️⃣ En segundo lugar, ¿qué variables se almacenan en el session_state?
” bot1_mesg ” es una lista, donde cada elemento de la lista es un diccionario que contiene los mensajes hablados por el primer chatbot. Tiene las siguientes claves: “rol”, “contenido” y “traducción”. La misma definición se aplica al ” bot2_mesg “.
” batch_flag” es una bandera booleana para indicar si los intercambios de conversación se muestran de una vez o con un retraso de tiempo. En el diseño actual, los chats entre dos bots aparecerán con un retraso de tiempo cuando su conversación se genere por primera vez. Después, el usuario puede querer ver las traducciones o agregar audio a la conversación generada, los mensajes de conversación almacenados (en ”bot1_mesg” y “bot2_mesg”) se mostrarán de una vez. Esto es beneficioso ya que no necesitamos llamar a la API de OpenAI nuevamente para reducir costos y latencia.
”translate_flag” y ”audio_flag” se utilizan para indicar si la traducción y/o audio se mostrarán junto a la conversación original.
”message_counter” es un contador que se incrementa en uno cada vez que se muestra un mensaje del chatbot. La idea es asignar el ID del mensaje con este contador, ya que Streamlit requiere que cada componente de la interfaz de usuario tenga un ID único.
Ahora podemos introducir la lógica de permitir que dos chatbots interactúen y generen conversaciones:
if 'dual_chatbots' not in st.session_state: if st.sidebar.button('Generar'): # Agregar una bandera para indicar si es la primera vez que se ejecuta el script st.session_state["first_time_exec"] = True with conversation_container: if learning_mode == 'Conversación': st.write(f"""#### La siguiente conversación ocurre entre {role1} y {role2} {scenario} 🎭""") else: st.write(f"""#### Debate 💬: {scenario}""") # Instanciar el sistema de chatbots doble dual_chatbots = DualChatbot(engine, role_dict, language, scenario, proficiency_level, learning_mode, session_length) st.session_state['dual_chatbots'] = dual_chatbots # Comenzar intercambios for _ in range(MAX_EXCHANGE_COUNTS[session_length][learning_mode]): output1, output2, translate1, translate2 = dual_chatbots.step() mesg_1 = {"rol": dual_chatbots.chatbots['role1']['name'], "contenido": output1, "traducción": translate1} mesg_2 = {"rol": dual_chatbots.chatbots['role2']['name'], "contenido": output2, "traducción": translate2} new_count = show_messages(mesg_1, mesg_2, st.session_state["message_counter"], time_delay=time_delay, batch=False, audio=False, translation=False) st.session_state["message_counter"] = new_count # Actualizar estado de sesión st.session_state.bot1_mesg.append(mesg_1) st.session_state.bot2_mesg.append(mesg_2)
Al ejecutar el script por primera vez, no habrá una clave ”dual_chatbots” almacenada en el state de la sesión (ya que el chatbot doble aún no ha sido creado). Como resultado, el fragmento de código mostrado arriba se ejecutará cuando el usuario presione el botón ”Generar” en la barra lateral. Los dos chatbots conversarán unas cuantas veces, y todos los mensajes de la conversación se registrarán en el state de la sesión. La función show_message()
es una función auxiliar diseñada para ser la única interfaz para dar estilo a la visualización de los mensajes. Volveremos a ella al final de esta sección.
Ahora, si el usuario interactúa con la aplicación y cambia algunas configuraciones, Streamlit volverá a ejecutar todo el script desde el principio. Dado que ya hemos generado el script de conversación deseado, no es necesario invocar la API de OpenAI nuevamente. En su lugar, simplemente podemos recuperar la información almacenada:
if 'dual_chatbots' in st.session_state: # Mostrar traducción if translate_col.button('Traducir al inglés'): st.session_state['translate_flag'] = True st.session_state['batch_flag'] = True # Mostrar texto original if original_col.button('Mostrar original'): st.session_state['translate_flag'] = False st.session_state['batch_flag'] = True # Agregar audio if audio_col.button('Reproducir audio'): st.session_state['audio_flag'] = True st.session_state['batch_flag'] = True # Recuperar conversación y chatbots generados mesg1_list = st.session_state.bot1_mesg mesg2_list = st.session_state.bot2_mesg dual_chatbots = st.session_state['dual_chatbots'] # Controlar la apariencia del mensaje if st.session_state["first_time_exec"]: st.session_state['first_time_exec'] = False else: # Mostrar mensaje completo with conversation_container: if learning_mode == 'Conversación': st.write(f"""#### {role1} y {role2} {scenario} 🎭""") else: st.write(f"""#### Debate 💬: {scenario}""") for mesg_1, mesg_2 in zip(mesg1_list, mesg2_list): new_count = show_messages(mesg_1, mesg_2, st.session_state["message_counter"], time_delay=time_delay, batch=st.session_state['batch_flag'], audio=st.session_state['audio_flag'], translation=st.session_state['translate_flag']) st.session_state["message_counter"] = new_count
Tenga en cuenta que hay otra bandera llamada ”first_time_exec” en el estado de sesión. Esto se utiliza para indicar si el script generado originalmente ya se ha mostrado en la aplicación. Si eliminamos esta comprobación, los mismos mensajes aparecerán dos veces al ejecutar la aplicación por primera vez.
Lo único que queda es la inclusión del resumen de los puntos clave de aprendizaje en la interfaz de usuario. Para eso, podemos usar st.expander
. En Streamlit, st.expander
es útil cuando tenemos una gran cantidad de contenido o información que queremos presentar de forma condensada, inicialmente oculto a la vista. Cuando el usuario hace clic en el expandible, el contenido dentro de él se expandirá o se contraerá, revelando u ocultando los detalles adicionales.
# Crear resumen para los puntos clave de aprendizaje summary_expander = st.expander('Puntos clave de aprendizaje') scripts = [] for mesg_1, mesg_2 in zip(mesg1_list, mesg2_list): for i, mesg in enumerate([mesg_1, mesg_2]): scripts.append(mesg['role'] + ': ' + mesg['content']) # Compilar resumen if "summary" not in st.session_state: summary = dual_chatbots.summary(scripts) st.session_state["summary"] = summary else: summary = st.session_state["summary"] with summary_expander: st.markdown(f"**Aquí está el resumen de aprendizaje:**") st.write(summary)
Dado que el resumen de los puntos clave de aprendizaje también se genera llamando a la API de OpenAI, podemos guardar el resumen generado en el estado de sesión para que el contenido se pueda recuperar si se ejecuta el script una segunda vez.
Finalmente, completamos el diseño de la interfaz de usuario de Streamlit con la función auxiliar show_message
:
def show_messages(mesg_1, mesg_2, message_counter, time_delay, batch=False, audio=False, translation=False): """Mostrar intercambios de conversación. Esta función auxiliar admite la visualización de textos originales, textos traducidos y habla de audio. Salida: ------- message_counter: contador actualizado para la clave ID """ for i, mesg in enumerate([mesg_1, mesg_2]): # Mostrar intercambio original () message(f"{mesg['content']}", is_user=i==1, avatar_style="bottts", seed=AVATAR_SEED[i], key=message_counter) message_counter += 1 # Limitar el intervalo de tiempo entre las conversaciones # (este retraso temporal solo aparece al generar # el script de conversación por primera vez) if not batch: time.sleep(time_delay) # Mostrar intercambio traducido if translation: message(f"{mesg['translation']}", is_user=i==1, avatar_style="bottts", seed=AVATAR_SEED[i], key=message_counter) message_counter += 1 # Agregar audio al intercambio if audio: tts = gTTS(text=mesg['content'], lang=AUDIO_SPEECH[language]) sound_file = BytesIO() tts.write_to_fp(sound_file) st.audio(sound_file) return message_counter
Algunos puntos requieren una explicación adicional:
1️⃣ El objeto message()
Esto es parte de la biblioteca streamlit_chat
y se utiliza para mostrar mensajes. En su forma más simple, tenemos:
import streamlit as stfrom streamlit_chat import messagemessage("Hola, soy un chatbot, ¿en qué puedo ayudarte?") message("Oye, ¿qué es un chatbot?", is_user=True)

donde el argumento is_user
determina si el mensaje debe estar alineado a la izquierda o a la derecha. En nuestro fragmento de código para show_message
, también hemos especificado avatar_style
y seed
para establecer los iconos de avatar para dos chatbots. El argumento key
es simplemente para asignar un ID único a cada mensaje, como se requiere por Streamlit.
2️⃣ Texto a voz
Aquí, usamos la biblioteca gTTS para crear habla de audio en el idioma objetivo en función del script generado. Esta biblioteca es fácil de usar, pero tiene una limitación: solo se puede tener una voz. Después de que se genera el objeto de audio, podemos usar st.audio
para crear un reproductor de audio para cada mensaje en la aplicación.
¡Genial! ¡Ya hemos completado el diseño de UI 🙂 Escribe el siguiente comando en tu terminal:
streamlit run app.py
Deberías ver la aplicación en tu navegador y ser capaz de interactuar con ella. ¡Buen trabajo!

5. Aprendizajes y Futuras Extensiones
Antes de terminar, quiero compartir contigo algunos aprendizajes clave de este proyecto y las posibles direcciones para futuras mejoras.
1️⃣ ¿Cómo detener la conversación?
Este problema es en realidad más difícil de lo que parece si quieres hacerlo bien. Idealmente, nos gustaría que la conversación terminara de manera natural. Sin embargo, en algunos de mis experimentos, noté que los chatbots solo seguirían diciendo “gracias” o “adiós” entre ellos hacia el final de la conversación, lo que alargaba innecesariamente la conversación. Algunas soluciones potenciales para este problema incluyen:
- Límite estricto de rondas de intercambio: esta es quizás la solución más fácil y también es lo que hemos adoptado en este proyecto. Sin embargo, puede que no siempre sea ideal ya que puede llevar a conversaciones terminadas prematuramente. Como solución alternativa, hemos instruido al bot en el
SystemMessage
para que termine la conversación dentro de un número determinado de intercambios. - Uso de “Palabras Señales”: el chatbot podría programarse para decir palabras específicas de “señal” (por ejemplo, “Conversación terminada”) cuando considere que la conversación ha terminado de manera natural. Se podría implementar una lógica para detectar estas “palabras señales” y finalizar el bucle en consecuencia.
- Posprocesamiento de la Conversación: una vez que los chatbots hayan generado la conversación, se podría implementar otro LLM como “editor” para podar la conversación. Esto podría ser un enfoque efectivo. Sin embargo, sus inconvenientes pueden incluir diseñar una nueva propuesta, incurrir en costos adicionales al llamar a la API de OpenAI nuevamente y aumentar la latencia.
2️⃣ ¿Cómo controlar la complejidad del lenguaje?
En mi experiencia, los chatbots desarrollados parecían tener dificultades para seguir las instrucciones sobre la complejidad del lenguaje utilizado en el chat: a veces aparecerá un nivel “intermedio” de uso del lenguaje incluso cuando el nivel de competencia se establece como “principiante”. Una razón puede ser que el diseño actual de la propuesta no es suficiente para especificar el matiz entre los diferentes niveles de complejidad.
Hay un par de formas de abordar este problema: para empezar, podemos realizar aprendizaje en contexto. Es decir, proporcionamos ejemplos a los chatbots y les mostramos qué tipo de uso del lenguaje deseamos para los diferentes niveles de complejidad. Otra forma de avanzar es similar a lo que hemos discutido anteriormente: podríamos usar otro LLM para ajustar la complejidad de la conversación. Esencialmente, este LLM adicional puede usar el guion generado como punto de partida y reescribir un nuevo guion para que coincida con el nivel de competencia deseado del usuario.
3️⃣ ¿Mejor biblioteca de texto a voz?
El proyecto actual solo utilizó la simple biblioteca gTTS para sintetizar voces, hay margen de mejora. Bibliotecas más avanzadas ofrecen soporte multilingüe, soporte para múltiples hablantes y una voz más natural. Algunas de ellas son: pyttsx3, Amazon Polly, IBM Watson TTS, Microsoft Azure Cognitive Services TTS, Coqui.ai-TTS, así como una versión reciente de Meta, Voicebox.
4️⃣ ¿Más pruebas con diferentes escenarios?
Debido a restricciones de tiempo, solo probé algunos escenarios para determinar si los chatbots pueden generar conversaciones significativas. Estas pruebas identificaron problemas en mi diseño de propuesta inicial, proporcionando oportunidades para su refinamiento. Pruebas adicionales de escenarios probablemente revelarían áreas pasadas por alto y sugerirían formas de mejorar la propuesta. He compilado una lista completa de escenarios típicos de “conversación” y temas de “debate”. Siéntete libre de probarlos y evaluar el rendimiento del diseño de propuesta actual.
5️⃣ ¿Incluir otras formas de IA generativa?
Este proyecto exploró principalmente técnicas de IA generativa de texto a texto (chatbot) y texto a voz. Podríamos mejorar aún más la experiencia del usuario aprovechando otras formas de IA generativa, como texto a imagen o texto a video.
- Texto a imagen: para cada escenario ingresado por el usuario, podríamos utilizar modelos de texto a imagen para crear figuras correspondientes. Mostrar estas figuras junto con la conversación generada puede proporcionar contexto visual y mejorar el compromiso de aprendizaje de idiomas. Se podrían utilizar modelos como StableDiffusion, Midjourney y DALL-E para este propósito.
- Texto a video: para hacer que la aplicación se centre más en los medios multimedia, podríamos generar videos basados en escenarios de entrada. Una herramienta como RunwayML podría ayudar con esto. Además, incluso podríamos intentar crear humanos digitales para presentar la conversación, lo que podría mejorar dramáticamente la experiencia del usuario si se ejecuta correctamente. Synthesia podría ser una herramienta adecuada para este propósito.
¿Más opciones de aprendizaje de idiomas? 6️⃣
En la actualidad, nuestra aplicación se centra principalmente en los modos de aprendizaje de “conversación” y “debate”. Sin embargo, el potencial de crecimiento es sustancial. Por ejemplo, podríamos introducir otros modos de aprendizaje como “narración de historias” y “aprendizaje cultural”. Además, podríamos ampliar la interacción de los chatbots para adaptarse a escenarios más profesionales y técnicos. Estos podrían incluir configuraciones como reuniones, negociaciones o sectores como ventas y marketing, derecho, ingeniería y más. Esta característica podría ser útil para los estudiantes de idiomas que buscan fortalecer su competencia lingüística profesional.
6. Conclusión
¡Guau, qué viaje! Muchas gracias por acompañarme hasta aquí 🤗 Desde el diseño de las indicaciones hasta la creación de los chatbots, hemos cubierto mucho terreno. Usando LangChain y Streamlit, hemos construido un sistema funcional de chatbots duales que se puede utilizar para aprender idiomas, ¡no está mal!
Espero que nuestra aventura haya despertado tu curiosidad y hecho que tus engranajes giren. Sigamos explorando, innovando y aprendiendo juntos. ¡Feliz codificación!