The Same Agent, Three Frameworks
The agent frameworks worth knowing — LangGraph, Strands, and PydanticAI — each building the identical bookshop agent, their abstractions compared, and when a framework earns its dependency over the Agent SDK or a custom loop.
By chapter 8 you can build an agent three ways yourself, from a hand-rolled loop up to the Agent SDK. You are not the only one who noticed the pattern. Every agent needs the same machinery: a tool-use loop, memory, context management, sometimes subagents. Rewriting it per project is wasteful. Frameworks exist to package those patterns once so you don’t have to.
Three are worth knowing, and they are the three the exam’s Agent Patterns and Frameworks skill names: Strands, LangGraph, and PydanticAI. The skill describes them as “agentic abstraction frameworks for building agents and workflows for multi-step tasks.” This chapter stands up all three and has each build the identical bookshop agent. Then it asks the real question: when is a framework worth its dependency over the SDK, or over a loop you already know how to write?
The task, three times
One tool, one question. A lookup_order(order_id) tool over a tiny database, and the question “What’s the status of order A17? Mention the ETA.” Each framework wires the model to the tool, runs the loop, and answers. Here’s each, verbatim in its own idiom.
Strands (AWS’s SDK): decorate a function as a tool, hand it to an Agent:
import os
from strands import Agent, tool
from strands.models.anthropic import AnthropicModel
KEY = os.environ["ANTHROPIC_API_KEY"]
DB = {"A17": {"status": "shipped", "eta": "June 2, 2026"}}
@tool
def lookup_order(order_id: str) -> dict:
"""Look up a bookshop order's status by id."""
return DB.get(order_id, {"status": "unknown"})
model = AnthropicModel(client_args={"api_key": KEY}, model_id="claude-haiku-4-5", max_tokens=256)
agent = Agent(model=model, tools=[lookup_order])
answer = str(agent("What's the status of order A17? Mention the ETA."))
PydanticAI: FastAPI-style; a model string, and tools attached by decorator:
from pydantic_ai import Agent
DB = {"A17": {"status": "shipped", "eta": "June 2, 2026"}}
agent = Agent("anthropic:claude-haiku-4-5", instructions="You are a bookshop assistant.")
@agent.tool_plain
def lookup_order(order_id: str) -> dict:
"""Look up a bookshop order's status by id."""
return DB.get(order_id, {"status": "unknown"})
answer = agent.run_sync("What's the status of order A17? Mention the ETA.").output
LangGraph: a prebuilt agent over a state graph:
from langchain_core.tools import tool
from langchain_anthropic import ChatAnthropic
from langchain.agents import create_agent
DB = {"A17": {"status": "shipped", "eta": "June 2, 2026"}}
@tool
def lookup_order(order_id: str) -> dict:
"""Look up a bookshop order's status by id."""
return DB.get(order_id, {"status": "unknown"})
agent = create_agent(ChatAnthropic(model="claude-haiku-4-5", max_tokens=256), tools=[lookup_order])
result = agent.invoke({"messages": [{"role": "user", "content": "What's the status of order A17? Mention the ETA."}]})
answer = result["messages"][-1].content
All three ran and produced equivalent answers, each calling lookup_order("A17") and reporting “shipped, ETA June 2, 2026.” The task is identical; only the ergonomics differ.
What each one feels like
The differences that matter in practice, observed while running them:
- Strands is the most minimal: a function, a decorator, an
Agent. It’s model-driven and provider-neutral. The same agent runs on Claude via the direct API (as above), via Bedrock, or on other providers by swapping the model object. It ships providers for Anthropic, Bedrock, Ollama, and more. Want the least ceremony and multi-provider flexibility? Strands is it. - PydanticAI brings Pydantic validation and FastAPI-style ergonomics to agents. The model is a string (
"anthropic:claude-haiku-4-5") and tools attach by decorator. Its real differentiator: outputs and tool arguments are validated through Pydantic models, so you get typed, validated results rather than raw dicts. If your team already lives in Pydantic and FastAPI, it feels native. - LangGraph is the heaviest and most powerful: agents are state graphs. Here’s the detail worth knowing.
create_agentreturns aCompiledStateGraph(confirmed bytype(agent).__name__). So the “agent” is really a graph of nodes and edges. You can inspect it, extend it with custom state, add cycles, and checkpoint it. For a simple tool loop that’s overkill. For complex, branching, stateful multi-agent systems it’s the most expressive of the three. (This blog’s LangGraph series covers it in depth.)
All three implement the same underlying patterns the blueprint lists: the tool-use loop, message/state memory, and context management. That’s the point. They’re different surfaces over the same agent mechanics you built by hand in chapter 8.
Framework, Agent SDK, or custom loop?
So when does a framework earn its dependency? The honest decision:
- Custom loop or Tool Runner (chapter 8) when the agent is small and Claude-only. No dependency, full control, nothing to learn.
- The Claude Agent SDK when you want Anthropic’s native harness — built-in file/web tools, sessions, subagents, hooks — and you’re committed to Claude. It’s the deepest integration with Claude Code’s own machinery.
- A framework (Strands / PydanticAI / LangGraph) when you value one of three things. Provider-neutrality: swap models without rewriting. An ecosystem: LangChain’s tools and integrations, Pydantic’s validation. Or a specific orchestration model: LangGraph’s graphs for complex state. The cost is a third-party dependency and its abstractions.
One caution the exam won’t test but production will: these frameworks are third-party and move fast. They are not maintained by Anthropic. Their APIs drift between versions, which is why this chapter pins exact versions. An agent written against last year’s LangGraph may not import today. That volatility is a real maintenance cost to weigh against the convenience.
Final thoughts
Frameworks package the agent patterns you’d otherwise rebuild — the tool loop, memory, context management — behind different surfaces. Strands is minimal and provider-neutral. PydanticAI brings typed, validated outputs and FastAPI ergonomics. LangGraph models agents as inspectable state graphs (create_agent returns a CompiledStateGraph) and scales to complex multi-agent systems. All three built the same bookshop agent and answered identically. The machinery underneath is the loop from chapter 8. Choose a custom loop for small Claude-only agents, the Agent SDK for Anthropic-native depth, and a framework when provider-neutrality, ecosystem, or a richer orchestration model is worth a fast-moving dependency. Knowing all four options — and that they’re the same mechanics dressed differently — is the skill.
Next: Arc 3 opens with tool implementation — the tool definitions and error contracts that decide whether an agent’s tools actually work.
Comments