Skip to main content
Functions in Pipecat Flows serve two key purposes:
  1. Process data by interfacing with external systems and APIs to read or write information
  2. Progress the conversation by transitioning between nodes in your flow

How Functions Work

When designing your nodes, clearly define the task in the task_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:
  1. The LLM asks the question
  2. The user provides their answer
  3. The LLM calls the function with the answer
  4. The function processes the data and determines the next node

Defining a Function

The preferred way to define a function is with a direct function: a single async function that is both the handler (the code that runs) and the schema (what is advertised to the LLM). Flows auto-derives the function’s metadata — name, description, parameter properties (with their descriptions), and which parameters are required — from the function’s signature and docstring. The first parameter is always flow_manager (a FlowManager); the function’s own parameters follow. Document each parameter in a Google-style docstring.
async def record_favorite_color(
    flow_manager: FlowManager,
    color: str,
) -> tuple[str, NodeConfig]:
    """Record the color the user said is their favorite.

    Args:
        color: The user's favorite color.
    """
    print(f"Your favorite color is: {color}")
    return color, create_end_node()

# List the function in a node
node_config = NodeConfig(
    # ...
    functions=[record_favorite_color],
)
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 can return any JSON-serializable value (string, dict, list, etc.) that gets passed to the LLM. To also specify the next node, return a tuple:
  • Result: Data provided to the LLM for context in subsequent completions, or None. Any JSON-serializable value is accepted.
  • Next Node: The NodeConfig for Flows to transition to next, or None.
A function that doesn’t need to change conversational state can return None for the next node. One that only changes conversational state, without doing other work, can return None for the result.

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 function with @flows_tool_options:
from pipecat_flows import flows_tool_options

# This lookup is only useful for the current turn, so cancel it if the user
# interrupts and the conversation moves on.
@flows_tool_options(cancel_on_interruption=True)
async def check_weather(
    flow_manager: FlowManager,
    city: str,
) -> tuple[dict, None]:
    """Look up the current weather for a city.

    Args:
        city: The city to look up.
    """
    return {"weather": await get_weather(city)}, None

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. 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:
from pipecat_flows import FlowsFunctionSchema

async def record_favorite_color(
    args: FlowArgs, flow_manager: FlowManager
) -> tuple[str, NodeConfig]:
    """Record the color, then set the next node."""
    print(f"Your favorite color is: {args['color']}")
    return args["color"], create_end_node()

record_favorite_color_func = FlowsFunctionSchema(
    name="record_favorite_color",
    description="Record the color the user said is their favorite.",
    properties={
        # A strict enum — the kind of explicit control a direct function can't yet express.
        "color": {"type": "string", "enum": ["red", "green", "blue"]},
    },
    required=["color"],
    handler=record_favorite_color,
)

# List the schema in a node
node_config = NodeConfig(
    # ...
    functions=[record_favorite_color_func],
)
The handler is required. It receives the LLM-supplied arguments and returns the same result and next-node values as a direct function. To override its call options, decorate it with @flows_tool_options, the same decorator direct functions use.