LangGraph Agents

Course Content

LangGraph Agents

7 sections · 49 lessons

How does a LangGraph-based agent differ from an “agent loop” implemented in plain code?


Same loop, different guaranteesTwelve-line while loop• Lost on crash or deploy• No pause for a human• Blank screen until done• Limits written by handcreate_agent on LangGraph• Checkpointed per thread• HumanInTheLoopMiddleware• stream_mode messages• ToolCallLimitMiddleware
The agent's reasoning did not change at all; the three production incidents were all orchestration gaps.

What you need to know

The plain loop

Python
messages = [{"role": "user", "content": "Refund order OD-88123"}]while True:    reply = llm_with_tools.invoke(messages)    messages.append(reply)    if not reply.tool_calls:        break    for call in reply.tool_calls:        messages.append(run_tool(call))     # returns a ToolMessage

Twelve lines, and it works. It has no memory across requests, no pause, no retry, no stream, and if the process dies mid-loop the work is lost.

The prebuilt agent

Python
from langchain.agents import create_agentfrom langchain.agents.middleware import HumanInTheLoopMiddleware, ToolCallLimitMiddlewarefrom langgraph.checkpoint.memory import InMemorySaveragent = create_agent(    "anthropic:claude-sonnet-4-5",    tools=[lookup_order, issue_refund],    system_prompt="You handle refunds for an online store. Be brief.",    middleware=[        HumanInTheLoopMiddleware(interrupt_on={"issue_refund": True}),        ToolCallLimitMiddleware(run_limit=8, exit_behavior="end"),    ],    checkpointer=InMemorySaver(),)agent.invoke({"messages": [{"role": "user", "content": "Refund OD-88123"}]},             {"configurable": {"thread_id": "cust-17"}})

create_agent replaced LangGraph's create_react_agent, which still runs in 1.x but is deprecated. Middleware hooks run before and after model and tool calls — approvals, summarisation, retries, call limits, PII redaction, model fallback.

Streaming comes free

stream_modeYou get
"values"Full state after each step
"updates"Only what each node changed
"messages"LLM tokens as (chunk, metadata) tuples
"custom"Your own events from get_stream_writer()
"checkpoints", "tasks", "debug"Detailed execution events

Pass a list to combine them. With version="v2", every chunk has the same shape: {"type", "ns", "data"}.

A real-life example

A fintech team's refund bot started as the twelve-line loop. Three incidents pushed them to a graph: a deploy killed 40 in-flight conversations; a refund above policy was issued with no human check; and users stared at a blank screen for 9 seconds. Moving to create_agent with a Postgres checkpointer, HumanInTheLoopMiddleware on issue_refund, and stream_mode="messages" to the UI fixed all three in about a week. The loop logic itself did not change.

Follow-up questions to expect

  • "When is the plain loop the right answer?" — Scripts, evals, one-off tools, or when you already have another durable runtime. Adding a framework there only adds dependencies.
  • "When is create_agent not enough?" — When the flow has fixed stages around the loop, parallel branches, several agents, or approval rules that depend on more than one tool call.
  • "Is create_agent a different engine?" — No. It returns a compiled LangGraph graph, so checkpointers, streaming and Command(resume=...) work the same.