ai_ml / agents orchestration / 03_langchain_langgraph.md

LangChain and LangGraph 1.0

6 interview angles 5 min read source

LangChain and LangGraph 1.0

Both reached 1.0 in October 2025; LangGraph is at 1.3.x as of mid-2026. The 1.0 line committed to API stability with no breaking changes until 2.0. If your knowledge is the chains-and-LCEL era, it’s a generation out of date.

The relationship: LangGraph is the runtime; LangChain 1.0 is an opinionated, middleware-driven high-level API on top of it. That’s the sentence to have ready.

What changed at 1.0

Before 1.0
Chains, LLMChain, sprawling abstractions focused on the agent loop
Subclass to customise middleware hooks
Ad-hoc state, lost on restart durable execution with checkpointing
Many overlapping entry points create_agent as the front door

LangChain 1.0 narrowed its scope deliberately: the core agent loop plus integrations, rather than an abstraction for everything.

create_agent and middleware

from langchain.agents import create_agent

agent = create_agent(
    model="...",
    tools=[search, calculator],
    middleware=[RateLimitMiddleware(), PIIRedactionMiddleware()],
)

Middleware is the 1.0-era customisation mechanism. The hooks wrap the loop:

Hook Fires
before_agent / after_agent around the whole run
before_model / after_model around each model call
wrap_model_call around the model call, can modify or short-circuit
wrap_tool_call around each tool execution

This is the same idea as HTTP middleware, and it’s what you use instead of subclassing. Practical uses: redact PII before the model sees it, enforce per-user rate limits, inject dynamic context, log and trace, retry on a schema violation, or block a tool call pending approval.

If asked “how do you customise agent behaviour in LangChain 1.0”, the answer is middleware, not inheritance.

LangGraph: state machines, not chains

You define a graph of nodes (functions) and edges (transitions). State flows through and is merged by reducers.

from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]   # reducer: append, don't replace
    retries: int

def call_model(state: State) -> dict:
    return {"messages": [llm.invoke(state["messages"])]}

def should_continue(state: State) -> str:
    return "tools" if state["messages"][-1].tool_calls else END

builder = StateGraph(State)
builder.add_node("model", call_model)
builder.add_node("tools", ToolNode(TOOLS))
builder.set_entry_point("model")
builder.add_conditional_edges("model", should_continue)
builder.add_edge("tools", "model")            # the loop
graph = builder.compile(checkpointer=checkpointer)

Three things to understand:

  • Reducers decide how a node’s output merges into state. add_messages appends; the default replaces. Getting this wrong silently drops history — a classic first bug.
  • Conditional edges are where control flow lives. That should_continue function is the agent loop’s termination check.
  • Cycles are explicit. tools -> model is the loop, drawn rather than implied.

Durable execution — the real 1.0 feature

LangGraph treats agent execution as durable graph execution rather than a Python function call. Every node transition is checkpointed through a pluggable persistence layer.

from langgraph.checkpoint.postgres import PostgresSaver

graph = builder.compile(checkpointer=PostgresSaver.from_conn_string(DSN))
config = {"configurable": {"thread_id": "conversation-42"}}
graph.invoke({"messages": [msg]}, config)     # resumes from wherever it left off

What that buys, and why it matters to a backend engineer:

  • Survives process restart. A deploy mid-conversation doesn’t lose state.
  • Pause and resume, including across days — which is what makes approval workflows practical.
  • Time-travel debugging. Rewind to any checkpoint, alter state, re-run.
  • Thread-scoped memory for free — conversation state is the checkpoint.

This is the thing that makes LangGraph more than a nicer loop. An agent as a durable, resumable process is a fundamentally different operational object from an agent as a function call that dies with its worker. See 05_durable_execution_hitl.md.

Human-in-the-loop

from langgraph.types import interrupt, Command

def approve_refund(state: State):
    decision = interrupt({"order": state["order_id"], "amount": state["amount"]})
    if decision != "approve":
        return {"messages": [{"role": "tool", "content": "Refund denied by reviewer."}]}
    return {"messages": [execute_refund(state)]}

# Later, possibly a different process, hours later:
graph.invoke(Command(resume="approve"), config)

interrupt suspends execution and persists state. Resumption can happen from anywhere with the thread_id. Without durable checkpointing this would require you to build a state machine and a job queue yourself.

The ecosystem

Piece Does
LangGraph the runtime — graphs, state, checkpointing
LangChain high-level agent API, middleware, 100+ integrations
LangSmith tracing, evaluation, prompt management
LangGraph Platform hosted deployment, scheduling, long-running runs

LangSmith is the observability answer within this stack; OpenTelemetry GenAI conventions are the vendor-neutral alternative. Either is fine — having none is not.

Alternatives

Framework Character
LangGraph most control, durable execution, steepest learning curve
Pydantic AI type-safe, Pythonic, lighter — see 12_pydantic_ai.md
OpenAI Agents SDK minimal, provider-aligned
CrewAI role-based multi-agent, opinionated
AutoGen conversational multi-agent, research-leaning
no framework a loop and a while statement

Choose LangGraph when you need durable state, human-in-the-loop, or non-trivial topology. Choose Pydantic AI when you want type safety and a smaller surface. Choose nothing when you have a loop with three tools — and be willing to say so.

Interview angle

  • “LangChain or LangGraph?” — LangGraph is the runtime; LangChain 1.0 is a middleware-driven high-level API on top of it. You’re not choosing between them so much as choosing a level of abstraction.
  • “What changed at 1.0?” — scope narrowed to the agent loop, middleware replaced subclassing as the customisation mechanism, and durable execution with checkpointing became the foundation. API stability committed until 2.0.
  • “What is durable execution and why does it matter?” — every node transition is checkpointed to a persistence layer, so a run survives restarts, can pause for hours awaiting approval, and can be rewound for debugging. It turns an agent from a function call into a resumable process.
  • “How do you customise agent behaviour in LangChain 1.0?” — middleware hooks around the agent, model call and tool call. Not subclassing.
  • “What’s a reducer in LangGraph state?” — it decides how a node’s returned value merges into state. add_messages appends to history; the default replaces it. Using the default on a message list silently discards the conversation.
  • “When would you not use a framework?” — a bounded loop with a few tools. Frameworks earn their cost when you need durability, approval gates, streaming to a UI, or complex topology.