Title: Lecture 7 — Two Ways to Build an Agent: Graphs vs. Schemas-and-Functions
Course: AIINFRA 300: Agentic AI & the Model Context Protocol (MCP) > Section: Week 07 — Code-First Agent Frameworks I: LangGraph and Pydantic AI

📖 Lecture — Two Ways to Build an Agent: Graphs vs. Schemas-and-Functions

Up to now this course has leaned on no-code and low-code agent builders. Starting this week, you write the agent yourself in Python, using two of the most widely adopted code-first frameworks: LangGraph and Pydantic AI. They solve overlapping problems but start from very different mental models, and knowing when to reach for each is the single most useful skill in this lecture.

LangGraph: an agent as a typed state graph

LangGraph models an agent as an explicit graph. Nodes are ordinary Python functions that accept the current State and return a partial update to it. Edges — static or conditional — define how control moves from node to node, which is what lets you build loops, branches, and human-approval gates instead of a single straight-line pass through a prompt. Critically, every transition is persisted through a checkpointer. For demos, an in-memory or SQLite checkpointer is fine; for anything you'd call production, you want Postgres (or another durable store) so a run can be paused, inspected, and resumed exactly where it left off — even after a process restart. Install it with pip install -U langgraph (version 1.1.6 as of April 2026). It requires Python 3.10+, and Python 3.14 support landed in April 2026 as well, so check your interpreter version before you install. State in LangGraph is typically defined as a TypedDict, with reducers describing how individual fields accumulate across node calls (for example, appending to a message list rather than overwriting it). LangGraph 1.1 added the ability to coerce Pydantic models and dataclasses into state as well, which is convenient if you're already validating data with Pydantic elsewhere in your app. That said, TypedDict remains the recommended default: it has the lowest overhead and the most complete support for checkpointing and streaming, which are two of LangGraph's core selling points. One of LangGraph's most distinctive features is interrupt(). Calling interrupt() inside a node pauses that node and returns a payload back to whatever called the graph — a person, a UI, an approval queue. A Command object is what resumes execution afterward, and it can optionally update state or route to a different node as it resumes. Three rules matter in practice: don't wrap interrupt() in a try/except block (it works by raising and re-raising a special exception under the hood, and swallowing that breaks the pause/resume mechanism); preserve the call order of interrupts across a single node's execution; and only pass JSON-safe values through an interrupt, since the payload has to be persisted and later deserialized by the checkpointer.

Pydantic AI: an agent as schemas plus functions

Pydantic AI takes a different starting point. A minimal agent is genuinely this small:

from pydantic_ai import Agent

agent = Agent("anthropic:claude-sonnet-4-6", instructions="You are a helpful assistant.")

Pydantic AI is type-safe by design — output types, tool signatures, and dependencies are all declared with Pydantic models, and the framework validates them at the boundary rather than hoping the model's output happens to parse. It launched in late 2024 and had picked up roughly 16,000 GitHub stars by early 2026, with a release cadence of nearly once a week, so expect the ecosystem to keep moving quickly. Where LangGraph treats an agent as a graph of states and transitions, Pydantic AI treats an agent as schemas plus Python functions: you define an output type, register tool functions, and let the agent's run loop handle the back-and-forth with the model internally. There's no explicit graph to draw.

Picking the right tool for the job

 | LangGraph | Pydantic AI
Mental model | Graph of states and transitions | Schemas + functions
Best fit | Genuine state machines: branching, loops, human-approval gates | Mostly linear workflows: input → tool calls → structured output
Durability | Built-in via checkpointer (resumable, auditable runs) | Not a core feature — add your own persistence if needed
State definition | TypedDict (recommended) or Pydantic/dataclass (1.1+) | Pydantic models throughout
Pause for human input | interrupt() • Command | No built-in equivalent
Overhead / speed | Higher, in exchange for structure | Lower, favors fast linear execution

Correcting two common misconceptions

"LangGraph is the more powerful choice, so I should always use it." Power isn't the axis that matters here — the two frameworks model agents differently. If your workflow is fundamentally a straight line (take input, call a tool or two, return a validated structured result), Pydantic AI gets you there with less code and less overhead. LangGraph earns its complexity only when the workflow is genuinely a state machine: it needs branching logic, it needs to pause for a human to approve a step, or it needs a durable, resumable, auditable record of every transition. Reach for LangGraph because the problem is graph-shaped, not because it sounds more advanced. "A richer state object is always better in LangGraph." New LangGraph users often respond to this flexibility by cramming in every field they can imagine needing. Resist that. State is updated by node return values and reducers, so every extra field is one more thing that can be mismanaged, one more merge conflict between concurrent updates, and one more thing to reason about when debugging a run. Keep state minimal — just what the graph actually needs to make its next decision — and add fields only when a real transition requires them. By the end of this week's lab, you'll have built a small agent in each framework and felt the difference in your hands, not just on the page.