📚 CLASE: CONSTRUCCIÓN DE UN ASISTENTE CONVERSACIONAL CON LANGCHAIN Y RAG
━━━━━━━━━━━━━━━━━━
🎯 IDEA PRINCIPAL
🟢 El objetivo es combinar un sistema RAG con memoria conversacional para crear un asistente que consulte documentos propios y mantenga el contexto de una conversación.
🔵 RAG permite recuperar información relevante desde una base vectorial antes de generar una respuesta.
🔵 Memoria conversacional permite utilizar mensajes anteriores para interpretar preguntas nuevas.
🔵 Prompt template define las instrucciones, el contexto y el formato que debe seguir el modelo.
🟣 La idea clave es integrar estos componentes en una cadena que conecte recuperación, contexto conversacional y generación de respuestas.
━━━━━━━━━━━━━━━━━━
⚡ RESUMEN ULTRA RÁPIDO
🔵 Retriever: busca fragmentos relevantes en el Vector Store.
🔵 Chat history: conserva los mensajes anteriores de una sesión.
🔵 Contextualización de preguntas: transforma preguntas dependientes del historial en consultas que se puedan buscar en los documentos.
🔵 RAG chain: conecta los documentos recuperados con el prompt y el modelo de lenguaje.
🔵 Session ID: identifica la conversación cuyo historial debe recuperarse.
🟢 Flujo general: pregunta + historial → consulta contextualizada → búsqueda documental → prompt con contexto → respuesta.
━━━━━━━━━━━━━━━━━━
🧠 MODELO MENTAL: CÓMO SE CONECTAN LAS PIEZAS
Imagina que tienes un PDF con recetas y preguntas al asistente:
- Primera pregunta: «¿Qué ingredientes necesito para preparar avena con frutas?»
- Segunda pregunta: «¿Y cuánto necesito de leche?»
🟢 La memoria permite interpretar que «leche» se refiere a la receta anterior.
🟢 El retriever consulta los documentos y recupera el fragmento relacionado con esa receta.
🟢 El modelo recibe la pregunta, el historial y el contexto recuperado para generar una respuesta coherente.
🔴 Importante: la memoria no sustituye al RAG. La memoria conserva la conversación; el RAG recupera información documental. Son componentes complementarios.
━━━━━━━━━━━━━━━━━━
🛠️ IMPLEMENTACIÓN PRÁCTICA CON LANGCHAIN
Como esta clase utiliza cadenas clásicas de LangChain, ten presente que, en entornos con LangChain 1.x, algunas funciones se importan desde langchain_classic.
🔵 1. Instala las dependencias
pip install -U langchain-classic langchain-openai langchain-core python-dotenv
🔵 2. Importa los componentes
Este ejemplo parte de que ya tienes un vector_store creado y cargado con documentos, como en la clase anterior.
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import (
ChatPromptTemplate,
MessagesPlaceholder,
)
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_classic.chains import (
create_history_aware_retriever,
create_retrieval_chain,
)
from langchain_classic.chains.combine_documents import (
create_stuff_documents_chain,
)
load_dotenv()
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0,
)
retriever = vector_store.as_retriever(
search_kwargs={"k": 3}
)
🟡 temperature=0 favorece respuestas menos variables, pero no garantiza que el modelo nunca invente información.
🔵 3. Crea el prompt que contextualiza las preguntas
contextualize_q_system_prompt = """
Reformula la pregunta más reciente para que se entienda
sin depender de mensajes anteriores.
Utiliza el historial únicamente para aclarar referencias.
No respondas la pregunta.
"""
contextualize_q_prompt = ChatPromptTemplate.from_messages([
("system", contextualize_q_system_prompt),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}"),
])
history_aware_retriever = create_history_aware_retriever(
llm,
retriever,
contextualize_q_prompt,
)
🔵 Este componente utiliza el historial para convertir una pregunta como «¿Y cuánto cuesta?» en una consulta comprensible para el buscador.
🔵 4. Crea el prompt que responde usando los documentos
qa_system_prompt = """
Eres un asistente que responde exclusivamente con el
contexto documental proporcionado.
Reglas:
- Utiliza únicamente la información del contexto.
- Si el contexto no contiene la respuesta, indica que
no encontraste información suficiente en los documentos.
- No completes los vacíos con conocimientos propios.
- No presentes suposiciones como hechos.
Contexto documental:
{context}
"""
qa_prompt = ChatPromptTemplate.from_messages([
("system", qa_system_prompt),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}"),
])
question_answer_chain = create_stuff_documents_chain(
llm,
qa_prompt,
)
rag_chain = create_retrieval_chain(
history_aware_retriever,
question_answer_chain,
)
🔴 Aunque el prompt establece límites claros, las instrucciones por sí solas no garantizan que el modelo respete esas reglas en todos los casos.
🔵 5. Añade memoria por sesión
historiales = {}
def get_session_history(session_id: str):
if session_id not in historiales:
historiales[session_id] = InMemoryChatMessageHistory()
return historiales[session_id]
conversational_rag_chain = RunnableWithMessageHistory(
rag_chain,
get_session_history,
input_messages_key="input",
history_messages_key="chat_history",
output_messages_key="answer",
)
🟡 Esta memoria vive en la RAM del proceso: se pierde al reiniciar el programa y no sirve, por sí sola, para conservar conversaciones entre despliegues.
🔵 6. Ejecuta una consulta
resultado = conversational_rag_chain.invoke(
{"input": "¿Qué ingredientes necesito para preparar avena?"},
config={"configurable": {"session_id": "usuario-001"}},
)
print(resultado["answer"])
Prueba una segunda consulta con el mismo identificador de sesión:
resultado = conversational_rag_chain.invoke(
{"input": "¿Y cuánto necesito de leche?"},
config={"configurable": {"session_id": "usuario-001"}},
)
print(resultado["answer"])
🟢 Ambas consultas comparten el historial. Si utilizas otro session_id, se creará una conversación diferente.
━━━━━━━━━━━━━━━━━━
🔴 PROBLEMA IMPORTANTE: ¿CÓMO EVITAR QUE RESPONDA SOBRE CUALQUIER TEMA?
En los comentarios de esta clase aparece una duda fundamental: si preguntas «¿Por qué las personas dicen mentiras?» o «¿Qué es Python?», el modelo puede contestar aunque la respuesta no esté en los PDF.
El motivo es que RAG no limita automáticamente el conocimiento del modelo. Recuperar documentos y restringir las respuestas son problemas relacionados, pero distintos.
Para mejorar el control, aplica estas medidas:
🟢 1. Comprueba los resultados recuperados. Antes de generar una respuesta, verifica que exista suficiente evidencia documental.
🟢 2. Utiliza un umbral de relevancia. Si ningún fragmento supera el criterio establecido, responde que no hay información suficiente. El umbral debe calibrarse con consultas reales; no existe un valor universal.
🟢 3. Filtra por documento o categoría. Si un cocinero solo debe consultar procesos de cocina, guarda metadatos como categoria="cocina" y aplica un filtro durante la recuperación.
🟢 4. Conserva las fuentes. Incluye nombre del archivo y página en los metadatos para poder comprobar de dónde sale cada respuesta.
🟢 5. Evalúa casos negativos. Prueba preguntas que claramente no aparecen en los documentos y comprueba que el asistente se abstenga de responder.
🟣 Para sistemas sensibles, no dependas exclusivamente del prompt: implementa controles de recuperación y validación antes de entregar la respuesta.
━━━━━━━━━━━━━━━━━━
⚠️ ERRORES FRECUENTES EN ESTA CLASE
🔴 ModuleNotFoundError: No module named 'langchain.chains'
En instalaciones de LangChain 1.x, prueba a instalar langchain-classic y actualizar los imports correspondientes. No mezcles paquetes de distintas versiones sin comprobar su compatibilidad.
🔴 NameError: name 'context' is not defined
Revisa si ejecutaste las celdas que definen el prompt y la cadena, y si estás utilizando exactamente los nombres de variables esperados.
🔴 AttributeError: 'tuple' object has no attribute 'page_content'
Una función que espera objetos Document está recibiendo tuplas. Revisa qué devuelve el retriever y qué estructura estás pasando a la cadena de documentos.
🔴 El asistente dice que no encuentra información aunque los documentos sí la contienen.
Comprueba la extracción del PDF, los fragmentos almacenados, la consulta que llega al retriever y los resultados recuperados. También verifica que el índice y los embeddings correspondan al contenido actual.
🟡 En Google Colab, si cambias las dependencias, reinicia el entorno cuando sea necesario y ejecuta las celdas desde el principio para reconstruir las variables.
━━━━━━━━━━━━━━━━━━
🧪 ACCIONES PARA HOY
🟢 Construye la cadena RAG y prueba dos preguntas relacionadas que dependan del historial.
🟢 Prueba una pregunta que no pueda responderse con tus documentos.
🟢 Inspecciona los tres fragmentos recuperados para comprobar si realmente contienen la respuesta.
🟢 Ejecuta una consulta con un identificador de sesión diferente y verifica que no reutilice el historial anterior.
🟢 Documenta los errores encontrados y las correcciones para que puedas reproducir el experimento.
━━━━━━━━━━━━━━━━━━
🏁 CIERRE: QUÉ DEBES RECORDAR
🔵 RAG aporta información recuperada desde documentos propios.
🔵 Memoria aporta continuidad entre mensajes de una conversación.
🔵 Prompt templates definen las instrucciones y cómo se presenta el contexto al modelo.
🔵 Retriever + LLM + memoria permiten construir un asistente conversacional más útil que una consulta aislada a un modelo.
🟣 El siguiente paso es aprender a integrar herramientas y agentes, donde el sistema no solo responde preguntas, sino que también puede decidir qué acción realizar según las instrucciones y los recursos disponibles.