Skip to content

teff.flow.compile

teff.flow.compile

Compilation and serialization builders for :class:~teff.flow.Flow.

:class:CompileBuilder implements compile, label/label_target and to_yaml. :class:Flow owns an instance and delegates to it.

Classes:

Name Description
CompileBuilder

Compile a :class:~teff.flow.Flow into an executable graph.

CompileBuilder

Compile a :class:~teff.flow.Flow into an executable graph.

Parameters:

Name Type Description Default
flow Flow

The owning Flow whose graph state is read.

required

Methods:

Name Description
compile

Compile the flow into a Graph ready for execution.

label

Attach a route name to the most recently added node.

label_target

Resolve a declarative goto against labels to a real node id.

to_yaml

Export the compiled flow as a workflow.yaml document.

Source code in teff/flow/compile.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 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
class CompileBuilder:
    """Compile a :class:`~teff.flow.Flow` into an executable graph.

    Args:
        flow: The owning ``Flow`` whose graph state is read.
    """

    def __init__(self, flow: "Flow"):
        self.flow = flow

    def compile(self):
        """Compile the flow into a ``Graph`` ready for execution.

        Raises:
            ValueError: If no nodes were added.
        """
        return self._compile()

    def label(self, name: str):
        """Attach a route *name* to the most recently added node.

        ``Command``-style ``goto`` targets (from a declarative ``route``
        step) must name a real node id in the compiled graph.  A loop's
        body has no node of its own — the *decider* (the node that reads
        the loop key) is its re-entry point.  Label it so sugar can route
        back to it::

            flow.step(extract_verdict, id="extract_verdict")
            flow.loop(key="verdict", until="pass", body=body, done=done)
            flow.label("refine")            # ("refine" now means "extract_verdict")
            flow.step(route_node)           # route: goto "refine" loops back

        Returns ``self`` for chaining.
        """
        target = self.flow
        prev = target._last_added
        if prev is None:
            raise ValueError("label() requires a preceding node")
        loop_target = target._loop_decider if target._loop_decider is not None else prev
        target._loop_labels[name] = loop_target
        return target

    def label_target(self, goto: str) -> str:
        """Resolve a declarative ``goto`` against labels to a real node id.

        ``label()`` maps a route name (e.g. ``"refine"``) to the loop
        decider's node id; this turns a sugar ``route: {goto: refine}``
        into an executable ``goto: <decider_id>``.  Names that are not
        labeled (real node ids, ``STOP``) pass through unchanged.
        """
        return self.flow._loop_labels.get(goto, goto)

    def _compile(self):
        """Compile the flow into a ``Graph`` ready for execution.

        Raises:
            ValueError: If no nodes were added.
        """
        from teff.graph import Graph

        target = self.flow
        if not target._nodes:
            raise ValueError("no nodes in flow")
        return Graph(
            nodes=dict(zip(target._node_ids, target._nodes)),
            edges=target._edges,
            entry_point=target._node_ids[0],
            providers=target._providers,
            default_provider=target._default_provider,
            default_model=target._default_model,
        )

    def to_yaml(
        self,
        *,
        tools: list | None = None,
        initial: dict | None = None,
        reducers: dict | None = None,
    ) -> str:
        """Export the compiled flow as a ``workflow.yaml`` document.

        The graph (``steps`` + ``edges``) is serialised faithfully —
        including the ReAct loop wiring produced by :meth:`harness` /
        :meth:`react`.  Tools and state are not tracked by ``Flow``, so
        pass them explicitly if you want them in the export::

            yaml_text = (
                Flow("repo")
                .react(model="llama3.1:8b", use_tools="all")
                .to_yaml(tools=[GitTool(), CsvQueryTool()])
            )
            with open("workflow.yaml", "w") as f:
                f.write(yaml_text)

        The result validates with ``teff validate`` and round-trips through
        :func:`teff.yaml.load_workflow`.
        """
        from teff.yaml import workflow_to_yaml

        return workflow_to_yaml(
            self.compile(),
            tools=tools,
            initial=initial,
            reducers=reducers,
            name=self.flow._name or "graph",
        )

compile

compile()

Compile the flow into a Graph ready for execution.

Raises:

Type Description
ValueError

If no nodes were added.

Source code in teff/flow/compile.py
23
24
25
26
27
28
29
def compile(self):
    """Compile the flow into a ``Graph`` ready for execution.

    Raises:
        ValueError: If no nodes were added.
    """
    return self._compile()

label

label(name)

Attach a route name to the most recently added node.

Command-style goto targets (from a declarative route step) must name a real node id in the compiled graph. A loop's body has no node of its own — the decider (the node that reads the loop key) is its re-entry point. Label it so sugar can route back to it::

flow.step(extract_verdict, id="extract_verdict")
flow.loop(key="verdict", until="pass", body=body, done=done)
flow.label("refine")            # ("refine" now means "extract_verdict")
flow.step(route_node)           # route: goto "refine" loops back

Returns self for chaining.

Source code in teff/flow/compile.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def label(self, name: str):
    """Attach a route *name* to the most recently added node.

    ``Command``-style ``goto`` targets (from a declarative ``route``
    step) must name a real node id in the compiled graph.  A loop's
    body has no node of its own — the *decider* (the node that reads
    the loop key) is its re-entry point.  Label it so sugar can route
    back to it::

        flow.step(extract_verdict, id="extract_verdict")
        flow.loop(key="verdict", until="pass", body=body, done=done)
        flow.label("refine")            # ("refine" now means "extract_verdict")
        flow.step(route_node)           # route: goto "refine" loops back

    Returns ``self`` for chaining.
    """
    target = self.flow
    prev = target._last_added
    if prev is None:
        raise ValueError("label() requires a preceding node")
    loop_target = target._loop_decider if target._loop_decider is not None else prev
    target._loop_labels[name] = loop_target
    return target

label_target

label_target(goto)

Resolve a declarative goto against labels to a real node id.

label() maps a route name (e.g. "refine") to the loop decider's node id; this turns a sugar route: {goto: refine} into an executable goto: <decider_id>. Names that are not labeled (real node ids, STOP) pass through unchanged.

Source code in teff/flow/compile.py
55
56
57
58
59
60
61
62
63
def label_target(self, goto: str) -> str:
    """Resolve a declarative ``goto`` against labels to a real node id.

    ``label()`` maps a route name (e.g. ``"refine"``) to the loop
    decider's node id; this turns a sugar ``route: {goto: refine}``
    into an executable ``goto: <decider_id>``.  Names that are not
    labeled (real node ids, ``STOP``) pass through unchanged.
    """
    return self.flow._loop_labels.get(goto, goto)

to_yaml

to_yaml(*, tools=None, initial=None, reducers=None)

Export the compiled flow as a workflow.yaml document.

The graph (steps + edges) is serialised faithfully — including the ReAct loop wiring produced by :meth:harness / :meth:react. Tools and state are not tracked by Flow, so pass them explicitly if you want them in the export::

yaml_text = (
    Flow("repo")
    .react(model="llama3.1:8b", use_tools="all")
    .to_yaml(tools=[GitTool(), CsvQueryTool()])
)
with open("workflow.yaml", "w") as f:
    f.write(yaml_text)

The result validates with teff validate and round-trips through :func:teff.yaml.load_workflow.

Source code in teff/flow/compile.py
 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
def to_yaml(
    self,
    *,
    tools: list | None = None,
    initial: dict | None = None,
    reducers: dict | None = None,
) -> str:
    """Export the compiled flow as a ``workflow.yaml`` document.

    The graph (``steps`` + ``edges``) is serialised faithfully —
    including the ReAct loop wiring produced by :meth:`harness` /
    :meth:`react`.  Tools and state are not tracked by ``Flow``, so
    pass them explicitly if you want them in the export::

        yaml_text = (
            Flow("repo")
            .react(model="llama3.1:8b", use_tools="all")
            .to_yaml(tools=[GitTool(), CsvQueryTool()])
        )
        with open("workflow.yaml", "w") as f:
            f.write(yaml_text)

    The result validates with ``teff validate`` and round-trips through
    :func:`teff.yaml.load_workflow`.
    """
    from teff.yaml import workflow_to_yaml

    return workflow_to_yaml(
        self.compile(),
        tools=tools,
        initial=initial,
        reducers=reducers,
        name=self.flow._name or "graph",
    )