-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Move Pipecat Flows into core Pipecat (pipecat.flows) #4882
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
d0e9b1c
Move Pipecat Flows source into pipecat.flows
kompfner fffbe91
Move Pipecat Flows tests into the pipecat test suite
kompfner cf616f2
Move Pipecat Flows examples into examples/flows
kompfner eccaa2d
Wire Pipecat Flows into the docs tooling
kompfner 504078f
Document Pipecat Flows as part of core in the README
kompfner 6da91ab
Name the Flows changelog fragment with its PR number
kompfner 0fa1e6d
Fold Flows into the update-docs mapping by tier, not as a silo
kompfner c87b9c7
Drop redundant flows/__init__.py skip-list entry
kompfner 81c6b02
Warn when the standalone pipecat-ai-flows is installed alongside core…
kompfner 8b38aeb
Note the coexistence guard in the Flows changelog entry
kompfner 42ba08e
Restructure README Getting started and Code examples sections
kompfner 19627b6
Rename the developer-setup section to "Developing Pipecat"
kompfner aeedc19
Run the standalone-Flows guard on any pipecat import
kompfner aea32e5
Addressing some PR feedback
kompfner 83c6419
Fix Flows deprecation directives so the registry generates correctly
kompfner bead163
Mark deprecated FlowResult with the @deprecated decorator
kompfner ba091bb
Lead the RESET_WITH_SUMMARY directive with its replacement symbol
kompfner 8760481
Update comment to avoid referencing a deprecated field
kompfner 3ec7e44
Use `pipecat init` in the README now that `create` is gone
kompfner bcbccf2
Update a link in the README, from the CLI reference to the Build Your…
kompfner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| - Pipecat Flows is now part of `pipecat-ai`. The conversation-flow framework previously published as the separate `pipecat-ai-flows` package now ships with Pipecat under the `pipecat.flows` namespace — `from pipecat.flows import FlowManager, NodeConfig` — so there is no longer a separate package to install or keep version-matched. Code importing from `pipecat_flows` should switch to `pipecat.flows`. If the deprecated `pipecat-ai-flows` package is still installed alongside this Pipecat, Pipecat logs an error prompting you to remove it. The standalone package's release history remains available in the archived [pipecat-flows repository](https://github.com/pipecat-ai/pipecat-flows/blob/main/CHANGELOG.md). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # Pipecat Flows Examples | ||
|
|
||
| [Pipecat Flows](../../src/pipecat/flows) is the structured-conversation framework built into Pipecat. It lets you build both predefined conversation paths and dynamically generated flows while handling the complexities of state management and LLM interactions. These examples show it in action. | ||
|
|
||
| ## Hello, world | ||
|
|
||
| [`hello_world.py`](./hello_world.py) is the smallest possible Flow: a bot that asks for your favorite color and then says goodbye. It's a good first read — it shows the basics of nodes, functions, and transitions. To run it, see Setup below. | ||
|
|
||
| ## Setup | ||
|
|
||
| 1. Follow the [README](../../README.md#%EF%B8%8F-developing-pipecat) steps to configure your local environment. Run the commands from the repo root. | ||
|
|
||
| 2. Copy the [`env.example`](../../env.example) file and add API keys for the services you plan to use: | ||
|
|
||
| ```bash | ||
| cp env.example .env | ||
| # Edit .env with your API keys | ||
| ``` | ||
|
|
||
| 3. Run any example: | ||
|
|
||
| ```bash | ||
| uv run python examples/flows/food_ordering.py | ||
| ``` | ||
|
|
||
| 4. Open the web interface at http://localhost:7860/client/ and click "Connect". | ||
|
|
||
| All examples support multiple LLM providers (OpenAI, Anthropic, Google Gemini, AWS Bedrock) to demonstrate cross-provider compatibility. Like the other Pipecat examples, they default to the SmallWebRTC transport and also support Daily (`-t daily`) and telephony providers (`-t twilio -x NGROK_HOST_NAME`) — see the [examples README](../README.md#running-examples-with-other-transports) for transport details. | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Core flows | ||
|
|
||
| - [`food_ordering.py`](./food_ordering.py) — restaurant order flow demonstrating node and edge functions | ||
| - [`restaurant_reservation.py`](./restaurant_reservation.py) — reservation system with availability checking | ||
| - [`patient_intake.py`](./patient_intake.py) — medical intake system showing complex state management | ||
| - [`insurance_quote.py`](./insurance_quote.py) — insurance quote system with data collection | ||
| - [`podcast_interview.py`](./podcast_interview.py) — podcast interview flow | ||
|
|
||
| ### Advanced features | ||
|
|
||
| - [`llm_switching.py`](./llm_switching.py) — switching between LLM providers during a conversation | ||
| - [`warm_transfer.py`](./warm_transfer.py) — transferring calls between flows (DailyTransport only) | ||
| - [`multi_worker_handoff.py`](./multi_worker_handoff.py) — composing Flows with Pipecat's multi-worker framework: a structured Flows reservation worker hands off to and from a free-form `LLMWorker` router over the bus, sharing a single conversation context | ||
| - [`food_ordering_advanced_functionschema.py`](./food_ordering_advanced_functionschema.py) — the food-ordering flow defined with `FlowsFunctionSchema`s instead of direct functions, for when you need to specify a function's schema explicitly | ||
|
|
||
| The examples define their functions as "direct functions" — async functions whose schema is derived from the signature and docstring — which is the recommended pattern. `food_ordering_advanced_functionschema.py` shows the alternative `FlowsFunctionSchema` approach. | ||
|
|
||
| ## Learn more | ||
|
|
||
| See the [Pipecat Flows guide](https://docs.pipecat.ai/guides/features/pipecat-flows) for a full walkthrough of nodes, functions, context strategies, and actions. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # Hold Music Player | ||
|
|
||
| This project is a hold music player, based on the `wav_audio_send` example from the [daily-python repository](https://github.com/daily-co/daily-python/blob/main/demos/audio/wav_audio_send.py). It is designed to serve as a helper for other examples, providing a reusable component for scenarios that require hold music functionality. | ||
|
|
||
| The hold music WAV file used in this example was sourced from [No Copyright Music](https://www.no-copyright-music.com/). | ||
|
|
||
| To see this hold music player in action, check out the [warm transfer example](../warm_transfer.py). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| # | ||
| # This demo will join a Daily meeting and send the audio from a WAV file into | ||
| # the meeting. It uses the asyncio library. | ||
| # | ||
| # Usage: python3 hold_music.py -m MEETING_URL -i FILE.wav | ||
| # | ||
|
|
||
| import argparse | ||
| import asyncio | ||
| import signal | ||
| import wave | ||
|
|
||
| from daily import * | ||
|
|
||
| SAMPLE_RATE = 16000 | ||
| NUM_CHANNELS = 1 | ||
|
|
||
|
|
||
| class AsyncSendWavApp: | ||
| def __init__(self, input_file_name, sample_rate, num_channels): | ||
| self.__mic_device = Daily.create_microphone_device( | ||
| "my-mic", | ||
| sample_rate=sample_rate, | ||
| channels=num_channels, | ||
| non_blocking=True, | ||
| ) | ||
|
|
||
| self.__client = CallClient() | ||
|
|
||
| self.__client.update_subscription_profiles( | ||
| {"base": {"camera": "unsubscribed", "microphone": "unsubscribed"}} | ||
| ) | ||
|
|
||
| self.__app_error = None | ||
|
|
||
| self.__start_event = asyncio.Event() | ||
| self.__task = asyncio.get_running_loop().create_task(self.send_wav_file(input_file_name)) | ||
|
|
||
| async def run(self, meeting_url, meeting_token): | ||
| (data, error) = await self.join(meeting_url, meeting_token) | ||
|
|
||
| if error: | ||
| print(f"Unable to join meeting: {error}") | ||
| self.__app_error = error | ||
|
|
||
| self.__start_event.set() | ||
|
|
||
| await self.__task | ||
|
|
||
| async def join(self, meeting_url, meeting_token): | ||
| future = asyncio.get_running_loop().create_future() | ||
|
|
||
| def join_completion(data, error): | ||
| future.get_loop().call_soon_threadsafe(future.set_result, (data, error)) | ||
|
|
||
| self.__client.join( | ||
| meeting_url, | ||
| meeting_token, | ||
| client_settings={ | ||
| "inputs": { | ||
| "camera": False, | ||
| "microphone": {"isEnabled": True, "settings": {"deviceId": "my-mic"}}, | ||
| } | ||
| }, | ||
| completion=join_completion, | ||
| ) | ||
|
|
||
| return await future | ||
|
|
||
| async def leave(self): | ||
| future = asyncio.get_running_loop().create_future() | ||
|
|
||
| def leave_completion(error): | ||
| future.get_loop().call_soon_threadsafe(future.set_result, error) | ||
|
|
||
| self.__client.leave(completion=leave_completion) | ||
|
|
||
| await future | ||
|
|
||
| self.__client.release() | ||
|
|
||
| self.__task.cancel() | ||
| await self.__task | ||
|
|
||
| async def write_frames(self, frames): | ||
| future = asyncio.get_running_loop().create_future() | ||
|
|
||
| def write_completion(count): | ||
| future.get_loop().call_soon_threadsafe(future.set_result, count) | ||
|
|
||
| self.__mic_device.write_frames(frames, completion=write_completion) | ||
|
|
||
| await future | ||
|
|
||
| async def send_wav_file(self, file_name): | ||
| await self.__start_event.wait() | ||
|
|
||
| if self.__app_error: | ||
| print(f"Unable to send WAV file!") | ||
| return | ||
|
|
||
| try: | ||
| wav = wave.open(file_name, "rb") | ||
|
|
||
| sent_frames = 0 | ||
| total_frames = wav.getnframes() | ||
| sample_rate = wav.getframerate() | ||
| while sent_frames < total_frames: | ||
| # Read 100ms worth of audio frames. | ||
| frames = wav.readframes(int(sample_rate / 10)) | ||
| if len(frames) > 0: | ||
| await self.write_frames(frames) | ||
| sent_frames += sample_rate / 10 | ||
| except asyncio.CancelledError: | ||
| pass | ||
|
|
||
|
|
||
| async def sig_handler(app): | ||
| print("Ctrl-C detected. Exiting!") | ||
| await app.leave() | ||
|
|
||
|
|
||
| async def main(): | ||
| parser = argparse.ArgumentParser() | ||
| parser.add_argument("-m", "--meeting", required=True, help="Meeting URL") | ||
| parser.add_argument("-t", "--token", required=True, help="Meeting token") | ||
| parser.add_argument("-i", "--input", required=True, help="WAV input file") | ||
| parser.add_argument( | ||
| "-c", "--channels", type=int, default=NUM_CHANNELS, help="Number of channels" | ||
| ) | ||
| parser.add_argument("-r", "--rate", type=int, default=SAMPLE_RATE, help="Sample rate") | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| Daily.init() | ||
|
|
||
| app = AsyncSendWavApp(args.input, args.rate, args.channels) | ||
|
|
||
| loop = asyncio.get_running_loop() | ||
|
|
||
| loop.add_signal_handler(signal.SIGINT, lambda *args: asyncio.create_task(sig_handler(app))) | ||
|
|
||
| await app.run(args.meeting, args.token) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| asyncio.run(main()) |
Binary file not shown.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.