Sandbox — LSD is in active testing. All systems operational·payments are test-mode (no real charges).
Overview Use Cases News Docs Tool Store Support Community Get LSD
All docs
Docs/ Building Tools/ Multiple outputs
Building Tools

Multiple outputs

Emit several tables from one node with outputs=[...] and wire each with step.output('name'). Add graph_outputs for live read-only scalars on the card.

TL;DRPass outputs=["a","b"] to a step and return a dict of tables — each key becomes a named output port, selected downstream with step.output("a"). Pass graph_outputs={"n": int} for read-only scalar badges shown on the node card, set inside the run with ctx.set_output(...); those scalars are excluded from the cache key.

By default a step returns one Table on one output port. Two features change that: outputs=[...] declares several named data ports, and graph_outputs={...} declares read-only scalar metrics shown on the node card.

Usage#

Python
from lsdtools import Tool, Table

tool = Tool("mesh")

@tool.load(outputs=["vertices", "faces"])
def make_mesh(n: int = 8) -> dict:
    """Produce two tables from one node."""
    return {"vertices": build_vertices(n), "faces": build_faces(n)}
# → the node shows two output ports: vertices, faces

With outputs=[...] the function returns a dict whose keys match the declared names, each mapping to a Table. This works on @tool.load and @tool.shape.

Wiring a specific output#

Downstream, select a port with step.output("name") on the node handle and pass it into the next node's input:

Python
m = make_mesh(n=8)
render_points(m.output("vertices")) # wire just the vertices port
build_index(m.output("faces")) # …and the faces port elsewhere

In a Python run, RunResult.outputs gives you every port as a {name: Table} dict; .output is the primary one. See Engine.

Read-only metrics: graph_outputs#

graph_outputs={"name": type} declares small scalars that surface live on the node card — a row count, a bounds string, a checksum. They are not data ports. Set them inside the run with ctx.set_output(...) from an injected StepContext:

Python
from lsdtools import Tool, Table, StepContext

@tool.shape(graph_outputs={"n_rows": int, "n_dropped": int})
def drop_nulls(t: Table, column: str, ctx: StepContext = None) -> Table:
    kept = t.filter(t[column].is_valid())
    ctx.set_output("n_rows", len(kept))
    ctx.set_output("n_dropped", len(t) - len(kept))
    return kept
# → the node card shows n_rows and n_dropped after each run

Because graph_outputs are metrics about a run rather than inputs to it, they are excluded from the cache key — reporting a count never invalidates a cached result.

Combining both#

A step can declare data ports and metrics at once — return the dict of tables and set the scalars:

Python
@tool.shape(outputs=["keep", "reject"], graph_outputs={"kept": int})
def partition(t: Table, floor: float = 0.0, ctx: StepContext = None) -> dict:
    mask = t["score"] >= floor
    keep, reject = t.filter(mask), t.filter(~mask)
    ctx.set_output("kept", len(keep))
    return {"keep": keep, "reject": reject}

LearnShape steps · Load steps · Parameters vs ports

APITool · Context · Engine

ExamplesMulti-output mesh · Live node metrics

Frequently asked questions

What is the difference between outputs and graph_outputs?

outputs=[...] declares named data ports — Tables you return in a dict and wire downstream. graph_outputs={...} declares small read-only scalars (a row count, a bounds string) that show on the node card but are not data ports and are excluded from the cache key.

How do I wire only one of a node's outputs?

Call step.output("name") on the node handle and pass that into the downstream port. A plain reference to the node uses its first/primary output.

By LSD Team · Last updated Jul 17, 2026 Ask on Discord View as Markdown