Building in Python¶
GraphBuilder is a fluent API that constructs the same Graph IR as YAML, with full control-flow support. Every method returns self.
from neurosurfer.graph import GraphBuilder
g = (
GraphBuilder("triage", description="Route a ticket")
.input("text", type="string", required=True)
.base("classify", purpose="Classify urgency of: {text}", writes="label")
.router(
"route",
cases=[{"when": "nodes.classify == 'urgent'", "to": "page"}],
default="queue",
depends_on=["classify"],
)
.base("page", purpose="Page on-call", depends_on=["route"])
.base("queue", purpose="Add to queue", depends_on=["route"])
.outputs("page", "queue")
.build()
)
Builder output and YAML are the same thing¶
build() runs the same semantic validation the YAML loader does, so builder output and YAML output are identical and equally validated. There is no second code path to keep in step, and no class of bug that reaches one and not the other.
The round-trip holds:
from neurosurfer.graph import load_graph_from_dict
load_graph_from_dict(g.model_dump(mode="json")) # reproduces g
The methods¶
| Method | Adds |
|---|---|
.input(name, *, type="string", required=True) | A declared graph input. |
.base(id, *, purpose=…, goal=…, …) | A base node. |
.react(id, *, tools=[…], …) | A react node. |
.tool(id, *, tools=[…], tool_args={…}) | A tool node. |
.function(id, *, callable="mod:fn") | A function node. |
.router(id, *, routes={…} \| cases=[…], default=…) | A router node. |
.loop(id, *, body=[…], max_iterations=N) | A loop node. |
.map(id, *, over=…, body=[…], as_="item") | A map node. |
.subgraph(id, *, body=[…]) | A subgraph node. |
.input_node(id, *, purpose=…) | An input node. |
.node(node_or_dict) | Any pre-built GraphNode or its dict form. |
.outputs(*node_ids) | Declares which nodes' outputs the graph returns. |
.to_dict() / .build() | The dict form / the validated Graph. |
map takes as_, not item_var
The trailing underscore is there because as is a Python keyword. It sets the same thing item_var sets in YAML — the name each item is bound to inside the body.
Or construct nodes directly¶
The builder is a convenience, not the only door. Node classes produce the same IR:
from neurosurfer.graph import Graph, RouterNode, BaseNode
graph = Graph(
name="triage",
nodes=[
BaseNode(id="classify", instructions="Classify urgency of: {text}", writes="label"),
RouterNode(id="route", cases=[{"when": "nodes.classify == 'urgent'", "to": "page"}],
default="queue", depends_on=["classify"]),
],
inputs=[{"name": "text", "type": "string"}],
)
See Node kinds for how classes and kind= strings relate.
Next¶
- Node kinds — the field reference.
- Validation — what
build()checks. - Workflow packages — persisting what you built.