Open-Source Wikis

/

LangChain

/

Primitives

/

Runnables and LCEL

langchain-ai/langchain

Runnables and LCEL

Runnable is the universal invocation protocol — almost everything in LangChain implements it. LCEL (LangChain Expression Language) is the syntax for composing runnables with |, RunnableParallel, RunnableBranch, and friends.

Source root: libs/core/langchain_core/runnables/.

Purpose

Before LCEL, LangChain had Chain.run, Chain.__call__, LLM.predict, LLM.generate, Tool.run, and dozens of other call methods. Different objects did the same thing under different names. Runnable collapses everything into one contract:

class Runnable(Generic[Input, Output]):
    def invoke(self, input: Input, config: RunnableConfig | None = None) -> Output: ...
    async def ainvoke(self, input: Input, config: RunnableConfig | None = None) -> Output: ...
    def batch(self, inputs: list[Input], config: ... = None) -> list[Output]: ...
    async def abatch(self, inputs: list[Input], config: ... = None) -> list[Output]: ...
    def stream(self, input: Input, config: ... = None) -> Iterator[Output]: ...
    async def astream(self, input: Input, config: ... = None) -> AsyncIterator[Output]: ...
    def astream_events(self, input: Input, ...) -> AsyncIterator[StreamEvent]: ...

Every implementer gets the full set "for free" — base implementations of batch/abatch use invoke/ainvoke with a thread pool, astream falls back to a single-chunk stream of invoke's result, and so on. Implementers override the methods they can do better.

Directory layout

libs/core/langchain_core/runnables/
├── __init__.py            # Public re-exports
├── base.py                # The Runnable class hierarchy (~5,800 lines)
├── branch.py              # RunnableBranch (if-then-else routing)
├── config.py              # RunnableConfig, ensure_config, patch_config
├── configurable.py        # ConfigurableField, ConfigurableFieldSpec
├── fallbacks.py           # RunnableWithFallbacks
├── graph.py               # The graph rendering API (.get_graph())
├── graph_ascii.py         # ASCII graph rendering
├── graph_mermaid.py       # Mermaid graph rendering
├── graph_png.py           # PNG graph rendering via mermaid.ink
├── history.py             # RunnableWithMessageHistory
├── passthrough.py         # RunnableAssign, RunnablePassthrough, RunnablePick
├── retry.py               # RunnableRetry (legacy; prefer with_retry)
├── router.py              # RouterRunnable
├── schema.py              # StreamEvent
└── utils.py               # AddableDict, gather helpers

Key abstractions

Symbol File Description
Runnable libs/core/langchain_core/runnables/base.py The base class — every other concept on this page derives from it
RunnableSerializable libs/core/langchain_core/runnables/base.py A Runnable that can be saved/loaded via langchain_core.load.serializable
RunnableSequence libs/core/langchain_core/runnables/base.py What the | operator builds: a chain of runnables
RunnableParallel (RunnableMap) libs/core/langchain_core/runnables/base.py Run multiple runnables on the same input, return a dict of outputs
RunnableLambda libs/core/langchain_core/runnables/base.py Wrap a plain function as a Runnable
RunnableGenerator libs/core/langchain_core/runnables/base.py Wrap a generator function for streaming
RunnableBinding libs/core/langchain_core/runnables/base.py Apply a config / kwargs override to a runnable
RunnableBranch libs/core/langchain_core/runnables/branch.py If-elif-else routing
RunnableWithFallbacks libs/core/langchain_core/runnables/fallbacks.py Try a runnable, fall back to another on failure
RunnableWithMessageHistory libs/core/langchain_core/runnables/history.py Wrap a runnable to carry chat history per session
RunnableAssign, RunnablePassthrough, RunnablePick libs/core/langchain_core/runnables/passthrough.py Add/keep/select keys in a dict input
RouterRunnable libs/core/langchain_core/runnables/router.py Route to one of N runnables based on a key
RunnableConfig libs/core/langchain_core/runnables/config.py The config TypedDict — callbacks, tags, metadata, run_name, configurable
ConfigurableField libs/core/langchain_core/runnables/configurable.py Mark an attribute as runtime-configurable via .configurable_fields
chain libs/core/langchain_core/runnables/base.py Decorator that turns a function into a RunnableLambda

How LCEL works

The | operator is implemented via Runnable.__or__. prompt | model returns a RunnableSequence([prompt, model]). Adding | parser extends it to RunnableSequence([prompt, model, parser]). The sequence's invoke walks the steps in order; astream chains the per-step streams.

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_messages([("user", "Translate to French: {text}")])
chain = prompt | model | StrOutputParser()

chain.invoke({"text": "Hello"})       # -> "Bonjour"
chain.batch([{"text": "Hi"}, {"text": "Bye"}])  # parallel
async for chunk in chain.astream({"text": "Hi"}):
    print(chunk, end="")

RunnableParallel is constructed implicitly when a dict is on either side of the pipe:

from langchain_core.runnables import RunnablePassthrough

retrieve_and_answer = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

The dict literal coerces to RunnableParallel({"context": ..., "question": ...}), which runs both branches concurrently and feeds the merged output to prompt.

Configuration propagation

RunnableConfig flows through every nested call. It carries:

  • callbacks — a list of BaseCallbackHandler instances
  • tags, metadata — for filtering in LangSmith
  • run_name, run_id — display names and identifiers
  • max_concurrency — parallelism cap for batch, abatch, RunnableParallel
  • recursion_limit — for nested RunnableLambda calls
  • configurable — runtime overrides for ConfigurableField attributes

ensure_config and patch_config (in libs/core/langchain_core/runnables/config.py) are the helpers used at every level to merge user-provided config with the defaults set on a runnable.

Streaming

There are two streaming APIs:

  • stream / astream — yield outputs as they arrive. The unit depends on the runnable: a chat model yields AIMessageChunk per token; a sequence yields whatever the last runnable yields.
  • astream_events — yield typed events for every runnable in the tree (on_chain_start, on_llm_stream, on_tool_end, …). This is what powers UIs that need fine-grained progress (e.g. "the model is calling the search tool").

StreamEvent is defined in libs/core/langchain_core/runnables/schema.py. Versions: v1 and v2 (default in newer code).

Error handling and retries

Every Runnable has helper methods that wrap it:

  • .with_retry(retry_if_exception_type=..., stop_after_attempt=..., wait_exponential_jitter=True) returns a new runnable that retries on the given exceptions using tenacity.
  • .with_fallbacks([alt1, alt2], exceptions_to_handle=...) returns a RunnableWithFallbacks that falls through to alternates on failure.
  • .with_config(callbacks=..., tags=..., ...) returns a RunnableBinding with the override applied.
  • .with_listeners(on_start=..., on_end=...) adds simple lifecycle listeners.

Graph rendering

Calling runnable.get_graph() returns a Graph object representing the runnable's structure. It can be rendered to ASCII (graph.draw_ascii()), Mermaid (graph.draw_mermaid(), optionally draw_mermaid_png()), or PNG via the mermaid.ink API. This is what generates the diagrams in LangChain docs.

Integration points

  • Every primitive (chat models, LLMs, tools, prompts, output parsers, retrievers, vector stores) implements Runnable.
  • langchain (v1) wraps langgraph graphs with the Runnable interface so they can be composed.
  • langchain-classic chains and agents implement Runnable so legacy code can pipe them.
  • LangSmith tracing plugs in via the callbacks field of RunnableConfig.

Entry points for modification

  • For a new operator, prefer composition. If you must add a new class, place it in libs/core/langchain_core/runnables/ and re-export from __init__.py.
  • For a new event in astream_events, edit libs/core/langchain_core/runnables/schema.py and libs/core/langchain_core/tracers/event_stream.py (the tracer that emits them).
  • For a new graph renderer, look at the graph_*.py files for the existing pattern.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Runnables and LCEL – LangChain wiki | Factory