Agent Frameworks: LangGraph & PydanticAI
When a hand-rolled ReAct loop stops being enough, and what graph-based orchestration and type-safe agent frameworks actually buy you over it.
Where the Hand-Rolled Loop Starts to Strain
The refund agent's hand-rolled ReAct loop — a while-loop calling the model, parsing the requested action, executing it, feeding the result back — works fine for the eligibility-check-then-refund task from the last guide. Then requirements grow: refunds over $500 need a human approval step before issue_refund actually runs, the workflow needs to survive a server restart mid-approval without losing its place, and the branching logic (eligible / ineligible / needs-exception-review / needs-approval) is turning the loop's body into a tangle of conditionals that's getting harder to reason about with every new case. None of this is impossible to hand-roll — it's that hand-rolling it well means slowly reinventing pieces of two separate concerns: state management and control flow.
LangGraph: Agents as Explicit State Machines
LangGraph models an agent as a graph: nodes are steps (an LLM call, a tool execution, a human-approval gate), and edges define transitions between them — including conditional edges, where the next node depends on the state so far. The refund workflow becomes an explicit graph instead of implicit if/else branching buried in a loop body:
The two things this buys over the hand-rolled version are the ones that were actually getting painful: built-in state persistence (the graph's state checkpoints automatically, so await_human_approval can genuinely pause — surviving a restart — and resume exactly where it left off once a human responds, which the next phase covers in more depth), and explicit, inspectable control flow (the branching logic lives in the graph's edges, not scattered through conditional statements, which makes the eligible/ineligible/needs-approval paths something you can look at rather than trace through code).
PydanticAI: Agents That Take Type Safety Seriously
PydanticAI starts from a different problem: Phase 2 covered getting structured, schema-validated output from a single model call, but a multi-step agent has many such calls chained together, each one a place where an unvalidated or malformed value can silently corrupt everything downstream. PydanticAI extends the structured-output discipline through the entire agent — tool inputs, tool outputs, and the agent's final result are all defined and validated as Pydantic models end to end, not just the last response. For the refund agent, that means the eligibility check's output, the refund amount calculation, and the payment API call's parameters are each independently type-checked, so a malformed amount can't silently slip from one step into the next and only surface once money has actually moved.
class RefundDecision(BaseModel):
eligible: bool
amount_cents: int = Field(ge=0)
reason: str
class RefundResult(BaseModel):
order_id: str
decision: RefundDecision
confirmation_id: str | NoneTwo Different Philosophies, Not Competing Products
| LangGraph | PydanticAI | |
|---|---|---|
| Core abstraction | Graph of nodes and edges | Type-validated agent loop |
| Strongest for | Complex branching, persistence, human-in-the-loop pauses | Correctness of data flowing between steps |
| Mental model | State machine | Strongly-typed function pipeline |
These aren't mutually exclusive concerns — a production refund agent benefits from both explicit control flow and validated data at every step, and it's common to see LangGraph's orchestration used alongside Pydantic models defining the shape of data at each node, rather than treating the two frameworks as an either/or choice.
Check yourself
The refund agent's hand-rolled loop needs to pause for human approval on refunds over $500 and reliably resume — even across a server restart — once approved. What does a framework like LangGraph provide here that a hand-rolled while-loop doesn't have by default?
What's Next
A framework handles orchestration for one agent well. The refund agent is about to become one piece of something larger — a broader customer-operations system that also handles shipping and account questions — and cramming all of that into a single agent's toolset and system prompt creates its own new problems.
Frequently asked questions
Do I need a framework for every agent, even a simple one?
No — a single tool call, or even a short, bounded ReAct loop with two or three steps and no need to pause or persist state, is often simpler to hand-roll than to wire into a framework's abstractions. Frameworks earn their complexity specifically once you need persistence, non-trivial branching, or human-in-the-loop pauses, which is exactly the point where the refund agent in this guide needed one.
Can LangGraph and PydanticAI be used together in the same system?
Yes — they solve different layers of the same problem (control flow versus data validation), and using LangGraph's graph orchestration with Pydantic models defining each node's inputs and outputs is a common, complementary combination rather than a conflict between competing frameworks.
Does using an agent framework eliminate the need to understand ReAct from the last guide?
No — frameworks like LangGraph implement orchestration patterns that often include a ReAct-style reasoning loop internally, or make it straightforward to build one as part of a graph node. Understanding the underlying pattern is what lets you use the framework's abstractions deliberately instead of treating them as a black box.