Dynamic flows
Build each node at runtime from what the conversation has learned.
In a dynamic flow, nodes are functions that return a NodeConfig, and a tool's handler returns the
node to go to next. Because the next node is built when the tool is called, it can depend on
arguments, state or anything else you can compute.
This flow books a slot in three steps. The hour node is built for the day the caller picked, and the last node confirms the slot and ends the call:
interface Slot {
day: string;
hour: number;
}
function createDayNode(): NodeConfig {
return {
name: "day",
taskMessages: [{ role: "developer", content: "Ask which weekday the caller prefers." }],
functions: [
new FlowsFunctionSchema({
name: "choose_day",
description: "Record the preferred weekday.",
properties: { day: { type: "string" } },
required: ["day"],
handler: async (args, fm) => {
fm.state.day = args.day;
// Build the next node from what the caller just said.
return [{ day: args.day }, createHourNode(String(args.day))];
},
}),
],
};
}
function createHourNode(day: string): NodeConfig {
return {
name: "hour",
taskMessages: [{ role: "developer", content: `Offer times on ${day} and ask which one.` }],
functions: [
new FlowsFunctionSchema({
name: "choose_hour",
description: "Record the chosen hour.",
properties: { hour: { type: "integer" } },
required: ["hour"],
handler: async (args, fm) => {
const slot: Slot = { day, hour: Number(args.hour) };
fm.state.slot = slot;
return [slot, createDoneNode(slot)];
},
}),
],
};
}
function createDoneNode(slot: Slot): NodeConfig {
return {
name: "done",
taskMessages: [
{ role: "developer", content: `Confirm ${slot.day} at ${slot.hour}:00, then say goodbye.` },
],
postActions: [{ type: "end_conversation" }],
};
}
await flowManager.setNodeFromConfig(createDayNode());Each handler returns [result, nextNode]. The result goes back to the LLM as the tool result;
the manager then enters the next node, which swaps in its task messages and tools and asks the LLM to
respond. A handler can return null as the node to stay put, or NO_RESPONSE to stay quiet.
Start it with flowManager.initialize(createDayNode()), or enter it later with
setNodeFromConfig. Use flow configs instead when the graph is fixed
and you want it reviewable as data.
pipedog is an unofficial community TypeScript port of Pipecat. It is not affiliated with or endorsed by Daily or the Pipecat project.
Portions adapted from the Pipecat documentation, Copyright (c) 2024–2025 Daily, BSD-2-Clause (see apps/docs/LICENSE-pipecat-docs).