Skip to content
derpx06Notes on systems, models & learning
2. LCEL & Prompt Architecture · lesson 9 of 68 · 3 min · January 10, 2026

LCEL: Why LangChain Is a Composition Engine

Understanding chaining in AI workflows and how LangChain Expression Language (LCEL) simplifies it.

When people first start using language models, they usually write code like this:

send a prompt → get a response → done

That works for demos. It does not work for real applications.

Real AI systems are workflows, not single calls.

They often need to:

  • call a model more than once
  • prepare data before the model runs
  • do something useful with the model's output afterward

The idea that connects all of this is called chaining.


Chaining simply means:

taking multiple steps and connecting them so the output of one step becomes the input of the next.

With language models, this idea shows up in a few common ways.


Language models are powerful, but they are not deterministic. They can make mistakes, hallucinate, or miss details.

Because of this, many applications use more than one model call.

Common patterns include:

  • breaking a big task into smaller steps
  • asking one model to generate an answer
  • asking another call to review, fix, or improve it

For example:

  • plan → execute → verify
  • draft → critique → rewrite
  • reason → answer → double-check

Chaining makes these multi-step processes clear and manageable.


Very often, the model cannot work with raw user input alone.

Before calling the LLM, applications may need to:

  • format a prompt template
  • fetch documents or database records
  • perform retrieval based on the user's query
  • combine multiple sources of information

This means the model call is only one step in a larger chain.

The data must be transformed and enriched before the LLM ever sees it.


Chaining does not stop when the model responds.

In many systems, the LLM's output is used to:

  • generate Python code and then run it
  • generate SQL and execute it on a database
  • produce structured data for another service
  • drive business logic or automation

In these cases, the LLM is part of a pipeline, not the end of it.


All of this could be done with traditional code. So why do people keep reaching for chaining abstractions?

Because text has become a universal interface.

Language models take text as input and produce text as output. Prompts, responses, SQL, code, JSON—everything is text.

When everything is text flowing between steps, chaining becomes the most natural way to think.

This is why visual tools like Flowwise and LangFlow became popular, built on top of LangChain.


LangChain started with pre-built chains like LLMChain and ConversationalRetrievalChain. They helped get started but were hard to combine, customize, and lacked consistent async/batching/streaming support.

Developers wanted composable, consistent chains with built-in features. The old approach couldn't deliver cleanly.

So LangChain introduced LCEL.


LangChain Expression Language (LCEL) is a declarative way to build chains using the pipe (|) operator.

Instead of large classes, compose small building blocks. Describe data flow, not execution control.


LCEL uses the pipe to connect components:

concept.py
prompt | model

This passes the prompt's output directly to the model.

Example:

simple_chain.py
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

model = ChatOpenAI()
prompt = ChatPromptTemplate.from_template("tell me a joke about {foo}")

chain = prompt | model

# Run it
result = chain.invoke({"foo": "bears"})
print(result.content)

Install LangChain and try this simple chain:

install.sh
pip install langchain langchain-openai

Then run:

summarizer.py
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

# Set your OpenAI API key
import os
os.environ["OPENAI_API_KEY"] = "your-api-key-here"

model = ChatOpenAI()
prompt = ChatPromptTemplate.from_template("Summarize this text: {text}")

chain = prompt | model

# Test it
result = chain.invoke({"text": "LangChain makes building AI apps easy."})
print(result.content)

This creates a summarizer chain. Change the prompt or add more steps!


Every LCEL chain supports:

invoke.py
result = chain.invoke({"text": "Hello world"})
batch.py
results = chain.batch([{"text": "First"}, {"text": "Second"}])
stream.py
for chunk in chain.stream({"text": "Long text"}):
  print(chunk.content, end="")
async.py
result = await chain.ainvoke({"text": "Async call"})

All built-in—no extra code needed.


LCEL chains are composable. Add steps easily:

extend.py
from langchain.output_parsers import StrOutputParser

# Add output parsing
chain = prompt | model | StrOutputParser()

Swap models, add retrieval, or combine chains.


Here is a real chain that explains a concept, then roasts its own explanation, and finally compliments it.

First, choose your LLM provider:

from langchain_ollama import ChatOllama
model = ChatOllama(
    model="gemma3:4b",
    temperature=1.2,     # higher = more randomness
    top_p=0.95,          # allow wider token choices
    top_k=50,            # explore more candidate tokens
)

Now build the pipeline:

Pipeline
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()

explain = ChatPromptTemplate.from_template(
    "Explain {topic} in ONE clear sentence."
)
#llms are too polite btw
roast = ChatPromptTemplate.from_template(
    "You are a sharp stand-up comedian.\n"
    "Write EXACTLY ONE savage, funny one-liner roasting the explanation.\n"
    "No politeness. No explanations. No emojis. No markdown.\n"
    "Be blunt, witty, and a little mean.\n\n"
    "Explanation:\n{explanation}"
)

compliment = ChatPromptTemplate.from_template(
    "Compliment the explanation in ONE warm sentence.\n"
    "{explanation}"
)

chain = (
    explain
    | model
    | parser
    | {
        "🧠 Explanation": lambda x: x,
        "🔥 Roast": roast | model | parser,
        "💖 Compliment": compliment | model | parser
    }
)

result = chain.invoke({"topic": "context window in LLMs"})

# Pretty-print output
for section, content in result.items():
    print(f"\n{section}\n{'-' * 40}\n{content}")
Output

🧠 Explanation
----------------------------------------
The context window in an LLM refers to the amount of preceding text the model can consider when generating its next output, acting like a short-term memory for understanding and responding to prompts.

🔥 Roast
----------------------------------------
That explanation is just a fancy way of saying it forgets everything five seconds ago.

💖 Compliment
----------------------------------------
That's a wonderfully clear and approachable explanation of the context window – you’ve really captured the essence of how LLMs process information!

This pipeline demonstrates LCEL's power:

  • Takes a topic input
  • Uses the explain prompt to generate an explanation
  • Feeds that explanation into both a roast and compliment prompt in parallel
  • Returns all three results in a structured dictionary

The pipeline runs automatically in parallel where possible, streaming results as they arrive.


Think of LCEL as an expression language for AI workflows.

You write:

flow_concept.txt
input | transform | model | transform | output

The system handles execution, async, batching, streaming, tracing.

Once you think in flows, chaining becomes simple.

LCEL makes chaining clear, composable, and scalable.