LangChain Expression Language (LCEL): guía práctica

By Carlos Montiel | Especialista en IA Empresarial
Publicado: 2026-07-28 | Por: Carlos Montiel | Lectura: ~4 minutos

LCEL es la forma correcta de construir cadenas en LangChain desde 2024. Esta guía cubre la sintaxis, los patrones de composición y los errores comunes que vemos en código de producción.

Qué es realmente un Runnable

Todo en LCEL implementa la interfaz `Runnable`: un prompt, un modelo de chat, un parser de salida, un retriever, incluso una función de Python decorada. Esta interfaz garantiza cuatro métodos síncronos y sus equivalentes asíncronos: `invoke`, `batch`, `stream`, y sus versiones `a*`. El operador `|` no es azúcar sintáctica trivial: internamente construye un `RunnableSequence` que encadena la salida de un componente como entrada del siguiente.

from langchain_core.runnables import RunnableLambda doblar = RunnableLambda(lambda x: x * 2) sumar_uno = RunnableLambda(lambda x: x + 1) pipeline = doblar | sumar_uno print(pipeline.invoke(5)) # 11

Esto significa que puedes insertar lógica de negocio pura de Python en medio de una cadena de LLM sin envolturas artificiales.

Componiendo un pipeline de RAG con pipe

El patrón más común en producción es recuperación + generación. Con LCEL se expresa de forma lineal y legible:

from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser from langchain_openai import ChatOpenAI retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) prompt = ChatPromptTemplate.from_template( "Contexto:\n{context}\n\nPregunta: {question}\nResponde solo con el contexto dado." ) def formatear_docs(docs): return "\n\n".join(d.page_content for d in docs) rag_chain = ( {"context": retriever | formatear_docs, "question": RunnablePassthrough()} | prompt | ChatOpenAI(model="gpt-4o-mini", temperature=0) | StrOutputParser() ) respuesta = rag_chain.invoke("¿Cuál es la política de reembolsos?")

El diccionario de entrada se convierte automáticamente en un `RunnableParallel`: cada clave se ejecuta de forma concurrente, no secuencial, lo que reduce latencia cuando hay múltiples fuentes de datos independientes.

RunnableParallel y RunnableBranch

Cuando necesitas ejecutar varias sub-cadenas sobre la misma entrada —por ejemplo, generar un resumen y extraer entidades al mismo tiempo— `RunnableParallel` lo hace explícito:

from langchain_core.runnables import RunnableParallel analisis = RunnableParallel( resumen=prompt_resumen | llm | StrOutputParser(), entidades=prompt_entidades | llm | StrOutputParser(), ) resultado = analisis.invoke({"texto": documento}) # resultado = {"resumen": "...", "entidades": "..."}

Para lógica condicional simple (sin ciclos, que es terreno de LangGraph), `RunnableBranch` permite enrutar según el contenido de entrada, útil para clasificar la intención del usuario antes de elegir el prompt correcto.

Streaming token por token

Un beneficio directo de LCEL es que el streaming funciona de forma consistente en toda la cadena, no solo en la llamada final al modelo:

for chunk in rag_chain.stream("¿Cuánto dura la garantía?"): print(chunk, end="", flush=True)

Esto es crítico para UX en aplicaciones conversacionales: el usuario ve la respuesta generarse en tiempo real en vez de esperar el bloque completo, incluso cuando hay pasos de recuperación antes del modelo.

Manejo de errores y reintentos

LCEL expone `.with_retry()` y `.with_fallbacks()` directamente sobre cualquier `Runnable`, lo cual evita envolver llamadas en bloques try/except manuales:

llm_robusto = ChatOpenAI(model="gpt-4o-mini").with_retry( stop_after_attempt=3 ).with_fallbacks([ChatOpenAI(model="gpt-4o-mini", temperature=0.2)])

En producción, combinamos esto con `with_fallbacks` apuntando a un segundo proveedor (por ejemplo, Anthropic) para tener resiliencia real ante caídas de API, algo que en la capa de agentes antigua requería código custom.

Errores comunes que vemos en auditorías de código

El más frecuente es mezclar `Runnable` con la vieja API de `Chain` en el mismo proyecto sin razón, lo que duplica patrones de manejo de errores. El segundo es no usar `.batch()` cuando se procesan lotes de documentos, dejando en su lugar un `for` con `.invoke()` secuencial, perdiendo el paralelismo interno que LangChain gestiona con un `ThreadPoolExecutor`. El tercero es no tipar la entrada de la cadena con `RunnableConfig` cuando se necesita pasar metadata (como `run_name` o `tags`) para trazabilidad en LangSmith, lo cual complica el debugging después.

Carlos Montiel
Arquitecto de Soluciones IA Empresarial
Especialista en LLMs, Agentes y Orquestación
guatemalia.com/#contacto · info@guatemalia.com

¿Necesitas implementar IA en tu empresa?

Carlos Montiel es arquitecto de soluciones IA empresarial. Implementa LLMs, Agentes, RAG y orquestadores en empresas de Guatemala y Latinoamérica. Contáctalo para una consultoría.

Contactar a Carlos Montiel

info@guatemalia.com