An Agent in LangGraph
The loop as a graph.
Now we'll rebuild Juno as a graph, with a real model. There'll be two nodes. One calls the model, and one runs tools. And one conditional edge. That's it. It's the same agent as before, but we'll soon get superpowers for free.

- Rebuild Juno as a graph
- Two nodes, one conditional edge
- Superpowers for free
The shape of every LangGraph agent
Here's the shape that you'll draw a hundred times. Start goes to the agent. The agent either asks for a tool, or it finishes. Tools always go back to the agent. It's our think, act, observe loop, drawn as a picture.
Juno, as a graph
It's longer, but you know every line. State holds messages, and that annotation means, add to the list, rather than replacing it. The agent node calls the model. The tools node runs whatever was requested, and returns the results. Route checks the last message. If it has a function call, go to tools, otherwise finish. Then we wire it together. And a recursion limit caps the steps. Run it. It chose the tools by itself, and then answered. Same agent, but now every step is a checkpoint-ready graph node.
import operator
from typing import Annotated, TypedDict
from google import genai
from google.genai import types
from langgraph.graph import StateGraph, START, END
from shop import get_order, check_stock
TOOLS = {"get_order": get_order, "check_stock": check_stock}
client = genai.Client()
config = types.GenerateContentConfig(
system_instruction="You are Juno at Velo Supply. Use tools. Reply in two sentences.",
tools=list(TOOLS.values()),
automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True))
class State(TypedDict):
messages: Annotated[list, operator.add]
def agent(state):
r = client.models.generate_content(model="gemini-flash-latest", config=config,
contents=state["messages"])
return {"messages": [r.candidates[0].content]}
def tools(state):
parts = []
for p in state["messages"][-1].parts:
if p.function_call:
c = p.function_call
print("Tool:", c.name, dict(c.args))
parts.append(types.Part.from_function_response(
name=c.name, response=TOOLS[c.name](**c.args)))
return {"messages": [types.Content(role="user", parts=parts)]}
def route(state):
return "tools" if any(p.function_call for p in state["messages"][-1].parts) else END
g = StateGraph(State)
g.add_node("agent", agent)
g.add_node("tools", tools)
g.add_edge(START, "agent")
g.add_conditional_edges("agent", route, ["tools", END])
g.add_edge("tools", "agent")
app = g.compile()
ask = types.Content(role="user", parts=[types.Part(text="Is the item in order B42 in stock?")])
out = app.invoke({"messages": [ask]}, {"recursion_limit": 12})
print(out["messages"][-1].parts[-1].text)Tool: get_order {'order_id': 'B42'}
Tool: check_stock {'item': 'Chain'}
Yes, the item in order B42 is a chain, and it is currently in stock. We have 14 units available right now.Also uses shop.py
ORDERS = {
"A17": {"item": "Carbon wheelset", "price": 640, "status": "delivered", "days_ago": 12},
"B42": {"item": "Chain", "price": 28, "status": "shipped", "days_ago": 2},
"C09": {"item": "Helmet", "price": 95, "status": "delivered", "days_ago": 45},
}
STOCK = {"Carbon wheelset": 3, "Chain": 14, "Helmet": 0}
REFUNDS = []
def get_order(order_id: str) -> dict:
"""Look up one order by its ID, such as A17."""
return ORDERS.get(order_id, {"error": f"No order {order_id}"})
def check_stock(item: str) -> dict:
"""Return how many of an item are in stock."""
return {"item": item, "in_stock": STOCK.get(item, 0)}
def refund(order_id: str, amount: float) -> dict:
"""Refund money to the customer for an order."""
REFUNDS.append((order_id, amount))
return {"refunded": amount, "order_id": order_id}Also uses scripted.py
class Scripted:
"""A stand-in model that replays a fixed list of replies."""
def __init__(self, replies):
self.replies = list(replies)
self.calls = 0
def ask(self, messages):
self.calls += 1
return self.replies.pop(0)LangGraph or a prebuilt agent?
For a standard loop like this one, LangGraph includes a prebuilt helper, called create react agent, which builds this exact graph in one line. Use that if you're happy with the defaults. Write your own graph when you need custom steps, such as approvals, or checks after each tool. Now you know what's inside the helper.
Summary
An agent node, a tools node, and a route. That's every LangGraph agent. Next, a detail that trips up nearly everyone. How state updates really work.
Key takeaways- Two nodes, one route
- Tools always return to the agent
- Prebuilt helpers exist