GraphLoupe documentation
A self-hosted, node-level LangGraph debugger for VS Code. This page is the copy-paste guide: how to install, connect your graph, and enable each feature.
What it is
GraphLoupe runs your compiled LangGraph and lets you step through it, inspect and diff state between nodes, time-travel, count tokens per LLM node, and pause a node to paste an answer from any chat back in as the model's output — all locally, no account or cloud. The IDE never imports your graph; an isolated Python sidecar worker does.
Install
- From the Marketplace: search "GraphLoupe" in the Extensions view, or
code --install-extension byGao.graphloupe. - From a .vsix: download a release (or build with
npm install && npm run package), then Extensions view →⋯→ Install from VSIX….
Python: the sidecar needs Python with the deps in requirements.lock.
GraphLoupe resolves the interpreter in this order: graphloupe.pythonPath setting →
the Python extension's selected interpreter (ms-python.python) → py -3 /
python3 / python on PATH. It builds a managed environment for the sidecar
and runs your graph in your interpreter. The Health tab shows exactly which
interpreter and langgraph version are in use.
langchain/langgraph
import — retry, or raise graphloupe.loadTimeout (default 30s).Connect your graph
Ctrl/Cmd+Shift+P → GraphLoupe: Open Graph Panel, then GraphLoupe: Select
Graph. It AST-scans your project (no code is run) for a graph factory. Pick one, or choose
✎ Specify manually… to point at a file + symbol (a factory function, or an already-compiled
graph exposed as a module variable). Your choice is saved to .vscode/settings.json
(graphloupe.graphEntry), per-workspace.
An entry is module.path:callable, e.g. pipeline.graph:build_app. To just
try everything, use the built-in graphloupe_sidecar.graph:showcase_graph.
Minimal graph GraphLoupe can load
Any file with a top-level factory that returns a compiled graph:
from typing import TypedDict
from langgraph.graph import START, END, StateGraph
class State(TypedDict):
messages: list
steps: int
def build_graph():
def prepare(state: State) -> dict:
"""Seed the state (this docstring shows under the node)."""
return {"steps": state.get("steps", 0) + 1}
g = StateGraph(State)
g.add_node("prepare", prepare)
g.add_edge(START, "prepare")
g.add_edge("prepare", END)
return g.compile()
A docstring on each node function shows as the node's purpose in the overview.
Step debugging — add a checkpointer
Breakpoints, state snapshot + diff, single-step, and time-travel all need a checkpointer. Compile with one:
from langgraph.checkpoint.memory import MemorySaver
def build_graph():
g = StateGraph(State)
# … add_node / add_edge …
return g.compile(checkpointer=MemorySaver()) # ← this line unlocks debugging
- Breakpoint: click a node → it pauses before that node on the next run.
- State tab: the checkpoint's state values + a diff from the previous step.
- Step / Continue: advance one node, or run to the next breakpoint.
- Time-travel: the State tab lists the run's checkpoints — click one to fork and re-run from there.
Manual inference — pause and paste an answer
Turn "human + any chat" into a pluggable model. Call interrupt() with a
contract-shaped payload; the run pauses, you copy the prompt into ChatGPT/Copilot/Claude, paste
the answer back, and it resumes with zero state loss.
from langgraph.types import interrupt
def review(state: State) -> dict:
prompt = "Review this plan and reply 'redo' / 'abort' / anything to proceed."
answer = interrupt({
"renderedText": prompt,
"messages": [{"role": "human", "content": prompt, "name": None, "toolCallId": None}],
"expects": "text", # or "tool_call"
"toolSchema": None, # a JSON Schema when expects == "tool_call"
"promptTokens": {"prompt": 0, "completion": None, "source": "sidecar_estimate"},
})
return {"messages": [answer]}
On a run, the Manual tab opens with the rendered prompt → Copy prompt → paste into
any chat → paste the reply back → Send resume. For tool_call, paste a JSON args
object; a bad paste is rejected and the run stays paused so you can fix it.
Token economy — invoke a chat model in a node
Any node that invokes a chat model shows per-node prompt/completion counts in the Tokens
tab. Exact when the model returns usage_metadata (real APIs), otherwise a flagged
~ estimate.
async def plan(state: State) -> dict:
reply = await model.ainvoke(state["messages"]) # model closed over / read from a global
return {"messages": [reply]}
Referencing a BaseChatModel also puts the node in the ⚡ llm lane on the canvas.
Run-input form — describe your inputs
GraphLoupe reads get_input_jsonschema() and renders a field per input. Use a
Pydantic input/state with Field(description=…) to get hints under each field
(a plain TypedDict yields names only):
from pydantic import BaseModel, Field
class Input(BaseModel):
repo_path: str = Field(description="Path to the repo to analyze")
target: str = Field(description="What to summarize")
Name path-like fields with repo/dir/out/path/file
to get a Browse… folder picker. Toggle JSON for a raw box.
Jump to source
Each node row in the left overview has a ↗ — click it to open that node's function in your
editor at its def line (beside the graph, reusing one tab). Works for functions
wrapped by langgraph too. Nothing to configure.
Health panel
The Health tab is a one-glance compatibility check of the loaded graph: whether it loaded, whether it has a checkpointer (debugging), the run-input schema (and whether fields are described), how many LLM/inference nodes, and node→source coverage — plus the GraphLoupe version, the langgraph version, and the interpreter running your graph. If a graph fails to load, it shows the cause here.
graphloupe.pythonPath.Troubleshooting
| Symptom | Fix |
|---|---|
| "Select Graph" finds nothing | Name your factory build_graph/build_app/…, or use ✎ Specify manually…. |
graph_load_failed: … | The entry couldn't import / has no such callable; the message names the cause. The Health tab shows it too. |
run failed: KeyError: 'x' | Your graph needs input key x — fill that field in the run-input form. |
| Load timed out | Cold langchain/langgraph import — retry, or raise graphloupe.loadTimeout. |
| Missing dep (e.g. fastapi) | The doctor names the interpreter and the fix; install into that interpreter or pick another. |
| Can't pause / step | Compile with checkpointer=…. The Health tab flags a missing checkpointer. |
How it works
The VS Code extension hosts a Webview (React Flow canvas, read-only execution view) and spawns a
Python sidecar (FastAPI). The sidecar runs your graph in an isolated worker subprocess
and streams events back over a frozen protocol contract using
astream_events(version="v2"). Discovery is a static AST scan (never runs your code);
execution is isolated (guards against buggy graphs, not deliberately malicious code). 100 % local —
the only traffic is a localhost WebSocket. See
SECURITY.md for the threat model.
MIT-licensed · github.com/byGao/GraphLoupe