Compare commits
1 Commits
v0.0.72
...
aleix/queu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41695806e8 |
18
.github/workflows/android.yaml
vendored
@@ -6,13 +6,11 @@ on:
|
|||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- "examples/simple-chatbot/client/android/**"
|
- "examples/simple-chatbot/client/android/**"
|
||||||
- "examples/p2p-webrtc/video-transform/client/android/**"
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- "**"
|
- "**"
|
||||||
paths:
|
paths:
|
||||||
- "examples/simple-chatbot/client/android/**"
|
- "examples/simple-chatbot/client/android/**"
|
||||||
- "examples/p2p-webrtc/video-transform/client/android/**"
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
sdk_git_ref:
|
sdk_git_ref:
|
||||||
@@ -25,7 +23,7 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sdk:
|
sdk:
|
||||||
name: "Demo apps"
|
name: "Simple chatbot demo"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
@@ -39,22 +37,12 @@ jobs:
|
|||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|
||||||
- name: "Example app: Simple Chatbot"
|
- name: Build demo app
|
||||||
working-directory: examples/simple-chatbot/client/android
|
working-directory: examples/simple-chatbot/client/android
|
||||||
run: ./gradlew :simple-chatbot-client:assembleDebug
|
run: ./gradlew :simple-chatbot-client:assembleDebug
|
||||||
|
|
||||||
- name: Upload Simple Chatbot APK
|
- name: Upload demo APK
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Simple Chatbot Android Client
|
name: Simple Chatbot Android Client
|
||||||
path: examples/simple-chatbot/client/android/simple-chatbot-client/build/outputs/apk/debug/simple-chatbot-client-debug.apk
|
path: examples/simple-chatbot/client/android/simple-chatbot-client/build/outputs/apk/debug/simple-chatbot-client-debug.apk
|
||||||
|
|
||||||
- name: "Example app: Small WebRTC Client"
|
|
||||||
working-directory: examples/p2p-webrtc/video-transform/client/android
|
|
||||||
run: ./gradlew :small-webrtc-client:assembleDebug
|
|
||||||
|
|
||||||
- name: Upload Small WebRTC APK
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Small WebRTC Android Client
|
|
||||||
path: examples/p2p-webrtc/video-transform/client/android/small-webrtc-client/build/outputs/apk/debug/small-webrtc-client-debug.apk
|
|
||||||
|
|||||||
495
CHANGELOG.md
@@ -5,501 +5,6 @@ All notable changes to **Pipecat** will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [0.0.72] - 2025-06-26
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added logging and improved error handling to help diagnose and prevent potential
|
|
||||||
Pipeline freezes.
|
|
||||||
|
|
||||||
- Added `WatchdogQueue`, `WatchdogPriorityQueue`, `WatchdogEvent` and
|
|
||||||
`WatchdogAsyncIterator`. These helper utilities reset watchdog timers
|
|
||||||
appropriately before they expire. When watchdog timers are disabled, the
|
|
||||||
utilities behave as standard counterparts without side effects.
|
|
||||||
|
|
||||||
- Introduce task watchdog timers. Watchdog timers are used to detect if a
|
|
||||||
Pipecat task is taking longer than expected (by default 5 seconds). Watchdog
|
|
||||||
timers are disabled by default and can be enabled globally by passing
|
|
||||||
`enable_watchdog_timers` argument to `PipelineTask` constructor. It is
|
|
||||||
possible to change the default watchdog timer timeout by using the
|
|
||||||
`watchdog_timeout` argument. You can also log how long it takes to reset the
|
|
||||||
watchdog timers which is done with the `enable_watchdog_logging`. You can
|
|
||||||
control all these settings per each frame processor or even per task. That is,
|
|
||||||
you can set `enable_watchdog_timers`, `enable_watchdog_logging` and
|
|
||||||
`watchdog_timeout` when creating any frame processor through their constructor
|
|
||||||
arguments or when you create a task with `FrameProcessor.create_task()`. Note
|
|
||||||
that watchdog timers only work with Pipecat tasks and will not work if you use
|
|
||||||
`asycio.create_task()` or similar.
|
|
||||||
|
|
||||||
- Added `lexicon_names` parameter to `AWSPollyTTSService.InputParams`.
|
|
||||||
|
|
||||||
- Added reconnection logic and audio buffer management to `GladiaSTTService`.
|
|
||||||
|
|
||||||
- The `TurnTrackingObserver` now ends a turn upon observing an `EndFrame` or
|
|
||||||
`CancelFrame`.
|
|
||||||
|
|
||||||
- Added Polish support to `AWSTranscribeSTTService`.
|
|
||||||
|
|
||||||
- Added new frames `FrameProcessorPauseFrame` and `FrameProcessorResumeFrame`
|
|
||||||
which allow pausing and resuming frame processing for a given frame
|
|
||||||
processor. These are control frames, so they are ordered. Pausing frame
|
|
||||||
processor will keep old frames in the internal queues until resume takes
|
|
||||||
place. Frames being pushed while a frame processor is paused will be pushed to
|
|
||||||
the queues. When frame processing is resumed all queued frames will be
|
|
||||||
processed in order. Also added `FrameProcessorPauseUrgentFrame` and
|
|
||||||
`FrameProcessorResumeUrgentFrame` which are system frames and therefore they
|
|
||||||
have high priority.
|
|
||||||
|
|
||||||
- Added a property called `has_function_calls_in_progress` in
|
|
||||||
`LLMAssistantContextAggregator` that exposes whether a function call is in
|
|
||||||
progress.
|
|
||||||
|
|
||||||
- Added `SambaNovaLLMService` which provides llm api integration with an
|
|
||||||
OpenAI-compatible interface.
|
|
||||||
|
|
||||||
- Added `SambaNovaTTSService` which provides speech-to-text functionality using
|
|
||||||
SambaNovas's (whisper) API.
|
|
||||||
|
|
||||||
- Add fundational examples for function calling and transcription
|
|
||||||
`14s-function-calling-sambanova.py`, `13g-sambanova-transcription.py`
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- `HeartbeatFrame`s are now control frames. This will make it easier to detect
|
|
||||||
pipeline freezes. Previously, heartbeat frames were system frames which meant
|
|
||||||
they were not get queued with other frames, making it difficult to detect
|
|
||||||
pipeline stalls.
|
|
||||||
|
|
||||||
- Updated `OpenAIRealtimeBetaLLMService` to accept `language` in the
|
|
||||||
`InputAudioTranscription` class for all models.
|
|
||||||
|
|
||||||
- Updated the default model for `OpenAIRealtimeBetaLLMService` to
|
|
||||||
`gpt-4o-realtime-preview-2025-06-03`.
|
|
||||||
|
|
||||||
- The `PipelineParams` arg `allow_interruptions` now defaults to `True`.
|
|
||||||
|
|
||||||
- `TavusTransport` and `TavusVideoService` now send audio to Tavus using WebRTC
|
|
||||||
audio tracks instead of `app-messages` over WebSocket. This should improve the
|
|
||||||
overall audio quality.
|
|
||||||
|
|
||||||
- Upgraded `daily-python` to 0.19.3.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixed an issue that would cause heartbeat frames to be sent before processors
|
|
||||||
were started.
|
|
||||||
|
|
||||||
- Fixed an event loop blocking issue when using `SentryMetrics`.
|
|
||||||
|
|
||||||
- Fixed an issue in `FastAPIWebsocketClient` to ensure proper disconnection
|
|
||||||
when the websocket is already closed.
|
|
||||||
|
|
||||||
- Fixed an issue where the `UserStoppedSpeakingFrame` was not received if the
|
|
||||||
transport was not receiving new audio frames.
|
|
||||||
|
|
||||||
- Fixed an edge case where if the user interrupted the bot but no new aggregation
|
|
||||||
was received, the bot would not resume speaking.
|
|
||||||
|
|
||||||
- Fixed an issue with `TelnyxFrameSerializer` where it would throw an exception
|
|
||||||
when the user hung up the call.
|
|
||||||
|
|
||||||
- Fixed an issue with `ElevenLabsTTSService` where the context was not being
|
|
||||||
closed.
|
|
||||||
|
|
||||||
- Fixed function calling in `AWSNovaSonicLLMService`.
|
|
||||||
|
|
||||||
- Fixed an issue that would cause multiple `PipelineTask.on_idle_timeout`
|
|
||||||
events to be triggered repeatedly.
|
|
||||||
|
|
||||||
- Fixed an issue that was causing user and bot speech to not be synchronized
|
|
||||||
during recordings.
|
|
||||||
|
|
||||||
- Fixed an issue where voice settings weren't applied to ElevenLabsTTSService.
|
|
||||||
|
|
||||||
- Fixed an issue with `GroqTTSService` where it was not properly parsing the
|
|
||||||
WAV file header.
|
|
||||||
|
|
||||||
- Fixed an issue with `GoogleSTTService` where it was constantly reconnecting
|
|
||||||
before starting to receive audio from the user.
|
|
||||||
|
|
||||||
- Fixed an issue where `GoogleLLMService`'s TTFB value was incorrect.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
- `AudioBufferProcessor` parameter `user_continuos_stream` is deprecated.
|
|
||||||
|
|
||||||
### Other
|
|
||||||
|
|
||||||
- Rename `14e-function-calling-gemini.py` to `14e-function-calling-google.py`.
|
|
||||||
|
|
||||||
## [0.0.71] - 2025-06-10
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Adds a parameter called `additional_span_attributes` to PipelineTask that
|
|
||||||
lets you add any additional attributes you'd like to the conversation span.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixed an issue with `CartesiaSTTService` initialization.
|
|
||||||
|
|
||||||
## [0.0.70] - 2025-06-10
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added `ExotelFrameSerializer` to handle telephony calls via Exotel.
|
|
||||||
|
|
||||||
- Added the option `informal` to `TranslationConfig` on Gladia config.
|
|
||||||
Allowing to force informal language forms when available.
|
|
||||||
|
|
||||||
- Added `CartesiaSTTService` which is a websocket based implementation to
|
|
||||||
transcribe audio. Added a foundational example in
|
|
||||||
`13f-cartesia-transcription.py`
|
|
||||||
|
|
||||||
- Added an `websocket` example, showing how to use the new Pipecat client
|
|
||||||
`WebsocketTransport` to connect with Pipecat `FastAPIWebsocketTransport` or
|
|
||||||
`WebsocketServerTransport`.
|
|
||||||
|
|
||||||
- Added language support to `RimeHttpTTSService`. Extended languages to include
|
|
||||||
German and French for both `RimeTTSService` and `RimeHttpTTSService`.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Upgraded `daily-python` to 0.19.2.
|
|
||||||
|
|
||||||
- Make `PipelineTask.add_observer()` synchronous. This allows callers to call it
|
|
||||||
before doing the work of running the `PipelineTask` (i.e. without invoking
|
|
||||||
`PipelineTask.set_event_loop()` first).
|
|
||||||
|
|
||||||
- Pipecat 0.0.69 forced `uvloop` event loop on Linux on macOS. Unfortunately,
|
|
||||||
this is causing issue in some systems. So, `uvloop` is not enabled by default
|
|
||||||
anymore. If you want to use `uvloop` you can just set the `asyncio` event
|
|
||||||
policy before starting your agent with:
|
|
||||||
|
|
||||||
```python
|
|
||||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixed an issue with various TTS services that would cause audio glitches at
|
|
||||||
the start of every bot turn.
|
|
||||||
|
|
||||||
- Fixed an `ElevenLabsTTSService` issue where a context warning was printed
|
|
||||||
when pushing a `TTSSpeakFrame`.
|
|
||||||
|
|
||||||
- Fixed an `AssemblyAISTTService` issue that could cause unexpected behavior
|
|
||||||
when yielding empty `Frame()`s.
|
|
||||||
|
|
||||||
- Fixed an issue where `OutputAudioRawFrame.transport_destination` was being
|
|
||||||
reset to `None` instead of retaining its intended value before sending the
|
|
||||||
audio frame to `write_audio_frame`.
|
|
||||||
|
|
||||||
- Fixed a typo in Livekit transport that prevented initialization.
|
|
||||||
|
|
||||||
## [0.0.69] - 2025-06-02 "AI Engineer World's Fair release" ✨
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added a new frame `FunctionCallsStartedFrame`. This frame is pushed both
|
|
||||||
upstream and downstream from the LLM service to indicate that one or more
|
|
||||||
function calls are going to be executed.
|
|
||||||
|
|
||||||
- Added LLM services `on_function_calls_started` event. This event will be
|
|
||||||
triggered when the LLM service receives function calls from the model and is
|
|
||||||
going to start executing them.
|
|
||||||
|
|
||||||
- Function calls can now be executed sequentially (in the order received in the
|
|
||||||
completion) by passing `run_in_parallel=False` when creating your LLM
|
|
||||||
service. By default, if the LLM completion returns 2 or more function calls
|
|
||||||
they run concurrently. In both cases, concurrently and sequentially, a new LLM
|
|
||||||
completion will run when the last function call finishes.
|
|
||||||
|
|
||||||
- Added OpenTelemetry tracing for `GeminiMultimodalLiveLLMService` and
|
|
||||||
`OpenAIRealtimeBetaLLMService`.
|
|
||||||
|
|
||||||
- Added initial support for interruption strategies, which determine if the user
|
|
||||||
should interrupt the bot while the bot is speaking. Interruption strategies
|
|
||||||
can be based on factors such as audio volume or the number of words spoken by
|
|
||||||
the user. These can be specified via the new `interruption_strategies` field
|
|
||||||
in `PipelineParams`. A new `MinWordsInterruptionStrategy` strategy has been
|
|
||||||
introduced which triggers an interruption if the user has spoken a minimum
|
|
||||||
number of words. If no interruption strategies are specified, the normal
|
|
||||||
interruption behavior applies. If multiple strategies are provided, the first
|
|
||||||
one that evaluates to true will trigger the interruption.
|
|
||||||
|
|
||||||
- `BaseInputTransport` now handles `StopFrame`. When a `StopFrame` is received
|
|
||||||
the transport will pause sending frames downstream until a new `StartFrame` is
|
|
||||||
received. This allows the transport to be reused (keeping the same connection)
|
|
||||||
in a different pipeline.
|
|
||||||
|
|
||||||
- Updated AssemblyAI STT service to support their latest streaming
|
|
||||||
speech-to-text model with improved transcription latency and endpointing.
|
|
||||||
|
|
||||||
- You can now access STT service results through the new
|
|
||||||
`TranscriptionFrame.result` and `InterimTranscriptionFrame.result` field. This
|
|
||||||
is useful in case you use some specific settings for the STT and you want to
|
|
||||||
access the STT results.
|
|
||||||
|
|
||||||
- The examples runner is now public from the `pipecat.examples` package. This
|
|
||||||
allows everyone to build their own examples and run them easily.
|
|
||||||
|
|
||||||
- It is now possible to push `OutputDTMFFrame` or `OutputDTMFUrgentFrame` with
|
|
||||||
`DailyTransport`. This will be sent properly if a Daily dial-out connection
|
|
||||||
has been established.
|
|
||||||
|
|
||||||
- Added `OutputDTMFUrgentFrame` to send a DTMF keypress quickly. The previous
|
|
||||||
`OutputDTMFFrame` queues the keypress with the rest of data frames.
|
|
||||||
|
|
||||||
- Added `DTMFAggregator`, which aggregates keypad presses into
|
|
||||||
`TranscriptionFrame`s. Aggregation occurs after a timeout, termination key
|
|
||||||
press, or user interruption. You can specify the prefix of the
|
|
||||||
`TranscriptionFrame`.
|
|
||||||
|
|
||||||
- Added new functions `DailyTransport.start_transcription()` and
|
|
||||||
`DailyTransport.stop_transcription()` to be able to start and stop Daily
|
|
||||||
transcription dynamically (maybe with different settings).
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Reverted the default model for `GeminiMultimodalLiveLLMService` back to
|
|
||||||
`models/gemini-2.0-flash-live-001`.
|
|
||||||
`gemini-2.5-flash-preview-native-audio-dialog` has inconsistent performance.
|
|
||||||
You can opt in to using this model by setting the `model` arg.
|
|
||||||
|
|
||||||
- Function calls are now cancelled by default if there's an interruption. To
|
|
||||||
disable this behavior you can set `cancel_on_interruption=False` when
|
|
||||||
registering the function call. Since function calls are executed as tasks you
|
|
||||||
can tell if a function call has been cancelled by catching the
|
|
||||||
`asyncio.CancelledError` exception (and don't forget to raise it again!).
|
|
||||||
|
|
||||||
- Updated OpenTelemetry tracing attribute `metrics.ttfb_ms` to `metrics.ttfb`.
|
|
||||||
The attribute reports TTFB in seconds.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
- `DailyTransport.send_dtmf()` is deprecated, push an `OutputDTMFFrame` or an
|
|
||||||
`OutputDTMFUrgentFrame` instead.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixed an issue with `ElevenLabsTTSService` where long responses would
|
|
||||||
continue generating output even after an interruption.
|
|
||||||
|
|
||||||
- Fixed an issue with the `OpenAILLMContext` where non-Roman characters were
|
|
||||||
being incorrectly encoded as Unicode escape sequences. This was a logging
|
|
||||||
issue and did not impact the actual conversation.
|
|
||||||
|
|
||||||
- In `AWSBedrockLLMService`, worked around a possible bug in AWS Bedrock where
|
|
||||||
a `toolConfig` is required if there has been previous tool use in the
|
|
||||||
messages array. This workaround includes a no_op factory function call is
|
|
||||||
used to satisfy the requirement.
|
|
||||||
|
|
||||||
- Fixed `WebsocketClientTransport` to use `FrameProcessorSetup.task_manager`
|
|
||||||
instead of `StartFrame.task_manager`.
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
- Use `uvloop` as the new event loop on Linux and macOS systems.
|
|
||||||
|
|
||||||
## [0.0.68] - 2025-05-28
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added `GoogleHttpTTSService` which uses Google's HTTP TTS API.
|
|
||||||
|
|
||||||
- Added `TavusTransport`, a new transport implementation compatible with any
|
|
||||||
Pipecat pipeline. When using the `TavusTransport`the Pipecat bot will
|
|
||||||
connect in the same room as the Tavus Avatar and the user.
|
|
||||||
|
|
||||||
- Added `PlivoFrameSerializer` to support Plivo calls. A full running example
|
|
||||||
has also been added to `examples/plivo-chatbot`.
|
|
||||||
|
|
||||||
- Added `UserBotLatencyLogObserver`. This is an observer that logs the latency
|
|
||||||
between when the user stops speaking and when the bot starts speaking. This
|
|
||||||
gives you an initial idea on how quickly the AI services respond.
|
|
||||||
|
|
||||||
- Added `SarvamTTSService`, which implements Sarvam AI's TTS API:
|
|
||||||
https://docs.sarvam.ai/api-reference-docs/text-to-speech/convert.
|
|
||||||
|
|
||||||
- Added `PipelineTask.add_observer()` and `PipelineTask.remove_observer()` to
|
|
||||||
allow mangaging observers at runtime. This is useful for cases where the task
|
|
||||||
is passed around to other code components that might want to observe the
|
|
||||||
pipeline dynamically.
|
|
||||||
|
|
||||||
- Added `user_id` field to `TranscriptionMessage`. This allows identifying the
|
|
||||||
user in a multi-user scenario. Note that this requires that
|
|
||||||
`TranscriptionFrame` has the `user_id` properly set.
|
|
||||||
|
|
||||||
- Added new `PipelineTask` event handlers `on_pipeline_started`,
|
|
||||||
`on_pipeline_stopped`, `on_pipeline_ended` and `on_pipeline_cancelled`, which
|
|
||||||
correspond to the `StartFrame`, `StopFrame`, `EndFrame` and `CancelFrame`
|
|
||||||
respectively.
|
|
||||||
|
|
||||||
- Added additional languages to `LmntTTSService`. Languages include: `hi`,
|
|
||||||
`id`, `it`, `ja`, `nl`, `pl`, `ru`, `sv`, `th`, `tr`, `uk`, `vi`.
|
|
||||||
|
|
||||||
- Added a `model` parameter to the `LmntTTSService` constructor, allowing
|
|
||||||
switching between LMNT models.
|
|
||||||
|
|
||||||
- Added `MiniMaxHttpTTSService`, which implements MiniMax's T2A API for TTS.
|
|
||||||
Learn more: https://www.minimax.io/platform_overview
|
|
||||||
|
|
||||||
- A new function `FrameProcessor.setup()` has been added to allow setting up
|
|
||||||
frame processors before receiving a `StartFrame`. This is what's happening
|
|
||||||
internally: `FrameProcessor.setup()` is called, `StartFrame` is pushed from
|
|
||||||
the beginning of the pipeline, your regular pipeline operations, `EndFrame`
|
|
||||||
or `CancelFrame` are pushed from the beginning of the pipeline and finally
|
|
||||||
`FrameProcessor.cleanup()` is called.
|
|
||||||
|
|
||||||
- Added support for OpenTelemetry tracing in Pipecat. This initial
|
|
||||||
implementation includes:
|
|
||||||
|
|
||||||
- A `setup_tracing` method where you can specify your OpenTelemetry exporter
|
|
||||||
- Service decorators for STT (`@traced_stt`), LLM (`@traced_llm`), and TTS
|
|
||||||
(`@traced_tts`) which trace the execution and collect properties and
|
|
||||||
metrics (TTFB, token usage, character counts, etc.)
|
|
||||||
- Class decorators that provide execution tracking; these are generic and can
|
|
||||||
be used for service tracking as needed
|
|
||||||
- Spans that help track traces on a per conversations and turn basis:
|
|
||||||
|
|
||||||
```
|
|
||||||
conversation-uuid
|
|
||||||
├── turn-1
|
|
||||||
│ ├── stt_deepgramsttservice
|
|
||||||
│ ├── llm_openaillmservice
|
|
||||||
│ └── tts_cartesiattsservice
|
|
||||||
...
|
|
||||||
└── turn-n
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, Pipecat has implemented service decorators to trace execution of
|
|
||||||
STT, LLM, and TTS services. You can enable tracing by setting
|
|
||||||
`enable_tracing` to `True` in the PipelineTask.
|
|
||||||
|
|
||||||
- Added `TurnTrackingObserver`, which tracks the start and end of a user/bot
|
|
||||||
turn pair and emits events `on_turn_started` and `on_turn_stopped`
|
|
||||||
corresponding to the start and end of a turn, respectively.
|
|
||||||
|
|
||||||
- Allow passing observers to `run_test()` while running unit tests.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Upgraded `daily-python` to 0.19.1.
|
|
||||||
|
|
||||||
- ⚠️ Updated `SmallWebRTCTransport` to align with how other transports handle
|
|
||||||
`on_client_disconnected`. Now, when the connection is closed and no reconnection
|
|
||||||
is attempted, `on_client_disconnected` is called instead of `on_client_close`. The
|
|
||||||
`on_client_close` callback is no longer used, use `on_client_disconnected` instead.
|
|
||||||
|
|
||||||
- Check if `PipelineTask` has already been cancelled.
|
|
||||||
|
|
||||||
- Don't raise an exception if event handler is not registered.
|
|
||||||
|
|
||||||
- Upgraded `deepgram-sdk` to 4.1.0.
|
|
||||||
|
|
||||||
- Updated `GoogleTTSService` to use Google's streaming TTS API. The default
|
|
||||||
voice also updated to `en-US-Chirp3-HD-Charon`.
|
|
||||||
|
|
||||||
- ⚠️ Refactored the `TavusVideoService`, so it acts like a proxy, sending audio
|
|
||||||
to Tavus and receiving both audio and video. This will make
|
|
||||||
`TavusVideoService` usable with any Pipecat pipeline and with any transport.
|
|
||||||
This is a **breaking change**, check the
|
|
||||||
`examples/foundational/21a-tavus-layer-small-webrtc.py` to see how to use it.
|
|
||||||
|
|
||||||
- `DailyTransport` now uses custom microphone audio tracks instead of virtual
|
|
||||||
microphones. Now, multiple Daily transports can be used in the same process.
|
|
||||||
|
|
||||||
- `DailyTransport` now captures audio from individual participants instead of
|
|
||||||
the whole room. This allows identifying audio frames per participant.
|
|
||||||
|
|
||||||
- Updated the default model for `AnthropicLLMService` to
|
|
||||||
`claude-sonnet-4-20250514`.
|
|
||||||
|
|
||||||
- Updated the default model for `GeminiMultimodalLiveLLMService` to
|
|
||||||
`models/gemini-2.5-flash-preview-native-audio-dialog`.
|
|
||||||
|
|
||||||
- `BaseTextFilter` methods `filter()`, `update_settings()`,
|
|
||||||
`handle_interruption()` and `reset_interruption()` are now async.
|
|
||||||
|
|
||||||
- `BaseTextAggregator` methods `aggregate()`, `handle_interruption()` and
|
|
||||||
`reset()` are now async.
|
|
||||||
|
|
||||||
- The API version for `CartesiaTTSService` and `CartesiaHttpTTSService` has
|
|
||||||
been updated. Also, the `cartesia` dependency has been updated to 2.x.
|
|
||||||
|
|
||||||
- `CartesiaTTSService` and `CartesiaHttpTTSService` now support Cartesia's new
|
|
||||||
`speed` parameter which accepts values of `slow`, `normal`, and `fast`.
|
|
||||||
|
|
||||||
- `GeminiMultimodalLiveLLMService` now uses the user transcription and usage
|
|
||||||
metrics provided by Gemini Live.
|
|
||||||
|
|
||||||
- `GoogleLLMService` has been updated to use `google-genai` instead of the
|
|
||||||
deprecated `google-generativeai`.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
- In `CartesiaTTSService` and `CartesiaHttpTTSService`, `emotion` has been
|
|
||||||
deprecated by Cartesia. Pipecat is following suit and deprecating `emotion`
|
|
||||||
as well.
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- Since `GeminiMultimodalLiveLLMService` now transcribes it's own audio, the
|
|
||||||
`transcribe_user_audio` arg has been removed. Audio is now transcribed
|
|
||||||
automatically.
|
|
||||||
|
|
||||||
- Removed `SileroVAD` frame processor, just use `SileroVADAnalyzer`
|
|
||||||
instead. Also removed, `07a-interruptible-vad.py` example.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixed a `DailyTransport` issue that was not allow capturing video frames if
|
|
||||||
framerate was greater than zero.
|
|
||||||
|
|
||||||
- Fixed a `DeegramSTTService` connection issue when the user provided their own
|
|
||||||
`LiveOptions`.
|
|
||||||
|
|
||||||
- Fixed a `DailyTransport` issue that would cause images needing resize to block
|
|
||||||
the event loop.
|
|
||||||
|
|
||||||
- Fixed an issue with `ElevenLabsTTSService` where changing the model or voice
|
|
||||||
while the service is running wasn't working.
|
|
||||||
|
|
||||||
- Fixed an issue that would cause multiple instances of the same class to behave
|
|
||||||
incorrectly if any of the given constructor arguments defaulted to a mutable
|
|
||||||
value (e.g. lists, dictionaries, objects).
|
|
||||||
|
|
||||||
- Fixed an issue with `CartesiaTTSService` where `TTSTextFrame` messages weren't
|
|
||||||
being emitted when the model was set to `sonic`. This resulted in the
|
|
||||||
assistant context not being updated with assistant messages.
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
- `DailyTransport`: process audio, video and events in separate tasks.
|
|
||||||
|
|
||||||
- Don't create event handler tasks if no user event handlers have been
|
|
||||||
registered.
|
|
||||||
|
|
||||||
### Other
|
|
||||||
|
|
||||||
- It is now possible to run all (or most) foundational example with multiple
|
|
||||||
transports. By default, they run with P2P (Peer-To-Peer) WebRTC so you can try
|
|
||||||
everything locally. You can also run them with Daily or even with a Twilio
|
|
||||||
phone number.
|
|
||||||
|
|
||||||
- Added foundation examples `07y-interruptible-minimax.py` and
|
|
||||||
`07z-interruptible-sarvam.py`to show how to use the `MiniMaxHttpTTSService`
|
|
||||||
and `SarvamTTSService`, respectively.
|
|
||||||
|
|
||||||
- Added an `open-telemetry-tracing` example, showing how to setup tracing. The
|
|
||||||
example also includes Jaeger as an open source OpenTelemetry client to review
|
|
||||||
traces from the example runs.
|
|
||||||
|
|
||||||
- Added foundational example `29-turn-tracking-observer.py` to show how to use
|
|
||||||
the `TurnTrackingObserver`.
|
|
||||||
|
|
||||||
## [0.0.67] - 2025-05-07
|
## [0.0.67] - 2025-05-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -41,76 +41,36 @@ We use Ruff for code linting and formatting. Please ensure your code passes all
|
|||||||
|
|
||||||
We follow Google-style docstrings with these specific conventions:
|
We follow Google-style docstrings with these specific conventions:
|
||||||
|
|
||||||
**Regular Classes:**
|
- Class docstrings should fully document all parameters used in `__init__`
|
||||||
|
- We don't require separate docstrings for `__init__` methods when parameters are documented in the class docstring
|
||||||
|
- Property methods should have docstrings explaining their purpose and return value
|
||||||
|
|
||||||
- Class docstring describes the class purpose and documents all `__init__` parameters in an `Args:` section
|
Example of correctly documented class:
|
||||||
- No separate `__init__` docstring needed
|
|
||||||
- All public methods must have docstrings with `Args:` and `Returns:` sections as appropriate
|
|
||||||
|
|
||||||
**Dataclasses:**
|
|
||||||
|
|
||||||
- Class docstring describes the purpose and documents all fields in a `Parameters:` section
|
|
||||||
- No `__init__` docstring (auto-generated)
|
|
||||||
|
|
||||||
**Properties:**
|
|
||||||
|
|
||||||
- Must have docstrings with `Returns:` section
|
|
||||||
|
|
||||||
**Abstract Methods:**
|
|
||||||
|
|
||||||
- Must have docstrings explaining what subclasses should implement
|
|
||||||
|
|
||||||
#### Examples:
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Regular class
|
class MyClass:
|
||||||
class MyService(BaseService):
|
"""Class description.
|
||||||
"""Description of what the service does.
|
|
||||||
|
Additional details about the class.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
param1: Description of param1.
|
param1: Description of first parameter.
|
||||||
param2: Description of param2. Defaults to True.
|
param2: Description of second parameter.
|
||||||
**kwargs: Additional arguments passed to parent.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, param1: str, param2: bool = True, **kwargs):
|
def __init__(self, param1, param2):
|
||||||
# No docstring - parameters documented above
|
# No docstring required here as parameters are documented above
|
||||||
super().__init__(**kwargs)
|
self.param1 = param1
|
||||||
|
self.param2 = param2
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sample_rate(self) -> int:
|
def some_property(self) -> str:
|
||||||
"""Get the current sample rate.
|
"""Get the formatted property value.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The sample rate in Hz.
|
A string representation of the property.
|
||||||
"""
|
"""
|
||||||
return self._sample_rate
|
return f"Property: {self.param1}"
|
||||||
|
|
||||||
async def process_data(self, data: str) -> bool:
|
|
||||||
"""Process the provided data.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data: The data to process.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if processing succeeded.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Dataclass
|
|
||||||
@dataclass
|
|
||||||
class ConfigParams:
|
|
||||||
"""Configuration parameters for the service.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
host: The host address.
|
|
||||||
port: The port number. Defaults to 8080.
|
|
||||||
timeout: Connection timeout in seconds.
|
|
||||||
"""
|
|
||||||
|
|
||||||
host: str
|
|
||||||
port: int = 8080
|
|
||||||
timeout: float = 30.0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Contributor Covenant Code of Conduct
|
# Contributor Covenant Code of Conduct
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
prune docs
|
|
||||||
prune examples
|
|
||||||
prune scripts
|
|
||||||
prune tests
|
|
||||||
27
README.md
@@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
**Pipecat** is an open-source Python framework for building real-time voice and multimodal conversational agents. Orchestrate audio and video, AI services, different transports, and conversation pipelines effortlessly—so you can focus on what makes your agent unique.
|
**Pipecat** is an open-source Python framework for building real-time voice and multimodal conversational agents. Orchestrate audio and video, AI services, different transports, and conversation pipelines effortlessly—so you can focus on what makes your agent unique.
|
||||||
|
|
||||||
> Want to dive right in? [Install Pipecat](https://docs.pipecat.ai/getting-started/installation) then try the [quickstart](https://docs.pipecat.ai/getting-started/quickstart).
|
|
||||||
|
|
||||||
## 🚀 What You Can Build
|
## 🚀 What You Can Build
|
||||||
|
|
||||||
- **Voice Assistants** – natural, streaming conversations with AI
|
- **Voice Assistants** – natural, streaming conversations with AI
|
||||||
@@ -51,19 +49,18 @@ You can connect to Pipecat from any platform using our official SDKs:
|
|||||||
|
|
||||||
## 🧩 Available services
|
## 🧩 Available services
|
||||||
|
|
||||||
| Category | Services |
|
| Category | Services |
|
||||||
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| Speech-to-Text | [AssemblyAI](https://docs.pipecat.ai/server/services/stt/assemblyai), [AWS](https://docs.pipecat.ai/server/services/stt/aws), [Azure](https://docs.pipecat.ai/server/services/stt/azure), [Cartesia](https://docs.pipecat.ai/server/services/stt/cartesia), [Deepgram](https://docs.pipecat.ai/server/services/stt/deepgram), [Fal Wizper](https://docs.pipecat.ai/server/services/stt/fal), [Gladia](https://docs.pipecat.ai/server/services/stt/gladia), [Google](https://docs.pipecat.ai/server/services/stt/google), [Groq (Whisper)](https://docs.pipecat.ai/server/services/stt/groq), [OpenAI (Whisper)](https://docs.pipecat.ai/server/services/stt/openai), [Parakeet (NVIDIA)](https://docs.pipecat.ai/server/services/stt/parakeet), [SambaNova (Whisper)](https://docs.pipecat.ai/server/services/stt/sambanova) [Ultravox](https://docs.pipecat.ai/server/services/stt/ultravox), [Whisper](https://docs.pipecat.ai/server/services/stt/whisper) |
|
| Speech-to-Text | [AssemblyAI](https://docs.pipecat.ai/server/services/stt/assemblyai), [AWS](https://docs.pipecat.ai/server/services/stt/aws), [Azure](https://docs.pipecat.ai/server/services/stt/azure), [Deepgram](https://docs.pipecat.ai/server/services/stt/deepgram), [Fal Wizper](https://docs.pipecat.ai/server/services/stt/fal), [Gladia](https://docs.pipecat.ai/server/services/stt/gladia), [Google](https://docs.pipecat.ai/server/services/stt/google), [Groq (Whisper)](https://docs.pipecat.ai/server/services/stt/groq), [OpenAI (Whisper)](https://docs.pipecat.ai/server/services/stt/openai), [Parakeet (NVIDIA)](https://docs.pipecat.ai/server/services/stt/parakeet), [Ultravox](https://docs.pipecat.ai/server/services/stt/ultravox), [Whisper](https://docs.pipecat.ai/server/services/stt/whisper) |
|
||||||
| LLMs | [Anthropic](https://docs.pipecat.ai/server/services/llm/anthropic), [AWS](https://docs.pipecat.ai/server/services/llm/aws), [Azure](https://docs.pipecat.ai/server/services/llm/azure), [Cerebras](https://docs.pipecat.ai/server/services/llm/cerebras), [DeepSeek](https://docs.pipecat.ai/server/services/llm/deepseek), [Fireworks AI](https://docs.pipecat.ai/server/services/llm/fireworks), [Gemini](https://docs.pipecat.ai/server/services/llm/gemini), [Grok](https://docs.pipecat.ai/server/services/llm/grok), [Groq](https://docs.pipecat.ai/server/services/llm/groq), [NVIDIA NIM](https://docs.pipecat.ai/server/services/llm/nim), [Ollama](https://docs.pipecat.ai/server/services/llm/ollama), [OpenAI](https://docs.pipecat.ai/server/services/llm/openai), [OpenRouter](https://docs.pipecat.ai/server/services/llm/openrouter), [Perplexity](https://docs.pipecat.ai/server/services/llm/perplexity), [Qwen](https://docs.pipecat.ai/server/services/llm/qwen), [SambaNova](https://docs.pipecat.ai/server/services/llm/sambanova) [Together AI](https://docs.pipecat.ai/server/services/llm/together) |
|
| LLMs | [Anthropic](https://docs.pipecat.ai/server/services/llm/anthropic), [AWS](https://docs.pipecat.ai/server/services/llm/aws), [Azure](https://docs.pipecat.ai/server/services/llm/azure), [Cerebras](https://docs.pipecat.ai/server/services/llm/cerebras), [DeepSeek](https://docs.pipecat.ai/server/services/llm/deepseek), [Fireworks AI](https://docs.pipecat.ai/server/services/llm/fireworks), [Gemini](https://docs.pipecat.ai/server/services/llm/gemini), [Grok](https://docs.pipecat.ai/server/services/llm/grok), [Groq](https://docs.pipecat.ai/server/services/llm/groq), [NVIDIA NIM](https://docs.pipecat.ai/server/services/llm/nim), [Ollama](https://docs.pipecat.ai/server/services/llm/ollama), [OpenAI](https://docs.pipecat.ai/server/services/llm/openai), [OpenRouter](https://docs.pipecat.ai/server/services/llm/openrouter), [Perplexity](https://docs.pipecat.ai/server/services/llm/perplexity), [Qwen](https://docs.pipecat.ai/server/services/llm/qwen), [Together AI](https://docs.pipecat.ai/server/services/llm/together) |
|
||||||
| Text-to-Speech | [AWS](https://docs.pipecat.ai/server/services/tts/aws), [Azure](https://docs.pipecat.ai/server/services/tts/azure), [Cartesia](https://docs.pipecat.ai/server/services/tts/cartesia), [Deepgram](https://docs.pipecat.ai/server/services/tts/deepgram), [ElevenLabs](https://docs.pipecat.ai/server/services/tts/elevenlabs), [FastPitch (NVIDIA)](https://docs.pipecat.ai/server/services/tts/fastpitch), [Fish](https://docs.pipecat.ai/server/services/tts/fish), [Google](https://docs.pipecat.ai/server/services/tts/google), [LMNT](https://docs.pipecat.ai/server/services/tts/lmnt), [MiniMax](https://docs.pipecat.ai/server/services/tts/minimax), [Neuphonic](https://docs.pipecat.ai/server/services/tts/neuphonic), [OpenAI](https://docs.pipecat.ai/server/services/tts/openai), [Piper](https://docs.pipecat.ai/server/services/tts/piper), [PlayHT](https://docs.pipecat.ai/server/services/tts/playht), [Rime](https://docs.pipecat.ai/server/services/tts/rime), [Sarvam](https://docs.pipecat.ai/server/services/tts/sarvam), [XTTS](https://docs.pipecat.ai/server/services/tts/xtts) |
|
| Text-to-Speech | [AWS](https://docs.pipecat.ai/server/services/tts/aws), [Azure](https://docs.pipecat.ai/server/services/tts/azure), [Cartesia](https://docs.pipecat.ai/server/services/tts/cartesia), [Deepgram](https://docs.pipecat.ai/server/services/tts/deepgram), [ElevenLabs](https://docs.pipecat.ai/server/services/tts/elevenlabs), [FastPitch (NVIDIA)](https://docs.pipecat.ai/server/services/tts/fastpitch), [Fish](https://docs.pipecat.ai/server/services/tts/fish), [Google](https://docs.pipecat.ai/server/services/tts/google), [LMNT](https://docs.pipecat.ai/server/services/tts/lmnt), [Neuphonic](https://docs.pipecat.ai/server/services/tts/neuphonic), [OpenAI](https://docs.pipecat.ai/server/services/tts/openai), [Piper](https://docs.pipecat.ai/server/services/tts/piper), [PlayHT](https://docs.pipecat.ai/server/services/tts/playht), [Rime](https://docs.pipecat.ai/server/services/tts/rime), [XTTS](https://docs.pipecat.ai/server/services/tts/xtts) |
|
||||||
| Speech-to-Speech | [AWS Nova Sonic](https://docs.pipecat.ai/server/services/s2s/aws), [Gemini Multimodal Live](https://docs.pipecat.ai/server/services/s2s/gemini), [OpenAI Realtime](https://docs.pipecat.ai/server/services/s2s/openai) |
|
| Speech-to-Speech | [Gemini Multimodal Live](https://docs.pipecat.ai/server/services/s2s/gemini), [OpenAI Realtime](https://docs.pipecat.ai/server/services/s2s/openai) |
|
||||||
| Transport | [Daily (WebRTC)](https://docs.pipecat.ai/server/services/transport/daily), [FastAPI Websocket](https://docs.pipecat.ai/server/services/transport/fastapi-websocket), [SmallWebRTCTransport](https://docs.pipecat.ai/server/services/transport/small-webrtc), [WebSocket Server](https://docs.pipecat.ai/server/services/transport/websocket-server), Local |
|
| Transport | [Daily (WebRTC)](https://docs.pipecat.ai/server/services/transport/daily), [FastAPI Websocket](https://docs.pipecat.ai/server/services/transport/fastapi-websocket), [SmallWebRTCTransport](https://docs.pipecat.ai/server/services/transport/small-webrtc), [WebSocket Server](https://docs.pipecat.ai/server/services/transport/websocket-server), Local |
|
||||||
| Serializers | [Plivo](https://docs.pipecat.ai/server/utilities/serializers/plivo), [Twilio](https://docs.pipecat.ai/server/utilities/serializers/twilio), [Telnyx](https://docs.pipecat.ai/server/utilities/serializers/telnyx) |
|
| Video | [Tavus](https://docs.pipecat.ai/server/services/video/tavus), [Simli](https://docs.pipecat.ai/server/services/video/simli) |
|
||||||
| Video | [Tavus](https://docs.pipecat.ai/server/services/video/tavus), [Simli](https://docs.pipecat.ai/server/services/video/simli) |
|
| Memory | [mem0](https://docs.pipecat.ai/server/services/memory/mem0) |
|
||||||
| Memory | [mem0](https://docs.pipecat.ai/server/services/memory/mem0) |
|
| Vision & Image | [fal](https://docs.pipecat.ai/server/services/image-generation/fal), [Google Imagen](https://docs.pipecat.ai/server/services/image-generation/fal), [Moondream](https://docs.pipecat.ai/server/services/vision/moondream) |
|
||||||
| Vision & Image | [fal](https://docs.pipecat.ai/server/services/image-generation/fal), [Google Imagen](https://docs.pipecat.ai/server/services/image-generation/fal), [Moondream](https://docs.pipecat.ai/server/services/vision/moondream) |
|
| Audio Processing | [Silero VAD](https://docs.pipecat.ai/server/utilities/audio/silero-vad-analyzer), [Krisp](https://docs.pipecat.ai/server/utilities/audio/krisp-filter), [Koala](https://docs.pipecat.ai/server/utilities/audio/koala-filter), [Noisereduce](https://docs.pipecat.ai/server/utilities/audio/noisereduce-filter) |
|
||||||
| Audio Processing | [Silero VAD](https://docs.pipecat.ai/server/utilities/audio/silero-vad-analyzer), [Krisp](https://docs.pipecat.ai/server/utilities/audio/krisp-filter), [Koala](https://docs.pipecat.ai/server/utilities/audio/koala-filter), [Noisereduce](https://docs.pipecat.ai/server/utilities/audio/noisereduce-filter) |
|
| Analytics & Metrics | [Sentry](https://docs.pipecat.ai/server/services/analytics/sentry) |
|
||||||
| Analytics & Metrics | [OpenTelemetry](https://docs.pipecat.ai/server/utilities/opentelemetry), [Sentry](https://docs.pipecat.ai/server/services/analytics/sentry) |
|
|
||||||
|
|
||||||
📚 [View full services documentation →](https://docs.pipecat.ai/server/services/supported-services)
|
📚 [View full services documentation →](https://docs.pipecat.ai/server/services/supported-services)
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ coverage~=7.6.12
|
|||||||
grpcio-tools~=1.67.1
|
grpcio-tools~=1.67.1
|
||||||
pip-tools~=7.4.1
|
pip-tools~=7.4.1
|
||||||
pre-commit~=4.0.1
|
pre-commit~=4.0.1
|
||||||
pyright~=1.1.400
|
pyright~=1.1.397
|
||||||
pytest~=8.3.4
|
pytest~=8.3.4
|
||||||
pytest-asyncio~=0.25.3
|
pytest-asyncio~=0.25.3
|
||||||
pytest-aiohttp==1.1.0
|
pytest-aiohttp==1.1.0
|
||||||
ruff~=0.11.13
|
ruff~=0.11.1
|
||||||
setuptools~=70.0.0
|
setuptools~=70.0.0
|
||||||
setuptools_scm~=8.1.0
|
setuptools_scm~=8.1.0
|
||||||
python-dotenv~=1.0.1
|
python-dotenv~=1.0.1
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
@@ -14,8 +13,7 @@ sys.path.insert(0, str(project_root / "src"))
|
|||||||
|
|
||||||
# Project information
|
# Project information
|
||||||
project = "pipecat-ai"
|
project = "pipecat-ai"
|
||||||
current_year = datetime.now().year
|
copyright = "2024, Daily"
|
||||||
copyright = f"2024-{current_year}, Daily" if current_year > 2024 else "2024, Daily"
|
|
||||||
author = "Daily"
|
author = "Daily"
|
||||||
|
|
||||||
# General configuration
|
# General configuration
|
||||||
@@ -29,14 +27,15 @@ extensions = [
|
|||||||
# Napoleon settings
|
# Napoleon settings
|
||||||
napoleon_google_docstring = True
|
napoleon_google_docstring = True
|
||||||
napoleon_numpy_docstring = False
|
napoleon_numpy_docstring = False
|
||||||
napoleon_include_init_with_doc = False
|
napoleon_include_init_with_doc = True
|
||||||
|
|
||||||
# AutoDoc settings
|
# AutoDoc settings
|
||||||
autodoc_default_options = {
|
autodoc_default_options = {
|
||||||
"members": True,
|
"members": True,
|
||||||
"member-order": "bysource",
|
"member-order": "bysource",
|
||||||
|
"special-members": "__init__",
|
||||||
"undoc-members": True,
|
"undoc-members": True,
|
||||||
"exclude-members": "__weakref__,__init__",
|
"exclude-members": "__weakref__",
|
||||||
"no-index": True,
|
"no-index": True,
|
||||||
"show-inheritance": True,
|
"show-inheritance": True,
|
||||||
}
|
}
|
||||||
@@ -146,28 +145,6 @@ autodoc_mock_imports = [
|
|||||||
"transformers.AutoFeatureExtractor",
|
"transformers.AutoFeatureExtractor",
|
||||||
# Also add specific classes that are imported
|
# Also add specific classes that are imported
|
||||||
"AutoFeatureExtractor",
|
"AutoFeatureExtractor",
|
||||||
# Sentry dependencies
|
|
||||||
"sentry_sdk",
|
|
||||||
# AWS Nova Sonic dependencies
|
|
||||||
"aws_sdk_bedrock_runtime",
|
|
||||||
"aws_sdk_bedrock_runtime.client",
|
|
||||||
"aws_sdk_bedrock_runtime.config",
|
|
||||||
"aws_sdk_bedrock_runtime.models",
|
|
||||||
"smithy_aws_core",
|
|
||||||
"smithy_aws_core.credentials_resolvers",
|
|
||||||
"smithy_aws_core.credentials_resolvers.static",
|
|
||||||
"smithy_aws_core.identity",
|
|
||||||
"smithy_core",
|
|
||||||
"smithy_core.aio",
|
|
||||||
"smithy_core.aio.eventstream",
|
|
||||||
# MCP dependencies (you may already have these)
|
|
||||||
"mcp",
|
|
||||||
"mcp.client",
|
|
||||||
"mcp.client.session_group",
|
|
||||||
"mcp.client.sse",
|
|
||||||
"mcp.client.stdio",
|
|
||||||
"mcp.ClientSession",
|
|
||||||
"mcp.StdioServerParameters",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# HTML output settings
|
# HTML output settings
|
||||||
@@ -272,9 +249,6 @@ def clean_title(title: str) -> str:
|
|||||||
"playht": "PlayHT",
|
"playht": "PlayHT",
|
||||||
"xtts": "XTTS",
|
"xtts": "XTTS",
|
||||||
"lmnt": "LMNT",
|
"lmnt": "LMNT",
|
||||||
"stt": "STT",
|
|
||||||
"tts": "TTS",
|
|
||||||
"llm": "LLM",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if the entire title is a special case
|
# Check if the entire title is a special case
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ pipecat-ai[openai]
|
|||||||
pipecat-ai[qwen]
|
pipecat-ai[qwen]
|
||||||
pipecat-ai[remote-smart-turn]
|
pipecat-ai[remote-smart-turn]
|
||||||
# pipecat-ai[riva] # Mocked
|
# pipecat-ai[riva] # Mocked
|
||||||
pipecat-ai[sambanova]
|
|
||||||
pipecat-ai[silero]
|
pipecat-ai[silero]
|
||||||
pipecat-ai[simli]
|
pipecat-ai[simli]
|
||||||
pipecat-ai[soundfile]
|
pipecat-ai[soundfile]
|
||||||
|
|||||||
@@ -95,22 +95,9 @@ OPENROUTER_API_KEY=...
|
|||||||
PIPER_BASE_URL=...
|
PIPER_BASE_URL=...
|
||||||
|
|
||||||
# Smart turn
|
# Smart turn
|
||||||
LOCAL_SMART_TURN_MODEL_PATH=...
|
LOCAL_SMART_TURN_MODEL_PATH=
|
||||||
FAL_SMART_TURN_API_KEY=...
|
FAL_SMART_TURN_API_KEY=...
|
||||||
|
|
||||||
# Twilio
|
# Twilio
|
||||||
TWILIO_ACCOUNT_SID=...
|
TWILIO_ACCOUNT_SID=
|
||||||
TWILIO_AUTH_TOKEN=...
|
TWILIO_AUTH_TOKEN=
|
||||||
|
|
||||||
# MiniMax
|
|
||||||
MINIMAX_API_KEY=...
|
|
||||||
MINIMAX_GROUP_ID=...
|
|
||||||
|
|
||||||
# Sarvam AI
|
|
||||||
SARVAM_API_KEY=...
|
|
||||||
|
|
||||||
# SambaNova
|
|
||||||
SAMBANOVA_API_KEY=...
|
|
||||||
|
|
||||||
# Sentry
|
|
||||||
SENTRY_DSN=...
|
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"@daily-co/daily-js": "0.74.0"
|
"@daily-co/daily-js": "0.74.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^6.3.5"
|
"vite": "^6.0.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
@@ -999,9 +999,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.3.5",
|
"version": "6.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz",
|
||||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
"integrity": "sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^6.3.5"
|
"vite": "^6.0.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@daily-co/daily-js": "0.74.0"
|
"@daily-co/daily-js": "0.74.0"
|
||||||
|
|||||||
@@ -128,15 +128,7 @@ async def main():
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
task = PipelineTask(
|
task = PipelineTask(pipeline, params=PipelineParams(allow_interruptions=True))
|
||||||
pipeline,
|
|
||||||
params=PipelineParams(
|
|
||||||
audio_in_sample_rate=16000,
|
|
||||||
audio_out_sample_rate=16000,
|
|
||||||
enable_metrics=True,
|
|
||||||
enable_usage_metrics=True,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@audiobuffer.event_handler("on_audio_data")
|
@audiobuffer.event_handler("on_audio_data")
|
||||||
async def on_audio_data(buffer, audio, sample_rate, num_channels):
|
async def on_audio_data(buffer, audio, sample_rate, num_channels):
|
||||||
|
|||||||
@@ -71,8 +71,6 @@ async def main():
|
|||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
audio_in_sample_rate=16000,
|
audio_in_sample_rate=16000,
|
||||||
audio_out_sample_rate=16000,
|
audio_out_sample_rate=16000,
|
||||||
enable_metrics=True,
|
|
||||||
enable_usage_metrics=True,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -148,8 +148,10 @@ async def main():
|
|||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
audio_in_sample_rate=16000,
|
audio_in_sample_rate=16000,
|
||||||
audio_out_sample_rate=16000,
|
audio_out_sample_rate=16000,
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
observers=[TranscriptionLogObserver()],
|
observers=[TranscriptionLogObserver()],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -75,13 +75,7 @@ async def main(room_url: str, token: str):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
task = PipelineTask(
|
task = PipelineTask(pipeline, params=PipelineParams(allow_interruptions=True))
|
||||||
pipeline,
|
|
||||||
params=PipelineParams(
|
|
||||||
enable_metrics=True,
|
|
||||||
enable_usage_metrics=True,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@transport.event_handler("on_first_participant_joined")
|
@transport.event_handler("on_first_participant_joined")
|
||||||
async def on_first_participant_joined(transport, participant):
|
async def on_first_participant_joined(transport, participant):
|
||||||
|
|||||||
3
examples/deployment/modal-example/.gitignore
vendored
@@ -1,6 +1,3 @@
|
|||||||
# Modal clone
|
|
||||||
modal-examples
|
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
|||||||
@@ -1,91 +1,37 @@
|
|||||||
# Deploying Pipecat to Modal.com
|
# Deploying Pipecat to Modal.com
|
||||||
|
|
||||||
Deployment example for [modal.com](https://www.modal.com). This example demonstrates how to deploy a FastAPI webapp to Modal with an RTVI compatible `/connect` endpoint that launches a Pipecat pipeline in a separate Modal container and returns a room/token for the client to join. This example also supports providing a parameter to the `/connect` endpoint for specifying which Pipecat pipeline to launch; openai, gemini, or vllm. The vllm pipeline points to a self-hosted OpenAI compatible LLM, using a llama model (neuralmagic/Meta-Llama-3.1-8B-Instruct-quantized.w4a16), deployed to Modal.
|
Barebones deployment example for [modal.com](https://www.modal.com)
|
||||||
|
|
||||||

|
1. Install dependencies
|
||||||
|
|
||||||
# Running this Example
|
```bash
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/active # or OS equivalent
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
## Install the Modal CLI
|
2. Setup .env
|
||||||
|
|
||||||
Setup a Modal account and install it on your machine if you have not already, following their easy 3 steps in their [Getting Started Guide](https://modal.com/docs/guide#getting-started)
|
```bash
|
||||||
|
cp env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
## Deploy a self-serve LLM
|
Alternatively, you can configure your Modal app to use [secrets](https://modal.com/docs/guide/secrets)
|
||||||
|
|
||||||
1. Deploy Modal's OpenAI-compatible LLM service:
|
3. Test the app locally
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/modal-labs/modal-examples
|
modal serve app.py
|
||||||
cd modal-examples
|
```
|
||||||
modal deploy 06_gpu_and_ml/llm-serving/vllm_inference.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Refer to Modal's guide and example for [Deploying an OpenAI-compatible LLM service with vLLM](https://modal.com/docs/examples/vllm_inference) for more details.
|
|
||||||
|
|
||||||
2. Take note of the endpoint URL from the previous step, which will look like:
|
|
||||||
```
|
|
||||||
https://{your-workspace}--example-vllm-openai-compatible-serve.modal.run
|
|
||||||
```
|
|
||||||
You'll need this for the `bot_vllm.py` file in the next section.
|
|
||||||
|
|
||||||
**Note:** The default Modal LLM example uses Llama-3.1 and will shut down after 15 minutes of inactivity. Cold starts take 5-10 minutes. To prepare the service, we recommend visiting the `/docs` endpoint (`https://<Modal workspace>--example-vllm-openai-compatible-serve.modal.run/docs`) for your deployed LLM and wait for it to fully load before connecting your client.
|
|
||||||
|
|
||||||
## Deploy FastAPI App and Pipecat pipeline to Modal
|
|
||||||
|
|
||||||
1. Setup environment variables
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd server
|
|
||||||
cp env.example .env
|
|
||||||
# Modify .env to provide your service API Keys
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can configure your Modal app to use [secrets](https://modal.com/docs/guide/secrets)
|
|
||||||
|
|
||||||
2. Update the `modal_url` in `server/src/bot_vllm.py` to point to the url produced from the self-serve llm deploy, mentioned above.
|
|
||||||
|
|
||||||
3. From within the `server` directory, test the app locally:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
modal serve app.py
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Deploy to production
|
4. Deploy to production
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
modal deploy app.py
|
modal deploy app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Note the endpoint URL produced from this deployment. It will look like:
|
## Configuration options
|
||||||
|
|
||||||
```bash
|
This app sets some sensible defaults for reducing cold starts, such as `minkeep_warm=1`, which will keep at least 1 warm instance ready for your bot function.
|
||||||
https://{your-workspace}--pipecat-modal-fastapi-app.modal.run
|
|
||||||
```
|
|
||||||
|
|
||||||
You'll need this URL for the client's `app.js` configuration mentioned in its README.
|
It has been configured to only allow a concurrency of 1 (`max_inputs=1`) as each user will require their own running function.
|
||||||
|
|
||||||
## Launch your bots on Modal
|
|
||||||
|
|
||||||
### Option 1: Direct Link
|
|
||||||
|
|
||||||
Simply click on the url displayed after running the server or deploy step to launch an agent and be redirected to a Daily room to talk with the launched bot. This will use the OpenAI pipeline.
|
|
||||||
|
|
||||||
### Option 2: Connect via an RTVI Client
|
|
||||||
|
|
||||||
Follow the instructions provided in the [client folder's README](client/javascript/README.md) for building and running a custom client that connects to your Modal endpoint. The provided client provides a dropdown for choosing which bot pipeline to run.
|
|
||||||
|
|
||||||
# Navigating your llm, server, and Pipecat logs
|
|
||||||
|
|
||||||
In your [Modal dashboard](https://modal.com/apps), you should have two Apps listed under Live Apps:
|
|
||||||
|
|
||||||
1. `example-vllm-openai-compatible`: This App contains the containers and logs used to run your self-hosted LLM. There will be just one App Function listed: `serve`. Click on this function to view logs for your LLM.
|
|
||||||
2. `pipecat-modal`: This App contains the containers and logs used to run your `connect` endpoints and Pipecat pipelines. It will list two App Functions:
|
|
||||||
1. `fastapi_app`: This function is running the endpoints that your client will interact with and initiate starting a new pipeline (`/`, `/connect`, `/status`). Click on this function to see logs for each endpoint hit.
|
|
||||||
2. `bot_runner`: This function handles launching and running a bot pipeline. Click on this function to get a list of all pipeline runs and access each run's logs.
|
|
||||||
|
|
||||||
# Modal + Pipecat Tips
|
|
||||||
|
|
||||||
- In most other Pipecat examples, we use `Popen` to launch the pipeline process from the `/connect` endpoint. In this example, we use a Modal function instead. This allows us to run the pipelines using a separately defined Modal image as well as run each pipeline in an isolated container.
|
|
||||||
- For the FastAPI and most common Pipecat Pipeline containers, a default `debian_slim` CPU-only should be all that's required to run. GPU containers are needed for self-hosted services.
|
|
||||||
- To minimize cold starts of the pipeline and reduce latency for users, set `min_containers=1` on the Modal Function that launches the pipeline to ensure at least one warm instance of your function is always available.
|
|
||||||
- For next steps on running a self-hosted llm and reducing latency, check out all of [Modal's LLM examples](https://modal.com/docs/examples/vllm_inference).
|
|
||||||
80
examples/deployment/modal-example/app.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import modal
|
||||||
|
from bot import _voice_bot_process
|
||||||
|
from fastapi import HTTPException
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
MAX_SESSION_TIME = 15 * 60 # 15 minutes
|
||||||
|
|
||||||
|
app = modal.App("pipecat-modal")
|
||||||
|
|
||||||
|
|
||||||
|
image = modal.Image.debian_slim(python_version="3.12").pip_install_from_requirements(
|
||||||
|
"requirements.txt"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.function(
|
||||||
|
image=image,
|
||||||
|
cpu=1.0,
|
||||||
|
secrets=[modal.Secret.from_dotenv()],
|
||||||
|
keep_warm=1,
|
||||||
|
enable_memory_snapshot=True,
|
||||||
|
max_inputs=1, # Do not reuse instances across requests
|
||||||
|
retries=0,
|
||||||
|
)
|
||||||
|
def launch_bot_process(room_url: str, token: str):
|
||||||
|
_voice_bot_process(room_url, token)
|
||||||
|
|
||||||
|
|
||||||
|
@app.function(
|
||||||
|
image=image,
|
||||||
|
secrets=[modal.Secret.from_dotenv()],
|
||||||
|
)
|
||||||
|
@modal.web_endpoint(method="POST")
|
||||||
|
async def start():
|
||||||
|
from pipecat.transports.services.helpers.daily_rest import (
|
||||||
|
DailyRESTHelper,
|
||||||
|
DailyRoomParams,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("Request received")
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
daily_rest_helper = DailyRESTHelper(
|
||||||
|
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
||||||
|
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||||
|
aiohttp_session=session,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create new Daily room
|
||||||
|
room = await daily_rest_helper.create_room(DailyRoomParams())
|
||||||
|
if not room.url:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail="Unable to create room",
|
||||||
|
)
|
||||||
|
logger.info(f"Created room: {room.url}")
|
||||||
|
|
||||||
|
# Create bot token for room
|
||||||
|
token = await daily_rest_helper.get_token(room.url, MAX_SESSION_TIME)
|
||||||
|
if not token:
|
||||||
|
raise HTTPException(status_code=500, detail=f"Failed to get token for room: {room.url}")
|
||||||
|
|
||||||
|
logger.info(f"Bot token created: {token}")
|
||||||
|
|
||||||
|
# Spawn a new bot process
|
||||||
|
launch_bot_process.spawn(room_url=room.url, token=token)
|
||||||
|
|
||||||
|
# Return room URL to the user to join
|
||||||
|
# Note: in production, you would want to return a token to the user
|
||||||
|
return JSONResponse(content={"room_url": room.url, token: token})
|
||||||
95
examples/deployment/modal-example/bot.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
||||||
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
|
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
||||||
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||||
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
|
|
||||||
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
logger.remove(0)
|
||||||
|
logger.add(sys.stderr, level="DEBUG")
|
||||||
|
|
||||||
|
|
||||||
|
async def main(room_url: str, token: str):
|
||||||
|
transport = DailyTransport(
|
||||||
|
room_url,
|
||||||
|
token,
|
||||||
|
"bot",
|
||||||
|
DailyParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
transcription_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
tts = CartesiaTTSService(
|
||||||
|
api_key=os.getenv("CARTESIA_API_KEY", ""), voice_id="71a7ad14-091c-4e8e-a314-022ece01c121"
|
||||||
|
)
|
||||||
|
|
||||||
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"))
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "You are a helpful LLM in a WebRTC call. Your goal is to demonstrate your capabilities in a succinct way. Your output will be converted to audio so don't include special characters in your answers. Respond to what the user said in a creative and helpful way.",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
context = OpenAILLMContext(messages)
|
||||||
|
context_aggregator = llm.create_context_aggregator(context)
|
||||||
|
|
||||||
|
pipeline = Pipeline(
|
||||||
|
[
|
||||||
|
transport.input(),
|
||||||
|
context_aggregator.user(),
|
||||||
|
llm,
|
||||||
|
tts,
|
||||||
|
transport.output(),
|
||||||
|
context_aggregator.assistant(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
task = PipelineTask(
|
||||||
|
pipeline,
|
||||||
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
|
enable_metrics=True,
|
||||||
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@transport.event_handler("on_first_participant_joined")
|
||||||
|
async def on_first_participant_joined(transport, participant):
|
||||||
|
await transport.capture_participant_transcription(participant["id"])
|
||||||
|
messages.append({"role": "system", "content": "Please introduce yourself to the user."})
|
||||||
|
await task.queue_frames([context_aggregator.user().get_context_frame()])
|
||||||
|
|
||||||
|
@transport.event_handler("on_participant_left")
|
||||||
|
async def on_participant_left(transport, participant, reason):
|
||||||
|
await task.cancel()
|
||||||
|
|
||||||
|
runner = PipelineRunner()
|
||||||
|
|
||||||
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
|
def _voice_bot_process(room_url: str, token: str):
|
||||||
|
asyncio.run(main(room_url, token))
|
||||||
@@ -1 +0,0 @@
|
|||||||
node_modules
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# JavaScript Implementation
|
|
||||||
|
|
||||||
Basic implementation using the [Pipecat JavaScript SDK](https://docs.pipecat.ai/client/js/introduction).
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
1. Deploy the Modal server. See the main [README](../../README).
|
|
||||||
|
|
||||||
2. Navigate to the `client/javascript` directory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd client/javascript
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Modify the baseUrl in src/app.js to point to your deployed Modal endpoint
|
|
||||||
|
|
||||||
4. Install dependencies:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Run the client app:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
6. Visit http://localhost:5173 in your browser.
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>AI Chatbot</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="status-bar">
|
|
||||||
<div class="status">
|
|
||||||
Status: <span id="connection-status">Disconnected</span>
|
|
||||||
</div>
|
|
||||||
<div class="controls">
|
|
||||||
<select id="bot-selector">
|
|
||||||
<option value="openai">OpenAI</option>
|
|
||||||
<option value="gemini">Gemini</option>
|
|
||||||
<option value="vllm">Llama</option>
|
|
||||||
</select>
|
|
||||||
<button id="connect-btn">Connect</button>
|
|
||||||
<button id="disconnect-btn" disabled>Disconnect</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="main-content">
|
|
||||||
<div class="bot-container">
|
|
||||||
<div id="bot-video-container"></div>
|
|
||||||
<audio id="bot-audio" autoplay></audio>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="device-bar">
|
|
||||||
<div class="device-controls">
|
|
||||||
<select id="device-selector"></select>
|
|
||||||
<button id="mic-toggle-btn">Mute Mic</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="debug-panel">
|
|
||||||
<h3>Debug Info</h3>
|
|
||||||
<div id="debug-log"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="module" src="/src/app.js"></script>
|
|
||||||
<link rel="stylesheet" href="/src/style.css" />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "client",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "vite build",
|
|
||||||
"preview": "vite preview"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"description": "",
|
|
||||||
"devDependencies": {
|
|
||||||
"vite": "^6.3.5"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@pipecat-ai/client-js": "^0.3.5",
|
|
||||||
"@pipecat-ai/daily-transport": "^0.3.10"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,381 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2024–2025, Daily
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD 2-Clause License
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RTVI Client Implementation
|
|
||||||
*
|
|
||||||
* This client connects to an RTVI-compatible bot server using WebRTC (via Daily).
|
|
||||||
* It handles audio/video streaming and manages the connection lifecycle.
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
* - A running RTVI bot server (defaults to http://localhost:7860)
|
|
||||||
* - The server must implement the /connect endpoint that returns Daily.co room credentials
|
|
||||||
* - Browser with WebRTC support
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { RTVIClient, RTVIEvent } from '@pipecat-ai/client-js';
|
|
||||||
import { DailyTransport } from '@pipecat-ai/daily-transport';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ChatbotClient handles the connection and media management for a real-time
|
|
||||||
* voice and video interaction with an AI bot.
|
|
||||||
*/
|
|
||||||
class ChatbotClient {
|
|
||||||
constructor() {
|
|
||||||
// Initialize client state
|
|
||||||
this.rtviClient = null;
|
|
||||||
this.setupDOMElements();
|
|
||||||
this.initializeClientAndTransport();
|
|
||||||
this.setupEventListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up references to DOM elements and create necessary media elements
|
|
||||||
*/
|
|
||||||
setupDOMElements() {
|
|
||||||
// Get references to UI control elements
|
|
||||||
this.connectBtn = document.getElementById('connect-btn');
|
|
||||||
this.disconnectBtn = document.getElementById('disconnect-btn');
|
|
||||||
this.statusSpan = document.getElementById('connection-status');
|
|
||||||
this.debugLog = document.getElementById('debug-log');
|
|
||||||
this.botVideoContainer = document.getElementById('bot-video-container');
|
|
||||||
this.deviceSelector = document.getElementById('device-selector');
|
|
||||||
|
|
||||||
// Create an audio element for bot's voice output
|
|
||||||
this.botAudio = document.createElement('audio');
|
|
||||||
this.botAudio.autoplay = true;
|
|
||||||
this.botAudio.playsInline = true;
|
|
||||||
document.body.appendChild(this.botAudio);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up event listeners for connect/disconnect buttons
|
|
||||||
*/
|
|
||||||
setupEventListeners() {
|
|
||||||
this.connectBtn.addEventListener('click', () => this.connect());
|
|
||||||
this.disconnectBtn.addEventListener('click', () => this.disconnect());
|
|
||||||
|
|
||||||
// Populate device selector
|
|
||||||
this.rtviClient.getAllMics().then((mics) => {
|
|
||||||
console.log('Available mics:', mics);
|
|
||||||
mics.forEach((device) => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = device.deviceId;
|
|
||||||
option.textContent = device.label || `Microphone ${device.deviceId}`;
|
|
||||||
this.deviceSelector.appendChild(option);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.deviceSelector.addEventListener('change', (event) => {
|
|
||||||
const selectedDeviceId = event.target.value;
|
|
||||||
console.log('Selected device ID:', selectedDeviceId);
|
|
||||||
this.rtviClient.updateMic(selectedDeviceId);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle mic mute/unmute toggle
|
|
||||||
const micToggleBtn = document.getElementById('mic-toggle-btn');
|
|
||||||
|
|
||||||
micToggleBtn.addEventListener('click', () => {
|
|
||||||
let micEnabled = this.rtviClient.isMicEnabled;
|
|
||||||
micToggleBtn.textContent = micEnabled ? 'Unmute Mic' : 'Mute Mic';
|
|
||||||
this.rtviClient.enableMic(!micEnabled);
|
|
||||||
// Add logic to mute/unmute the mic
|
|
||||||
if (micEnabled) {
|
|
||||||
console.log('Mic muted');
|
|
||||||
// Add code to mute the mic
|
|
||||||
} else {
|
|
||||||
console.log('Mic unmuted');
|
|
||||||
// Add code to unmute the mic
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the RTVI client and Daily transport
|
|
||||||
*/
|
|
||||||
async initializeClientAndTransport() {
|
|
||||||
// Initialize the RTVI client with a DailyTransport and our configuration
|
|
||||||
this.rtviClient = new RTVIClient({
|
|
||||||
transport: new DailyTransport(),
|
|
||||||
params: {
|
|
||||||
// REPLACE WITH YOUR MODAL URL ENDPOINT
|
|
||||||
baseUrl:
|
|
||||||
'https://<Modal workspace>--pipecat-modal-bot-launcher.modal.run',
|
|
||||||
endpoints: {
|
|
||||||
connect: '/connect',
|
|
||||||
},
|
|
||||||
requestData: {
|
|
||||||
bot_name: 'openai',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
enableMic: true, // Enable microphone for user input
|
|
||||||
enableCam: false,
|
|
||||||
callbacks: {
|
|
||||||
// Handle connection state changes
|
|
||||||
onConnected: () => {
|
|
||||||
this.updateStatus('Connected');
|
|
||||||
this.connectBtn.disabled = true;
|
|
||||||
this.disconnectBtn.disabled = false;
|
|
||||||
this.log('Client connected');
|
|
||||||
},
|
|
||||||
onDisconnected: () => {
|
|
||||||
this.updateStatus('Disconnected');
|
|
||||||
this.connectBtn.disabled = false;
|
|
||||||
this.disconnectBtn.disabled = true;
|
|
||||||
this.log('Client disconnected');
|
|
||||||
},
|
|
||||||
// Handle transport state changes
|
|
||||||
onTransportStateChanged: (state) => {
|
|
||||||
this.updateStatus(`Transport: ${state}`);
|
|
||||||
this.log(`Transport state changed: ${state}`);
|
|
||||||
if (state === 'connecting') {
|
|
||||||
window.startTime = Date.now();
|
|
||||||
}
|
|
||||||
if (state === 'ready') {
|
|
||||||
this.setupMediaTracks();
|
|
||||||
console.warn('TIME TO BOT READY:', Date.now() - window.startTime);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Handle bot connection events
|
|
||||||
onBotConnected: (participant) => {
|
|
||||||
this.log(`Bot connected: ${JSON.stringify(participant)}`);
|
|
||||||
},
|
|
||||||
onBotDisconnected: (participant) => {
|
|
||||||
this.log(`Bot disconnected: ${JSON.stringify(participant)}`);
|
|
||||||
},
|
|
||||||
onBotReady: (data) => {
|
|
||||||
this.log(`Bot ready: ${JSON.stringify(data)}`);
|
|
||||||
this.setupMediaTracks();
|
|
||||||
},
|
|
||||||
// Transcript events
|
|
||||||
onUserTranscript: (data) => {
|
|
||||||
// Only log final transcripts
|
|
||||||
if (data.final) {
|
|
||||||
this.log(`User: ${data.text}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onBotTranscript: (data) => {
|
|
||||||
this.log(`Bot: ${data.text}`);
|
|
||||||
},
|
|
||||||
// Error handling
|
|
||||||
onMessageError: (error) => {
|
|
||||||
console.log('Message error:', error);
|
|
||||||
},
|
|
||||||
onMicUpdated: (data) => {
|
|
||||||
console.log('Mic updated:', data);
|
|
||||||
this.deviceSelector.value = data.deviceId;
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
console.log('Error:', JSON.stringify(error));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set up listeners for media track events
|
|
||||||
this.setupTrackListeners();
|
|
||||||
|
|
||||||
await this.rtviClient.initDevices();
|
|
||||||
window.client = this.rtviClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a timestamped message to the debug log
|
|
||||||
*/
|
|
||||||
log(message) {
|
|
||||||
const entry = document.createElement('div');
|
|
||||||
entry.textContent = `${new Date().toISOString()} - ${message}`;
|
|
||||||
|
|
||||||
// Add styling based on message type
|
|
||||||
if (message.startsWith('User: ')) {
|
|
||||||
entry.style.color = '#2196F3'; // blue for user
|
|
||||||
} else if (message.startsWith('Bot: ')) {
|
|
||||||
entry.style.color = '#4CAF50'; // green for bot
|
|
||||||
}
|
|
||||||
|
|
||||||
this.debugLog.appendChild(entry);
|
|
||||||
this.debugLog.scrollTop = this.debugLog.scrollHeight;
|
|
||||||
console.log(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the connection status display
|
|
||||||
*/
|
|
||||||
updateStatus(status) {
|
|
||||||
this.statusSpan.textContent = status;
|
|
||||||
this.log(`Status: ${status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for available media tracks and set them up if present
|
|
||||||
* This is called when the bot is ready or when the transport state changes to ready
|
|
||||||
*/
|
|
||||||
setupMediaTracks() {
|
|
||||||
if (!this.rtviClient) return;
|
|
||||||
|
|
||||||
// Get current tracks from the client
|
|
||||||
const tracks = this.rtviClient.tracks();
|
|
||||||
|
|
||||||
// Set up any available bot tracks
|
|
||||||
if (tracks.bot?.audio) {
|
|
||||||
this.setupAudioTrack(tracks.bot.audio);
|
|
||||||
}
|
|
||||||
if (tracks.bot?.video) {
|
|
||||||
this.setupVideoTrack(tracks.bot.video);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up listeners for track events (start/stop)
|
|
||||||
* This handles new tracks being added during the session
|
|
||||||
*/
|
|
||||||
setupTrackListeners() {
|
|
||||||
if (!this.rtviClient) return;
|
|
||||||
|
|
||||||
// Listen for new tracks starting
|
|
||||||
this.rtviClient.on(RTVIEvent.TrackStarted, (track, participant) => {
|
|
||||||
// Only handle non-local (bot) tracks
|
|
||||||
if (!participant?.local) {
|
|
||||||
if (track.kind === 'audio') {
|
|
||||||
this.setupAudioTrack(track);
|
|
||||||
} else if (track.kind === 'video') {
|
|
||||||
this.setupVideoTrack(track);
|
|
||||||
}
|
|
||||||
this.log(
|
|
||||||
`Track started event: ${track.kind} from ${
|
|
||||||
participant?.name || 'unknown'
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.log('Local mic unmuted');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen for tracks stopping
|
|
||||||
this.rtviClient.on(RTVIEvent.TrackStopped, (track, participant) => {
|
|
||||||
if (participant.local) {
|
|
||||||
this.log('Local mic muted');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.log(
|
|
||||||
`Track stopped event: ${track.kind} from ${
|
|
||||||
participant?.name || 'unknown'
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up an audio track for playback
|
|
||||||
* Handles both initial setup and track updates
|
|
||||||
*/
|
|
||||||
setupAudioTrack(track) {
|
|
||||||
this.log('Setting up audio track');
|
|
||||||
// Check if we're already playing this track
|
|
||||||
if (this.botAudio.srcObject) {
|
|
||||||
const oldTrack = this.botAudio.srcObject.getAudioTracks()[0];
|
|
||||||
if (oldTrack?.id === track.id) return;
|
|
||||||
}
|
|
||||||
// Create a new MediaStream with the track and set it as the audio source
|
|
||||||
this.botAudio.srcObject = new MediaStream([track]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up a video track for display
|
|
||||||
* Handles both initial setup and track updates
|
|
||||||
*/
|
|
||||||
setupVideoTrack(track) {
|
|
||||||
this.log('Setting up video track');
|
|
||||||
const videoEl = document.createElement('video');
|
|
||||||
videoEl.autoplay = true;
|
|
||||||
videoEl.playsInline = true;
|
|
||||||
videoEl.muted = true;
|
|
||||||
videoEl.style.width = '100%';
|
|
||||||
videoEl.style.height = '100%';
|
|
||||||
videoEl.style.objectFit = 'cover';
|
|
||||||
|
|
||||||
// Check if we're already displaying this track
|
|
||||||
if (this.botVideoContainer.querySelector('video')?.srcObject) {
|
|
||||||
const oldTrack = this.botVideoContainer
|
|
||||||
.querySelector('video')
|
|
||||||
.srcObject.getVideoTracks()[0];
|
|
||||||
if (oldTrack?.id === track.id) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new MediaStream with the track and set it as the video source
|
|
||||||
videoEl.srcObject = new MediaStream([track]);
|
|
||||||
this.botVideoContainer.innerHTML = '';
|
|
||||||
this.botVideoContainer.appendChild(videoEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize and connect to the bot
|
|
||||||
* This sets up the RTVI client, initializes devices, and establishes the connection
|
|
||||||
*/
|
|
||||||
async connect() {
|
|
||||||
try {
|
|
||||||
const botSelector = document.getElementById('bot-selector');
|
|
||||||
const selectedBot = botSelector.value;
|
|
||||||
this.rtviClient.params.requestData.bot_name = selectedBot;
|
|
||||||
|
|
||||||
// Initialize audio/video devices
|
|
||||||
this.log('Initializing devices...');
|
|
||||||
await this.rtviClient.initDevices();
|
|
||||||
|
|
||||||
// Connect to the bot
|
|
||||||
this.log(`Connecting to bot: ${selectedBot}`);
|
|
||||||
await this.rtviClient.connect();
|
|
||||||
|
|
||||||
this.log('Connection complete');
|
|
||||||
} catch (error) {
|
|
||||||
// Handle any errors during connection
|
|
||||||
console.error('Connection error:', error);
|
|
||||||
this.log(`Error connecting: ${JSON.stringify(error.message)}`);
|
|
||||||
this.log(`Error stack: ${error.stack}`);
|
|
||||||
this.updateStatus('Error');
|
|
||||||
|
|
||||||
// Clean up if there's an error
|
|
||||||
if (this.rtviClient) {
|
|
||||||
try {
|
|
||||||
await this.rtviClient.disconnect();
|
|
||||||
} catch (disconnectError) {
|
|
||||||
this.log(`Error during disconnect: ${disconnectError.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnect from the bot and clean up media resources
|
|
||||||
*/
|
|
||||||
async disconnect() {
|
|
||||||
if (this.rtviClient) {
|
|
||||||
try {
|
|
||||||
// Disconnect the RTVI client
|
|
||||||
await this.rtviClient.disconnect();
|
|
||||||
|
|
||||||
// Clean up audio
|
|
||||||
if (this.botAudio.srcObject) {
|
|
||||||
this.botAudio.srcObject.getTracks().forEach((track) => track.stop());
|
|
||||||
this.botAudio.srcObject = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up video
|
|
||||||
if (this.botVideoContainer.querySelector('video')?.srcObject) {
|
|
||||||
const video = this.botVideoContainer.querySelector('video');
|
|
||||||
video.srcObject.getTracks().forEach((track) => track.stop());
|
|
||||||
video.srcObject = null;
|
|
||||||
}
|
|
||||||
this.botVideoContainer.innerHTML = '';
|
|
||||||
} catch (error) {
|
|
||||||
this.log(`Error disconnecting: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the client when the page loads
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
|
||||||
new ChatbotClient();
|
|
||||||
});
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-bar,
|
|
||||||
.device-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls,
|
|
||||||
.device-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px; /* Adds spacing between elements */
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-controls {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls button,
|
|
||||||
.device-controls button {
|
|
||||||
padding: 8px 16px;
|
|
||||||
margin-left: 10px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#bot-selector,
|
|
||||||
#device-selector {
|
|
||||||
padding: 8px 16px;
|
|
||||||
padding-right: 40px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #6c757d; /* Gray background */
|
|
||||||
color: white; /* White text */
|
|
||||||
cursor: pointer;
|
|
||||||
appearance: none; /* Removes default browser styling for dropdowns */
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E"); /* Custom arrow */
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: right 8px center; /* Position the arrow */
|
|
||||||
}
|
|
||||||
|
|
||||||
#bot-selector:focus,
|
|
||||||
#device-selector:focus {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.3); /* Add a subtle focus effect */
|
|
||||||
}
|
|
||||||
|
|
||||||
#connect-btn {
|
|
||||||
background-color: #4caf50;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#disconnect-btn {
|
|
||||||
background-color: #f44336;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mic-toggle-btn {
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bot-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#bot-video-container {
|
|
||||||
width: 640px;
|
|
||||||
height: 360px;
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin: 20px auto;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#bot-video-container video {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-panel {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-panel h3 {
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#debug-log {
|
|
||||||
height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 114 KiB |
3
examples/deployment/modal-example/env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
DAILY_API_KEY=
|
||||||
|
OPENAI_API_KEY=
|
||||||
|
CARTESIA_API_KEY=
|
||||||
4
examples/deployment/modal-example/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
python-dotenv==1.0.1
|
||||||
|
modal==0.71.3
|
||||||
|
pipecat-ai[daily,silero,cartesia,openai]
|
||||||
|
fastapi==0.115.6
|
||||||
@@ -1,307 +0,0 @@
|
|||||||
"""modal_example.
|
|
||||||
|
|
||||||
This module shows a simple example of how to deploy a bot using Modal and FastAPI.
|
|
||||||
|
|
||||||
It includes:
|
|
||||||
- FastAPI endpoints for starting agents and checking bot statuses.
|
|
||||||
- Dynamic loading of bot implementations.
|
|
||||||
- Use of a Daily transport for bot communication.
|
|
||||||
"""
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright (c) 2024–2025, Daily
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
|
||||||
#
|
|
||||||
|
|
||||||
import importlib
|
|
||||||
import os
|
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
from typing import Any, Dict, Literal
|
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import modal
|
|
||||||
from fastapi import APIRouter, FastAPI, HTTPException
|
|
||||||
from fastapi.responses import JSONResponse, RedirectResponse
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
# container specifications for the FastAPI web server
|
|
||||||
web_image = (
|
|
||||||
modal.Image.debian_slim(python_version="3.13")
|
|
||||||
.pip_install_from_requirements("requirements.txt")
|
|
||||||
.pip_install("pipecat-ai[daily]")
|
|
||||||
.add_local_dir("src", remote_path="/root/src")
|
|
||||||
)
|
|
||||||
|
|
||||||
# container specifications for the Pipecat pipeline
|
|
||||||
bot_image = (
|
|
||||||
modal.Image.debian_slim(python_version="3.13")
|
|
||||||
.apt_install("ffmpeg")
|
|
||||||
.pip_install_from_requirements("requirements.txt")
|
|
||||||
.pip_install("pipecat-ai[daily,elevenlabs,openai,silero,google]")
|
|
||||||
.add_local_dir("src", remote_path="/root/src")
|
|
||||||
)
|
|
||||||
|
|
||||||
app = modal.App("pipecat-modal", secrets=[modal.Secret.from_dotenv()])
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
bot_jobs = {}
|
|
||||||
daily_helpers = {}
|
|
||||||
|
|
||||||
# Names of all supported bot implementations
|
|
||||||
# These correspond to the bot files in the src directory
|
|
||||||
BotName = Literal["openai", "gemini", "vllm"]
|
|
||||||
|
|
||||||
|
|
||||||
def cleanup():
|
|
||||||
"""Cleanup function to terminate all bot processes.
|
|
||||||
|
|
||||||
Called during server shutdown.
|
|
||||||
"""
|
|
||||||
for entry in bot_jobs.values():
|
|
||||||
func = modal.FunctionCall.from_id(entry[0])
|
|
||||||
if func:
|
|
||||||
func.cancel()
|
|
||||||
|
|
||||||
|
|
||||||
def get_bot_file(bot_name: BotName) -> str:
|
|
||||||
"""Retrieve the bot file name corresponding to the provided bot_name.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
bot_name (BotName): The name of the bot (e.g., 'openai', 'gemini', 'vllm').
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: The file name corresponding to the bot implementation.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If the bot name is invalid or not supported.
|
|
||||||
"""
|
|
||||||
# bot_implementation = os.getenv("BOT_IMPLEMENTATION", "openai").lower().strip()
|
|
||||||
bot_implementation = bot_name.lower().strip()
|
|
||||||
if not bot_implementation:
|
|
||||||
bot_implementation = "openai"
|
|
||||||
if bot_implementation not in ["openai", "gemini", "vllm"]:
|
|
||||||
raise ValueError(
|
|
||||||
f"Invalid BOT_IMPLEMENTATION: {bot_implementation}. Must be 'openai' or 'gemini' or 'vllm'"
|
|
||||||
)
|
|
||||||
|
|
||||||
return f"bot_{bot_implementation}"
|
|
||||||
|
|
||||||
|
|
||||||
def get_runner(path: str, bot_file: str) -> callable:
|
|
||||||
"""Dynamically import the run_bot function based on the bot name.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path (str): The path to the bot files (e.g., 'src').
|
|
||||||
bot_file (str): The file name of the bot implementation (e.g., 'openai', 'gemini', 'vllm').
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
function: The run_bot function from the specified bot module.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ImportError: If the specified bot module or run_bot function is not found.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Dynamically construct the module name
|
|
||||||
module_name = f"{path}.{bot_file}"
|
|
||||||
# Import the module
|
|
||||||
module = importlib.import_module(module_name)
|
|
||||||
# Get the run_bot function from the module
|
|
||||||
return getattr(module, "run_bot")
|
|
||||||
except (ImportError, AttributeError) as e:
|
|
||||||
raise ImportError(f"Failed to import run_bot from {module_name}: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
async def create_room_and_token() -> tuple[str, str]:
|
|
||||||
"""Create a Daily room and generate an authentication token.
|
|
||||||
|
|
||||||
This function checks for existing room URL and token in the environment variables.
|
|
||||||
If not found, it creates a new room using the Daily API and generates a token for it.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
tuple[str, str]: A tuple containing the room URL and the authentication token.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPException: If room creation or token generation fails.
|
|
||||||
"""
|
|
||||||
from pipecat.transports.services.helpers.daily_rest import DailyRoomParams
|
|
||||||
|
|
||||||
room_url = os.getenv("DAILY_SAMPLE_ROOM_URL", None)
|
|
||||||
token = os.getenv("DAILY_SAMPLE_ROOM_TOKEN", None)
|
|
||||||
if not room_url:
|
|
||||||
room = await daily_helpers["rest"].create_room(DailyRoomParams())
|
|
||||||
if not room.url:
|
|
||||||
raise HTTPException(status_code=500, detail="Failed to create room")
|
|
||||||
room_url = room.url
|
|
||||||
|
|
||||||
token = await daily_helpers["rest"].get_token(room_url)
|
|
||||||
if not token:
|
|
||||||
raise HTTPException(status_code=500, detail=f"Failed to get token for room: {room_url}")
|
|
||||||
|
|
||||||
return room_url, token
|
|
||||||
|
|
||||||
|
|
||||||
@app.function(image=bot_image, min_containers=1)
|
|
||||||
async def bot_runner(room_url, token, bot_name: BotName = "openai"):
|
|
||||||
"""Launch the provided bot process, providing the given room URL and token for the bot to join.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
room_url (str): The URL of the Daily room where the bot and client will communicate.
|
|
||||||
token (str): The authentication token for the room.
|
|
||||||
bot_name (BotName): The name of the bot implementation to use. Defaults to "openai".
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPException: If the bot pipeline fails to start.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
path = "src"
|
|
||||||
bot_file = get_bot_file(bot_name)
|
|
||||||
run_bot = get_runner(path, bot_file)
|
|
||||||
|
|
||||||
print(f"Starting bot process: {bot_file} -u {room_url} -t {token}")
|
|
||||||
await run_bot(room_url, token)
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=500, detail=f"Failed to start bot pipeline: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def lifespan(app: FastAPI):
|
|
||||||
"""FastAPI lifespan manager that handles startup and shutdown tasks.
|
|
||||||
|
|
||||||
- Creates aiohttp session
|
|
||||||
- Initializes Daily API helper
|
|
||||||
- Cleans up resources on shutdown
|
|
||||||
"""
|
|
||||||
from pipecat.transports.services.helpers.daily_rest import DailyRESTHelper
|
|
||||||
|
|
||||||
aiohttp_session = aiohttp.ClientSession()
|
|
||||||
daily_helpers["rest"] = DailyRESTHelper(
|
|
||||||
daily_api_key=os.getenv("DAILY_API_KEY", ""),
|
|
||||||
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
|
||||||
aiohttp_session=aiohttp_session,
|
|
||||||
)
|
|
||||||
yield
|
|
||||||
await aiohttp_session.close()
|
|
||||||
cleanup()
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectData(BaseModel):
|
|
||||||
"""Data provided by client to specify the bot pipeline.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
bot_name (BotName): The name of the bot to connect to. Defaults to "openai".
|
|
||||||
"""
|
|
||||||
|
|
||||||
bot_name: BotName = "openai"
|
|
||||||
|
|
||||||
|
|
||||||
async def start(data: ConnectData):
|
|
||||||
"""Internal method to start a bot agent and return the room URL and token.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data (ConnectData): The data containing the bot name to use.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
tuple[str, str]: A tuple containing the room URL and token.
|
|
||||||
"""
|
|
||||||
room_url, token = await create_room_and_token()
|
|
||||||
launch_bot_func = modal.Function.from_name("pipecat-modal", "bot_runner")
|
|
||||||
function_id = launch_bot_func.spawn(room_url, token, data.bot_name)
|
|
||||||
bot_jobs[function_id] = (function_id, room_url)
|
|
||||||
|
|
||||||
return room_url, token
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
|
||||||
async def start_agent():
|
|
||||||
"""A user endpoint for launching a bot agent and redirecting to the created room URL.
|
|
||||||
|
|
||||||
This function retrieves the bot implementation from the environment,
|
|
||||||
starts the bot agent, and redirects the user to the room URL to
|
|
||||||
interact with the bot through a Daily Prebuilt Interface.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
RedirectResponse: A response that redirects to the room URL.
|
|
||||||
"""
|
|
||||||
bot_name = os.getenv("BOT_IMPLEMENTATION", "openai").lower().strip()
|
|
||||||
print(f"Starting bot: {bot_name}")
|
|
||||||
room_url, token = await start(ConnectData(bot_name=bot_name))
|
|
||||||
|
|
||||||
return RedirectResponse(room_url)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/connect")
|
|
||||||
async def rtvi_connect(data: ConnectData) -> Dict[Any, Any]:
|
|
||||||
"""A user endpoint for launching a bot agent and retrieving the room/token credentials.
|
|
||||||
|
|
||||||
This function retrieves the bot implementation from the request, if provided,
|
|
||||||
starts the bot agent, and returns the room URL and token for the bot. This allows the
|
|
||||||
client to then connect to the bot using their own RTVI interface.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data (ConnectData): Optional. The data containing the bot name to use.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dict[Any, Any]: A dictionary containing the room URL and token.
|
|
||||||
"""
|
|
||||||
print(f"Starting bot: {data.bot_name}")
|
|
||||||
if data is None or not data.bot_name:
|
|
||||||
data.bot_name = os.getenv("BOT_IMPLEMENTATION", "openai").lower().strip()
|
|
||||||
room_url, token = await start(data)
|
|
||||||
|
|
||||||
return {"room_url": room_url, "token": token}
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/status/{fid}")
|
|
||||||
def get_status(fid: str):
|
|
||||||
"""Retrieve the status of a bot process by its function ID.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fid (str): The function ID of the bot process.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
JSONResponse: A JSON response containing the bot's status and result code.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPException: If the bot process with the given ID is not found.
|
|
||||||
"""
|
|
||||||
func = modal.FunctionCall.from_id(fid)
|
|
||||||
if not func:
|
|
||||||
raise HTTPException(status_code=404, detail=f"Bot with process id: {fid} not found")
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = func.get(timeout=0)
|
|
||||||
return JSONResponse({"bot_id": fid, "status": "finished", "code": result})
|
|
||||||
except modal.exception.OutputExpiredError:
|
|
||||||
return JSONResponse({"bot_id": fid, "status": "finished", "code": 404})
|
|
||||||
except TimeoutError:
|
|
||||||
return JSONResponse({"bot_id": fid, "status": "running", "code": 202})
|
|
||||||
|
|
||||||
|
|
||||||
@app.function(image=web_image, min_containers=1)
|
|
||||||
@modal.concurrent(max_inputs=1)
|
|
||||||
@modal.asgi_app()
|
|
||||||
def fastapi_app():
|
|
||||||
"""Create and configure the FastAPI application.
|
|
||||||
|
|
||||||
This function initializes the FastAPI app with middleware, routes, and lifespan management.
|
|
||||||
It is decorated to be used as a Modal ASGI app.
|
|
||||||
"""
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
|
||||||
|
|
||||||
# Initialize FastAPI app
|
|
||||||
web_app = FastAPI(lifespan=lifespan)
|
|
||||||
|
|
||||||
web_app.add_middleware(
|
|
||||||
CORSMiddleware,
|
|
||||||
allow_origins=["*"],
|
|
||||||
allow_credentials=True,
|
|
||||||
allow_methods=["*"],
|
|
||||||
allow_headers=["*"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Include the endpoints from endpoints.py
|
|
||||||
web_app.include_router(router)
|
|
||||||
|
|
||||||
return web_app
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
DAILY_API_KEY=
|
|
||||||
|
|
||||||
# determines which bot file to default to: 'openai', 'gemini', or 'vllm'
|
|
||||||
BOT_IMPLEMENTATION=openai
|
|
||||||
|
|
||||||
# needed for the openai bot pipeline
|
|
||||||
OPENAI_API_KEY=
|
|
||||||
ELEVENLABS_API_KEY=
|
|
||||||
|
|
||||||
# needed for the gemini live bot pipeline
|
|
||||||
GOOGLE_API_KEY=
|
|
||||||
|
|
||||||
# needed if you modified the API Key for your self-hosted LLM
|
|
||||||
VLLM_API_KEY=
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
python-dotenv==1.0.1
|
|
||||||
modal==0.71.3
|
|
||||||
|
Before Width: | Height: | Size: 759 KiB |
|
Before Width: | Height: | Size: 884 KiB |
|
Before Width: | Height: | Size: 876 KiB |
|
Before Width: | Height: | Size: 881 KiB |
|
Before Width: | Height: | Size: 866 KiB |
|
Before Width: | Height: | Size: 874 KiB |
|
Before Width: | Height: | Size: 882 KiB |
|
Before Width: | Height: | Size: 885 KiB |
|
Before Width: | Height: | Size: 888 KiB |
|
Before Width: | Height: | Size: 890 KiB |
|
Before Width: | Height: | Size: 898 KiB |
|
Before Width: | Height: | Size: 836 KiB |
|
Before Width: | Height: | Size: 903 KiB |
|
Before Width: | Height: | Size: 908 KiB |
|
Before Width: | Height: | Size: 908 KiB |
|
Before Width: | Height: | Size: 905 KiB |
|
Before Width: | Height: | Size: 903 KiB |
|
Before Width: | Height: | Size: 866 KiB |
|
Before Width: | Height: | Size: 849 KiB |
|
Before Width: | Height: | Size: 866 KiB |
|
Before Width: | Height: | Size: 866 KiB |
|
Before Width: | Height: | Size: 864 KiB |
|
Before Width: | Height: | Size: 858 KiB |
|
Before Width: | Height: | Size: 875 KiB |
|
Before Width: | Height: | Size: 881 KiB |
@@ -1,197 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2024–2025, Daily
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
|
||||||
#
|
|
||||||
|
|
||||||
"""Gemini Bot Implementation.
|
|
||||||
|
|
||||||
This module implements a chatbot using Google's Gemini Multimodal Live model.
|
|
||||||
It includes:
|
|
||||||
- Real-time audio/video interaction through Daily
|
|
||||||
- Animated robot avatar
|
|
||||||
- Speech-to-speech model
|
|
||||||
|
|
||||||
The bot runs as part of a pipeline that processes audio/video frames and manages
|
|
||||||
the conversation flow using Gemini's streaming capabilities.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from loguru import logger
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
|
||||||
from pipecat.audio.vad.vad_analyzer import VADParams
|
|
||||||
from pipecat.frames.frames import (
|
|
||||||
BotStartedSpeakingFrame,
|
|
||||||
BotStoppedSpeakingFrame,
|
|
||||||
Frame,
|
|
||||||
OutputImageRawFrame,
|
|
||||||
SpriteFrame,
|
|
||||||
)
|
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
|
||||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
|
||||||
from pipecat.processors.frameworks.rtvi import RTVIConfig, RTVIObserver, RTVIProcessor
|
|
||||||
from pipecat.services.gemini_multimodal_live.gemini import GeminiMultimodalLiveLLMService
|
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
|
||||||
|
|
||||||
load_dotenv(override=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.remove(0)
|
|
||||||
logger.add(sys.stderr, level="DEBUG")
|
|
||||||
except ValueError:
|
|
||||||
# Handle the case where logger is already initialized
|
|
||||||
pass
|
|
||||||
|
|
||||||
sprites = []
|
|
||||||
script_dir = os.path.dirname(__file__)
|
|
||||||
|
|
||||||
for i in range(1, 26):
|
|
||||||
# Build the full path to the image file
|
|
||||||
full_path = os.path.join(script_dir, f"assets/robot0{i}.png")
|
|
||||||
# Get the filename without the extension to use as the dictionary key
|
|
||||||
# Open the image and convert it to bytes
|
|
||||||
with Image.open(full_path) as img:
|
|
||||||
sprites.append(OutputImageRawFrame(image=img.tobytes(), size=img.size, format=img.format))
|
|
||||||
|
|
||||||
# Create a smooth animation by adding reversed frames
|
|
||||||
flipped = sprites[::-1]
|
|
||||||
sprites.extend(flipped)
|
|
||||||
|
|
||||||
# Define static and animated states
|
|
||||||
quiet_frame = sprites[0] # Static frame for when bot is listening
|
|
||||||
talking_frame = SpriteFrame(images=sprites) # Animation sequence for when bot is talking
|
|
||||||
|
|
||||||
|
|
||||||
class TalkingAnimation(FrameProcessor):
|
|
||||||
"""Manages the bot's visual animation states.
|
|
||||||
|
|
||||||
Switches between static (listening) and animated (talking) states based on
|
|
||||||
the bot's current speaking status.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self._is_talking = False
|
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
|
||||||
"""Process incoming frames and update animation state.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
frame: The incoming frame to process
|
|
||||||
direction: The direction of frame flow in the pipeline
|
|
||||||
"""
|
|
||||||
await super().process_frame(frame, direction)
|
|
||||||
|
|
||||||
# Switch to talking animation when bot starts speaking
|
|
||||||
if isinstance(frame, BotStartedSpeakingFrame):
|
|
||||||
if not self._is_talking:
|
|
||||||
await self.push_frame(talking_frame)
|
|
||||||
self._is_talking = True
|
|
||||||
# Return to static frame when bot stops speaking
|
|
||||||
elif isinstance(frame, BotStoppedSpeakingFrame):
|
|
||||||
await self.push_frame(quiet_frame)
|
|
||||||
self._is_talking = False
|
|
||||||
|
|
||||||
await self.push_frame(frame, direction)
|
|
||||||
|
|
||||||
|
|
||||||
async def run_bot(room_url: str, token: str):
|
|
||||||
"""Main bot execution function.
|
|
||||||
|
|
||||||
Sets up and runs the bot pipeline including:
|
|
||||||
- Daily video transport with specific audio parameters
|
|
||||||
- Gemini Live multimodal model integration
|
|
||||||
- Voice activity detection
|
|
||||||
- Animation processing
|
|
||||||
- RTVI event handling
|
|
||||||
"""
|
|
||||||
# Set up Daily transport with specific audio/video parameters for Gemini
|
|
||||||
transport = DailyTransport(
|
|
||||||
room_url,
|
|
||||||
token,
|
|
||||||
"Chatbot",
|
|
||||||
DailyParams(
|
|
||||||
audio_out_enabled=True,
|
|
||||||
camera_out_enabled=True,
|
|
||||||
camera_out_width=1024,
|
|
||||||
camera_out_height=576,
|
|
||||||
vad_enabled=True,
|
|
||||||
vad_audio_passthrough=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(params=VADParams(stop_secs=0.5)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize the Gemini Multimodal Live model
|
|
||||||
llm = GeminiMultimodalLiveLLMService(
|
|
||||||
api_key=os.getenv("GOOGLE_API_KEY"),
|
|
||||||
voice_id="Puck", # Aoede, Charon, Fenrir, Kore, Puck
|
|
||||||
transcribe_user_audio=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
messages = [
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "You are Chatbot, a friendly, helpful robot. Your goal is to demonstrate your capabilities in a succinct way. Your output will be converted to audio so don't include special characters in your answers. Respond to what the user said in a creative and helpful way, but keep your responses brief. Start by introducing yourself.",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
# Set up conversation context and management
|
|
||||||
# The context_aggregator will automatically collect conversation context
|
|
||||||
context = OpenAILLMContext(messages)
|
|
||||||
context_aggregator = llm.create_context_aggregator(context)
|
|
||||||
|
|
||||||
ta = TalkingAnimation()
|
|
||||||
|
|
||||||
#
|
|
||||||
# RTVI events for Pipecat client UI
|
|
||||||
#
|
|
||||||
rtvi = RTVIProcessor(config=RTVIConfig(config=[]))
|
|
||||||
|
|
||||||
pipeline = Pipeline(
|
|
||||||
[
|
|
||||||
transport.input(),
|
|
||||||
rtvi,
|
|
||||||
context_aggregator.user(),
|
|
||||||
llm,
|
|
||||||
ta,
|
|
||||||
transport.output(),
|
|
||||||
context_aggregator.assistant(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
task = PipelineTask(
|
|
||||||
pipeline,
|
|
||||||
params=PipelineParams(
|
|
||||||
enable_metrics=True,
|
|
||||||
enable_usage_metrics=True,
|
|
||||||
),
|
|
||||||
observers=[RTVIObserver(rtvi)],
|
|
||||||
)
|
|
||||||
await task.queue_frame(quiet_frame)
|
|
||||||
|
|
||||||
@rtvi.event_handler("on_client_ready")
|
|
||||||
async def on_client_ready(rtvi):
|
|
||||||
await rtvi.set_bot_ready()
|
|
||||||
# Kick off the conversation
|
|
||||||
await task.queue_frames([context_aggregator.user().get_context_frame()])
|
|
||||||
|
|
||||||
@transport.event_handler("on_first_participant_joined")
|
|
||||||
async def on_first_participant_joined(transport, participant):
|
|
||||||
await transport.capture_participant_transcription(participant["id"])
|
|
||||||
|
|
||||||
@transport.event_handler("on_participant_left")
|
|
||||||
async def on_participant_left(transport, participant, reason):
|
|
||||||
print(f"Participant left: {participant}")
|
|
||||||
await task.cancel()
|
|
||||||
|
|
||||||
runner = PipelineRunner()
|
|
||||||
|
|
||||||
await runner.run(task)
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2024–2025, Daily
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
|
||||||
#
|
|
||||||
|
|
||||||
"""OpenAI Bot Implementation.
|
|
||||||
|
|
||||||
This module implements a chatbot using OpenAI's GPT-4 model for natural language
|
|
||||||
processing. It includes:
|
|
||||||
- Real-time audio/video interaction through Daily
|
|
||||||
- Animated robot avatar
|
|
||||||
- Text-to-speech using ElevenLabs
|
|
||||||
- Support for both English and Spanish
|
|
||||||
|
|
||||||
The bot runs as part of a pipeline that processes audio/video frames and manages
|
|
||||||
the conversation flow.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from loguru import logger
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
|
||||||
from pipecat.frames.frames import (
|
|
||||||
BotStartedSpeakingFrame,
|
|
||||||
BotStoppedSpeakingFrame,
|
|
||||||
Frame,
|
|
||||||
OutputImageRawFrame,
|
|
||||||
SpriteFrame,
|
|
||||||
)
|
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
|
||||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
|
||||||
from pipecat.processors.frameworks.rtvi import RTVIConfig, RTVIObserver, RTVIProcessor
|
|
||||||
from pipecat.services.elevenlabs.tts import ElevenLabsTTSService
|
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
|
||||||
|
|
||||||
load_dotenv(override=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.remove(0)
|
|
||||||
logger.add(sys.stderr, level="DEBUG")
|
|
||||||
except ValueError:
|
|
||||||
# Handle the case where logger is already initialized
|
|
||||||
pass
|
|
||||||
|
|
||||||
sprites = []
|
|
||||||
script_dir = os.path.dirname(__file__)
|
|
||||||
|
|
||||||
# Load sequential animation frames
|
|
||||||
for i in range(1, 26):
|
|
||||||
# Build the full path to the image file
|
|
||||||
full_path = os.path.join(script_dir, f"assets/robot0{i}.png")
|
|
||||||
# Get the filename without the extension to use as the dictionary key
|
|
||||||
# Open the image and convert it to bytes
|
|
||||||
with Image.open(full_path) as img:
|
|
||||||
sprites.append(OutputImageRawFrame(image=img.tobytes(), size=img.size, format=img.format))
|
|
||||||
|
|
||||||
# Create a smooth animation by adding reversed frames
|
|
||||||
flipped = sprites[::-1]
|
|
||||||
sprites.extend(flipped)
|
|
||||||
|
|
||||||
# Define static and animated states
|
|
||||||
quiet_frame = sprites[0] # Static frame for when bot is listening
|
|
||||||
talking_frame = SpriteFrame(images=sprites) # Animation sequence for when bot is talking
|
|
||||||
|
|
||||||
|
|
||||||
class TalkingAnimation(FrameProcessor):
|
|
||||||
"""Manages the bot's visual animation states.
|
|
||||||
|
|
||||||
Switches between static (listening) and animated (talking) states based on
|
|
||||||
the bot's current speaking status.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self._is_talking = False
|
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
|
||||||
"""Process incoming frames and update animation state.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
frame: The incoming frame to process
|
|
||||||
direction: The direction of frame flow in the pipeline
|
|
||||||
"""
|
|
||||||
await super().process_frame(frame, direction)
|
|
||||||
|
|
||||||
# Switch to talking animation when bot starts speaking
|
|
||||||
if isinstance(frame, BotStartedSpeakingFrame):
|
|
||||||
if not self._is_talking:
|
|
||||||
await self.push_frame(talking_frame)
|
|
||||||
self._is_talking = True
|
|
||||||
# Return to static frame when bot stops speaking
|
|
||||||
elif isinstance(frame, BotStoppedSpeakingFrame):
|
|
||||||
await self.push_frame(quiet_frame)
|
|
||||||
self._is_talking = False
|
|
||||||
|
|
||||||
await self.push_frame(frame, direction)
|
|
||||||
|
|
||||||
|
|
||||||
async def run_bot(room_url: str, token: str):
|
|
||||||
"""Main bot execution function.
|
|
||||||
|
|
||||||
Sets up and runs the bot pipeline including:
|
|
||||||
- Daily video transport
|
|
||||||
- Speech-to-text and text-to-speech services
|
|
||||||
- Language model integration
|
|
||||||
- Animation processing
|
|
||||||
- RTVI event handling
|
|
||||||
"""
|
|
||||||
# Set up Daily transport with video/audio parameters
|
|
||||||
transport = DailyTransport(
|
|
||||||
room_url,
|
|
||||||
token,
|
|
||||||
"Chatbot",
|
|
||||||
DailyParams(
|
|
||||||
audio_out_enabled=True,
|
|
||||||
camera_out_enabled=True,
|
|
||||||
camera_out_width=1024,
|
|
||||||
camera_out_height=576,
|
|
||||||
vad_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
transcription_enabled=True,
|
|
||||||
#
|
|
||||||
# Spanish
|
|
||||||
#
|
|
||||||
# transcription_settings=DailyTranscriptionSettings(
|
|
||||||
# language="es",
|
|
||||||
# tier="nova",
|
|
||||||
# model="2-general"
|
|
||||||
# )
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize text-to-speech service
|
|
||||||
tts = ElevenLabsTTSService(
|
|
||||||
api_key=os.getenv("ELEVENLABS_API_KEY"),
|
|
||||||
#
|
|
||||||
# English
|
|
||||||
#
|
|
||||||
voice_id="SAz9YHcvj6GT2YYXdXww",
|
|
||||||
#
|
|
||||||
# Spanish
|
|
||||||
#
|
|
||||||
# model="eleven_multilingual_v2",
|
|
||||||
# voice_id="gD1IexrzCvsXPHUuT0s3",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize LLM service
|
|
||||||
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"))
|
|
||||||
|
|
||||||
messages = [
|
|
||||||
{
|
|
||||||
"role": "system",
|
|
||||||
#
|
|
||||||
# English
|
|
||||||
#
|
|
||||||
"content": "You are an incessant one-upper. Start by asking the user how their day is going.",
|
|
||||||
#
|
|
||||||
# Spanish
|
|
||||||
#
|
|
||||||
# "content": "Eres Chatbot, un amigable y útil robot. Tu objetivo es demostrar tus capacidades de una manera breve. Tus respuestas se convertiran a audio así que nunca no debes incluir caracteres especiales. Contesta a lo que el usuario pregunte de una manera creativa, útil y breve. Empieza por presentarte a ti mismo.",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
# Set up conversation context and management
|
|
||||||
# The context_aggregator will automatically collect conversation context
|
|
||||||
context = OpenAILLMContext(messages)
|
|
||||||
context_aggregator = llm.create_context_aggregator(context)
|
|
||||||
|
|
||||||
ta = TalkingAnimation()
|
|
||||||
|
|
||||||
#
|
|
||||||
# RTVI events for Pipecat client UI
|
|
||||||
#
|
|
||||||
rtvi = RTVIProcessor(config=RTVIConfig(config=[]))
|
|
||||||
|
|
||||||
pipeline = Pipeline(
|
|
||||||
[
|
|
||||||
transport.input(),
|
|
||||||
rtvi,
|
|
||||||
context_aggregator.user(),
|
|
||||||
llm,
|
|
||||||
tts,
|
|
||||||
ta,
|
|
||||||
transport.output(),
|
|
||||||
context_aggregator.assistant(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
task = PipelineTask(
|
|
||||||
pipeline,
|
|
||||||
params=PipelineParams(
|
|
||||||
enable_metrics=True,
|
|
||||||
enable_usage_metrics=True,
|
|
||||||
),
|
|
||||||
observers=[RTVIObserver(rtvi)],
|
|
||||||
)
|
|
||||||
await task.queue_frame(quiet_frame)
|
|
||||||
|
|
||||||
@rtvi.event_handler("on_client_ready")
|
|
||||||
async def on_client_ready(rtvi):
|
|
||||||
await rtvi.set_bot_ready()
|
|
||||||
# Kick off the conversation
|
|
||||||
await task.queue_frames([context_aggregator.user().get_context_frame()])
|
|
||||||
|
|
||||||
@transport.event_handler("on_first_participant_joined")
|
|
||||||
async def on_first_participant_joined(transport, participant):
|
|
||||||
await transport.capture_participant_transcription(participant["id"])
|
|
||||||
|
|
||||||
@transport.event_handler("on_participant_left")
|
|
||||||
async def on_participant_left(transport, participant, reason):
|
|
||||||
print(f"Participant left: {participant}")
|
|
||||||
await task.cancel()
|
|
||||||
|
|
||||||
runner = PipelineRunner()
|
|
||||||
|
|
||||||
await runner.run(task)
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2024–2025, Daily
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
|
||||||
#
|
|
||||||
|
|
||||||
"""OpenAI Bot Implementation.
|
|
||||||
|
|
||||||
This module implements a chatbot using OpenAI's GPT-4 model for natural language
|
|
||||||
processing. It includes:
|
|
||||||
- Real-time audio/video interaction through Daily
|
|
||||||
- Animated robot avatar
|
|
||||||
- Text-to-speech using ElevenLabs
|
|
||||||
- Support for both English and Spanish
|
|
||||||
|
|
||||||
The bot runs as part of a pipeline that processes audio/video frames and manages
|
|
||||||
the conversation flow.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from loguru import logger
|
|
||||||
from openai.types.chat import ChatCompletionMessageParam
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
|
||||||
from pipecat.frames.frames import (
|
|
||||||
BotStartedSpeakingFrame,
|
|
||||||
BotStoppedSpeakingFrame,
|
|
||||||
Frame,
|
|
||||||
OutputImageRawFrame,
|
|
||||||
SpriteFrame,
|
|
||||||
)
|
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
|
||||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
|
||||||
from pipecat.processors.frameworks.rtvi import RTVIConfig, RTVIObserver, RTVIProcessor
|
|
||||||
from pipecat.services.elevenlabs.tts import ElevenLabsTTSService
|
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
|
||||||
|
|
||||||
load_dotenv(override=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.remove(0)
|
|
||||||
logger.add(sys.stderr, level="DEBUG")
|
|
||||||
except ValueError:
|
|
||||||
# Handle the case where logger is already initialized
|
|
||||||
pass
|
|
||||||
|
|
||||||
# REPLACE WITH YOUR MODAL URL ENDPOINT
|
|
||||||
modal_url = "https://<Modal workspace>--example-vllm-openai-compatible-serve.modal.run"
|
|
||||||
api_key = os.getenv("VLLM_API_KEY", "super-secret-key")
|
|
||||||
|
|
||||||
|
|
||||||
sprites = []
|
|
||||||
script_dir = os.path.dirname(__file__)
|
|
||||||
|
|
||||||
# Load sequential animation frames
|
|
||||||
for i in range(1, 26):
|
|
||||||
# Build the full path to the image file
|
|
||||||
full_path = os.path.join(script_dir, f"assets/robot0{i}.png")
|
|
||||||
# Get the filename without the extension to use as the dictionary key
|
|
||||||
# Open the image and convert it to bytes
|
|
||||||
with Image.open(full_path) as img:
|
|
||||||
sprites.append(OutputImageRawFrame(image=img.tobytes(), size=img.size, format=img.format))
|
|
||||||
|
|
||||||
# Create a smooth animation by adding reversed frames
|
|
||||||
flipped = sprites[::-1]
|
|
||||||
sprites.extend(flipped)
|
|
||||||
|
|
||||||
# Define static and animated states
|
|
||||||
quiet_frame = sprites[0] # Static frame for when bot is listening
|
|
||||||
talking_frame = SpriteFrame(images=sprites) # Animation sequence for when bot is talking
|
|
||||||
|
|
||||||
|
|
||||||
class TalkingAnimation(FrameProcessor):
|
|
||||||
"""Manages the bot's visual animation states.
|
|
||||||
|
|
||||||
Switches between static (listening) and animated (talking) states based on
|
|
||||||
the bot's current speaking status.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self._is_talking = False
|
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
|
||||||
"""Process incoming frames and update animation state.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
frame: The incoming frame to process
|
|
||||||
direction: The direction of frame flow in the pipeline
|
|
||||||
"""
|
|
||||||
await super().process_frame(frame, direction)
|
|
||||||
|
|
||||||
# Switch to talking animation when bot starts speaking
|
|
||||||
if isinstance(frame, BotStartedSpeakingFrame):
|
|
||||||
if not self._is_talking:
|
|
||||||
await self.push_frame(talking_frame)
|
|
||||||
self._is_talking = True
|
|
||||||
# Return to static frame when bot stops speaking
|
|
||||||
elif isinstance(frame, BotStoppedSpeakingFrame):
|
|
||||||
await self.push_frame(quiet_frame)
|
|
||||||
self._is_talking = False
|
|
||||||
|
|
||||||
await self.push_frame(frame, direction)
|
|
||||||
|
|
||||||
|
|
||||||
async def run_bot(room_url: str, token: str):
|
|
||||||
"""Main bot execution function.
|
|
||||||
|
|
||||||
Sets up and runs the bot pipeline including:
|
|
||||||
- Daily video transport
|
|
||||||
- Speech-to-text and text-to-speech services
|
|
||||||
- Language model integration
|
|
||||||
- Animation processing
|
|
||||||
- RTVI event handling
|
|
||||||
"""
|
|
||||||
# Set up Daily transport with video/audio parameters
|
|
||||||
transport = DailyTransport(
|
|
||||||
room_url,
|
|
||||||
token,
|
|
||||||
"Chatbot",
|
|
||||||
DailyParams(
|
|
||||||
audio_out_enabled=True,
|
|
||||||
camera_out_enabled=True,
|
|
||||||
camera_out_width=1024,
|
|
||||||
camera_out_height=576,
|
|
||||||
vad_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
transcription_enabled=True,
|
|
||||||
#
|
|
||||||
# Spanish
|
|
||||||
#
|
|
||||||
# transcription_settings=DailyTranscriptionSettings(
|
|
||||||
# language="es",
|
|
||||||
# tier="nova",
|
|
||||||
# model="2-general"
|
|
||||||
# )
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize text-to-speech service
|
|
||||||
tts = ElevenLabsTTSService(
|
|
||||||
api_key=os.getenv("ELEVENLABS_API_KEY"),
|
|
||||||
#
|
|
||||||
# English
|
|
||||||
#
|
|
||||||
voice_id="D38z5RcWu1voky8WS1ja",
|
|
||||||
#
|
|
||||||
# Spanish
|
|
||||||
#
|
|
||||||
# model="eleven_multilingual_v2",
|
|
||||||
# voice_id="gD1IexrzCvsXPHUuT0s3",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize LLM service
|
|
||||||
llm = OpenAILLMService(
|
|
||||||
# To use OpenAI
|
|
||||||
api_key=api_key,
|
|
||||||
# Or, to use a local vLLM (or similar) api server
|
|
||||||
model="neuralmagic/Meta-Llama-3.1-8B-Instruct-quantized.w4a16",
|
|
||||||
base_url=f"{modal_url}/v1",
|
|
||||||
)
|
|
||||||
|
|
||||||
messages = [
|
|
||||||
{
|
|
||||||
"role": "system",
|
|
||||||
#
|
|
||||||
# English
|
|
||||||
#
|
|
||||||
"content": "You are a salesman for Modal, the cloud-native serverless Python computing platform.",
|
|
||||||
#
|
|
||||||
# Spanish
|
|
||||||
#
|
|
||||||
# "content": "Eres Chatbot, un amigable y útil robot. Tu objetivo es demostrar tus capacidades de una manera breve. Tus respuestas se convertiran a audio así que nunca no debes incluir caracteres especiales. Contesta a lo que el usuario pregunte de una manera creativa, útil y breve. Empieza por presentarte a ti mismo.",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
# Set up conversation context and management
|
|
||||||
# The context_aggregator will automatically collect conversation context
|
|
||||||
context = OpenAILLMContext(messages)
|
|
||||||
context_aggregator = llm.create_context_aggregator(context)
|
|
||||||
|
|
||||||
ta = TalkingAnimation()
|
|
||||||
|
|
||||||
#
|
|
||||||
# RTVI events for Pipecat client UI
|
|
||||||
#
|
|
||||||
rtvi = RTVIProcessor(config=RTVIConfig(config=[]))
|
|
||||||
|
|
||||||
pipeline = Pipeline(
|
|
||||||
[
|
|
||||||
transport.input(),
|
|
||||||
rtvi,
|
|
||||||
context_aggregator.user(),
|
|
||||||
llm,
|
|
||||||
tts,
|
|
||||||
ta,
|
|
||||||
transport.output(),
|
|
||||||
context_aggregator.assistant(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
task = PipelineTask(
|
|
||||||
pipeline,
|
|
||||||
params=PipelineParams(
|
|
||||||
enable_metrics=True,
|
|
||||||
enable_usage_metrics=True,
|
|
||||||
),
|
|
||||||
observers=[RTVIObserver(rtvi)],
|
|
||||||
)
|
|
||||||
await task.queue_frame(quiet_frame)
|
|
||||||
|
|
||||||
@rtvi.event_handler("on_client_ready")
|
|
||||||
async def on_client_ready(rtvi):
|
|
||||||
await rtvi.set_bot_ready()
|
|
||||||
# Kick off the conversation
|
|
||||||
await task.queue_frames([context_aggregator.user().get_context_frame()])
|
|
||||||
|
|
||||||
@transport.event_handler("on_first_participant_joined")
|
|
||||||
async def on_first_participant_joined(transport, participant):
|
|
||||||
await transport.capture_participant_transcription(participant["id"])
|
|
||||||
|
|
||||||
@transport.event_handler("on_participant_left")
|
|
||||||
async def on_participant_left(transport, participant, reason):
|
|
||||||
print(f"Participant left: {participant}")
|
|
||||||
await task.cancel()
|
|
||||||
|
|
||||||
runner = PipelineRunner()
|
|
||||||
|
|
||||||
await runner.run(task)
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2024–2025, Daily
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
|
||||||
#
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import asyncio
|
|
||||||
import importlib
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def get_bot_file(arg_bot: str | None) -> str:
|
|
||||||
bot_implementation = arg_bot or os.getenv("BOT_IMPLEMENTATION", "openai").lower().strip()
|
|
||||||
if not bot_implementation:
|
|
||||||
bot_implementation = "openai"
|
|
||||||
if bot_implementation not in ["openai", "gemini", "vllm"]:
|
|
||||||
raise ValueError(
|
|
||||||
f"Invalid BOT_IMPLEMENTATION: {bot_implementation}. Must be 'openai' or 'gemini'"
|
|
||||||
)
|
|
||||||
return f"bot_{bot_implementation}"
|
|
||||||
|
|
||||||
|
|
||||||
def get_runner(bot_file: str):
|
|
||||||
"""Dynamically import the run_bot function based on the bot name.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
bot_name (str): The name of the bot implementation (e.g., 'openai', 'gemini').
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
function: The run_bot function from the specified bot module.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ImportError: If the specified bot module or run_bot function is not found.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Dynamically construct the module name
|
|
||||||
module_name = f"{bot_file}"
|
|
||||||
# Import the module
|
|
||||||
module = importlib.import_module(module_name)
|
|
||||||
# Get the run_bot function from the module
|
|
||||||
return getattr(module, "run_bot")
|
|
||||||
except (ImportError, AttributeError) as e:
|
|
||||||
raise ImportError(f"Failed to import run_bot from {module_name}: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Parse the args to launch the appropriate bot using the given room/token."""
|
|
||||||
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
|
||||||
parser.add_argument(
|
|
||||||
"-u", "--url", type=str, required=False, help="URL of the Daily room to join"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-t",
|
|
||||||
"--token",
|
|
||||||
type=str,
|
|
||||||
required=False,
|
|
||||||
help="Daily room token",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-b",
|
|
||||||
"--bot",
|
|
||||||
type=str,
|
|
||||||
required=False,
|
|
||||||
help="Bot runner to use (e.g., openai, gemini)",
|
|
||||||
)
|
|
||||||
|
|
||||||
args, unknown = parser.parse_known_args()
|
|
||||||
|
|
||||||
url = args.url or os.getenv("DAILY_SAMPLE_ROOM_URL")
|
|
||||||
token = args.token or os.getenv("DAILY_SAMPLE_ROOM_TOKEN")
|
|
||||||
bot_file = get_bot_file(args.bot)
|
|
||||||
|
|
||||||
if not url:
|
|
||||||
raise Exception(
|
|
||||||
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL."
|
|
||||||
)
|
|
||||||
|
|
||||||
run_bot = get_runner(bot_file)
|
|
||||||
asyncio.run(run_bot(url, token))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -100,28 +100,7 @@ phone numbers with valid values for your use case.
|
|||||||
|
|
||||||
### Dialin Request
|
### Dialin Request
|
||||||
|
|
||||||
The server will receive a request when a call is received from Daily.
|
The server will receive a request when a call is received from Daily.
|
||||||
The payload that the webhook received is as follows:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
// for dial-in from webhook
|
|
||||||
"To": "+14152251493",
|
|
||||||
"From": "+14158483432",
|
|
||||||
"callId": "string-contains-uuid",
|
|
||||||
"callDomain": "string-contains-uuid",
|
|
||||||
"sipHeaders": {
|
|
||||||
"X-My-Custom-Header": "value",
|
|
||||||
"x-caller": "+1234567890",
|
|
||||||
"x-called": "+1987654321",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
The `To`, `From`, `callId`, `callDomain` fields are converted to
|
|
||||||
`snake_case` and mapped to `dialin_settings`. In addition, `sipHeader`
|
|
||||||
contains any custom SIP headers received by Daily on the SIP
|
|
||||||
interconnect address (`sip_uri`). These are headers sent from
|
|
||||||
Twilio or other external SIP platforms, for example, to send the
|
|
||||||
caller's phone number.
|
|
||||||
|
|
||||||
### Dialout Request
|
### Dialout Request
|
||||||
|
|
||||||
@@ -179,7 +158,6 @@ curl -X POST http://localhost:3000/api/dial \
|
|||||||
"From": "+1987654321",
|
"From": "+1987654321",
|
||||||
"callId": "call-uuid-123",
|
"callId": "call-uuid-123",
|
||||||
"callDomain": "domain-uuid-456",
|
"callDomain": "domain-uuid-456",
|
||||||
"sipHeader": {},
|
|
||||||
"dialout_settings": [
|
"dialout_settings": [
|
||||||
{
|
{
|
||||||
"phoneNumber": "+1234567890",
|
"phoneNumber": "+1234567890",
|
||||||
|
|||||||
@@ -39,11 +39,6 @@ class RoomRequest(BaseModel):
|
|||||||
None, description="A flag to perform voicemail or answeing-machine detection"
|
None, description="A flag to perform voicemail or answeing-machine detection"
|
||||||
)
|
)
|
||||||
call_transfer: Optional[Dict[str, Any]] = Field(None, description="to initiate a call transfer")
|
call_transfer: Optional[Dict[str, Any]] = Field(None, description="to initiate a call transfer")
|
||||||
sipHeaders: Optional[Dict[str, Any]] = Field(
|
|
||||||
None,
|
|
||||||
alias="sip_headers",
|
|
||||||
description="Custom SIP headers received from the external SIP provider",
|
|
||||||
)
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
populate_by_name = True
|
populate_by_name = True
|
||||||
@@ -62,14 +57,6 @@ class RoomRequest(BaseModel):
|
|||||||
"callDomain": "string-contains-uuid"
|
"callDomain": "string-contains-uuid"
|
||||||
These need to be remapped to dialin_settings
|
These need to be remapped to dialin_settings
|
||||||
|
|
||||||
In addition, we may receive in the body that can be
|
|
||||||
sent to the bot as a custom field, sip_headers
|
|
||||||
"sipHeaders": {
|
|
||||||
"X-My-Custom-Header": "value",
|
|
||||||
"x-caller": "+14158483432",
|
|
||||||
"x-called": "+14152251493",
|
|
||||||
},
|
|
||||||
|
|
||||||
"dialout_settings": [
|
"dialout_settings": [
|
||||||
{"phoneNumber": "+14158483432", "callerId": "+14152251493"},
|
{"phoneNumber": "+14158483432", "callerId": "+14152251493"},
|
||||||
{"sipUri": "sip:username@sip.hostname"}
|
{"sipUri": "sip:username@sip.hostname"}
|
||||||
@@ -170,7 +157,6 @@ async def dial(request: RoomRequest, raw_request: Request):
|
|||||||
"dialout_settings": request.dialout_settings,
|
"dialout_settings": request.dialout_settings,
|
||||||
"voicemail_detection": request.voicemail_detection,
|
"voicemail_detection": request.voicemail_detection,
|
||||||
"call_transfer": request.call_transfer,
|
"call_transfer": request.call_transfer,
|
||||||
"sip_headers": request.sipHeaders, # passing the SIP headers to the bot
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ export default async function handler(req, res) {
|
|||||||
From,
|
From,
|
||||||
callId,
|
callId,
|
||||||
callDomain,
|
callDomain,
|
||||||
sipHeaders,
|
|
||||||
dialout_settings,
|
dialout_settings,
|
||||||
voicemail_detection,
|
voicemail_detection,
|
||||||
call_transfer
|
call_transfer
|
||||||
@@ -118,7 +117,6 @@ export default async function handler(req, res) {
|
|||||||
dialout_settings,
|
dialout_settings,
|
||||||
voicemail_detection,
|
voicemail_detection,
|
||||||
call_transfer,
|
call_transfer,
|
||||||
sip_headers: sipHeaders,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -67,8 +67,10 @@ async def main(transport: DailyTransport):
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ async def main(transport: DailyTransport):
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -16,26 +16,24 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineTask
|
from pipecat.pipeline.task import PipelineTask
|
||||||
from pipecat.services.piper.tts import PiperTTSService
|
from pipecat.services.piper.tts import PiperTTSService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(audio_out_enabled=True),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(audio_out_enabled=True),
|
|
||||||
"webrtc": lambda: TransportParams(audio_out_enabled=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
# Create a transport using the WebRTC connection
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_out_enabled=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Create an HTTP session
|
# Create an HTTP session
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
tts = PiperTTSService(
|
tts = PiperTTSService(
|
||||||
@@ -49,12 +47,12 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
async def on_client_connected(transport, client):
|
async def on_client_connected(transport, client):
|
||||||
await task.queue_frames([TTSSpeakFrame(f"Hello there!"), EndFrame()])
|
await task.queue_frames([TTSSpeakFrame(f"Hello there!"), EndFrame()])
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -16,25 +16,24 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineTask
|
from pipecat.pipeline.task import PipelineTask
|
||||||
from pipecat.services.rime.tts import RimeHttpTTSService
|
from pipecat.services.rime.tts import RimeHttpTTSService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(audio_out_enabled=True),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(audio_out_enabled=True),
|
|
||||||
"webrtc": lambda: TransportParams(audio_out_enabled=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
# Create a transport using the WebRTC connection
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_out_enabled=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Create an HTTP session
|
# Create an HTTP session
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
tts = RimeHttpTTSService(
|
tts = RimeHttpTTSService(
|
||||||
@@ -50,12 +49,12 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
async def on_client_connected(transport, client):
|
async def on_client_connected(transport, client):
|
||||||
await task.queue_frames([TTSSpeakFrame(f"Hello there!"), EndFrame()])
|
await task.queue_frames([TTSSpeakFrame(f"Hello there!"), EndFrame()])
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -15,26 +15,24 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineTask
|
from pipecat.pipeline.task import PipelineTask
|
||||||
from pipecat.services.cartesia.tts import CartesiaTTSService
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(audio_out_enabled=True),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(audio_out_enabled=True),
|
|
||||||
"webrtc": lambda: TransportParams(audio_out_enabled=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
# Create a transport using the WebRTC connection
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_out_enabled=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
voice_id="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady
|
voice_id="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady
|
||||||
@@ -47,12 +45,12 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
async def on_client_connected(transport, client):
|
async def on_client_connected(transport, client):
|
||||||
await task.queue_frames([TTSSpeakFrame(f"Hello there!"), EndFrame()])
|
await task.queue_frames([TTSSpeakFrame(f"Hello there!"), EndFrame()])
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -77,36 +77,37 @@ async def configure_livekit():
|
|||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
(url, token, room_name) = await configure_livekit()
|
async with aiohttp.ClientSession() as session:
|
||||||
|
(url, token, room_name) = await configure_livekit()
|
||||||
|
|
||||||
transport = LiveKitTransport(
|
transport = LiveKitTransport(
|
||||||
url=url,
|
url=url,
|
||||||
token=token,
|
token=token,
|
||||||
room_name=room_name,
|
room_name=room_name,
|
||||||
params=LiveKitParams(audio_out_enabled=True),
|
params=LiveKitParams(audio_out_enabled=True),
|
||||||
)
|
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
|
||||||
voice_id="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady
|
|
||||||
)
|
|
||||||
|
|
||||||
runner = PipelineRunner()
|
|
||||||
|
|
||||||
task = PipelineTask(Pipeline([tts, transport.output()]))
|
|
||||||
|
|
||||||
# Register an event handler so we can play the audio when the
|
|
||||||
# participant joins.
|
|
||||||
@transport.event_handler("on_first_participant_joined")
|
|
||||||
async def on_first_participant_joined(transport, participant_id):
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
await task.queue_frame(
|
|
||||||
TextFrame(
|
|
||||||
"Hello there! How are you doing today? Would you like to talk about the weather?"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await runner.run(task)
|
tts = CartesiaTTSService(
|
||||||
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
|
voice_id="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady
|
||||||
|
)
|
||||||
|
|
||||||
|
runner = PipelineRunner()
|
||||||
|
|
||||||
|
task = PipelineTask(Pipeline([tts, transport.output()]))
|
||||||
|
|
||||||
|
# Register an event handler so we can play the audio when the
|
||||||
|
# participant joins.
|
||||||
|
@transport.event_handler("on_first_participant_joined")
|
||||||
|
async def on_first_participant_joined(transport, participant_id):
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
await task.queue_frame(
|
||||||
|
TextFrame(
|
||||||
|
"Hello there! How are you doing today? Would you like to talk about the weather?"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -15,26 +15,24 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineTask
|
from pipecat.pipeline.task import PipelineTask
|
||||||
from pipecat.services.riva.tts import FastPitchTTSService
|
from pipecat.services.riva.tts import FastPitchTTSService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(audio_out_enabled=True),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(audio_out_enabled=True),
|
|
||||||
"webrtc": lambda: TransportParams(audio_out_enabled=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
# Create a transport using the WebRTC connection
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_out_enabled=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
tts = FastPitchTTSService(api_key=os.getenv("NVIDIA_API_KEY"))
|
tts = FastPitchTTSService(api_key=os.getenv("NVIDIA_API_KEY"))
|
||||||
|
|
||||||
task = PipelineTask(Pipeline([tts, transport.output()]))
|
task = PipelineTask(Pipeline([tts, transport.output()]))
|
||||||
@@ -44,12 +42,12 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
async def on_client_connected(transport, client):
|
async def on_client_connected(transport, client):
|
||||||
await task.queue_frames([TTSSpeakFrame(f"Hello there!"), EndFrame()])
|
await task.queue_frames([TTSSpeakFrame(f"Hello there!"), EndFrame()])
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -16,26 +16,24 @@ from pipecat.pipeline.runner import PipelineRunner
|
|||||||
from pipecat.pipeline.task import PipelineTask
|
from pipecat.pipeline.task import PipelineTask
|
||||||
from pipecat.services.cartesia.tts import CartesiaTTSService
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(audio_out_enabled=True),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(audio_out_enabled=True),
|
|
||||||
"webrtc": lambda: TransportParams(audio_out_enabled=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
# Create a transport using the WebRTC connection
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_out_enabled=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
voice_id="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady
|
voice_id="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady
|
||||||
@@ -57,12 +55,12 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
async def on_client_connected(transport, client):
|
async def on_client_connected(transport, client):
|
||||||
await task.queue_frames([LLMMessagesFrame(messages), EndFrame()])
|
await task.queue_frames([LLMMessagesFrame(messages), EndFrame()])
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -16,32 +16,26 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineTask
|
from pipecat.pipeline.task import PipelineTask
|
||||||
from pipecat.services.fal.image import FalImageGenService
|
from pipecat.services.fal.image import FalImageGenService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
video_out_enabled=True,
|
|
||||||
video_out_width=1024,
|
|
||||||
video_out_height=1024,
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
video_out_enabled=True,
|
|
||||||
video_out_width=1024,
|
|
||||||
video_out_height=1024,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
# Create a transport using the WebRTC connection
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
video_out_enabled=True,
|
||||||
|
video_out_width=1024,
|
||||||
|
video_out_height=1024,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Create an HTTP session
|
# Create an HTTP session
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
imagegen = FalImageGenService(
|
imagegen = FalImageGenService(
|
||||||
@@ -60,14 +54,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -15,42 +15,33 @@ from pipecat.pipeline.pipeline import Pipeline
|
|||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.services.google.image import GoogleImageGenService
|
from pipecat.services.google.image import GoogleImageGenService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
video_out_enabled=True,
|
|
||||||
video_out_width=1024,
|
|
||||||
video_out_height=1024,
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
video_out_enabled=True,
|
|
||||||
video_out_width=1024,
|
|
||||||
video_out_height=1024,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
# Create a transport using the WebRTC connection
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
video_out_enabled=True,
|
||||||
|
video_out_width=1024,
|
||||||
|
video_out_height=1024,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
imagegen = GoogleImageGenService(
|
imagegen = GoogleImageGenService(
|
||||||
api_key=os.getenv("GOOGLE_API_KEY"),
|
api_key=os.getenv("GOOGLE_API_KEY"),
|
||||||
)
|
)
|
||||||
|
|
||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
Pipeline([imagegen, transport.output()]),
|
Pipeline([imagegen, transport.output()]),
|
||||||
params=PipelineParams(
|
params=PipelineParams(enable_metrics=True),
|
||||||
enable_metrics=True,
|
|
||||||
enable_usage_metrics=True,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Register an event handler so we can play the audio when the client joins
|
# Register an event handler so we can play the audio when the client joins
|
||||||
@@ -63,14 +54,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -5,17 +5,10 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
|
||||||
import os
|
import os
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
import uvicorn
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from fastapi import BackgroundTasks, FastAPI
|
|
||||||
from fastapi.responses import RedirectResponse
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pipecat_ai_small_webrtc_prebuilt.frontend import SmallWebRTCPrebuiltUI
|
|
||||||
|
|
||||||
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
@@ -27,29 +20,14 @@ from pipecat.services.deepgram.stt import DeepgramSTTService
|
|||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.network.webrtc_connection import IceServer, SmallWebRTCConnection
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
# Store connections by pc_id
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
pcs_map: Dict[str, SmallWebRTCConnection] = {}
|
|
||||||
|
|
||||||
ice_servers = [
|
|
||||||
IceServer(
|
|
||||||
urls="stun:stun.l.google.com:19302",
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Mount the frontend at /
|
|
||||||
app.mount("/client", SmallWebRTCPrebuiltUI)
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(webrtc_connection: SmallWebRTCConnection):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
# Create a transport using the WebRTC connection
|
|
||||||
transport = SmallWebRTCTransport(
|
transport = SmallWebRTCTransport(
|
||||||
webrtc_connection=webrtc_connection,
|
webrtc_connection=webrtc_connection,
|
||||||
params=TransportParams(
|
params=TransportParams(
|
||||||
@@ -93,8 +71,10 @@ async def run_example(webrtc_connection: SmallWebRTCConnection):
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -108,6 +88,10 @@ async def run_example(webrtc_connection: SmallWebRTCConnection):
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=False)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
@@ -115,58 +99,7 @@ async def run_example(webrtc_connection: SmallWebRTCConnection):
|
|||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/", include_in_schema=False)
|
|
||||||
async def root_redirect():
|
|
||||||
return RedirectResponse(url="/client/")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/offer")
|
|
||||||
async def offer(request: dict, background_tasks: BackgroundTasks):
|
|
||||||
pc_id = request.get("pc_id")
|
|
||||||
|
|
||||||
if pc_id and pc_id in pcs_map:
|
|
||||||
pipecat_connection = pcs_map[pc_id]
|
|
||||||
logger.info(f"Reusing existing connection for pc_id: {pc_id}")
|
|
||||||
await pipecat_connection.renegotiate(
|
|
||||||
sdp=request["sdp"],
|
|
||||||
type=request["type"],
|
|
||||||
restart_pc=request.get("restart_pc", False),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
pipecat_connection = SmallWebRTCConnection(ice_servers)
|
|
||||||
await pipecat_connection.initialize(sdp=request["sdp"], type=request["type"])
|
|
||||||
|
|
||||||
@pipecat_connection.event_handler("closed")
|
|
||||||
async def handle_disconnected(webrtc_connection: SmallWebRTCConnection):
|
|
||||||
logger.info(f"Discarding peer connection for pc_id: {webrtc_connection.pc_id}")
|
|
||||||
pcs_map.pop(webrtc_connection.pc_id, None)
|
|
||||||
|
|
||||||
# Run example function with SmallWebRTC transport arguments.
|
|
||||||
background_tasks.add_task(run_example, pipecat_connection)
|
|
||||||
|
|
||||||
answer = pipecat_connection.get_answer()
|
|
||||||
# Updating the peer connection inside the map
|
|
||||||
pcs_map[answer["pc_id"]] = pipecat_connection
|
|
||||||
|
|
||||||
return answer
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def lifespan(app: FastAPI):
|
|
||||||
yield # Run app
|
|
||||||
coros = [pc.disconnect() for pc in pcs_map.values()]
|
|
||||||
await asyncio.gather(*coros)
|
|
||||||
pcs_map.clear()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description="Pipecat Bot Runner")
|
from run import main
|
||||||
parser.add_argument(
|
|
||||||
"--host", default="localhost", help="Host for HTTP server (default: localhost)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--port", type=int, default=7860, help="Port for HTTP server (default: 7860)"
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
uvicorn.run(app, host=args.host, port=args.port)
|
main()
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
from daily_runner import configure
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
||||||
from pipecat.examples.daily_runner import configure
|
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
@@ -37,9 +37,9 @@ async def main():
|
|||||||
token,
|
token,
|
||||||
"Respond bot",
|
"Respond bot",
|
||||||
DailyParams(
|
DailyParams(
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
transcription_enabled=True,
|
transcription_enabled=True,
|
||||||
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -75,8 +75,10 @@ async def main():
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
from deepgram import LiveOptions
|
from deepgram import LiveOptions
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from livekit import api
|
from livekit import api
|
||||||
@@ -103,101 +104,101 @@ async def configure_livekit():
|
|||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
(url, token, room_name) = await configure_livekit()
|
async with aiohttp.ClientSession() as session:
|
||||||
|
(url, token, room_name) = await configure_livekit()
|
||||||
|
|
||||||
transport = LiveKitTransport(
|
transport = LiveKitTransport(
|
||||||
url=url,
|
url=url,
|
||||||
token=token,
|
token=token,
|
||||||
room_name=room_name,
|
room_name=room_name,
|
||||||
params=LiveKitParams(
|
params=LiveKitParams(
|
||||||
audio_in_enabled=True,
|
audio_in_enabled=True,
|
||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(
|
stt = DeepgramSTTService(
|
||||||
api_key=os.getenv("DEEPGRAM_API_KEY"),
|
api_key=os.getenv("DEEPGRAM_API_KEY"),
|
||||||
live_options=LiveOptions(
|
live_options=LiveOptions(
|
||||||
vad_events=True,
|
vad_events=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"))
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"))
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
voice_id="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady
|
voice_id="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": "You are a helpful LLM in a WebRTC call. "
|
"content": "You are a helpful LLM in a WebRTC call. "
|
||||||
"Your goal is to demonstrate your capabilities in a succinct way. "
|
"Your goal is to demonstrate your capabilities in a succinct way. "
|
||||||
"Your output will be converted to audio so don't include special characters in your answers. "
|
"Your output will be converted to audio so don't include special characters in your answers. "
|
||||||
"Respond to what the user said in a creative and helpful way.",
|
"Respond to what the user said in a creative and helpful way.",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
context = OpenAILLMContext(messages)
|
context = OpenAILLMContext(messages)
|
||||||
context_aggregator = llm.create_context_aggregator(context)
|
context_aggregator = llm.create_context_aggregator(context)
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|
||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
Pipeline(
|
Pipeline(
|
||||||
[
|
[
|
||||||
transport.input(),
|
transport.input(),
|
||||||
stt,
|
stt,
|
||||||
context_aggregator.user(),
|
context_aggregator.user(),
|
||||||
llm,
|
llm,
|
||||||
tts,
|
tts,
|
||||||
transport.output(),
|
transport.output(),
|
||||||
context_aggregator.assistant(),
|
context_aggregator.assistant(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
enable_metrics=True,
|
allow_interruptions=True, enable_metrics=True, enable_usage_metrics=True
|
||||||
enable_usage_metrics=True,
|
),
|
||||||
),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
# Register an event handler so we can play the audio when the
|
# Register an event handler so we can play the audio when the
|
||||||
# participant joins.
|
# participant joins.
|
||||||
@transport.event_handler("on_first_participant_joined")
|
@transport.event_handler("on_first_participant_joined")
|
||||||
async def on_first_participant_joined(transport, participant_id):
|
async def on_first_participant_joined(transport, participant_id):
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
await task.queue_frame(
|
await task.queue_frame(
|
||||||
TextFrame(
|
TextFrame(
|
||||||
"Hello there! How are you doing today? Would you like to talk about the weather?"
|
"Hello there! How are you doing today? Would you like to talk about the weather?"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
# Register an event handler to receive data from the participant via text chat
|
# Register an event handler to receive data from the participant via text chat
|
||||||
# in the LiveKit room. This will be used to as transcription frames and
|
# in the LiveKit room. This will be used to as transcription frames and
|
||||||
# interrupt the bot and pass it to llm for processing and
|
# interrupt the bot and pass it to llm for processing and
|
||||||
# then pass back to the participant as audio output.
|
# then pass back to the participant as audio output.
|
||||||
@transport.event_handler("on_data_received")
|
@transport.event_handler("on_data_received")
|
||||||
async def on_data_received(transport, data, participant_id):
|
async def on_data_received(transport, data, participant_id):
|
||||||
logger.info(f"Received data from participant {participant_id}: {data}")
|
logger.info(f"Received data from participant {participant_id}: {data}")
|
||||||
# convert data from bytes to string
|
# convert data from bytes to string
|
||||||
json_data = json.loads(data)
|
json_data = json.loads(data)
|
||||||
|
|
||||||
await task.queue_frames(
|
await task.queue_frames(
|
||||||
[
|
[
|
||||||
BotInterruptionFrame(),
|
BotInterruptionFrame(),
|
||||||
UserStartedSpeakingFrame(),
|
UserStartedSpeakingFrame(),
|
||||||
TranscriptionFrame(
|
TranscriptionFrame(
|
||||||
user_id=participant_id,
|
user_id=participant_id,
|
||||||
timestamp=json_data["timestamp"],
|
timestamp=json_data["timestamp"],
|
||||||
text=json_data["message"],
|
text=json_data["message"],
|
||||||
),
|
),
|
||||||
UserStoppedSpeakingFrame(),
|
UserStoppedSpeakingFrame(),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
|||||||
from pipecat.services.cartesia.tts import CartesiaHttpTTSService
|
from pipecat.services.cartesia.tts import CartesiaHttpTTSService
|
||||||
from pipecat.services.fal.image import FalImageGenService
|
from pipecat.services.fal.image import FalImageGenService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
@@ -63,26 +64,7 @@ class MonthPrepender(FrameProcessor):
|
|||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_out_enabled=True,
|
|
||||||
video_out_enabled=True,
|
|
||||||
video_out_width=1024,
|
|
||||||
video_out_height=1024,
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_out_enabled=True,
|
|
||||||
video_out_enabled=True,
|
|
||||||
video_out_width=1024,
|
|
||||||
video_out_height=1024,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
"""Run the Calendar Month Narration bot using WebRTC transport.
|
"""Run the Calendar Month Narration bot using WebRTC transport.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -91,6 +73,17 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
"""
|
"""
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
# Create a transport using the WebRTC connection
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_out_enabled=True,
|
||||||
|
video_out_enabled=True,
|
||||||
|
video_out_width=1024,
|
||||||
|
video_out_height=1024,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Create an HTTP session for API calls
|
# Create an HTTP session for API calls
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"))
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"))
|
||||||
@@ -166,14 +159,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
# Run the pipeline
|
# Run the pipeline
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
|||||||
from pipecat.services.cartesia.tts import CartesiaTTSService
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
@@ -53,31 +53,18 @@ class MetricsLogger(FrameProcessor):
|
|||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -130,13 +117,17 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
|||||||
from pipecat.services.cartesia.tts import CartesiaTTSService
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
@@ -67,32 +68,21 @@ class ImageSyncAggregator(FrameProcessor):
|
|||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
video_out_enabled=True,
|
|
||||||
video_out_width=1024,
|
|
||||||
video_out_height=1024,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
video_out_enabled=True,
|
|
||||||
video_out_width=1024,
|
|
||||||
video_out_height=1024,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
video_out_enabled=True,
|
||||||
|
video_out_width=1024,
|
||||||
|
video_out_height=1024,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -133,8 +123,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -147,13 +139,17 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -18,37 +18,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.cartesia.tts import CartesiaTTSService
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -83,8 +71,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -98,14 +88,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -10,49 +10,37 @@ import os
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
||||||
from pipecat.services.cartesia.tts import CartesiaHttpTTSService
|
from pipecat.processors.audio.vad.silero import SileroVAD
|
||||||
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = CartesiaHttpTTSService(
|
vad = SileroVAD()
|
||||||
|
|
||||||
|
tts = CartesiaTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
voice_id="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady
|
voice_id="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady
|
||||||
)
|
)
|
||||||
@@ -71,21 +59,24 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
|
|
||||||
pipeline = Pipeline(
|
pipeline = Pipeline(
|
||||||
[
|
[
|
||||||
transport.input(), # Transport user input
|
transport.input(),
|
||||||
stt,
|
stt,
|
||||||
context_aggregator.user(), # User responses
|
vad,
|
||||||
llm, # LLM
|
context_aggregator.user(),
|
||||||
tts, # TTS
|
llm,
|
||||||
transport.output(), # Transport bot output
|
tts,
|
||||||
context_aggregator.assistant(), # Assistant spoken responses
|
transport.output(),
|
||||||
|
context_aggregator.assistant(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -99,14 +90,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
@@ -27,9 +27,9 @@ from pipecat.processors.aggregators.llm_response import (
|
|||||||
from pipecat.processors.frameworks.langchain import LangchainProcessor
|
from pipecat.processors.frameworks.langchain import LangchainProcessor
|
||||||
from pipecat.services.cartesia.tts import CartesiaTTSService
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
@@ -43,31 +43,18 @@ def get_session_history(session_id: str) -> BaseChatMessageHistory:
|
|||||||
return message_store[session_id]
|
return message_store[session_id]
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -113,8 +100,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -131,14 +120,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -24,35 +24,24 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.deepgram.tts import DeepgramTTSService
|
from pipecat.services.deepgram.tts import DeepgramTTSService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(
|
stt = DeepgramSTTService(
|
||||||
api_key=os.getenv("DEEPGRAM_API_KEY"),
|
api_key=os.getenv("DEEPGRAM_API_KEY"),
|
||||||
live_options=LiveOptions(vad_events=True, utterance_end_ms="1000"),
|
live_options=LiveOptions(vad_events=True, utterance_end_ms="1000"),
|
||||||
@@ -87,8 +76,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,14 +101,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -18,38 +18,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.deepgram.tts import DeepgramTTSService
|
from pipecat.services.deepgram.tts import DeepgramTTSService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = DeepgramTTSService(api_key=os.getenv("DEEPGRAM_API_KEY"), voice="aura-2-andromeda-en")
|
tts = DeepgramTTSService(api_key=os.getenv("DEEPGRAM_API_KEY"), voice="aura-2-andromeda-en")
|
||||||
@@ -81,8 +68,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -96,14 +85,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -19,38 +19,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.elevenlabs.tts import ElevenLabsHttpTTSService
|
from pipecat.services.elevenlabs.tts import ElevenLabsHttpTTSService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Create an HTTP session
|
# Create an HTTP session
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
@@ -88,8 +75,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,14 +92,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -18,38 +18,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.elevenlabs.tts import ElevenLabsTTSService
|
from pipecat.services.elevenlabs.tts import ElevenLabsTTSService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = ElevenLabsTTSService(
|
tts = ElevenLabsTTSService(
|
||||||
@@ -84,8 +71,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -99,14 +88,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -18,37 +18,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.services.playht.tts import PlayHTHttpTTSService
|
from pipecat.services.playht.tts import PlayHTHttpTTSService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = PlayHTHttpTTSService(
|
tts = PlayHTHttpTTSService(
|
||||||
@@ -84,8 +72,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -99,14 +89,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -19,37 +19,25 @@ from pipecat.services.deepgram.stt import DeepgramSTTService
|
|||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.services.playht.tts import PlayHTTTSService
|
from pipecat.services.playht.tts import PlayHTTTSService
|
||||||
from pipecat.transcriptions.language import Language
|
from pipecat.transcriptions.language import Language
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = PlayHTTTSService(
|
tts = PlayHTTTSService(
|
||||||
@@ -86,8 +74,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -101,14 +91,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -18,37 +18,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.azure.llm import AzureLLMService
|
from pipecat.services.azure.llm import AzureLLMService
|
||||||
from pipecat.services.azure.stt import AzureSTTService
|
from pipecat.services.azure.stt import AzureSTTService
|
||||||
from pipecat.services.azure.tts import AzureTTSService
|
from pipecat.services.azure.tts import AzureTTSService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = AzureSTTService(
|
stt = AzureSTTService(
|
||||||
api_key=os.getenv("AZURE_SPEECH_API_KEY"),
|
api_key=os.getenv("AZURE_SPEECH_API_KEY"),
|
||||||
region=os.getenv("AZURE_SPEECH_REGION"),
|
region=os.getenv("AZURE_SPEECH_REGION"),
|
||||||
@@ -90,8 +78,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -105,14 +95,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -18,37 +18,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.services.openai.stt import OpenAISTTService
|
from pipecat.services.openai.stt import OpenAISTTService
|
||||||
from pipecat.services.openai.tts import OpenAITTSService
|
from pipecat.services.openai.tts import OpenAITTSService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = OpenAISTTService(
|
stt = OpenAISTTService(
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
api_key=os.getenv("OPENAI_API_KEY"),
|
||||||
model="gpt-4o-transcribe",
|
model="gpt-4o-transcribe",
|
||||||
@@ -84,9 +72,11 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
audio_out_sample_rate=24000,
|
audio_out_sample_rate=24000,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -100,14 +90,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -19,37 +19,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.cartesia.tts import CartesiaTTSService
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.openpipe.llm import OpenPipeLLMService
|
from pipecat.services.openpipe.llm import OpenPipeLLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
@@ -89,8 +77,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,14 +94,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -19,37 +19,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.services.xtts.tts import XTTSService
|
from pipecat.services.xtts.tts import XTTSService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Create an HTTP session
|
# Create an HTTP session
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
@@ -87,8 +75,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -102,14 +92,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -20,37 +20,25 @@ from pipecat.services.gladia.config import GladiaInputParams, LanguageConfig
|
|||||||
from pipecat.services.gladia.stt import GladiaSTTService
|
from pipecat.services.gladia.stt import GladiaSTTService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transcriptions.language import Language
|
from pipecat.transcriptions.language import Language
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = GladiaSTTService(
|
stt = GladiaSTTService(
|
||||||
api_key=os.getenv("GLADIA_API_KEY", ""),
|
api_key=os.getenv("GLADIA_API_KEY", ""),
|
||||||
params=GladiaInputParams(
|
params=GladiaInputParams(
|
||||||
@@ -92,8 +80,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -107,13 +97,17 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -18,37 +18,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.lmnt.tts import LmntTTSService
|
from pipecat.services.lmnt.tts import LmntTTSService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = LmntTTSService(api_key=os.getenv("LMNT_API_KEY"), voice_id="morgan")
|
tts = LmntTTSService(api_key=os.getenv("LMNT_API_KEY"), voice_id="morgan")
|
||||||
@@ -80,8 +68,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -95,14 +85,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -19,37 +19,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.groq.llm import GroqLLMService
|
from pipecat.services.groq.llm import GroqLLMService
|
||||||
from pipecat.services.groq.stt import GroqSTTService
|
from pipecat.services.groq.stt import GroqSTTService
|
||||||
from pipecat.services.groq.tts import GroqTTSService
|
from pipecat.services.groq.tts import GroqTTSService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = GroqSTTService(api_key=os.getenv("GROQ_API_KEY"))
|
stt = GroqSTTService(api_key=os.getenv("GROQ_API_KEY"))
|
||||||
|
|
||||||
llm = GroqLLMService(
|
llm = GroqLLMService(
|
||||||
@@ -85,6 +73,7 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
),
|
),
|
||||||
@@ -100,14 +89,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -17,37 +17,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.aws.llm import AWSBedrockLLMService
|
from pipecat.services.aws.llm import AWSBedrockLLMService
|
||||||
from pipecat.services.aws.stt import AWSTranscribeSTTService
|
from pipecat.services.aws.stt import AWSTranscribeSTTService
|
||||||
from pipecat.services.aws.tts import AWSPollyTTSService
|
from pipecat.services.aws.tts import AWSPollyTTSService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = AWSTranscribeSTTService()
|
stt = AWSTranscribeSTTService()
|
||||||
|
|
||||||
tts = AWSPollyTTSService(
|
tts = AWSPollyTTSService(
|
||||||
@@ -87,8 +75,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -102,14 +92,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -19,37 +19,25 @@ from pipecat.services.google.llm import GoogleLLMService
|
|||||||
from pipecat.services.google.stt import GoogleSTTService
|
from pipecat.services.google.stt import GoogleSTTService
|
||||||
from pipecat.services.google.tts import GoogleTTSService
|
from pipecat.services.google.tts import GoogleTTSService
|
||||||
from pipecat.transcriptions.language import Language
|
from pipecat.transcriptions.language import Language
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = GoogleSTTService(
|
stt = GoogleSTTService(
|
||||||
params=GoogleSTTService.InputParams(languages=Language.EN_US),
|
params=GoogleSTTService.InputParams(languages=Language.EN_US),
|
||||||
credentials=os.getenv("GOOGLE_TEST_CREDENTIALS"),
|
credentials=os.getenv("GOOGLE_TEST_CREDENTIALS"),
|
||||||
@@ -88,8 +76,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,14 +93,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||
@@ -18,38 +18,25 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.services.assemblyai.stt import AssemblyAISTTService
|
from pipecat.services.assemblyai.stt import AssemblyAISTTService
|
||||||
from pipecat.services.cartesia.tts import CartesiaTTSService
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||||
from pipecat.transports.services.daily import DailyParams
|
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
|
async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespace):
|
||||||
# instantiated. The function will be called when the desired transport gets
|
|
||||||
# selected.
|
|
||||||
transport_params = {
|
|
||||||
"daily": lambda: DailyParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"twilio": lambda: FastAPIWebsocketParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
"webrtc": lambda: TransportParams(
|
|
||||||
audio_in_enabled=True,
|
|
||||||
audio_out_enabled=True,
|
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
|
transport = SmallWebRTCTransport(
|
||||||
|
webrtc_connection=webrtc_connection,
|
||||||
|
params=TransportParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
stt = AssemblyAISTTService(
|
stt = AssemblyAISTTService(
|
||||||
api_key=os.getenv("ASSEMBLYAI_API_KEY"),
|
api_key=os.getenv("ASSEMBLYAI_API_KEY"),
|
||||||
)
|
)
|
||||||
@@ -86,8 +73,10 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
task = PipelineTask(
|
task = PipelineTask(
|
||||||
pipeline,
|
pipeline,
|
||||||
params=PipelineParams(
|
params=PipelineParams(
|
||||||
|
allow_interruptions=True,
|
||||||
enable_metrics=True,
|
enable_metrics=True,
|
||||||
enable_usage_metrics=True,
|
enable_usage_metrics=True,
|
||||||
|
report_only_initial_ttfb=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -101,14 +90,18 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
@transport.event_handler("on_client_disconnected")
|
@transport.event_handler("on_client_disconnected")
|
||||||
async def on_client_disconnected(transport, client):
|
async def on_client_disconnected(transport, client):
|
||||||
logger.info(f"Client disconnected")
|
logger.info(f"Client disconnected")
|
||||||
|
|
||||||
|
@transport.event_handler("on_client_closed")
|
||||||
|
async def on_client_closed(transport, client):
|
||||||
|
logger.info(f"Client closed connection")
|
||||||
await task.cancel()
|
await task.cancel()
|
||||||
|
|
||||||
runner = PipelineRunner(handle_sigint=handle_sigint)
|
runner = PipelineRunner(handle_sigint=False)
|
||||||
|
|
||||||
await runner.run(task)
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from pipecat.examples.run import main
|
from run import main
|
||||||
|
|
||||||
main(run_example, transport_params=transport_params)
|
main()
|
||||||
|
|||||||