LangChain Parallel Runnables

A simple chain moves in a straight line: step one, step two, step three. Real tasks often split into side jobs that do not depend on each other, such as translating a text into three languages or summarizing and extracting keywords at the same time. Parallel runnables run those side jobs together and collect the results in one tidy dictionary.

Kitchen Analogy

A cook who boils rice, then chops vegetables, then fries chicken serves dinner late. A team of three cooks does all three jobs at once, and dinner lands on the table sooner. Parallel runnables put that team to work.

 SEQUENTIAL                       PARALLEL
 Input                            Input
   |                               |-----------|-----------|
   v                               v           v           v
 Task A (3s)                    Task A (3s) Task B (3s) Task C (3s)
   |                               |           |           |
   v                               -------------------------
 Task B (3s)                                  |
   |                                          v
   v                                  Combined result (3s total)
 Task C (3s)
   |
   v
 Total: 9s

RunnableParallel Basics

Create a RunnableParallel from a dictionary. Each value is a runnable, and each key names the output. All branches receive the same input.

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

summary_chain = (
    ChatPromptTemplate.from_template("Summarize in one sentence: {text}")
    | model | StrOutputParser()
)
keywords_chain = (
    ChatPromptTemplate.from_template("List 3 keywords for: {text}")
    | model | StrOutputParser()
)
mood_chain = (
    ChatPromptTemplate.from_template("Name the mood in one word: {text}")
    | model | StrOutputParser()
)

analysis = RunnableParallel(
    summary=summary_chain,
    keywords=keywords_chain,
    mood=mood_chain,
)

result = analysis.invoke({"text": "The monsoon arrived early and farmers celebrated."})
print(result["summary"])
print(result["keywords"])
print(result["mood"])

The Dictionary Shortcut

A plain dictionary inside a pipe chain converts to RunnableParallel automatically. This shortcut explains the pattern seen in many RAG examples.

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | model
)

The retriever and the passthrough run side by side. The prompt receives both values.

RunnablePassthrough

RunnablePassthrough forwards the input unchanged. Use it when one branch needs the original value untouched, such as the user question next to retrieved context.

Add extra fields to a dictionary while keeping existing ones with assign.

from langchain_core.runnables import RunnablePassthrough

chain = RunnablePassthrough.assign(
    word_count=lambda x: len(x["text"].split())
)
print(chain.invoke({"text": "one two three"}))
# {'text': 'one two three', 'word_count': 3}

RunnableLambda: Your Own Python Step

RunnableLambda wraps any Python function so it joins a chain like a built-in step.

from langchain_core.runnables import RunnableLambda

def clean(text):
    return text.strip().lower()

pipeline = RunnableLambda(clean) | RunnableLambda(lambda t: t.upper())

Branching with a Router

Branching chooses one path from several. Write a function that inspects the input and returns the chain to use. Wrap it in RunnableLambda.

 Question
    |
    v
 Classifier: "math" or "history"?
    |
    +---- math ------→ Math chain
    |
    +---- history ---→ History chain
classifier = (
    ChatPromptTemplate.from_template(
        "Answer with one word, math or history: {question}"
    ) | model | StrOutputParser()
)

math_chain = ChatPromptTemplate.from_template("Solve step by step: {question}") | model
history_chain = ChatPromptTemplate.from_template("Explain the history of: {question}") | model

def route(info):
    if "math" in info["topic"].lower():
        return math_chain
    return history_chain

full_chain = (
    {"topic": classifier, "question": lambda x: x["question"]}
    | RunnableLambda(route)
)

Controlling Concurrency

The batch method accepts a config with a concurrency limit. Use it to respect provider rate limits.

results = chain.batch(list_of_inputs, config={"max_concurrency": 5})

Common Mistakes

  • Running dependent steps in parallel. A step that needs another step's output must wait.
  • Forgetting that all branches receive identical input.
  • Launching dozens of simultaneous calls and hitting rate limits.

Real-World Scenario

A news app prepares each article for readers in three forms: a one-line summary, a list of keywords, and a reading level label. Running the three prompts one after another takes nine seconds. A parallel runnable sends all three prompts at once, and the result arrives in about three seconds. The dictionary output feeds straight into the app's article page. A router then sends sports articles to a chain with a special sports prompt and all other articles to a general chain.

Practice Tasks

  1. Build a parallel chain that returns a title, a summary, and three tags for a paragraph.
  2. Time the parallel version against three separate calls.
  3. Use RunnablePassthrough.assign to add a character count to an input dictionary.
  4. Write a router that sends questions containing numbers to a math chain.

Choosing Between Parallel and Sequential Steps

Ask one question about every pair of steps: does the second step need the first step's output? A yes means the steps run in sequence. A no means the steps can run side by side. Summaries, translations, and keyword extraction from the same text almost always run in parallel. A step that polishes a draft must wait for the draft. Drawing the flow on paper before coding prevents wrong wiring and saves debugging time.

Key Points

  • RunnableParallel runs independent branches together and returns a dictionary.
  • A dictionary inside a pipe chain becomes RunnableParallel.
  • RunnablePassthrough and RunnableLambda carry data and custom logic.
  • A routing function chooses one chain from many.
  • max_concurrency protects against rate limits.