Bordered avatar

Street Learner

Author
9 min read

Last Updated: a year ago

LangChain Expression Language (LCEL): A Beginner-Friendly Complete Guide with Examples

LangChain Expression Language (LCEL): A Beginner-Friendly Complete Guide with Examples

LangChain Expression Language (LCEL) is a powerful way to structure AI workflows. It allows you to build complex chains of operations — prompts, models, parsers, memory, and Python functions — in a clear, readable, and scalable way.

In this guide, you will learn step by step:

  • What LCEL is and why it exists
  • How to use invoke, batch, stream, and Runnables
  • How to pipe chains together
  • How to handle memory properly
  • How to visualize chains and build real AI workflows
  • How to use the @chain decorator for simplicity

We’ll use examples throughout to make the concepts concrete. By the end, you’ll be able to build your own AI pipelines confidently.

1. Why LCEL Exists

Traditional LangChain workflows might look like this:

prompt_text = template.format(input_data)
model_response = model(prompt_text)
parsed_data = parser(model_response)

Problems:

  • Hard to read
  • Hard to scale
  • Hard to reuse components
  • Hard to manage multiple inputs or parallel workflows

LCEL solves these issues by treating everything as a Runnable — prompts, models, parsers, or even Python functions. Runnables can be piped, run in parallel, batched, or streamed, creating clear, modular AI pipelines.

2. Installing and Setting Up

Before we begin, ensure you have:

  • Python 3.10+
  • langchain, langchain-openai, and python-dotenv installed
  • OpenAI API key in a .env file

Example .env:

OPENAI_API_KEY=your_api_key_here

Setup code:

import os
import time
from dotenv import load_dotenv

load_dotenv()

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser, CommaSeparatedListOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel, RunnableLambda, chain
from langchain.memory import ConversationSummaryMemory

We initialize the model:

chat = ChatOpenAI(
    model_name='gpt-4',
    model_kwargs={'seed': 365},
    temperature=0,
    max_tokens=100
)

This model will be used throughout.

3. Piping a Prompt, Model, and Parser

In LCEL, you can pipe components together using the | operator. This allows the data to flow from left to right.

Step 1: Create a Prompt Template

list_output_parser = CommaSeparatedListOutputParser()
list_instructions = list_output_parser.get_format_instructions()

chat_template_01 = ChatPromptTemplate.from_messages([
    ('human', f"I've recently adopted a {{pet}}. Could you suggest three {{pet}} names?\n{list_instructions}")
])

Here:

  • {pet} is a placeholder for input data
  • list_instructions tells the model to return comma-separated names

Step 2: Pipe with Model and Parser

chain_01 = chat_template_01 | chat | list_output_parser
  • Prompt formats input
  • Model generates output
  • Parser converts output into a Python list

Step 3: Invoke the Chain

result_01 = chain_01.invoke({'pet': 'dog'})
print(result_01)

Output example:

['Max', 'Bella', 'Charlie']

This shows structured AI output.

4. Running Multiple Inputs: batch()

Often, you want to run the same chain for multiple inputs at once.

chat_template_02 = ChatPromptTemplate.from_messages([
    ('human', "I've recently adopted a {pet} which is a {breed}. Could you suggest training tips?")
])
chain_02 = chat_template_02 | chat

batch_inputs = [
    {'pet': 'dog', 'breed': 'shepherd'},
    {'pet': 'dragon', 'breed': 'night fury'}
]

batch_results = chain_02.batch(batch_inputs)

Here:

  • batch() sends multiple prompts simultaneously
  • Returns a list of AI responses

5. Streaming AI Output: stream()

Sometimes you want real-time output, like in a chatbot.

stream_response = chain_02.stream({'pet': 'dragon', 'breed': 'night fury'})

for chunk in stream_response:
    print(chunk.content, end='', flush=True)
  • Each chunk arrives incrementally
  • Useful for long outputs or live interfaces

6. RunnableSequence

When you do:

sequence = chat_template_01 | chat
print(type(sequence))

LCEL automatically creates a RunnableSequence, which:

  • Chains multiple runnables
  • Ensures ordered execution

This allows modular workflow building.

7. RunnablePassthrough: Passing Data Along

Sometimes you want to pass data from one chain to another without modification.

chain_tools = (
    chat_template_tools
    | chat
    | StrOutputParser()
    | {'tools': RunnablePassthrough()}
)
  • Output of this chain is stored under 'tools'
  • Used in the next chain without changing the data

8. Piping Chains Together

You can combine multiple chains:

chain_combined = chain_tools | chain_strategy
  • chain_tools output feeds as input into chain_strategy
  • This enables complex multi-step workflows

9. Graphing Runnables

chain_combined.get_graph().print_ascii()
  • Shows an ASCII visualization of your pipeline
  • Useful for debugging or understanding flow

10. RunnableParallel: Running Multiple Chains Simultaneously

parallel_chain = RunnableParallel({
    'books': chain_books,
    'projects': chain_projects
})
  • Accepts one input
  • Runs multiple branches in parallel
  • Returns a dictionary of outputs

11. RunnableLambda: Turning Python Functions into Runnables

sum_runnable = RunnableLambda(lambda x: sum(x))
square_runnable = RunnableLambda(lambda x: x**2)
lambda_chain = sum_runnable | square_runnable

result = lambda_chain.invoke([1, 2, 5])
print(result)
  • Any Python function can become a first-class runnable
  • Fully compatible with |, batch, stream

12. The @chain Decorator

@chain
def sum_chain(x):
    return sum(x)

@chain
def square_chain(x):
    return x**2

decorated_chain = sum_chain | square_chain
print(decorated_chain.invoke([1,2,5]))
  • Simplifies lambda-style chains
  • Decorated functions behave exactly like any other runnable

13. Using Memory in LCEL

LCEL allows memory injection into prompts:

chat_memory = ConversationSummaryMemory(llm=chat, memory_key='message_log')

memory_chain = (
    RunnablePassthrough.assign(
        message_log=RunnableLambda(chat_memory.load_memory_variables) | itemgetter('message_log')
    )
    | chat_template_01
    | chat
    | StrOutputParser()
)
  • Loads previous conversation
  • Injects it into prompts dynamically
  • Keeps AI aware of context over time

14. Full Production Workflow with LCEL

  1. Load memory
  2. Pipe prompt → model → parser
  3. Save response back to memory
  4. Repeat with new input
question = "Tell me an interesting fact about octopuses."
response = memory_chain.invoke({'question': question})
chat_memory.save_context({'input': question}, {'output': response})

This is exactly how you build a stateful AI chatbot.

15. Summary

LCEL provides:

  • Runnables: everything is modular and composable
  • Pipe operator |: clear left-to-right data flow
  • invoke, batch, stream: flexible execution
  • RunnablePassthrough & RunnableLambda: manage data, custom logic
  • RunnableParallel: run multiple chains at once
  • Memory integration: maintain conversation context
  • Graphing: visualize complex workflows

Key Takeaways

  • Start simple: prompt → model → parser
  • Gradually add memory, parallelism, and lambda logic
  • Use decorators for reusable chains
  • Visualize pipelines for clarity

Related Stories