Runnables and LCEL
Purpose and Scope
Runnables are the shared execution contract behind many LangChain components, including prompt templates, chat models, retrievers, parsers, tools, and composed chains. LCEL, the LangChain Expression Language, is the composition style that lets those components be connected into pipelines with the pipe operator, while preserving a common runtime surface for invocation, batching, streaming, tracing, configuration, and graph inspection. This page explains how to think about that surface when building an application chain, debugging behavior, or preparing a runnable for evaluation in LangSmith.
Sources: libs/core/langchain_core/runnables/base.py, libs/core/langchain_core/runnables/config.py
The important idea is that a chain is still a runnable after composition. A prompt can produce messages, a model can produce an AI response, and an output parser can produce a final string, yet the combined object can be invoked as one unit. Official LangSmith evaluation guidance relies on this property: a prompt, model, and parser chain can be passed directly to evaluation APIs as long as the example input keys match the runnable input schema. That makes the runnable contract the bridge between local composition and external tooling such as evaluation, tracing, and deployment workflows.
Sources: libs/core/langchain_core/runnables/base.py, libs/core/langchain_core/runnables/config.py
Relevant Source Files
- libs/core/langchain_core/runnables/base.py — Defines the central Runnable and RunnableSerializable contracts used by composed chains, model wrappers, prompt templates, parsers, and helper wrappers.
- libs/core/langchain_core/runnables/config.py — Defines RunnableConfig, config keys, config merging behavior, callback manager helpers, concurrency helpers, and context propagation utilities.
- libs/core/langchain_core/runnables/configurable.py — Implements DynamicRunnable and the runtime selection behavior behind configurable fields and configurable alternatives.
- libs/core/langchain_core/runnables/fallbacks.py — Implements RunnableWithFallbacks, including ordered fallback attempts, handled exception classes, and optional exception injection into fallback inputs.
- libs/core/langchain_core/runnables/graph.py — Defines Graph-related node, edge, branch, style, and rendering data structures used when inspecting runnable composition.
Core Primitives
The runnable primitive is intentionally broader than a single model call. It represents a typed transformation from an input value to an output value, with synchronous, asynchronous, batched, and streaming execution variants. A composed LCEL chain uses the same primitive at every level, so a sequence can be treated as one callable unit, while each child still receives propagated runtime configuration. This is why the same mental model applies when invoking a model directly, calling a retriever, evaluating a full chain, or embedding a chain inside another chain.
Sources: libs/core/langchain_core/runnables/base.py, libs/core/langchain_core/runnables/config.py
RunnableConfig is the runtime envelope for a call. The config type includes tags, metadata, callbacks, run name, maximum concurrency, recursion limit, configurable values, and run identifier. It is intentionally partial, so small config fragments can be merged rather than replaced. The source comments describe parent-to-child propagation through a context variable, allowing a parent runnable to set values such as tags while children add their own values. This design matters for observability because callbacks and tracing metadata can follow the whole chain without every component manually forwarding every option.
Sources: libs/core/langchain_core/runnables/config.py
Dynamic configuration is the mechanism for changing selected runnable attributes at call time without rebuilding the whole chain. DynamicRunnable wraps a default serializable runnable and resolves the concrete runnable during prepare. Its methods delegate input schema, output schema, graph, and invocation behavior to the prepared runnable after merging stored and call-time config. The source names configurable_fields and configurable_alternatives as the intended user-facing entry points, which means application code can expose model choices, temperatures, prompt variants, or other declared alternatives through the config envelope rather than hard-coding them in the chain construction path.
Sources: libs/core/langchain_core/runnables/configurable.py, libs/core/langchain_core/runnables/config.py
Execution Flow
A typical LCEL flow starts by constructing small components and joining them into a pipeline. The official docs show the familiar shape: a chat prompt template receives a text input, a chat model generates a response, and a string output parser converts the model output into the final value. After composition, the caller invokes the chain with the same input keys expected by the first component. If the same chain is passed to an evaluator, examples must provide matching input keys, and a non-dictionary output may be placed under a default output key by the evaluation harness.
Sources: libs/core/langchain_core/runnables/base.py, libs/core/langchain_core/runnables/config.py
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate([("system", "Classify the user text."), ("user", "{text}")])
model = init_chat_model("openai:gpt-5.5")
chain = prompt | model | StrOutputParser()
result = chain.invoke({"text": "hello"}, config={"tags": ["demo"], "run_name": "classifier"})Batching and streaming are part of the same contract rather than separate framework concepts. Batch execution uses config lists and concurrency helpers so multiple inputs can be processed with controlled parallelism. The max_concurrency config key gives callers a common way to limit simultaneous work, which is especially useful when a chain fans out to provider APIs, retrievers, or custom functions. Streaming keeps the runnable abstraction useful for user interfaces and long-running agent-like flows because callers can consume incremental values while still benefiting from tags, metadata, callbacks, and inherited config.
Sources: libs/core/langchain_core/runnables/base.py, libs/core/langchain_core/runnables/config.py
Configuration and Runtime Behavior
Configuration merging is one of the key differences between a simple function pipeline and an application runtime. The config module distinguishes full config keys from copiable keys, and it treats tags, metadata, callbacks, and configurable values as values that can be copied through nested calls. It also excludes sensitive configurable values such as api_key from tracing metadata. That exclusion is a practical safety boundary: callers can still provide credentials or runtime selectors through config, but tracing systems should not automatically turn every configurable value into observable metadata.
Sources: libs/core/langchain_core/runnables/config.py
When a runnable is made configurable, calls to with_config do not mutate the original object in place. DynamicRunnable returns a new instance with merged configuration, and prepare walks through nested dynamic wrappers until it reaches the concrete runnable that should execute. This enables layered defaults: a library can publish a chain with default choices, an application can bind environment-specific options, and a request can still supply user- or tenant-specific alternatives. Because schemas and graphs delegate through prepare, inspection can reflect the selected runnable rather than only the generic wrapper.
Sources: libs/core/langchain_core/runnables/configurable.py
Fallbacks and Error Handling
Fallbacks provide a first-class resilience pattern for runnable pipelines. RunnableWithFallbacks stores the primary runnable, an ordered sequence of fallback runnables, the exception classes that should trigger fallback behavior, and an optional exception key. The source documentation frames this around external API degradation, such as a language model provider experiencing downtime. The primary runnable is tried first, then fallbacks are attempted in order until one succeeds or all fail. Exceptions outside the configured handled classes are raised immediately, which prevents fallbacks from hiding programming errors that should be fixed.
Sources: libs/core/langchain_core/runnables/fallbacks.py
The exception key option is useful but constraining. If it is set, handled exceptions are passed to fallback runnables as part of the input under the named key. That gives a fallback chain enough context to produce a graceful response, route to a different provider, or record diagnostic information. However, the source explicitly requires the base runnable and fallback runnables to accept dictionary inputs when this option is used. In practice, that means teams should design fallback-aware chains with input shape in mind rather than adding exception passing after the fact.
Sources: libs/core/langchain_core/runnables/fallbacks.py
Graph Inspection
Graph inspection turns an LCEL composition into a structure that can be rendered, inspected, or reasoned about. The graph module defines nodes with identifiers, names, data, and optional metadata; edges with source, target, optional data, and a conditional flag; and branches that represent conditional routing. It also contains Mermaid-oriented styling concepts such as curve styles and node styles. These structures allow a runnable sequence or router-like composition to be represented without executing it, which is valuable for debugging complex chains and for documentation of application topology.
Sources: libs/core/langchain_core/runnables/graph.py, libs/core/langchain_core/runnables/configurable.py
Graph inspection is also tied to dynamic configuration. DynamicRunnable delegates get_graph through prepare, so a selected alternative can change the inspected graph. That is important when a configurable chain can switch between models, retrievers, or subchains. A diagram generated from the default configuration may not describe a request that selects a different alternative. For production documentation and debugging, prefer inspecting the graph with the same configuration shape used by the request path you are investigating, especially when conditional branches or configurable alternatives affect downstream nodes.
Sources: libs/core/langchain_core/runnables/graph.py, libs/core/langchain_core/runnables/configurable.py
Compact API Reference
| Area | Public contract | Practical use |
|---|---|---|
| Invocation | invoke and asynchronous counterparts on Runnable | Run one input through a component or composed chain. |
| Batching | batch-style execution with config lists and concurrency helpers | Process many inputs while respecting max_concurrency. |
| Streaming | stream-style execution on runnable implementations | Feed incremental output to UIs, logs, or event consumers. |
| Runtime config | RunnableConfig fields: tags, metadata, callbacks, run_name, max_concurrency, recursion_limit, configurable, run_id | Control tracing, callback propagation, concurrency, recursion, and runtime alternatives. |
| Dynamic config | DynamicRunnable, configurable_fields, configurable_alternatives, with_config, prepare | Bind defaults and select declared alternatives per request. |
| Fallbacks | RunnableWithFallbacks, fallbacks, exceptions_to_handle, exception_key | Retry with alternate runnables when handled failures occur. |
| Graphs | Graph, Node, Edge, Branch, CurveStyle, NodeStyles | Inspect or render runnable topology before execution. |
Next Steps
Use runnables as the default shape for reusable LangChain application units. Start by composing a small prompt, model, and parser chain, then add config tags and a run name so traces are understandable. If provider reliability matters, wrap the model or the whole chain with fallbacks and decide whether fallback inputs need exception details. If a chain will vary by tenant, environment, or experiment, expose only intentional fields as configurable alternatives. For deeper API detail, continue to the runnable API reference and the pages on language models, prompts, structured output, callbacks, and event streaming.