LangChain History: How the Framework Evolved to LCEL and LangGraph
Why LangChain's History Actually Matters to You
Most developers meet LangChain in its current form: langchain-core, LCEL pipes, and LangGraph state machines. What they don't see is the two-plus years of iteration, breaking changes, and hard lessons that got the framework here. That history isn't trivia. If you've ever opened a tutorial from 2023 and found that half the imports no longer work, or wondered why LangChain suddenly has three separate packages instead of one, you're running into the consequences of that evolution directly.
Understanding how LangChain moved from "a Python library that chains prompts together" to "a graph-based orchestration platform" tells you something more useful than nostalgia — it tells you why the current architecture looks the way it does, which parts are stable enough to build on, and which parts are still being figured out. This matters even more now that agentic systems are the default way people build with LLMs, and orchestration frameworks are judged on how well they handle branching, memory, and human-in-the-loop control, not just prompt templating. If you're serious about production AI engineering, this history is the fastest way to understand the design decisions baked into the tools you use every day.
The Origin Story: A Side Project for Chaining Prompts
LangChain started in late 2022 as an open-source project by Harrison Chase, released around the same time GPT-3.5 style models were making API-driven LLM apps suddenly practical. The core insight was simple: a single prompt-response call to an LLM is rarely enough to build something useful. Real applications need to combine several LLM calls, external data sources, and tools into a pipeline — hence "chains."
The earliest version of LangChain was built almost entirely around Python classes with names like LLMChain, SimpleSequentialChain, and ConversationChain. Each chain wrapped one specific pattern: take input, format a prompt template, send it to an LLM, parse the output, maybe feed it into the next chain. This was genuinely useful at the time because almost nobody had solved the plumbing problem of "call an LLM, then do something with the result, then call it again."
Alongside chains, the original library introduced a few ideas that persist to this day in some form:
- Prompt templates — reusable, parameterized prompts instead of hardcoded strings
- Document loaders — a standard interface for pulling text out of PDFs, websites, and databases
- Text splitters — chunking long documents for retrieval
- Vector store wrappers — a common interface across Pinecone, Chroma, FAISS, and others
- Agents — an early attempt at letting the LLM decide which tool to call next, based on the ReAct pattern from academic research
This last one, agents, is worth pausing on. LangChain's original AgentExecutor was one of the first widely-used implementations of the "LLM picks a tool, observes the result, decides the next step" loop outside of a research paper. It was clunky, it hallucinated tool calls constantly, and it was very hard to debug — but it was also the first time a lot of developers saw an LLM reason iteratively over multiple steps instead of producing one shot output.
The Explosive Growth Phase and Its Side Effects
Through 2023, LangChain's growth was fast by any standard. Retrieval-augmented generation (RAG) was becoming the default architecture for grounding LLMs in private data, and LangChain's document loaders, splitters, and vector store integrations made it the path of least resistance for developers who didn't want to write that plumbing themselves. Nearly every RAG tutorial published that year used LangChain as the glue.
That growth came with real costs, and it's important to be honest about them rather than pretend the framework's history was a clean upward line:
- API instability. Because the library was evolving so quickly, method signatures, module paths, and class names changed release to release. Code that worked in March 2023 often broke by June.
- Abstraction overload. To support every possible LLM provider, vector store, and document type, LangChain added layer after layer of abstraction. This made simple things easy but made debugging genuinely difficult — a failure could be buried three or four wrapper classes deep.
- The "magic black box" complaint. Chains and agents did a lot of implicit work: building prompts internally, retrying silently, parsing output in ways that weren't always visible. Developers who wanted precise control over what the LLM actually saw found this frustrating.
- A crowded namespace. As integrations multiplied,
langchainas a single package became bloated. Installing it pulled in dependencies for vector stores and providers you might never touch.
These weren't small gripes — they showed up constantly in GitHub issues, and they were the direct motivation for the two biggest structural changes in LangChain's history: the split into multiple packages, and the introduction of LCEL.
The Package Split: langchain-core, langchain, and Partner Packages
By 2023 it was clear that a single monolithic langchain package couldn't scale as an architecture. The maintainers split the project into distinct layers, each with a clear responsibility:
- `langchain-core` — the foundational abstractions:
Runnable, messages, prompt templates, output parsers, and the interfaces that everything else builds on. This package is deliberately kept lightweight with minimal dependencies. - `langchain` — the higher-level chains, agents, and retrieval strategies built on top of
langchain-core. This is the "batteries included" layer. - `langchain-community` — the long tail of third-party integrations (document loaders, vector stores, tools) that don't need to live in the core release cycle.
- Partner packages (
langchain-openai,langchain-anthropic,langchain-google-genai, and so on) — provider-specific integrations maintained with tighter version alignment to the provider's own SDK, so a breaking change in an underlying API doesn't force a release of the entire framework.
This split mattered because it decoupled release cadence from risk. A bug fix in a community-maintained vector store integration no longer required bumping the version of the core runtime that production systems depended on. It also made it possible to depend only on langchain-core if you were building something lightweight and didn't need the full chain and agent ecosystem — a direct response to the bloat complaints.
If you're picking up LangChain for the first time today, this is why you'll see multiple pip install lines in almost every setup guide instead of a single package. That's not accidental complexity — it's the scar tissue from the monolith era, turned into a deliberate architecture.
LCEL: Rethinking Chains as Composable Runnables
The single biggest conceptual shift in LangChain's history is LCEL — the LangChain Expression Language. It was introduced to solve a problem that the original chain classes never handled well: composability.
In the old world, if you wanted to combine a prompt, a model call, and an output parser, you either used a purpose-built chain class that already did that combination, or you wrote custom Python glue code. There was no consistent way to take arbitrary pieces and combine them predictably. Streaming, batching, and async execution were bolted on separately for each chain type, which meant they behaved inconsistently.
LCEL replaced this with a single unifying abstraction: the Runnable. Every core building block — prompts, models, retrievers, output parsers, even entire chains — implements the same Runnable interface, which guarantees a consistent set of methods: invoke, batch, stream, and their async equivalents (ainvoke, abatch, astream). Because everything speaks the same interface, you can compose them with the pipe operator:
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_template("Explain {topic} in two sentences.")
model = ChatOpenAI(model="gpt-4o-mini")
parser = StrOutputParser()
chain = prompt | model | parser
result = chain.invoke({"topic": "vector databases"})That | operator is the signature of LCEL. Reading it left to right tells you exactly what happens to the data: it's formatted into a prompt, sent to a model, and the model's output is parsed into a plain string. There's no hidden chain class doing unexplained work behind the scenes — the composition *is* the logic.
The practical benefits went well beyond readability:
- Automatic streaming. Because every
Runnableimplementsstream, a composed chain streams by default, token by token, without special-casing. - Batching for free. Calling
.batch()on a chain runs it over a list of inputs with automatic parallelization where the underlying components support it. - First-class async.
ainvokeand friends mean you don't need to write a separate async version of your pipeline. - Built-in observability. LCEL chains automatically emit the structured events that power tracing in LangSmith, so you get visibility into every step of the pipeline without manually instrumenting each one.
- Declarative parallelism.
RunnableParallellets you fan out to multiple branches — for example, running a retriever and a query classifier at the same time — and merge the results, all expressed declaratively rather than with manual thread or task management.
LCEL also introduced RunnableLambda and RunnablePassthrough, which let you drop arbitrary Python functions or pass-through values into a chain without writing a full custom class. This closed the gap between "use a prebuilt chain" and "write everything yourself," which had been one of the biggest sources of friction in the old chain-class model.
For a good stretch of time, LCEL was positioned as the future of essentially everything in LangChain — the plan was to gradually reimplement legacy chains as LCEL-based equivalents. That mostly happened, and LCEL remains the standard way to build linear-to-moderately-branching pipelines in LangChain today. But it also revealed its own limits.
Where LCEL Hit a Wall: Agents Need Cycles, Not Pipes
LCEL is fundamentally a directed acyclic graph. Data flows from one Runnable to the next, and while you can branch with RunnableParallel or add conditional logic, you can't easily express something that loops back on itself. That's a real problem for agents, because the defining behavior of an agent is a loop: think, act, observe, and decide whether to loop again or stop.
The original AgentExecutor handled looping with hardcoded Python control flow hidden inside the class, which brought back exactly the kind of black-box behavior LCEL was designed to get away from. You could build an agent with LCEL components inside it, but the looping, retry, and branching logic that made it an *agent* rather than a *chain* still lived outside the composable, inspectable world that LCEL provided for everything else.
This became more pressing as the industry's idea of what an "agent" should do got more ambitious: multi-step tool use, human approval steps in the middle of a task, persistent memory across long-running sessions, error recovery that goes back several steps instead of just retrying the last one. None of that maps cleanly onto a pipe-shaped chain. It maps onto a graph with cycles, conditional edges, and explicit state — which is a fundamentally different computational model than LCEL was built to express.
LangGraph: Modeling Agents as State Machines
LangGraph is LangChain's answer to that gap, and it represents the second major conceptual leap in the framework's history. Instead of composing a pipeline, you define a graph: a set of nodes (each one a function or a Runnable) connected by edges, operating over a shared, explicitly-typed state object that gets passed and updated as execution moves through the graph.
A minimal LangGraph agent loop looks like this:
from typing import TypedDict
from langgraph.graph import StateGraph, END
class AgentState(TypedDict):
input: str
output: str
steps: int
def think(state: AgentState) -> AgentState:
state["steps"] += 1
state["output"] = f"processed: {state['input']}"
return state
def should_continue(state: AgentState) -> str:
return "think" if state["steps"] < 3 else END
graph = StateGraph(AgentState)
graph.add_node("think", think)
graph.set_entry_point("think")
graph.add_conditional_edges("think", should_continue)
app = graph.compile()
result = app.invoke({"input": "hello", "output": "", "steps": 0})A few things about this design directly address the limitations of the LCEL-plus-AgentExecutor era:
- Cycles are native. The graph can route back to the same node repeatedly, which is exactly what a reasoning loop needs. This wasn't possible in a strictly directed, acyclic LCEL chain.
- State is explicit and typed. Instead of state being implicitly threaded through nested function calls, it's a defined structure that every node reads from and writes to, which makes debugging and testing dramatically easier.
- Conditional routing is a first-class concept.
add_conditional_edgeslets you branch to different nodes based on the current state, rather than hiding that decision inside a class method. - Checkpointing and persistence are built in. LangGraph can persist state at every step, which means a long-running or multi-day agent process can pause and resume exactly where it left off — a hard requirement for anything involving human approval steps or asynchronous tool execution.
- Human-in-the-loop is a supported pattern, not a hack. You can interrupt a graph before or after a specific node, let a human inspect or edit the state, and then resume execution — something that was extremely awkward to bolt onto the old
AgentExecutor.
LangGraph didn't replace LCEL — it sits on top of it. Individual nodes in a LangGraph graph are frequently LCEL chains themselves. The relationship is complementary: LCEL is for composing the linear or lightly-branching steps within a single unit of work, and LangGraph is for orchestrating the overall control flow of an agent that needs to loop, branch, pause, and resume.
The Ecosystem That Grew Around the Core: LangSmith and LangServe
LangChain's history isn't just about the core library — it's also about the tooling that grew up around it, because chains and agents that work in a notebook often fail in ways that are invisible until you're staring at a wall of nested JSON in a terminal.
LangSmith emerged as the observability and evaluation layer. Because LCEL and LangGraph both emit structured traces automatically, LangSmith can show you exactly which step in a chain or which node in a graph produced a given output, how long each step took, and what the token usage was — without you writing custom logging. It also added evaluation datasets and automated grading, which turned "does this prompt change make things better or worse" from a subjective guess into something you could actually measure across a test set.
LangServe was built to solve deployment: taking a LangChain or LCEL object and exposing it as a production API with minimal boilerplate, including automatic streaming endpoints and a built-in playground UI. It reflected a broader shift in the framework's priorities — from "how do we make it easy to prototype a chain" toward "how do we make it easy to ship a chain as a real service."
Together, these tools mark a maturity shift in LangChain's own identity. It started as a library for gluing together LLM calls in a script. It evolved into a framework with an accompanying platform for observing, evaluating, and deploying what you build — which is a very different set of problems than the ones it solved in 2022.
What the Version History Teaches About Building With LangChain Today
Stepping back, a few lessons from this evolution are directly useful if you're building with LangChain now rather than just reading about its past:
- Prefer `langchain-core` and LCEL primitives over legacy chain classes. If you find a tutorial using
LLMChainor an old-styleAgentExecutorpattern, treat it as legacy. The composableRunnableinterface is where ongoing development and support are focused. - Reach for LangGraph as soon as you need a loop. If your use case involves an agent that might call a tool, check the result, and decide to try again, don't try to force that into a linear LCEL chain. That's exactly the scenario LangGraph exists for.
- Install only what you need. The multi-package split means you can depend on
langchain-coreplus a specific partner package likelangchain-anthropicwithout pulling in every community integration that exists. - Expect continued change, but at a different layer. The core
Runnableinterface and the general shape of LangGraph's state-machine model have stabilized. Where you should still expect movement is in newer integrations, evaluation tooling, and higher-level agent abstractions built on top of LangGraph. - Read version numbers carefully. Because of how fast the ecosystem moved through 2023 and into 2024, code samples that don't specify a version are a common source of confusion. Always check which package versions a tutorial assumes before you copy code from it.
LangChain's History Is a Case Study in Framework Design
What makes LangChain's evolution worth studying isn't just "here's what changed" — it's *why* it changed. Every major shift traces back to a real limitation that developers ran into at scale: monolithic packaging couldn't support an ecosystem of hundreds of integrations, so it split. Ad hoc chain classes couldn't compose predictably or stream consistently, so LCEL introduced a uniform Runnable interface. Directed acyclic pipelines couldn't express the looping, stateful behavior that real agents need, so LangGraph introduced explicit state machines with cycles and checkpointing.
If you're building agentic systems today — whether on LangChain or evaluating it against alternatives — this history gives you a framework for asking the right questions about any orchestration tool: Does it compose predictably? Does it stream and batch without special-casing? Can it express cycles and persistent state, not just linear pipelines? Can it pause for a human and resume later? LangChain's own answers to these questions evolved over roughly three years, in public, with the scar tissue still visible in its package structure.
That's exactly the kind of practical, systems-level understanding we build from the ground up in the LangChain Tutorial 2026 course at teachyou.ai — starting from core Runnable composition with LCEL, through building real, stateful agents in LangGraph, to observability and deployment with LangSmith and LangServe. If you want to stop patching together outdated tutorials and actually understand why the framework is built the way it is, that's where to start.
BootcampA 30-day guided bootcamp: build, harden and ship a production autonomous agent from scratch.
AI AgentsUnderstand how AI agents really work: the loop, the tools, the memory, and why most agent projects fail.