🔗 AI Engineering

LangChain Chains & LCEL — Composing LLM Steps with the Pipe

📅 Aug 5, 2026 ⏱ 6 min read

A "chain" is just steps connected end to end: prompt → model → parser. LangChain lets you build one with a single operator — the pipe | — borrowed from the Unix shell. This is LCEL: LangChain Expression Language, and it's the heart of the framework.

Your first chain

from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

model = init_chat_model("claude-opus-5", model_provider="anthropic")
prompt = ChatPromptTemplate.from_messages([
    ("system", "You explain {subject} concepts to beginners."),
    ("user", "{question}"),
])

# read it left to right: fill prompt -> call model -> parse to string
chain = prompt | model | StrOutputParser()

print(chain.invoke({"subject": "DBMS", "question": "What is normalization?"}))

That prompt | model | parser reads like a sentence. Data flows left to right; the output of each step becomes the input of the next. The whole chain is itself a Runnable — so it has .invoke() too, and can be dropped inside a bigger chain.

Why the pipe matters

Because every piece speaks the same interface, you get three things for free on any chain:

# streaming — print the answer word-by-word as it arrives
for piece in chain.stream({"subject": "networks", "question": "What is DNS?"}):
    print(piece, end="", flush=True)

Running steps in parallel

Need two things from one input at once? A dict of Runnables runs them in parallel and merges the results.

from langchain_core.runnables import RunnableParallel

summary = ChatPromptTemplate.from_template("Summarize in 1 line: {text}") | model | StrOutputParser()
keywords = ChatPromptTemplate.from_template("List 3 keywords for: {text}") | model | StrOutputParser()

both = RunnableParallel(summary=summary, keywords=keywords)
result = both.invoke({"text": "LangChain is a framework for building LLM apps."})
print(result["summary"])
print(result["keywords"])

Passing data through

RunnablePassthrough forwards the input untouched — essential for RAG, where you need to keep the original question and add retrieved context. We use it heavily in the RAG post.

from langchain_core.runnables import RunnablePassthrough

chain = {"question": RunnablePassthrough()} | prompt2 | model | StrOutputParser()

The takeaway

LCEL turns "call the model, then do the next thing, then the next" into one readable expression that streams, batches, and parallelizes automatically. Next we give a chain a memory so it remembers the conversation. Related: core concepts.

← All Articles