- Process data by interfacing with external systems and APIs to read or write information
- Progress the conversation by transitioning between nodes in your flow
How Functions Work
When designing your nodes, clearly define the task in thetask_messages and reference the available functions. The LLM will use these functions to complete the task and signal when it’s ready to move forward.
For example, if your node’s job is to collect a user’s favorite color:
- The LLM asks the question
- The user provides their answer
- The LLM calls the function with the answer
- The function processes the data and the conversation moves on
- A node function does work within the current conversation state without switching nodes.
- An edge function transitions to another conversation state, optionally doing work first.
transition_to is an edge function. In code the handler decides, by returning the next node or not.
Declarative
Afunctions entry names a tool and, optionally, where it leads:
str
required
The tool’s name, as the LLM sees it, and the name of the direct function in
the handlers. The config carries no description or parameters for an ordinary
entry — those come from the function’s signature and docstring.
str | Branch
The node to transition to after the tool completes, or a branch
table. Omit it for a node function, which stays put.
str
What the tool is for, for the LLM. Only a
transition_only entry takes one;
an ordinary entry describes itself in its docstring, and giving it a
description here is an error.The Handler Contract
The Python behind an ordinary entry is a direct function: a single async function that is both the handler and the schema. Flows derives the tool’s description, parameter properties, and which parameters are required from the signature and a Google-style docstring. The first parameter is alwaysflow_manager; the function’s own parameters follow.
handlers.py
(result, TRANSITION_IN_YAML). The result is any JSON-serializable value, or None, and is given to the LLM as context. TRANSITION_IN_YAML is a sentinel meaning “the config decides” — it is what keeps transitions out of your Python.
To leave the bot silent after the tool finishes, rather than responding immediately, return (result, NO_RESPONSE). That passes through to the manager unchanged.
Transition-Only Functions
A function that only moves the conversation needs no Python. Mark the entrytransition_only: true and give it a description for the LLM and a node name to transition to:
transition_only entry must have both a description and a transition_to that names a node — a branch table needs a result to branch on, and there is none.
Branch Tables
When the destination depends on the tool’s outcome,transition_to can be a branch table that routes on a field of the result:
str
required
Key of the tool’s result whose value selects the case. The result must be a
mapping with that key, or the call raises a
FlowError.dict[str, str]
required
Result value to node name. At least one entry.
str
Node to transition to when the value matches no case. When omitted, an
unmatched value stays on the current node.
true: in YAML matches a Python True, and "True": matches it too:
The Shim Pattern
A branch table routes on a field, not on arbitrary logic — and that is the point. Keep the business logic flow-agnostic, wrap it in a thin tool that reports the outcome as a named field, and let the config route on that field.check_availability in the restaurant reservation example is the shape:
handlers.py
Programmatic
In a flow built in code, the handler returns the next node itself. A direct function is written the same way —flow_manager first, then its own parameters, documented in a Google-style docstring:
The direct-function schema generator doesn’t yet map
Literal types to a
JSON-schema enum. Express enum-like constraints in the docstring prose
instead (e.g. ‘Must be one of “red”, “green”, or “blue”’). If you need a
strict enum in the schema, use the
FlowsFunctionSchema
pattern.Return Values
A function returns a tuple:- Result: Data provided to the LLM for context in subsequent completions, or
None. Any JSON-serializable value is accepted. - Next Node: The
NodeConfigfor Flows to transition to next, orNone.
None for the next node. One that only changes conversational state, without doing other work, can return None for the result. Returning NO_RESPONSE in the next-node slot keeps the bot silent after the call.
Advanced: Defining a Function with FlowsFunctionSchema
Direct functions cover most cases. Reach for FlowsFunctionSchema when you need explicit control over the schema — for example a strict enum constraint or a numeric minimum/maximum — that a direct function can’t yet express. This is one of the reasons to write a flow in code rather than a config.
A FlowsFunctionSchema spells out the function’s name, description, and parameters by hand, and takes the handler that runs when the LLM calls the function:
handler is required. It receives the LLM-supplied arguments and returns the same result and next-node values as a direct function.
Per-Function Call Options
By default, a function is not cancelled when the user interrupts, and it uses the LLM service’s global timeout. To override either, decorate the handler with@flows_tool_options. This works the same in both forms — it is a property of the Python, so a declarative flow’s handlers take it too: