Flows
Build structured conversations from nodes, functions and actions with @pipedog/flows, a port of Pipecat Flows.
Flows keeps a conversation on rails. Instead of one long prompt with every tool available at every moment, you describe the conversation as a graph of nodes. Each node gives the LLM one job (its task messages), the few tools it may call there, and optional actions to run on the way in or out. When the LLM calls a tool, the tool does its work and can send the conversation to the next node.
@pipedog/flows ports pipecat.flows. It works with any LLM service that does function calling:
OpenAI, Anthropic and Google are covered by the package's end-to-end tests.
bun add @pipedog/flowsConcepts
| Concept | What it is |
|---|---|
| Node | One step of the conversation: a role, task messages, functions, actions and a context strategy. |
| Function | A tool the LLM can call at a node. It does work, and may name the next node. |
| Action | Something that happens around the LLM's turn at a node: speaking a line, ending the call, or your own code. |
| Context strategy | What happens to the conversation history when a node is entered. |
| Flow manager | FlowManager runs the graph: it holds the current node and the shared state, registers each node's tools with the LLM service, and performs transitions. |
| Flow config | A flow described as YAML or JSON data, joined to your handlers with Flow. |
Wiring it up
FlowManager needs the pipeline worker (to queue frames), the LLM service, and the context
aggregators:
const flowManager = new FlowManager({
worker,
llm,
contextAggregator: aggregators,
});Then start in a node with flowManager.initialize(node). See Nodes for what a
node contains, or Dynamic flows for a graph built at runtime.
Two ways to define a flow
- In code. Node functions return
NodeConfigobjects, and a tool returns the next node. Nothing is fixed ahead of time, so the next node can depend on what the user said. See Dynamic flows. - As data. A
FlowConfiglists the nodes and the edges between them, in YAML or JSON. Your code supplies only the tools' behavior. See Flow configs.
Both run on the same FlowManager.
State
flowManager.state is a plain object shared by every node and handler. Store what the
conversation learns (a name, a booking) and read it back anywhere, including in
{{ placeholders }} in a config.
Differences from Pipecat
The behavior matches upstream; the API follows TypeScript conventions:
NodeConfigandActionConfigkeys are camelCase (taskMessages,preActions). The YAML/JSONFlowConfigfile format keeps upstream's snake_case keys.- A tool returns a two-element array,
[result, nextNode], where Python returns a tuple. - Handlers are called as
(args, flowManager). - A direct function's first parameter is named
flowManager, and its description and parameter schemas are attached as metadata (JavaScript has no docstrings or type hints at runtime). FlowConfig.fromFileand!includeread files through areadFilefunction you pass in, so the package runs on every runtime.- Config validation is hand-written and throws
ValidationError, listing every problem found. - Placeholders render with
String()(objects as JSON) instead of Python'sstr(). - Deprecated upstream APIs are not ported.
RESET_WITH_SUMMARYis kept and logs a deprecation warning once.
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).