All skills
langchain-ai avatar

/langgraph-fundamentals

@657ca08 official

INVOKE THIS SKILL when writing ANY LangGraph code. Covers StateGraph, state schemas, nodes, edges, Command, Send, invoke, streaming, and error handling.

Use this Skill: https://skilld.dev/gh/langchain-ai/langchain-skills/langgraph-fundamentals

This session only. Nothing lands on disk.

referencespython.md

≈2.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Python implementation reference

Use this reference only for LangGraph projects written in Python. The shared concepts, decisions, and invariants remain in ../SKILL.md.

Contents

State Management

State with reducer

Define state schema with reducers for accumulating lists and summing integers.

from typing_extensions import TypedDict, Annotated
import operator

class State(TypedDict):
    name: str  # Default: overwrites on update
    messages: Annotated[list, operator.add]  # Appends to list
    total: Annotated[int, operator.add]  # Sums integers

Forgot reducer for list

Without a reducer, returning a list overwrites previous values.

# WRONG: List will be OVERWRITTEN
class State(TypedDict):
    messages: list  # No reducer!

# Node 1 returns: {"messages": ["A"]}
# Node 2 returns: {"messages": ["B"]}
# Final: {"messages": ["B"]}  # "A" is LOST!

# CORRECT: Use Annotated with operator.add
from typing import Annotated
import operator

class State(TypedDict):
    messages: Annotated[list, operator.add]
# Final: {"messages": ["A", "B"]}

Return partial state updates

Nodes must return partial updates, not mutate and return full state.

# WRONG: Returning entire state object
def my_node(state: State) -> State:
    state["field"] = "updated"
    return state  # Don't mutate and return!

# CORRECT: Return dict with only the updates
def my_node(state: State) -> dict:
    return {"field": "updated"}

Nodes

Node function signatures

Signature When to Use
def node(state: State) Simple nodes that only need state
def node(state: State, config: RunnableConfig) Need thread_id, tags, or configurable values
def node(state: State, runtime: Runtime[Context]) Need runtime context, store, or stream_writer
from langchain_core.runnables import RunnableConfig
from langgraph.runtime import Runtime

def plain_node(state: State):
    return {"results": "done"}

def node_with_config(state: State, config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]
    return {"results": f"Thread: {thread_id}"}

def node_with_runtime(state: State, runtime: Runtime[Context]):
    user_id = runtime.context.user_id
    return {"results": f"User: {user_id}"}

Edges

Basic graph

Simple two-node graph with linear edges.

from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict

class State(TypedDict):
    input: str
    output: str

def process_input(state: State) -> dict:
    return {"output": f"Processed: {state['input']}"}

def finalize(state: State) -> dict:
    return {"output": state["output"].upper()}

graph = (
    StateGraph(State)
    .add_node("process", process_input)
    .add_node("finalize", finalize)
    .add_edge(START, "process")
    .add_edge("process", "finalize")
    .add_edge("finalize", END)
    .compile()
)

result = graph.invoke({"input": "hello"})
print(result["output"])  # "PROCESSED: HELLO"

Conditional edges

Route to different nodes based on state with conditional edges.

from typing import Literal
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    query: str
    route: str
    result: str

def classify(state: State) -> dict:
    if "weather" in state["query"].lower():
        return {"route": "weather"}
    return {"route": "general"}

def route_query(state: State) -> Literal["weather", "general"]:
    return state["route"]

graph = (
    StateGraph(State)
    .add_node("classify", classify)
    .add_node("weather", lambda s: {"result": "Sunny, 72F"})
    .add_node("general", lambda s: {"result": "General response"})
    .add_edge(START, "classify")
    .add_conditional_edges("classify", route_query, ["weather", "general"])
    .add_edge("weather", END)
    .add_edge("general", END)
    .compile()
)

Command

Command state and routing

Command lets you update state AND choose next node in one return.

from langgraph.types import Command
from typing import Literal

class State(TypedDict):
    count: int
    result: str

def node_a(state: State) -> Command[Literal["node_b", "node_c"]]:
    """Update state AND decide next node in one return."""
    new_count = state["count"] + 1
    if new_count > 5:
        return Command(update={"count": new_count}, goto="node_c")
    return Command(update={"count": new_count}, goto="node_b")

graph = (
    StateGraph(State)
    .add_node("node_a", node_a)
    .add_node("node_b", lambda s: {"result": "B"})
    .add_node("node_c", lambda s: {"result": "C"})
    .add_edge(START, "node_a")
    .add_edge("node_b", END)
    .add_edge("node_c", END)
    .compile()
)

Send API

Orchestrator worker

Fan out tasks to parallel workers using the Send API and aggregate results.

from langgraph.types import Send
from typing import Annotated
import operator

class OrchestratorState(TypedDict):
    tasks: list[str]
    results: Annotated[list, operator.add]
    summary: str

def orchestrator(state: OrchestratorState):
    """Fan out tasks to workers."""
    return [Send("worker", {"task": task}) for task in state["tasks"]]

def worker(state: dict) -> dict:
    return {"results": [f"Completed: {state['task']}"]}

def synthesize(state: OrchestratorState) -> dict:
    return {"summary": f"Processed {len(state['results'])} tasks"}

graph = (
    StateGraph(OrchestratorState)
    .add_node("worker", worker)
    .add_node("synthesize", synthesize)
    .add_conditional_edges(START, orchestrator, ["worker"])
    .add_edge("worker", "synthesize")
    .add_edge("synthesize", END)
    .compile()
)

result = graph.invoke({"tasks": ["Task A", "Task B", "Task C"]})

Send accumulator

Use a reducer to accumulate parallel worker results (otherwise last worker overwrites).

# WRONG: No reducer - last worker overwrites
class State(TypedDict):
    results: list

# CORRECT
class State(TypedDict):
    results: Annotated[list, operator.add]  # Accumulates

Running Graphs: Invoke and Stream

Invoke basics

result = graph.invoke({"input": "hello"})
# With config (for persistence, tags, etc.)
result = graph.invoke({"input": "hello"}, {"configurable": {"thread_id": "1"}})

Stream llm tokens

Stream LLM tokens in real-time for chat UI display.

for chunk in graph.stream(
    {"messages": [HumanMessage("Hello")]},
    stream_mode="messages"
):
    token, metadata = chunk
    if hasattr(token, "content"):
        print(token.content, end="", flush=True)

Stream custom data

Emit custom progress updates from within nodes using the stream writer.

from langgraph.config import get_stream_writer

def my_node(state):
    writer = get_stream_writer()
    writer("Processing step 1...")
    # Do work
    writer("Complete!")
    return {"result": "done"}

for chunk in graph.stream({"data": "test"}, stream_mode="custom"):
    print(chunk)

Error Handling

Retry policy

Use RetryPolicy for transient errors (network issues, rate limits).

from langgraph.types import RetryPolicy

workflow.add_node(
    "search_documentation",
    search_documentation,
    retry_policy=RetryPolicy(max_attempts=3, initial_interval=1.0)
)

Tool node error handling

Use ToolNode from langgraph.prebuilt to handle tool execution and errors. When handle_tool_errors=True, errors are returned as ToolMessages so the LLM can recover.

from langgraph.prebuilt import ToolNode

tool_node = ToolNode(tools, handle_tool_errors=True)

workflow.add_node("tools", tool_node)

Common Fixes

Compile before execution

Must compile() to get executable graph.

# WRONG
builder.invoke({"input": "test"})  # AttributeError!

# CORRECT
graph = builder.compile()
graph.invoke({"input": "test"})

Infinite loop needs exit

Provide conditional path to END to avoid infinite loops.

# WRONG: Loops forever
builder.add_edge("node_a", "node_b")
builder.add_edge("node_b", "node_a")

# CORRECT
def should_continue(state):
    return END if state["count"] > 10 else "node_b"
builder.add_conditional_edges("node_a", should_continue)

Additional common mistakes

# Router must return names of nodes that exist in the graph
builder.add_node("my_node", func)  # Add node BEFORE referencing in edges
builder.add_conditional_edges("node_a", router, ["my_node"])

# Command return type needs Literal for routing destinations (Python)
def node_a(state) -> Command[Literal["node_b", "node_c"]]:
    return Command(goto="node_b")

# START is entry-only - cannot route back to it
builder.add_edge("node_a", START)  # WRONG!
builder.add_edge("node_a", "entry")  # Use a named entry node instead

# Reducer expects matching types
return {"items": ["item"]}  # List for list reducer, not a string

Source: SKILL.md on GitHub

No alerts14d5 checks · Risk SAFE
  • Gen Agent Trust Hub14d

    This skill provides educational documentation and code examples for building agent workflows with LangGraph. No security issues were detected during analysis.

  • Socket14d

    No alerts

  • Snyk14d

    Risk: LOW · No issues

  • Runlayer7mo

    1 file scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 657ca08. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated 2 weeks ago
  • Python
  • TypeScript
  • langgraph
  • state-management
  • workflow-orchestration
  • agent-patterns
  • graph-based-systems
  • conditional-routing

README badge

README badge for langchain-ai/langchain-skills/langgraph-fundamentals

Builds directed graph workflows using StateGraph, nodes, edges, and state schemas with reducers. Covers state design, node function signatures, conditional routing with Command, and compilation — essential for any LangGraph application in Python or TypeScript.

Generated from the current SKILL.md.

What is a reducer and when do I need one?
A reducer controls how state updates are merged. Use one when a field should accumulate values (like appending to a list or summing integers) rather than overwriting. Without a reducer, returning a list from a node will overwrite the previous value instead of appending to it.
What should a node function return?
Node functions must return a partial update (a dict with only the fields being changed), not the full state object. Returning the entire state object or mutating state in place will cause errors.
When should I use Command instead of add_edge or add_conditional_edges?
Use Command when you need to update state and decide the next node to route to in a single return value. This is cleaner than using separate conditional edges when the routing decision depends on computed state changes.
What arguments can a node function accept?
A node can accept just state, or state plus config (to access thread_id or configurable values), or state plus runtime (for Python only, to access context and store). The signature determines what information is available to the node.
Do I need to compile a graph before running it?
Yes. Graphs must be compiled with .compile() before invoking or streaming them. Compilation prepares the graph for execution.

Generated from the current SKILL.md. These answers refresh after source changes.