LCEL has been the right way to build chains in LangChain since 2024. This guide covers the syntax, composition patterns, and common mistakes we see in production code.
Everything in LCEL implements the `Runnable` interface: a prompt, a chat model, an output parser, a retriever, even a decorated Python function. This interface guarantees four synchronous methods and their async equivalents: `invoke`, `batch`, `stream`, and their `a*` versions. The `|` operator isn't trivial syntactic sugar: internally it builds a `RunnableSequence` that chains one component's output as the next one's input.
This means you can insert pure Python business logic in the middle of an LLM chain without artificial wrappers.
The most common production pattern is retrieval + generation. With LCEL it's expressed in a linear, readable way:
The input dictionary automatically becomes a `RunnableParallel`: each key runs concurrently, not sequentially, which reduces latency when there are multiple independent data sources.
When you need to run several sub-chains over the same input — for example, generating a summary and extracting entities at the same time — `RunnableParallel` makes that explicit:
For simple conditional logic (no cycles, which is LangGraph territory), `RunnableBranch` lets you route based on the input's content, useful for classifying user intent before choosing the right prompt.
A direct benefit of LCEL is that streaming works consistently across the whole chain, not just on the final call to the model:
This is critical for UX in conversational applications: the user sees the response generate in real time instead of waiting for the whole block, even when there are retrieval steps before the model.
LCEL exposes `.with_retry()` and `.with_fallbacks()` directly on any `Runnable`, avoiding the need to wrap calls in manual try/except blocks:
In production, we combine this with `with_fallbacks` pointing to a second provider (for example, Anthropic) for real resilience against API outages — something that required custom code in the old agent layer.
The most frequent one is mixing `Runnable` with the old `Chain` API in the same project for no reason, which duplicates error-handling patterns. The second is not using `.batch()` when processing batches of documents, instead leaving a `for` loop with sequential `.invoke()` calls, losing the internal parallelism LangChain manages with a `ThreadPoolExecutor`. The third is not typing the chain's input with `RunnableConfig` when you need to pass metadata (like `run_name` or `tags`) for traceability in LangSmith, which complicates debugging later.
Carlos Montiel is an enterprise AI solutions architect. He implements LLMs, Agents, RAG, and orchestrators for companies across Guatemala and Latin America. Reach out for a consultation.
Contact Carlos Montiel