Skip to contents

puppeteeR can render your graph structure before execution - useful for sanity-checking topology, sharing diagrams, and documentation. Three engines are supported.

Build a sample graph

schema <- workflow_state(
  input    = list(default = ""),
  research = list(default = ""),
  draft    = list(default = ""),
  status   = list(default = "pending")
)

g <- state_graph(schema) |>
  add_node("research", function(s, cfg) list()) |>
  add_node("write",    function(s, cfg) list()) |>
  add_node("review",   function(s, cfg) list()) |>
  add_node("revise",   function(s, cfg) list()) |>
  add_edge(START, "research") |>
  add_edge("research", "write") |>
  add_edge("write", "review") |>
  add_conditional_edge(
    "review",
    routing_fn = function(s) if (s$get("status") == "approved") "done" else "needs_work",
    route_map  = list(done = END, needs_work = "revise")
  ) |>
  add_edge("revise", "write")

Mermaid (text output, zero dependencies)

as_mermaid() returns a Mermaid flowchart string. Paste it into mermaid.live or embed it in any Markdown that supports Mermaid fences.

cat(g$as_mermaid())
#> graph TD
#>   START((START)) --> research[research]
#>   research[research] --> write[write]
#>   write[write] --> review[review]
#>   revise[revise] --> write[write]
#>   review[review] -- "done" --> END((END))
#>   review[review] -- "needs_work" --> revise[revise]

DOT / Graphviz (requires DiagrammeR)

as_dot() returns a Graphviz DOT string. visualize("dot") renders it as an interactive widget.

cat(g$as_dot())
#> digraph workflow {
#>   graph [rankdir=TB fontname="Helvetica" bgcolor="transparent"]
#>   node  [shape=rect style="rounded,filled" fontname="Helvetica" fillcolor="#E8F0FE" color="#4A90D9"]
#>   edge  [fontname="Helvetica" fontsize=10 color="#666666"]
#> 
#>   __START__ [label="START" shape=oval fillcolor="#2D3748" fontcolor=white]
#>   __END__   [label="END"   shape=oval fillcolor="#2D3748" fontcolor=white]
#>   research [label="research"]
#>   write [label="write"]
#>   review [label="review" fillcolor="#FFF3CD"]
#>   revise [label="revise"]
#>   __START__ -> research
#>   research -> write
#>   write -> review
#>   revise -> write
#>   review -> __END__ [label="done" style=dashed]
#>   review -> revise [label="needs_work" style=dashed]
#> }
# Renders in RStudio Viewer / HTML output
g$visualize("dot")

Node colours:

  • Blue (#E8F0FE) - regular nodes
  • Yellow (#FFF3CD) - nodes with conditional outgoing edges
  • Dark - START and END sentinels

visNetwork (interactive, requires visNetwork)

Produces a pan-and-zoom interactive network widget. Solid lines = fixed edges, dashed lines = conditional edges (labelled with the routing key).

g$visualize("visnetwork")

Export to file

Export the DOT diagram to SVG or PNG (requires DiagrammeR, DiagrammeRsvg; PNG also needs rsvg).

g$export_diagram("workflow.svg")
g$export_diagram("workflow.png", width = 1200L, height = 800L)

Here is an example PNG exported from the email triage graph (classify - route to support/billing/discard):

Visualizing compiled runners

All visualization methods (visualize(), as_dot(), as_mermaid(), export_diagram()) work directly on a compiled GraphRunner - no need to keep a separate reference to the graph:

runner <- state_graph(schema) |>
  add_node("a", function(s, cfg) list()) |>
  add_edge(START, "a") |>
  add_edge("a", END) |>
  compile()

# All of these work on the runner directly
runner$visualize("dot")
runner$visualize("visnetwork")
cat(runner$as_mermaid())
runner$export_diagram("graph.png")

This is especially convenient with the workflow convenience constructors:

library(ellmer)

runner <- supervisor_workflow(
  manager = agent("manager", chat_anthropic(),
                  instructions = "Delegate to 'writer' or reply 'DONE'."),
  workers = list(writer = agent("writer", chat_anthropic()))
)

runner$visualize("dot")

The print() method gives a quick text summary of node and edge counts:

g
#> ! StateGraph
#>   Nodes (4): "research", "write", "review", and "revise"
#>   Edges: 5 total (4 fixed, 1 conditional)