If you’ve looked at LangGraph code before and felt lost in a pile of StateGraph, add_node, and add_edge calls, this guide is for you. We're going to slow down and actually explain what's happening at each step, not just show code and assume it's obvious.
By the end, you’ll understand six ways to structure an LLM-powered system, when to reach for each one, and how to read (or write) the code without guessing.
This article is adapted from the reference material: Workflow And Agents
A single call to an LLM is just a question and an answer. You send a prompt, you get text back, and that’s it. The model has no memory of what it should do next, no way to check its own work, and no ability to call a search engine or a calculator on its own.
The moment you need more than one step (write a draft, then check it, then fix it), you need something to hold the pieces together: what happened so far, what should happen next, and how to move from one step to the other. That “something” is what this whole guide is about.
There are two broad ways to build that structure. Anthropic’s own engineering team wrote a good explainer on this, and it’s worth reading directly: Building Effective Agents.
People throw these two words around interchangeably, which causes a lot of confusion. Here’s the distinction that matters:
A workflow is a fixed path you designed in advance. You, the developer, decide: step one happens, then step two, maybe with a branch depending on a condition, then step three. The LLM fills in the content at each step, but it doesn’t get to change the route. Think of it like a recipe: the steps are printed on the card, and the cook (the LLM) follows them in order.
An agent doesn’t have a fixed path. Instead, it’s given a goal, a set of tools, and the freedom to decide what to do next based on what just happened. It might call a tool, look at the result, decide it needs another tool, call that, and keep going until it thinks the job is done. Nobody wrote down the exact sequence of steps ahead of time. The model works that out as it goes.
Neither one is “better.” Workflows are predictable, easier to test, and cheaper to run, because you know exactly what will happen. Agents are more flexible and can handle problems you didn’t fully anticipate, but that flexibility comes at the cost of predictability. An agent can wander, loop unnecessarily, or make a bad call that a fixed workflow would never have allowed in the first place.
A rough rule of thumb: if you can draw the flowchart before you write any code, build a workflow. If you can’t, because the right next step genuinely depends on things you can’t predict, you probably need an agent.
Short answer: no, not technically. Every pattern in this guide could be written as plain Python: a few functions and some if/else logic. LangGraph doesn’t add new capabilities that Python doesn’t already have.
What it adds is infrastructure you’d otherwise have to build yourself, and would probably build badly the first few times:
If you’re just experimenting with a single prompt, skip LangGraph entirely. Once you’re chaining multiple steps together and need to keep track of what happened, it starts pulling its weight.
Every code example below follows the same seven-part layout. Learn this once, and you can read any of the patterns further down without re-learning how the code is organized each time.
Keeping this order consistent is the whole point. Once “State” always means the same thing and “Edges” always means the same thing, you stop having to reverse-engineer someone else’s code structure every time you open a new file.
Before any of the six patterns, you need one core idea: a plain LLM and an LLM that’s been given extra capabilities are not the same thing, and LangChain calls the upgraded version an “augmented” LLM.
On its own, an LLM just turns text into more text. Two upgrades change what it can do:
Structured output forces the model to answer in a specific shape, a Python object with named fields, instead of free-form prose you’d have to parse yourself. You describe the shape you want using a Pydantic model (a class that defines field names, types, and short descriptions), and the LLM fills it in.
Tool binding gives the model a menu of functions it’s allowed to call. The LLM doesn’t actually run the function. It decides whether a function is needed and what arguments to pass, and your code is the one that actually executes it and hands the result back.
from langchain_anthropic import ChatAnthropic# This is just a plain, unmodified LLM connection.llm = ChatAnthropic(model="claude-3-5-sonnet-latest")
Here’s structured output in practice:
from pydantic import BaseModel, Field# This class describes the exact shape we want the LLM's answer to take.# Two fields: the search query itself, and a short justification for it.class SearchQuery(BaseModel): search_query: str = Field(description="A web-search-optimized version of the question.") justification: str = Field(description="Why this query answers the user's request.")# .with_structured_output() wraps the LLM so it always replies in this shape.structured_llm = llm.with_structured_output(SearchQuery)output = structured_llm.invoke("How does a calcium CT score relate to cholesterol?")print(output.search_query) # a clean, searchable stringprint(output.justification) # the model's reasoning for picking that query
Without with_structured_output, you'd get back a paragraph of text and have to write regex or string-parsing logic to pull the query out of it. That's fragile: the model might phrase things slightly differently every time. Structured output sidesteps the problem entirely by making the shape non-negotiable.
And here’s tool binding:
def multiply(a: int, b: int) -> int: return a * b# bind_tools tells the LLM this function exists and describes what it does,# based on the function's name, type hints, and docstring.llm_with_tools = llm.bind_tools([multiply])msg = llm_with_tools.invoke("What is 2 times 3?")# The LLM doesn't compute 2*3 itself. It recognizes multiply() fits this task# and returns a *request* to call it, with the arguments filled in.msg.tool_calls# -> [{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': '...'}]
Notice the model never actually multiplies anything. It just says “call multiply with a=2, b=3." Your code has to be the one that runs it. We'll see exactly how in the full agent example near the end.
With those two ideas in hand, we can move through the six patterns, from simplest to most autonomous.
The idea: break one big task into several smaller LLM calls that run one after another, where each step builds on the output of the one before it. Use this when a task naturally breaks into stages, and getting each stage right on its own is easier than trying to get the whole thing right in a single shot.
A good comparison is editing an essay: you write a rough draft, then you tighten the language, then you do a final polish pass. Each pass has a narrow job, and narrow jobs are easier for an LLM (and for a human) to do well than “write a perfect essay in one attempt.”
Our example: write a joke, improve it with wordplay, then add a twist ending. There’s also a quality check in the middle: if the first draft doesn’t look like a real joke, we stop early instead of wasting two more LLM calls polishing something broken.
A few things worth slowing down on:
The idea: when the sub-tasks don’t depend on each other, run them at the same time instead of one after another. If you’re generating a joke, a story, and a poem about the same topic, there’s no reason the poem has to wait for the joke to finish first. Nothing in the poem depends on the joke.
This is also useful when you want several independent “opinions” on the same input, for example, asking three different prompts to search for the same information from different angles, then combining what they each found.
The key structural difference from prompt chaining: three separate edges leave START, instead of one edge chaining into the next. LangGraph sees that call_llm_1, call_llm_2, and call_llm_3 don't depend on each other's output, so it can run them concurrently and simply wait for all three before moving on to aggregator.
The idea: look at the input first, classify what kind of request it is, and send it down a different path depending on the answer. This is useful whenever different types of input genuinely need different handling. A customer support message about billing shouldn’t go through the same logic as a technical bug report.
The trick is that the “router” itself is just another LLM call, one that uses structured output to guarantee it returns one of a fixed set of labels, rather than a free-text guess you’d have to interpret.
Notice llm_call_1, llm_call_2, and llm_call_3 are nearly identical here. In a real project, they'd usually differ by using different system prompts, different tools, or even entirely different models suited to each type of task. The point of routing isn't the destination nodes; it's that the router reliably sends each input to the right specialist.
The idea: one LLM call plans out an unknown number of sub-tasks, then a worker handles each one (in parallel) and a final step stitches all the results together. The key difference from plain parallelization is that you don’t know ahead of time how many parallel branches you’ll need. In Pattern 2, we hardcoded exactly three branches (joke, story, poem). Here, the number of branches is decided at runtime by the orchestrator itself.
A good real-world case: writing a report. You don’t know in advance how many sections the report needs. That depends on the topic. So you let an LLM plan the sections first, and only then do you know how many workers to spin up.
This needs two new pieces of machinery:
If you only remember one thing from this pattern, make it this: Send is for when the number of parallel branches isn't known until the graph is already running. Everything else about it (nodes, edges, state) works the same way as the patterns before it.
The idea: one LLM generates something, a second LLM call grades it, and if it doesn’t pass, the feedback gets fed back into another generation attempt, looping until it’s good enough. This is essentially a built-in editor that won’t let a weak first draft through.
This is worth reaching for whenever “good” is something you can actually check for: grading a joke as funny or not, checking a generated SQL query for syntax errors, or verifying a RAG answer is actually backed by the retrieved documents rather than invented.
One practical warning: nothing in this graph stops it from looping forever if the evaluator is a harsh grader and the generator can’t satisfy it. In real projects, you’d usually add a retry counter to State and force an exit after, say, three attempts. Otherwise, a stubborn evaluator can burn through your API budget without you noticing.
The idea: this is where we stop pre-drawing the flowchart. Instead of you deciding the sequence of steps, the LLM decides (on every turn) whether it needs to call a tool, and it keeps looping between “think” and “act” until it decides the job is done.
This is genuinely different from every pattern above. In prompt chaining, routing, and the rest, you wrote down which node comes after which. Here, there’s really only one loop: the model thinks, optionally calls a tool, sees the result, and thinks again, and it can go around that loop as many times as it needs to.
Use this when the task is open-ended enough that you can’t lay out the steps in advance, for instance, a multi-step calculation where you don’t know how many operations will be needed until you’re partway through it.
Walk through what actually happens on that input, step by step:
Nobody wrote “call add, then call multiply” anywhere in the code. The model figured that sequence out on its own, one decision at a time. That’s the entire difference between an agent and a workflow.
Start simple and add complexity only when you hit its limits:
Every LangGraph Pattern You’ll Actually Use : Explained Properly was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.