LangChain's core abstraction is the Runnable: prompts, models, parsers, retrievers and plain functions all expose invoke, batch, stream and async versions. LCEL composes them with |: prompt | model | parser is itself a Runnable.
You get batching, streaming, async, retries and tracing hooks for free on any composed chain. RunnableParallel fans out to several branches; RunnablePassthrough carries inputs forward; RunnableLambda wraps any function.
The trade-off: another layer of abstraction to debug, and frequent API churn. Use it where it saves real work; drop to plain code where it obscures.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "Summarise for a busy engineer in 3 bullets."),
("user", "{text}"),
])
chain = prompt | llm | StrOutputParser()
chain.invoke({"text": doc})
chain.batch([{"text": d} for d in docs], config={"max_concurrency": 5})Going deeper
Current state (LangChain 1.0, released October 2025): LCEL is not deprecated, and it remains the recommended way to compose custom chains and RAG pipelines. For agents, the recommended entry point is now create_agent with middleware, which runs on LangGraph under the hood. Older tutorials that build agents with LCEL or AgentExecutor are outdated.
The langchain-core package holds the stable abstractions (Runnables, messages, prompts); integrations live in separate provider packages. Pin versions: the ecosystem moves fast.
Best resources for this lesson
- DocsLangChain overview (official docs)
- DocsLangChain v1 migration guide · what changed and why
- ArticleLangChain and LangGraph reach v1.0