LangGraph building blocks: state, nodes, edges, and invoke
LangGraph vocabulary before you build: typed state, small nodes, conditional edges, tool calling and messages-as-state, shown with a first simple agent.
This article is published in English.
As models gained tool use and multi-step reasoning, ad-hoc loops became hard to test and resume. LangGraph makes control flow explicit: state, nodes, edges, and invocation—so agents are graphs you can inspect, not prompt spaghetti.
The simplest graph
A minimal agent flow is: start → reason/act node(s) → end, with shared state passing between steps.
Build a first simple agent
Receive Order
Prepare Food
Deliver Food
Receive Order
↓
Prepare Food
↓
Deliver Food
Pizza
Address: 123 Main Street
Customer: John
Receive Order
↓
Prepare Food
↓
Deliver Food
from typing_extensions import TypedDict
class State(TypedDict):
graph_state: strNodes
def node_1(state):
print("---Node 1---")
return {"graph_state": state['graph_state'] + "I am"}
def node_2(state):
print("---Node 2---")
return {"graph_state": state['graph_state'] + "happy!"}
def node_3(state):
print("---Node 3---")
return {"graph_state": state['graph_state'] + "sad!"}
import random
from typing import Literal
def decide_mood(state) -> Literal["node_2", "node_3"]:
# Often, we will use state to decide on the next node to visit
user_input = state['graph_state']
# Here, let's just do a 50 / 50 split between nodes 2, 3
if random.random() < 0.5
# 50% of the time, we return Node 2
return "node_2"
# 50% of the time, we return Node 3
return "node_3"
from IPython.display import Image, display
from langgraph.graph import StateGraph, START, END
#Build Graph
builder = StateGraph(state)
builder.add_node("node_1", node_1)
builder.add_node("node_2", node_2)
builder.add_node("node_3", node_3)
#Logic
builder.add_edge(START, "node_1")
builder.add_conditional_edges("node_1", decide_mood)
builder.add_edge("node_2", END)
builder.add_edge("node_3", END)
#Add
graph = builder.compile()
#View
display(Image(graph.get_graph().draw_mermaid_png()))
graph.invoke({"graph_state" : "Hi, this is Lance."})
###OUTPUT
---Node 1---
---Node 3---
{'graph_state': 'Hi, this is Lance. I am sad!'}
{'graph_state': 'Hi, this is Lance. I am sad!'}
%%capture --no-stderr
%pip install --quiet -U langchain_openai langchain_core langgraph
from pprint import pprint
from langchain_core.messages import AIMessage, HumanMessage
messages = [AIMessage(content=f"So you said you were researching ocean mammals?", name="Model")]
messages.append(HumanMessage(content=f"Yes, that's right.",name="Lance"))
messages.append(AIMessage(content=f"Great, what would you like to learn about.", name="Model"))
messages.append(HumanMessage(content=f"I want to learn about the best place to see Orcas in the US.", name="Lance"))
for m in messages:
m.pretty_print()
#OUTPUT
================================== Ai Message ==================================
Name: Model
So you said you were researching ocean mammals?
================================ Human Message =================================
Name: Lance
Yes, that's right.
================================== Ai Message ==================================
Name: Model
Great, what would you like to learn about.
================================ Human Message =================================
Name: Lance
I want to learn about the best place to see Orcas in the US.
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
result = llm.invoke(messages)
type(result)
result
#OUTPUT
langchain_core.messages.ai.AIMessage
result
AIMessage(content='One of the best places to see orcas in the United States is the Pacific Northwest, particularly around the San Juan Islands in Washington State. Here are some details:\n\n1. **San Juan Islands, Washington**: These islands are a renowned spot for whale watching, with orcas frequently spotted between late spring and early fall. The waters around the San Juan Islands are home to both resident and transient orca pods, making it an excellent location for sightings.\n\n2. **Puget Sound, Washington**: This area, including places like Seattle and the surrounding waters, offers additional opportunities to see orcas, particularly the Southern Resident killer whale population.\n\n3. **Olympic National Park, Washington**: The coastal areas of the park provide a stunning backdrop for spotting orcas, especially during their migration periods.\n\nWhen planning a trip for whale watching, consider peak seasons for orca activity and book tours with reputable operators who adhere to responsible wildlife viewing practices. Additionally, land-based spots like Lime Kiln Point State Park, also known as “Whale Watch Park,” on San Juan Island, offer great opportunities for orca watching from shore.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 228, 'prompt_tokens': 67, 'total_tokens': 295, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_50cad350e4', 'finish_reason': 'stop', 'logprobs': None}, id='run-57ed2891-c426-4452-b44b-15d0a5c3f225-0', usage_metadata={'input_tokens': 67, 'output_tokens': 228, 'total_tokens': 295, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})
def multiply(a:int, b:int)-> int:
return a*b
llm_with_tools = llm.bind_tools([multiply])
tool_call = llm_with_tools.invoke([HumanMessage(content=f"What is 2 multiplied by 3
", name="Lance")])
tool_call.tool_calls
#OUTPUT
[{'name': 'multiply',
'args': {'a': 2, 'b': 3},
'id': 'call_lBBBNo5oYpHGRqwxNaNRbsiT',
'type': 'tool_call'}]
from typing_extentions import TypedDict
from langchain_core.messges import AnyMessage
class MessagesState(TypedDict):
messages : list[AnyMessage]
from typing import Annotated
from langgraph.graph.message import add_messages
class MessagesState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
from langgraph.graph import MessagesState
class MessagesState(MessagesState):
# Add any keys needed beyond messages, which is pre-built
pass
# Initial state
initial_messages = [AIMessage(content="Hello! How can I assist you?", name="Model"),
HumanMessage(content="I'm looking for information on marine biology.", name="Lance")
]
# New message to add
new_message = AIMessage(content="Sure, I can help with that. What specifically are you interested in?", name="Model")
# Test
add_messages(initial_messages , new_message)
from IPython.display import Image, display
from langgraph.graph import StateGraph, START, END
# Node
def tool_calling_llm(state: MessagesState):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
# Build graph
builder = StateGraph(MessagesState)
builder.add_node("tool_calling_llm", tool_calling_llm)
builder.add_edge(START, "tool_calling_llm")
builder.add_edge("tool_calling_llm", END)
graph = builder.compile()
# View
display(Image(graph.get_graph().draw_mermaid_png()))
messages = graph.invoke({"messages": HumanMessage(content="Hello!")})
for m in messages['messages']:
m.pretty_print()
#OUTPUT
================================ Human Message =================================
Hello!
================================== Ai Message ==================================
Hi there! How can I assist you today?
messages = graph.invoke({"messages": HumanMessage(content="Multiply 2 and 3")})
for m in messages['messages']:
m.pretty_print()
OUTPUT
================================ Human Message =================================
Multiply 2 and 3!
================================== Ai Message ==================================
Tool Calls:
multiply (call_Er4gChFoSGzU7lsuaGzfSGTQ)
Call ID: call_Er4gChFoSGzU7lsuaGzfSGTQ
Args:
a: 2
b: 3
State
Typed state (often a TypedDict or reducers) holds messages, flags, and intermediate results. Choose reducers deliberately—append for message lists, replace for scalars.
Nodes
Nodes are functions: read state, do work (LLM, tool, pure Python), return a partial update. Keep them small and testable.
Edges
Static edges fix order; conditional edges branch on state. Graphs earn their keep when branching and loops appear.
Graph construction
Register nodes, wire edges, compile with an optional checkpointer for durability.
Graph invocation
Invoke with an input state and config (thread id when checkpointing). Stream events for UX.
Chain vs graph
Linear chains still fit one-pass work. Graphs fit pause/resume, branches, and shared named state.
Messages and chat models
Message lists are the usual conversational state. Bind chat models inside nodes rather than hiding I/O in edge logic.
Tools
Expose tools to model-calling nodes; parse tool calls; write observations back into state.
Using messages as state
When messages are the state, reducers must append safely and truncation/summarization becomes an explicit node—not an accident.
Closing
Part 0 is vocabulary: state, nodes, edges, compile, invoke. Later parts add durable threads, HITL interrupts, and multi-agent patterns on the same foundation.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Name nodes after business steps (classify, retrieve, draft) rather than node1. Readable graphs are operable graphs.
Add a unit test that invokes the compiled graph with a fixture state and asserts which nodes ran. compile() alone does not prove business completeness.
Prefer deterministic nodes for routing tests: inject a fake LLM that returns fixed tool calls so CI does not need network access.
Document the thread_id authz rule early: locating a checkpoint is not permission to read it.
Keep side-effecting tools behind approval edges even in Part 0 prototypes—habits form fast.
Prefer deterministic nodes for routing tests: inject a fake LLM that returns fixed tool calls so CI does not need network access.
Document the thread_id authz rule early: locating a checkpoint is not permission to read it.
Keep side-effecting tools behind approval edges even in Part 0 prototypes—habits form fast.
Prefer deterministic nodes for routing tests: inject a fake LLM that returns fixed tool calls so CI does not need network access.
Document the thread_id authz rule early: locating a checkpoint is not permission to read it.
Keep side-effecting tools behind approval edges even in Part 0 prototypes—habits form fast.
Prefer deterministic nodes for routing tests: inject a fake LLM that returns fixed tool calls so CI does not need network access.