Wire an LLM into a multi-step pipeline: prompt, model call, parse, branch, retry. The first framework most teams try.
A factory assembly line. Each station takes inputs, transforms them, hands off to the next. LangChain Expression Language (LCEL) is the pipe syntax that glues the stations together.
prompt template -> llm -> output parser
| | |
v v v
chain.invoke({'topic':'cats'}) -> 'Cats are great'
LCEL: prompt | llm | parser (pipe composes runnables)
LangChain is the most common Python framework for LLM apps. The modern style is LCEL (LangChain Expression Language): you compose Runnables via the pipe operator into a chain. Every Runnable has the same interface (invoke, batch, stream); you can swap models, swap parsers, swap prompts without touching downstream code. Use LangChain when you want a stable composition API; use raw OpenAI/Anthropic SDKs for simple single-call apps.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_template('Tell me a joke about {topic}')
llm = ChatOpenAI(model='gpt-4o-mini')
chain = prompt | llm | StrOutputParser()
print(chain.invoke({'topic':'cats'}))
Prompt | llm | parser composes three Runnables into a single chain — standard pattern across LangChain.
# batch + stream work too on any Runnable
results = chain.batch([{'topic':'dogs'}, {'topic':'octopuses'}])
for token in chain.stream({'topic':'astronauts'}):
print(token, end='', flush=True)
.batch parallelises inputs; .stream yields tokens as they arrive — same chain, no rewrites.
# add fallback model on OpenAI outage
from langchain_anthropic import ChatAnthropic
robust = (prompt
| llm.with_fallbacks([ChatAnthropic(model='claude-haiku-4-5'),])
| StrOutputParser())
print(robust.invoke({'topic':'cats'}))
.with_fallbacks adds resilience without changing downstream logic — crucial for production.
Manually concatenating strings instead of using prompt templates. Result is a tangled mess of single quotes and missing variables. Always use a template.
LCEL is the modern path: prompt | llm | parser, composition with pipe, .invoke / .batch / .stream as one interface. Use with_fallbacks for resilience.
{topic} because the template was never formatted.