Skip to content

ant_ai.workflow.visualize

build_workflow_graph

build_workflow_graph(
    workflow: Workflow,
    *,
    engine: str = "dot",
    rankdir: str = "LR",
) -> Digraph

Build and return a graphviz.Digraph for workflow.

The returned object renders inline in Jupyter notebooks and can be passed to :func:render_workflow for file export.

Parameters:

Name Type Description Default
workflow Workflow

The workflow to visualise.

required
engine str

Graphviz layout engine ("dot", "neato", "fdp"…).

'dot'
rankdir str

Layout direction — "LR" (left-to-right, default) or "TB" (left-to-right).

'LR'

Raises:

Type Description
ImportError

if the graphviz package is not installed. Install with pip install ant-ai[viz].

Source code in src/ant_ai/workflow/visualize.py
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def build_workflow_graph(
    workflow: Workflow,
    *,
    engine: str = "dot",
    rankdir: str = "LR",
) -> Digraph:
    """
    Build and return a ``graphviz.Digraph`` for *workflow*.

    The returned object renders inline in Jupyter notebooks and can be passed
    to :func:`render_workflow` for file export.

    Args:
        workflow: The workflow to visualise.
        engine: Graphviz layout engine (``"dot"``, ``"neato"``, ``"fdp"``…).
        rankdir: Layout direction — ``"LR"`` (left-to-right, default) or ``"TB"``
            (left-to-right).

    Raises:
        ImportError: if the ``graphviz`` package is not installed.
            Install with ``pip install ant-ai[viz]``.
    """
    try:
        import graphviz
    except ImportError as exc:  # pragma: no cover
        raise ImportError(
            "Workflow visualisation requires the graphviz package. "
            "Install it with:  pip install ant-ai[viz]"
        ) from exc

    g = graphviz.Digraph(engine=engine)
    g.attr(rankdir=rankdir, fontname="Helvetica", fontsize="12")
    g.attr("node", fontname="Helvetica", fontsize="12")
    g.attr("edge", fontname="Helvetica", fontsize="10")

    # START / END — ellipse
    for special in (START, END):
        g.node(
            _gv_id(special),
            label=special,
            shape="ellipse",
            style="filled",
            fillcolor=_FILL_SPECIAL,
        )

    # Regular nodes — rectangle
    for name in workflow.nodes:
        g.node(
            _gv_id(name),
            label=name,
            shape="rectangle",
            style="filled,rounded",
            fillcolor=_FILL_NODE,
        )

    # Static edges
    for src, dst in workflow.edges.items():
        g.edge(_gv_id(src), _gv_id(dst))

    # Conditional edges — diamond + AST-extracted destinations
    for src, router in workflow.conditional_edges.items():
        diamond_id = f"__router_{_gv_id(src)}__"
        g.node(
            diamond_id,
            label=getattr(router, "__name__", repr(router)),
            shape="diamond",
            style="filled",
            fillcolor=_FILL_ROUTER,
        )
        g.edge(_gv_id(src), diamond_id)
        for dst in _router_destinations(router):
            g.edge(diamond_id, _gv_id(dst), style="dashed")

    return g

render_workflow

render_workflow(
    workflow: Workflow,
    path: str | Path,
    format: Literal[
        "png", "jpg", "pdf", "svg", "latex"
    ] = "png",
    *,
    engine: str = "dot",
    rankdir: str = "LR",
) -> Path

Render workflow to a file.

Parameters:

Name Type Description Default
workflow Workflow

The workflow to visualise.

required
path str | Path

Output path. The file extension is set automatically based on format (any existing extension is replaced).

required
format Literal['png', 'jpg', 'pdf', 'svg', 'latex']

"png", "jpg", "pdf", "svg", or "latex". The "latex" format produces a self-contained .tex file that can be compiled with pdflatex.

'png'
engine str

Graphviz layout engine.

'dot'
rankdir str

Layout direction — "LR" (left-to-right, default) or "TB".

'LR'

Returns:

Type Description
Path

class:~pathlib.Path of the rendered file.

Raises:

Type Description
ImportError

if the graphviz package is not installed.

ValueError

if format is not one of the supported values.

Source code in src/ant_ai/workflow/visualize.py
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
def render_workflow(
    workflow: Workflow,
    path: str | Path,
    format: Literal["png", "jpg", "pdf", "svg", "latex"] = "png",
    *,
    engine: str = "dot",
    rankdir: str = "LR",
) -> Path:
    """
    Render *workflow* to a file.

    Args:
        workflow: The workflow to visualise.
        path: Output path. The file extension is set automatically based on
            *format* (any existing extension is replaced).
        format: ``"png"``, ``"jpg"``, ``"pdf"``, ``"svg"``, or ``"latex"``.
            The ``"latex"`` format produces a self-contained ``.tex`` file
            that can be compiled with ``pdflatex``.
        engine: Graphviz layout engine.
        rankdir: Layout direction — ``"LR"`` (left-to-right, default) or ``"TB"``.

    Returns:
        :class:`~pathlib.Path` of the rendered file.

    Raises:
        ImportError: if the ``graphviz`` package is not installed.
        ValueError: if *format* is not one of the supported values.
    """
    if format not in _FORMATS:
        raise ValueError(f"format must be one of {_FORMATS!r}, got {format!r}")

    out = Path(path)

    if format == "latex":
        tex = _build_tikz(workflow, engine=engine)
        dest = out.with_suffix(".tex")
        dest.write_text(tex, encoding="utf-8")
        return dest

    g: Digraph = build_workflow_graph(workflow, engine=engine, rankdir=rankdir)
    gv_format: Literal["jpeg", "png", "pdf", "svg"] = (
        "jpeg" if format == "jpg" else format
    )
    rendered = g.render(
        filename=str(out.with_suffix("")),
        format=gv_format,
        cleanup=True,
    )
    return Path(rendered)