Construyendo el Motor. LangChain, LCEL y tu primer sistema RAG en Python

En los artículos anteriores de esta serie, hemos estado fabricando las piezas de un coche de Fórmula 1 por separado. Ya entendemos cómo funcionan los neumáticos (Modelos de Embeddings y Bases de Datos Vectoriales) y tenemos el motor de combustión listo en nuestro garaje (Modelos Locales como Llama 3 ejecutándose con Ollama).

Pero si pones un motor al lado de unos neumáticos, no tienes un coche. Necesitas un chasis, una transmisión y un volante que conecte todo para que la información fluya.

En el mundo de la Inteligencia Artificial, ese chasis se llama LangChain. Hoy nos ponemos el mono de trabajo, abrimos nuestro editor de Python y vamos a construir nuestro primer sistema RAG (Retrieval-Augmented Generation) 100% funcional y privado.


1. ¿Qué es LangChain y por qué todo ha cambiado?

LangChain es el framework (entorno de trabajo) open-source estándar de la industria para desarrollar aplicaciones impulsadas por modelos de lenguaje. Es el equivalente a lo que es React para el desarrollo web o Spring para Java. Su trabajo es ser el pegamento universal: tiene conectores para cientos de bases de datos, APIs y modelos locales.

Sin embargo, si buscas tutoriales de LangChain de 2023 o principios de 2024, te encontrarás con un problema técnico enorme: casi todo ese código está obsoleto.

En sus inicios, LangChain usaba herramientas llamadas Chains (Cadenas), como RetrievalQAChain. Eran «cajas negras». Tú le dabas una pregunta, la caja negra hacía su magia interna, y te escupía una respuesta.

  • El problema SecDevOops: En producción, si la IA alucinaba o fallaba, como ingenieros no teníamos forma de depurar (debuggear) en qué punto exacto se había roto el proceso. ¿Falló la base de datos? ¿Falló el prompt? Era imposible saberlo.

Por eso, LangChain revolucionó su propio sistema e introdujo el estándar actual que vamos a usar: LCEL.


2. La Filosofía LCEL (LangChain Expression Language)

LCEL destruye las cajas negras y adopta una filosofía de «tuberías de Unix» transparente y declarativa. En lugar de esconder la lógica, conectamos piezas modulares (llamadas Runnables) utilizando el operador de tubería o pipe (|).

El flujo de un RAG moderno en LCEL se lee casi como inglés básico:

Cadena = Buscar_Contexto | Inyectar_Prompt | Modelo_Llama3 | Limpiar_Texto

La salida exacta de un componente se convierte en la entrada del siguiente. Si algo falla, puedes imprimir el estado de la tubería en cualquier punto intermedio. Además, LCEL maneja automáticamente el procesamiento en paralelo y el streaming (para que las letras vayan apareciendo en la pantalla poco a poco, como en ChatGPT).


3. Manos a la obra: Nuestro primer RAG en Python

Vamos a unir las tres piezas de nuestro MVP:

  1. El Bibliotecario (ChromaDB) buscará los fragmentos de nuestros PDFs privados.

  2. El Prompt juntará nuestra pregunta con esos fragmentos.

  3. El Escritor (Llama 3 vía Ollama local) leerá todo y redactará.

Asegúrate de tener instaladas las librerías modernas (pip install -U langchain-core langchain-community langchain-chroma langchain-huggingface) y ejecuta este script:

Python
import chromadb
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.llms import Ollama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# 1. EL BIBLIOTECARIO (Conectamos a la Base de Datos Vectorial)
print("1. Despertando a ChromaDB...")
# Usamos un modelo ligero para convertir texto a vectores matemáticos
embedding_model = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
chroma_client = chromadb.HttpClient(host="localhost", port=8000)

# Suponiendo que ya hemos "ingerido" nuestros PDFs en esta colección
vector_db = Chroma(
    client=chroma_client,
    collection_name="rag_corporativo",
    embedding_function=embedding_model
)

# Configuramos el buscador para que traiga los 3 mejores fragmentos (k=3)
retriever = vector_db.as_retriever(search_kwargs={"k": 3})

# 2. EL ESCRITOR (Conectamos a Ollama Local)
print("2. Despertando a Llama 3...")
llm = Ollama(model="llama3")

# 3. LAS REGLAS (El Prompt Template)
prompt_template = ChatPromptTemplate.from_template("""
Eres un asistente técnico de la empresa. Responde a la pregunta usando ÚNICAMENTE el contexto proporcionado.
Si no sabes la respuesta basándote en el contexto, di "No puedo responder esto con el documento proporcionado". No inventes información.

<contexto>
{context}
</contexto>

Pregunta: {input}
""")

# Función auxiliar para unir los fragmentos de texto devueltos por Chroma
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

# 4. LA CADENA LCEL PURA (La Tubería de Producción)
print("3. Construyendo la tubería LCEL...\n")
rag_chain = (
    # A. Buscamos el contexto y pasamos la pregunta del usuario intacta
    {"context": retriever | format_docs, "input": RunnablePassthrough()}
    # B. Se lo inyectamos al Prompt
    | prompt_template
    # C. Se lo mandamos a Llama 3
    | llm
    # D. Limpiamos la salida para obtener solo texto puro
    | StrOutputParser()
)

# 5. ¡LA PRUEBA DE FUEGO!
pregunta_usuario = "¿Cuál es la política de vacaciones corporativa?"
print(f"👤 Usuario: {pregunta_usuario}")
print("🤖 IA Pensando (Procesando en local)...\n")

# Ejecutamos la tubería RAG
respuesta = rag_chain.invoke(pregunta_usuario)

print("="*50)
print(respuesta)
print("="*50)

¡Felicidades! Si has seguido esta serie, acabas de ejecutar tu primer sistema de Inteligencia Artificial que lee tus documentos privados y te responde de forma coherente y segura, todo ejecutándose en tu propia máquina (Zero Data Leakage).

Has construido un «Hola Mundo» glorioso de la ingeniería de IA.

Pero como buenos ingenieros, sabemos que el primer MVP nunca sobrevive al contacto con los usuarios reales. Este código es lo que llamamos un RAG Ingenuo (Naive RAG). Es literal (si el usuario busca mal, fallará) y es amnésico (no recuerda la pregunta anterior).

En nuestro próximo post, derribaremos este código para introducir RAG Avanzado: dotaremos a la IA de memoria a corto plazo, usaremos modelos expansores de consultas (Multi-Query) y añadiremos Re-Ranking. El verdadero grado Enterprise empieza ahora.


🚧 Road to Senior: Tareas AI para profundizar

TODO 1 (Debugeo en LCEL): «En LangChain, ¿cómo puedo usar el método .bind() o agregar un callback de consola (ConsoleCallbackHandler) en una tubería LCEL pura para ver exactamente qué texto se está inyectando en la variable {context} de mi prompt antes de que llegue al LLM?»

TODO 2 (Formatos de Salida): «Mi aplicación de frontend necesita recibir la respuesta del LLM no como un texto plano, sino como un JSON estructurado. Explícame cómo sustituir el StrOutputParser de LCEL por un JsonOutputParser o usar Pydantic para forzar a Llama 3 a devolver siempre un formato JSON válido.»

Categorías