Debug and Visualize LangGraph Graphs with Mermaid and Studio

As graphs grow, you will need to answer two questions: “What does my graph look like?” and “Why did it do that?” This chapter covers the tools for both, from simple print statements to LangGraph Studio.

Part 1: Visualizing your graph

Mermaid text (you have used this already)

print(graph.get_graph().draw_mermaid())

Paste the output into a Mermaid viewer such as mermaid.live to see the flowchart.

ASCII diagram in the terminal

pip install grandalf
graph.get_graph().print_ascii()

This draws boxes and arrows as plain text, so no browser is needed. It is handy for quick checks.

PNG image

png = graph.get_graph().draw_mermaid_png()

with open("my_graph.png", "wb") as f:
    f.write(png)

In Jupyter or Colab, display it directly:

from IPython.display import Image, display
display(Image(graph.get_graph().draw_mermaid_png()))

By default, PNG rendering sends your graph’s structure (node names and edges) to an online rendering service. That is fine for tutorial graphs, but use the Mermaid text or ASCII option for graphs with private names or logic.

Looking inside subgraphs

print(graph.get_graph(xray=True).draw_mermaid())

As you saw in Chapter 17, xray=True expands subgraphs so you can see their inner nodes.

Diagram tips

  • Use Literal return hints on routers and Command functions (Chapters 6 and 15), or the diagram may miss some branches
  • Use clear node names (check_answer, not n3), since they appear in diagrams, streams, and error messages

Part 2: Debugging by watching the run

Stream updates

Your first debugging tool is still stream():

for step in graph.stream(inputs, stream_mode="updates"):
    print(step)

It shows which node ran and what it returned, in order. Most bugs become obvious here: a node that never ran, a wrong value written, or a loop that repeats.

The debug stream mode

for event in graph.stream(inputs, stream_mode="debug"):
    print(event)

debug mode gives much more detail: when each step starts, which tasks are scheduled, and their results. It is noisy, so use it when updates isn’t enough.

Inspect saved state

With a checkpointer (Chapter 13), you can inspect any run afterward:

config = {"configurable": {"thread_id": "debug-1"}}
graph.invoke(inputs, config)

snapshot = graph.get_state(config)
print(snapshot.values)     # final state
print(snapshot.next)       # what would run next (empty when finished)

for past in graph.get_state_history(config):
    print(past.next, past.values)

get_state_history() lists every checkpoint, newest first. Reading it top to bottom, you can find the exact step where a value went wrong.

Part 3: Common errors and what they mean

Here is a bug on purpose, so you can practice reading the error:

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

class State(TypedDict):
    text: str
    word_count: int

def count_words(state: State):
    return {"word_count": len(state["txt"].split())}   # typo: "txt"

builder = StateGraph(State)
builder.add_node("count_words", count_words)
builder.add_edge(START, "count_words")
builder.add_edge("count_words", END)
graph = builder.compile()

graph.invoke({"text": "hello world"})

Result:

KeyError: 'txt'

The traceback points to the line inside count_words. The fix is state[“text”]. Errors inside a node are normal Python errors, so read them like any other.

A quick list of errors you may meet in this tutorial:

  • KeyError: ‘name’: reading a state field that doesn’t exist yet or is misspelled. Check the spelling and node order.
  • GraphRecursionError: a loop never reached its exit condition. Fix the exit check (Chapter 7).
  • InvalidUpdateError: parallel nodes wrote to one key without a reducer. Add a reducer (Chapter 19).
  • Error about a missing thread_id: a checkpointer is set, but the config has no thread ID. Pass {“configurable”: {“thread_id”: “…”}}.
  • Error about an unknown node: a misspelled node name in an edge or router. Compare names character by character.
  • Graph ends with no output: a missing edge to END, or a router returned nothing. Print the diagram.

Part 4: Tracing with LangSmith

For LLM apps, printing isn’t enough. You want to see the exact prompts, responses, tool calls, timing, and token use for every step. LangSmith is LangChain’s tracing service, and it has a free tier.

Add these to your .env file:

LANGSMITH_TRACING=true
LANGSMITH_API_KEY=your-langsmith-key

Run your graph as usual, then open LangSmith in your browser. Each run appears as a trace: a tree showing every node, model call, and tool call, with inputs, outputs, and durations. It is the best way to answer “why did the agent call that tool?” or “which step is slow?”

Setting names can change between versions, so confirm them in LangSmith’s current docs. Also remember that traces contain your prompts and model outputs, so don’t trace runs that include private user data unless you are comfortable with LangSmith storing it.

Part 5: LangGraph Studio

LangGraph Studio is a visual tool for running and debugging graphs. You see your graph as a diagram, run it with inputs, watch nodes light up, inspect state at each step, and even edit state and re-run from a checkpoint.

Requirements

  • The Studio local server needs Python 3.11 or higher, and a free LangSmith API key. This is newer than the Python 3.10 minimum from Chapter 3, so upgrade if needed.
  • The LangGraph CLI:
pip install -U "langgraph-cli[inmem]"

Project setup

The easiest start is the official template:

langgraph new my-studio-app --template new-langgraph-project-python
cd my-studio-app

Create .env from the example file and add your keys, including LANGSMITH_API_KEY. The project contains a langgraph.json file that tells Studio where your graph lives:

{
  "dependencies": ["."],
  "graphs": {
    "agent": "./src/agent/graph.py:graph"
  },
  "env": ".env"
}
  • graphs maps a name (agent) to a file and a variable holding your compiled graph (graph)
  • env points to your environment file
  • dependencies tells the CLI what to install

The exact file paths in the template may differ by version, so use what your generated langgraph.json says. To use your own graph, make sure your file defines a compiled graph variable and point graphs at it.

Start the server

langgraph dev

The terminal prints links similar to these, which come from the official docs:

- API: http://127.0.0.1:2024
- Studio UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
- API Docs: http://127.0.0.1:2024/docs

Open the Studio UI link. Your code stays on your machine, and the Studio web page connects to your local server. Note that Safari blocks localhost connections to Studio, so use the –tunnel option if you are on Safari.

What you can do in Studio

  • See the graph as a diagram, including branches and loops
  • Run it by typing inputs into the state fields
  • Watch each step as nodes run, with the state visible after each one
  • Pause at interrupts (Chapter 15) and answer them from the UI
  • Edit state and re-run from a past step, ideal for testing “what if the model had answered differently?”
  • Hot reload: when you edit your code, Studio picks up the changes

The local server is an in-memory development server, meant for development and testing, and not for production.

A simple debugging routine

When something goes wrong, work through this list:

  1. Print the diagram. Are the edges what you meant?
  2. Stream with updates. Which node ran, and what did it return?
  3. Check the reducer. Did a value get overwritten instead of appended, or did parallel writes clash?
  4. Inspect the state history and find the first checkpoint with a wrong value
  5. Trace the LLM steps in LangSmith, and read the exact prompt the model saw
  6. Shrink the problem: test the failing node alone by calling its function with a hand-made state dictionary

Point 6 is powerful because nodes are plain functions:

print(count_words({"text": "hello world", "word_count": 0}))

Common mistakes

  • Trusting the diagram of a router with no Literal hint: missing branches don’t mean missing code
  • Debugging with invoke() only: you see the result but not the path, so use stream()
  • Forgetting the checkpointer when calling get_state(): there is no saved state to inspect
  • Leaving tracing on with private data: turn LANGSMITH_TRACING off for sensitive runs
  • Pointing langgraph.json at the builder instead of the compiled graph: expose the result of .compile()
  • Using Safari without the tunnel option: Studio can’t reach your local server
  • Committing .env to Git: it holds your API keys (Chapter 3)

Summary

  • Visualize with draw_mermaid(), print_ascii(), or draw_mermaid_png(), and use xray=True for subgraphs
  • Debug by watching stream(), reading get_state() and get_state_history(), and testing nodes alone
  • Learn the common errors: KeyError, GraphRecursionError, InvalidUpdateError, and missing thread_id
  • LangSmith traces show every prompt, tool call, and timing
  • LangGraph Studio (langgraph dev) lets you run, inspect, and re-run graphs visually

Practice

  1. Print the ASCII diagram for your supervisor graph from Chapter 18
  2. Recreate the KeyError bug, then fix it using only the traceback
  3. Stream a graph with stream_mode=”debug” and compare it with updates
  4. Create a Studio project with langgraph new, run langgraph dev, and send one message through the UI

If you liked the tutorial, spread the word and share the link and our website, Studyopedia, with others.


For Videos, Join Our YouTube Channel: Join Now


Read More:

LangGraph Parallel Execution: Fan-Out and Fan-In
LangGraph Project: Build a Customer Support Bot
Studyopedia Editorial Staff
contact@studyopedia.com

We work to create programming tutorials for all.

No Comments

Post A Comment