dv-pipecat-ai 0.0.74.dev770__py3-none-any.whl → 0.0.82.dev776__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dv-pipecat-ai might be problematic. Click here for more details.
- {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/METADATA +137 -93
- dv_pipecat_ai-0.0.82.dev776.dist-info/RECORD +340 -0
- pipecat/__init__.py +17 -0
- pipecat/adapters/base_llm_adapter.py +36 -1
- pipecat/adapters/schemas/direct_function.py +296 -0
- pipecat/adapters/schemas/function_schema.py +15 -6
- pipecat/adapters/schemas/tools_schema.py +55 -7
- pipecat/adapters/services/anthropic_adapter.py +22 -3
- pipecat/adapters/services/aws_nova_sonic_adapter.py +23 -3
- pipecat/adapters/services/bedrock_adapter.py +22 -3
- pipecat/adapters/services/gemini_adapter.py +16 -3
- pipecat/adapters/services/open_ai_adapter.py +17 -2
- pipecat/adapters/services/open_ai_realtime_adapter.py +23 -3
- pipecat/audio/filters/base_audio_filter.py +30 -6
- pipecat/audio/filters/koala_filter.py +37 -2
- pipecat/audio/filters/krisp_filter.py +59 -6
- pipecat/audio/filters/noisereduce_filter.py +37 -0
- pipecat/audio/interruptions/base_interruption_strategy.py +25 -5
- pipecat/audio/interruptions/min_words_interruption_strategy.py +21 -4
- pipecat/audio/mixers/base_audio_mixer.py +30 -7
- pipecat/audio/mixers/soundfile_mixer.py +53 -6
- pipecat/audio/resamplers/base_audio_resampler.py +17 -9
- pipecat/audio/resamplers/resampy_resampler.py +26 -1
- pipecat/audio/resamplers/soxr_resampler.py +32 -1
- pipecat/audio/resamplers/soxr_stream_resampler.py +101 -0
- pipecat/audio/utils.py +194 -1
- pipecat/audio/vad/silero.py +60 -3
- pipecat/audio/vad/vad_analyzer.py +114 -30
- pipecat/clocks/base_clock.py +19 -0
- pipecat/clocks/system_clock.py +25 -0
- pipecat/extensions/voicemail/__init__.py +0 -0
- pipecat/extensions/voicemail/voicemail_detector.py +707 -0
- pipecat/frames/frames.py +590 -156
- pipecat/metrics/metrics.py +64 -1
- pipecat/observers/base_observer.py +58 -19
- pipecat/observers/loggers/debug_log_observer.py +56 -64
- pipecat/observers/loggers/llm_log_observer.py +8 -1
- pipecat/observers/loggers/transcription_log_observer.py +19 -7
- pipecat/observers/loggers/user_bot_latency_log_observer.py +32 -5
- pipecat/observers/turn_tracking_observer.py +26 -1
- pipecat/pipeline/base_pipeline.py +5 -7
- pipecat/pipeline/base_task.py +52 -9
- pipecat/pipeline/parallel_pipeline.py +121 -177
- pipecat/pipeline/pipeline.py +129 -20
- pipecat/pipeline/runner.py +50 -1
- pipecat/pipeline/sync_parallel_pipeline.py +132 -32
- pipecat/pipeline/task.py +263 -280
- pipecat/pipeline/task_observer.py +85 -34
- pipecat/pipeline/to_be_updated/merge_pipeline.py +32 -2
- pipecat/processors/aggregators/dtmf_aggregator.py +29 -22
- pipecat/processors/aggregators/gated.py +25 -24
- pipecat/processors/aggregators/gated_openai_llm_context.py +22 -2
- pipecat/processors/aggregators/llm_response.py +398 -89
- pipecat/processors/aggregators/openai_llm_context.py +161 -13
- pipecat/processors/aggregators/sentence.py +25 -14
- pipecat/processors/aggregators/user_response.py +28 -3
- pipecat/processors/aggregators/vision_image_frame.py +24 -14
- pipecat/processors/async_generator.py +28 -0
- pipecat/processors/audio/audio_buffer_processor.py +78 -37
- pipecat/processors/consumer_processor.py +25 -6
- pipecat/processors/filters/frame_filter.py +23 -0
- pipecat/processors/filters/function_filter.py +30 -0
- pipecat/processors/filters/identity_filter.py +17 -2
- pipecat/processors/filters/null_filter.py +24 -1
- pipecat/processors/filters/stt_mute_filter.py +56 -21
- pipecat/processors/filters/wake_check_filter.py +46 -3
- pipecat/processors/filters/wake_notifier_filter.py +21 -3
- pipecat/processors/frame_processor.py +488 -131
- pipecat/processors/frameworks/langchain.py +38 -3
- pipecat/processors/frameworks/rtvi.py +719 -34
- pipecat/processors/gstreamer/pipeline_source.py +41 -0
- pipecat/processors/idle_frame_processor.py +26 -3
- pipecat/processors/logger.py +23 -0
- pipecat/processors/metrics/frame_processor_metrics.py +77 -4
- pipecat/processors/metrics/sentry.py +42 -4
- pipecat/processors/producer_processor.py +34 -14
- pipecat/processors/text_transformer.py +22 -10
- pipecat/processors/transcript_processor.py +48 -29
- pipecat/processors/user_idle_processor.py +31 -21
- pipecat/runner/__init__.py +1 -0
- pipecat/runner/daily.py +132 -0
- pipecat/runner/livekit.py +148 -0
- pipecat/runner/run.py +543 -0
- pipecat/runner/types.py +67 -0
- pipecat/runner/utils.py +515 -0
- pipecat/serializers/base_serializer.py +42 -0
- pipecat/serializers/exotel.py +17 -6
- pipecat/serializers/genesys.py +95 -0
- pipecat/serializers/livekit.py +33 -0
- pipecat/serializers/plivo.py +16 -15
- pipecat/serializers/protobuf.py +37 -1
- pipecat/serializers/telnyx.py +18 -17
- pipecat/serializers/twilio.py +32 -16
- pipecat/services/ai_service.py +5 -3
- pipecat/services/anthropic/llm.py +113 -43
- pipecat/services/assemblyai/models.py +63 -5
- pipecat/services/assemblyai/stt.py +64 -11
- pipecat/services/asyncai/__init__.py +0 -0
- pipecat/services/asyncai/tts.py +501 -0
- pipecat/services/aws/llm.py +185 -111
- pipecat/services/aws/stt.py +217 -23
- pipecat/services/aws/tts.py +118 -52
- pipecat/services/aws/utils.py +101 -5
- pipecat/services/aws_nova_sonic/aws.py +82 -64
- pipecat/services/aws_nova_sonic/context.py +15 -6
- pipecat/services/azure/common.py +10 -2
- pipecat/services/azure/image.py +32 -0
- pipecat/services/azure/llm.py +9 -7
- pipecat/services/azure/stt.py +65 -2
- pipecat/services/azure/tts.py +154 -23
- pipecat/services/cartesia/stt.py +125 -8
- pipecat/services/cartesia/tts.py +102 -38
- pipecat/services/cerebras/llm.py +15 -23
- pipecat/services/deepgram/stt.py +19 -11
- pipecat/services/deepgram/tts.py +36 -0
- pipecat/services/deepseek/llm.py +14 -23
- pipecat/services/elevenlabs/tts.py +330 -64
- pipecat/services/fal/image.py +43 -0
- pipecat/services/fal/stt.py +48 -10
- pipecat/services/fireworks/llm.py +14 -21
- pipecat/services/fish/tts.py +109 -9
- pipecat/services/gemini_multimodal_live/__init__.py +1 -0
- pipecat/services/gemini_multimodal_live/events.py +83 -2
- pipecat/services/gemini_multimodal_live/file_api.py +189 -0
- pipecat/services/gemini_multimodal_live/gemini.py +218 -21
- pipecat/services/gladia/config.py +17 -10
- pipecat/services/gladia/stt.py +82 -36
- pipecat/services/google/frames.py +40 -0
- pipecat/services/google/google.py +2 -0
- pipecat/services/google/image.py +39 -2
- pipecat/services/google/llm.py +176 -58
- pipecat/services/google/llm_openai.py +26 -4
- pipecat/services/google/llm_vertex.py +37 -15
- pipecat/services/google/rtvi.py +41 -0
- pipecat/services/google/stt.py +65 -17
- pipecat/services/google/test-google-chirp.py +45 -0
- pipecat/services/google/tts.py +390 -19
- pipecat/services/grok/llm.py +8 -6
- pipecat/services/groq/llm.py +8 -6
- pipecat/services/groq/stt.py +13 -9
- pipecat/services/groq/tts.py +40 -0
- pipecat/services/hamsa/__init__.py +9 -0
- pipecat/services/hamsa/stt.py +241 -0
- pipecat/services/heygen/__init__.py +5 -0
- pipecat/services/heygen/api.py +281 -0
- pipecat/services/heygen/client.py +620 -0
- pipecat/services/heygen/video.py +338 -0
- pipecat/services/image_service.py +5 -3
- pipecat/services/inworld/__init__.py +1 -0
- pipecat/services/inworld/tts.py +592 -0
- pipecat/services/llm_service.py +127 -45
- pipecat/services/lmnt/tts.py +80 -7
- pipecat/services/mcp_service.py +85 -44
- pipecat/services/mem0/memory.py +42 -13
- pipecat/services/minimax/tts.py +74 -15
- pipecat/services/mistral/__init__.py +0 -0
- pipecat/services/mistral/llm.py +185 -0
- pipecat/services/moondream/vision.py +55 -10
- pipecat/services/neuphonic/tts.py +275 -48
- pipecat/services/nim/llm.py +8 -6
- pipecat/services/ollama/llm.py +27 -7
- pipecat/services/openai/base_llm.py +54 -16
- pipecat/services/openai/image.py +30 -0
- pipecat/services/openai/llm.py +7 -5
- pipecat/services/openai/stt.py +13 -9
- pipecat/services/openai/tts.py +42 -10
- pipecat/services/openai_realtime_beta/azure.py +11 -9
- pipecat/services/openai_realtime_beta/context.py +7 -5
- pipecat/services/openai_realtime_beta/events.py +10 -7
- pipecat/services/openai_realtime_beta/openai.py +37 -18
- pipecat/services/openpipe/llm.py +30 -24
- pipecat/services/openrouter/llm.py +9 -7
- pipecat/services/perplexity/llm.py +15 -19
- pipecat/services/piper/tts.py +26 -12
- pipecat/services/playht/tts.py +227 -65
- pipecat/services/qwen/llm.py +8 -6
- pipecat/services/rime/tts.py +128 -17
- pipecat/services/riva/stt.py +160 -22
- pipecat/services/riva/tts.py +67 -2
- pipecat/services/sambanova/llm.py +19 -17
- pipecat/services/sambanova/stt.py +14 -8
- pipecat/services/sarvam/tts.py +60 -13
- pipecat/services/simli/video.py +82 -21
- pipecat/services/soniox/__init__.py +0 -0
- pipecat/services/soniox/stt.py +398 -0
- pipecat/services/speechmatics/stt.py +29 -17
- pipecat/services/stt_service.py +47 -11
- pipecat/services/tavus/video.py +94 -25
- pipecat/services/together/llm.py +8 -6
- pipecat/services/tts_service.py +77 -53
- pipecat/services/ultravox/stt.py +46 -43
- pipecat/services/vision_service.py +5 -3
- pipecat/services/websocket_service.py +12 -11
- pipecat/services/whisper/base_stt.py +58 -12
- pipecat/services/whisper/stt.py +69 -58
- pipecat/services/xtts/tts.py +59 -2
- pipecat/sync/base_notifier.py +19 -0
- pipecat/sync/event_notifier.py +24 -0
- pipecat/tests/utils.py +73 -5
- pipecat/transcriptions/language.py +24 -0
- pipecat/transports/base_input.py +112 -8
- pipecat/transports/base_output.py +235 -13
- pipecat/transports/base_transport.py +119 -0
- pipecat/transports/local/audio.py +76 -0
- pipecat/transports/local/tk.py +84 -0
- pipecat/transports/network/fastapi_websocket.py +174 -15
- pipecat/transports/network/small_webrtc.py +383 -39
- pipecat/transports/network/webrtc_connection.py +214 -8
- pipecat/transports/network/websocket_client.py +171 -1
- pipecat/transports/network/websocket_server.py +147 -9
- pipecat/transports/services/daily.py +792 -70
- pipecat/transports/services/helpers/daily_rest.py +122 -129
- pipecat/transports/services/livekit.py +339 -4
- pipecat/transports/services/tavus.py +273 -38
- pipecat/utils/asyncio/task_manager.py +92 -186
- pipecat/utils/base_object.py +83 -1
- pipecat/utils/network.py +2 -0
- pipecat/utils/string.py +114 -58
- pipecat/utils/text/base_text_aggregator.py +44 -13
- pipecat/utils/text/base_text_filter.py +46 -0
- pipecat/utils/text/markdown_text_filter.py +70 -14
- pipecat/utils/text/pattern_pair_aggregator.py +18 -14
- pipecat/utils/text/simple_text_aggregator.py +43 -2
- pipecat/utils/text/skip_tags_aggregator.py +21 -13
- pipecat/utils/time.py +36 -0
- pipecat/utils/tracing/class_decorators.py +32 -7
- pipecat/utils/tracing/conversation_context_provider.py +12 -2
- pipecat/utils/tracing/service_attributes.py +80 -64
- pipecat/utils/tracing/service_decorators.py +48 -21
- pipecat/utils/tracing/setup.py +13 -7
- pipecat/utils/tracing/turn_context_provider.py +12 -2
- pipecat/utils/tracing/turn_trace_observer.py +27 -0
- pipecat/utils/utils.py +14 -14
- dv_pipecat_ai-0.0.74.dev770.dist-info/RECORD +0 -319
- pipecat/examples/daily_runner.py +0 -64
- pipecat/examples/run.py +0 -265
- pipecat/utils/asyncio/watchdog_async_iterator.py +0 -72
- pipecat/utils/asyncio/watchdog_event.py +0 -42
- pipecat/utils/asyncio/watchdog_priority_queue.py +0 -48
- pipecat/utils/asyncio/watchdog_queue.py +0 -48
- {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/WHEEL +0 -0
- {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/licenses/LICENSE +0 -0
- {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/top_level.txt +0 -0
- /pipecat/{examples → extensions}/__init__.py +0 -0
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2024–2025, Daily
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
"""Tavus transport implementation for Pipecat.
|
|
8
|
+
|
|
9
|
+
This module provides integration with the Tavus platform for creating conversational
|
|
10
|
+
AI applications with avatars. It manages conversation sessions and provides real-time
|
|
11
|
+
audio/video streaming capabilities through the Tavus API.
|
|
12
|
+
"""
|
|
13
|
+
|
|
1
14
|
import os
|
|
2
15
|
from functools import partial
|
|
3
16
|
from typing import Any, Awaitable, Callable, Mapping, Optional
|
|
@@ -7,7 +20,6 @@ from daily.daily import AudioData
|
|
|
7
20
|
from loguru import logger
|
|
8
21
|
from pydantic import BaseModel
|
|
9
22
|
|
|
10
|
-
from pipecat.audio.utils import create_default_resampler
|
|
11
23
|
from pipecat.frames.frames import (
|
|
12
24
|
CancelFrame,
|
|
13
25
|
EndFrame,
|
|
@@ -31,8 +43,10 @@ from pipecat.transports.services.daily import (
|
|
|
31
43
|
|
|
32
44
|
|
|
33
45
|
class TavusApi:
|
|
34
|
-
"""
|
|
35
|
-
|
|
46
|
+
"""Helper class for interacting with the Tavus API (v2).
|
|
47
|
+
|
|
48
|
+
Provides methods for creating and managing conversations with Tavus avatars,
|
|
49
|
+
including conversation lifecycle management and persona information retrieval.
|
|
36
50
|
"""
|
|
37
51
|
|
|
38
52
|
BASE_URL = "https://tavusapi.com/v2"
|
|
@@ -40,12 +54,11 @@ class TavusApi:
|
|
|
40
54
|
MOCK_PERSONA_NAME = "TestTavusTransport"
|
|
41
55
|
|
|
42
56
|
def __init__(self, api_key: str, session: aiohttp.ClientSession):
|
|
43
|
-
"""
|
|
44
|
-
Initialize the TavusApi client.
|
|
57
|
+
"""Initialize the TavusApi client.
|
|
45
58
|
|
|
46
59
|
Args:
|
|
47
|
-
api_key
|
|
48
|
-
session
|
|
60
|
+
api_key: Tavus API key for authentication.
|
|
61
|
+
session: An aiohttp session for making HTTP requests.
|
|
49
62
|
"""
|
|
50
63
|
self._api_key = api_key
|
|
51
64
|
self._session = session
|
|
@@ -54,6 +67,15 @@ class TavusApi:
|
|
|
54
67
|
self._dev_room_url = os.getenv("TAVUS_SAMPLE_ROOM_URL")
|
|
55
68
|
|
|
56
69
|
async def create_conversation(self, replica_id: str, persona_id: str) -> dict:
|
|
70
|
+
"""Create a new conversation with the specified replica and persona.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
replica_id: ID of the replica to use in the conversation.
|
|
74
|
+
persona_id: ID of the persona to use in the conversation.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dictionary containing conversation_id and conversation_url.
|
|
78
|
+
"""
|
|
57
79
|
if self._dev_room_url:
|
|
58
80
|
return {
|
|
59
81
|
"conversation_id": self.MOCK_CONVERSATION_ID,
|
|
@@ -73,6 +95,11 @@ class TavusApi:
|
|
|
73
95
|
return response
|
|
74
96
|
|
|
75
97
|
async def end_conversation(self, conversation_id: str):
|
|
98
|
+
"""End an existing conversation.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
conversation_id: ID of the conversation to end.
|
|
102
|
+
"""
|
|
76
103
|
if conversation_id is None or conversation_id == self.MOCK_CONVERSATION_ID:
|
|
77
104
|
return
|
|
78
105
|
|
|
@@ -82,6 +109,14 @@ class TavusApi:
|
|
|
82
109
|
logger.debug(f"Ended Tavus conversation {conversation_id}")
|
|
83
110
|
|
|
84
111
|
async def get_persona_name(self, persona_id: str) -> str:
|
|
112
|
+
"""Get the name of a persona by ID.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
persona_id: ID of the persona to retrieve.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
The name of the persona.
|
|
119
|
+
"""
|
|
85
120
|
if self._dev_room_url is not None:
|
|
86
121
|
return self.MOCK_PERSONA_NAME
|
|
87
122
|
|
|
@@ -94,11 +129,11 @@ class TavusApi:
|
|
|
94
129
|
|
|
95
130
|
|
|
96
131
|
class TavusCallbacks(BaseModel):
|
|
97
|
-
"""Callback handlers for
|
|
132
|
+
"""Callback handlers for Tavus events.
|
|
98
133
|
|
|
99
|
-
|
|
100
|
-
on_participant_joined: Called when a participant joins.
|
|
101
|
-
on_participant_left: Called when a participant leaves.
|
|
134
|
+
Parameters:
|
|
135
|
+
on_participant_joined: Called when a participant joins the conversation.
|
|
136
|
+
on_participant_left: Called when a participant leaves the conversation.
|
|
102
137
|
"""
|
|
103
138
|
|
|
104
139
|
on_participant_joined: Callable[[Mapping[str, Any]], Awaitable[None]]
|
|
@@ -106,7 +141,13 @@ class TavusCallbacks(BaseModel):
|
|
|
106
141
|
|
|
107
142
|
|
|
108
143
|
class TavusParams(DailyParams):
|
|
109
|
-
"""Configuration parameters for the Tavus transport.
|
|
144
|
+
"""Configuration parameters for the Tavus transport.
|
|
145
|
+
|
|
146
|
+
Parameters:
|
|
147
|
+
audio_in_enabled: Whether to enable audio input from participants.
|
|
148
|
+
audio_out_enabled: Whether to enable audio output to participants.
|
|
149
|
+
microphone_out_enabled: Whether to enable microphone output track.
|
|
150
|
+
"""
|
|
110
151
|
|
|
111
152
|
audio_in_enabled: bool = True
|
|
112
153
|
audio_out_enabled: bool = True
|
|
@@ -114,24 +155,14 @@ class TavusParams(DailyParams):
|
|
|
114
155
|
|
|
115
156
|
|
|
116
157
|
class TavusTransportClient:
|
|
117
|
-
"""
|
|
158
|
+
"""Transport client that integrates Pipecat with the Tavus platform.
|
|
159
|
+
|
|
118
160
|
A transport client that integrates a Pipecat Bot with the Tavus platform by managing
|
|
119
161
|
conversation sessions using the Tavus API.
|
|
120
162
|
|
|
121
163
|
This client uses `TavusApi` to interact with the Tavus backend services. When a conversation
|
|
122
164
|
is started via `TavusApi`, Tavus provides a `roomURL` that can be used to connect the Pipecat Bot
|
|
123
165
|
into the same virtual room where the TavusBot is operating.
|
|
124
|
-
|
|
125
|
-
Args:
|
|
126
|
-
bot_name (str): The name of the Pipecat bot instance.
|
|
127
|
-
params (TavusParams): Optional parameters for Tavus operation. Defaults to `TavusParams()`.
|
|
128
|
-
callbacks (TavusCallbacks): Callback handlers for Tavus-related events.
|
|
129
|
-
api_key (str): API key for authenticating with Tavus API.
|
|
130
|
-
replica_id (str): ID of the replica to use in the Tavus conversation.
|
|
131
|
-
persona_id (str): ID of the Tavus persona. Defaults to "pipecat-stream", which signals Tavus to use
|
|
132
|
-
the TTS voice of the Pipecat bot instead of a Tavus persona voice.
|
|
133
|
-
session (aiohttp.ClientSession): The aiohttp session for making async HTTP requests.
|
|
134
|
-
sample_rate: Audio sample rate to be used by the client.
|
|
135
166
|
"""
|
|
136
167
|
|
|
137
168
|
def __init__(
|
|
@@ -145,6 +176,19 @@ class TavusTransportClient:
|
|
|
145
176
|
persona_id: str = "pipecat-stream",
|
|
146
177
|
session: aiohttp.ClientSession,
|
|
147
178
|
) -> None:
|
|
179
|
+
"""Initialize the Tavus transport client.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
bot_name: The name of the Pipecat bot instance.
|
|
183
|
+
params: Optional parameters for Tavus operation.
|
|
184
|
+
callbacks: Callback handlers for Tavus-related events.
|
|
185
|
+
api_key: API key for authenticating with Tavus API.
|
|
186
|
+
replica_id: ID of the replica to use in the Tavus conversation.
|
|
187
|
+
persona_id: ID of the Tavus persona. Defaults to "pipecat-stream",
|
|
188
|
+
which signals Tavus to use the TTS voice of the Pipecat bot
|
|
189
|
+
instead of a Tavus persona voice.
|
|
190
|
+
session: The aiohttp session for making async HTTP requests.
|
|
191
|
+
"""
|
|
148
192
|
self._bot_name = bot_name
|
|
149
193
|
self._api = TavusApi(api_key, session)
|
|
150
194
|
self._replica_id = replica_id
|
|
@@ -155,11 +199,17 @@ class TavusTransportClient:
|
|
|
155
199
|
self._params = params
|
|
156
200
|
|
|
157
201
|
async def _initialize(self) -> str:
|
|
202
|
+
"""Initialize the conversation and return the room URL."""
|
|
158
203
|
response = await self._api.create_conversation(self._replica_id, self._persona_id)
|
|
159
204
|
self._conversation_id = response["conversation_id"]
|
|
160
205
|
return response["conversation_url"]
|
|
161
206
|
|
|
162
207
|
async def setup(self, setup: FrameProcessorSetup):
|
|
208
|
+
"""Setup the client and initialize the conversation.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
setup: The frame processor setup configuration.
|
|
212
|
+
"""
|
|
163
213
|
if self._conversation_id is not None:
|
|
164
214
|
logger.debug(f"Conversation ID already defined: {self._conversation_id}")
|
|
165
215
|
return
|
|
@@ -195,6 +245,10 @@ class TavusTransportClient:
|
|
|
195
245
|
on_recording_started=partial(self._on_handle_callback, "on_recording_started"),
|
|
196
246
|
on_recording_stopped=partial(self._on_handle_callback, "on_recording_stopped"),
|
|
197
247
|
on_recording_error=partial(self._on_handle_callback, "on_recording_error"),
|
|
248
|
+
on_transcription_stopped=partial(
|
|
249
|
+
self._on_handle_callback, "on_transcription_stopped"
|
|
250
|
+
),
|
|
251
|
+
on_transcription_error=partial(self._on_handle_callback, "on_transcription_error"),
|
|
198
252
|
)
|
|
199
253
|
self._client = DailyTransportClient(
|
|
200
254
|
room_url, None, "Pipecat", self._params, daily_callbacks, self._bot_name
|
|
@@ -206,29 +260,44 @@ class TavusTransportClient:
|
|
|
206
260
|
self._conversation_id = None
|
|
207
261
|
|
|
208
262
|
async def cleanup(self):
|
|
263
|
+
"""Cleanup client resources."""
|
|
209
264
|
try:
|
|
210
265
|
await self._client.cleanup()
|
|
211
266
|
except Exception as e:
|
|
212
267
|
logger.exception(f"Exception during cleanup: {e}")
|
|
213
268
|
|
|
214
269
|
async def _on_joined(self, data):
|
|
270
|
+
"""Handle joined event."""
|
|
215
271
|
logger.debug("TavusTransportClient joined!")
|
|
216
272
|
|
|
217
273
|
async def _on_left(self):
|
|
274
|
+
"""Handle left event."""
|
|
218
275
|
logger.debug("TavusTransportClient left!")
|
|
219
276
|
|
|
220
277
|
async def _on_handle_callback(self, event_name, *args, **kwargs):
|
|
278
|
+
"""Handle generic callback events."""
|
|
221
279
|
logger.trace(f"[Callback] {event_name} called with args={args}, kwargs={kwargs}")
|
|
222
280
|
|
|
223
281
|
async def get_persona_name(self) -> str:
|
|
282
|
+
"""Get the persona name from the API.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
The name of the current persona.
|
|
286
|
+
"""
|
|
224
287
|
return await self._api.get_persona_name(self._persona_id)
|
|
225
288
|
|
|
226
289
|
async def start(self, frame: StartFrame):
|
|
290
|
+
"""Start the client and join the room.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
frame: The start frame containing initialization parameters.
|
|
294
|
+
"""
|
|
227
295
|
logger.debug("TavusTransportClient start invoked!")
|
|
228
296
|
await self._client.start(frame)
|
|
229
297
|
await self._client.join()
|
|
230
298
|
|
|
231
299
|
async def stop(self):
|
|
300
|
+
"""Stop the client and end the conversation."""
|
|
232
301
|
await self._client.leave()
|
|
233
302
|
await self._api.end_conversation(self._conversation_id)
|
|
234
303
|
self._conversation_id = None
|
|
@@ -241,6 +310,15 @@ class TavusTransportClient:
|
|
|
241
310
|
video_source: str = "camera",
|
|
242
311
|
color_format: str = "RGB",
|
|
243
312
|
):
|
|
313
|
+
"""Capture video from a participant.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
participant_id: ID of the participant to capture video from.
|
|
317
|
+
callback: Callback function to handle video frames.
|
|
318
|
+
framerate: Desired framerate for video capture.
|
|
319
|
+
video_source: Video source to capture from.
|
|
320
|
+
color_format: Color format for video frames.
|
|
321
|
+
"""
|
|
244
322
|
await self._client.capture_participant_video(
|
|
245
323
|
participant_id, callback, framerate, video_source, color_format
|
|
246
324
|
)
|
|
@@ -253,22 +331,47 @@ class TavusTransportClient:
|
|
|
253
331
|
sample_rate: int = 16000,
|
|
254
332
|
callback_interval_ms: int = 20,
|
|
255
333
|
):
|
|
334
|
+
"""Capture audio from a participant.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
participant_id: ID of the participant to capture audio from.
|
|
338
|
+
callback: Callback function to handle audio data.
|
|
339
|
+
audio_source: Audio source to capture from.
|
|
340
|
+
sample_rate: Desired sample rate for audio capture.
|
|
341
|
+
callback_interval_ms: Interval between audio callbacks in milliseconds.
|
|
342
|
+
"""
|
|
256
343
|
await self._client.capture_participant_audio(
|
|
257
344
|
participant_id, callback, audio_source, sample_rate, callback_interval_ms
|
|
258
345
|
)
|
|
259
346
|
|
|
260
347
|
async def send_message(self, frame: TransportMessageFrame | TransportMessageUrgentFrame):
|
|
348
|
+
"""Send a message to participants.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
frame: The message frame to send.
|
|
352
|
+
"""
|
|
261
353
|
await self._client.send_message(frame)
|
|
262
354
|
|
|
263
355
|
@property
|
|
264
356
|
def out_sample_rate(self) -> int:
|
|
357
|
+
"""Get the output sample rate.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
The output sample rate in Hz.
|
|
361
|
+
"""
|
|
265
362
|
return self._client.out_sample_rate
|
|
266
363
|
|
|
267
364
|
@property
|
|
268
365
|
def in_sample_rate(self) -> int:
|
|
366
|
+
"""Get the input sample rate.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
The input sample rate in Hz.
|
|
370
|
+
"""
|
|
269
371
|
return self._client.in_sample_rate
|
|
270
372
|
|
|
271
373
|
async def send_interrupt_message(self) -> None:
|
|
374
|
+
"""Send an interrupt message to the conversation."""
|
|
272
375
|
transport_frame = TransportMessageUrgentFrame(
|
|
273
376
|
message={
|
|
274
377
|
"message_type": "conversation",
|
|
@@ -279,6 +382,12 @@ class TavusTransportClient:
|
|
|
279
382
|
await self.send_message(transport_frame)
|
|
280
383
|
|
|
281
384
|
async def update_subscriptions(self, participant_settings=None, profile_settings=None):
|
|
385
|
+
"""Update subscription settings for participants.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
participant_settings: Per-participant subscription settings.
|
|
389
|
+
profile_settings: Global subscription profile settings.
|
|
390
|
+
"""
|
|
282
391
|
if not self._client:
|
|
283
392
|
return
|
|
284
393
|
|
|
@@ -287,11 +396,21 @@ class TavusTransportClient:
|
|
|
287
396
|
)
|
|
288
397
|
|
|
289
398
|
async def write_audio_frame(self, frame: OutputAudioRawFrame):
|
|
399
|
+
"""Write an audio frame to the transport.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
frame: The audio frame to write.
|
|
403
|
+
"""
|
|
290
404
|
if not self._client:
|
|
291
405
|
return
|
|
292
406
|
await self._client.write_audio_frame(frame)
|
|
293
407
|
|
|
294
408
|
async def register_audio_destination(self, destination: str):
|
|
409
|
+
"""Register an audio destination for output.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
destination: The destination identifier to register.
|
|
413
|
+
"""
|
|
295
414
|
if not self._client:
|
|
296
415
|
return
|
|
297
416
|
|
|
@@ -299,29 +418,51 @@ class TavusTransportClient:
|
|
|
299
418
|
|
|
300
419
|
|
|
301
420
|
class TavusInputTransport(BaseInputTransport):
|
|
421
|
+
"""Input transport for receiving audio and events from Tavus conversations.
|
|
422
|
+
|
|
423
|
+
Handles incoming audio streams from participants and manages audio capture
|
|
424
|
+
from the Daily room connected to the Tavus conversation.
|
|
425
|
+
"""
|
|
426
|
+
|
|
302
427
|
def __init__(
|
|
303
428
|
self,
|
|
304
429
|
client: TavusTransportClient,
|
|
305
430
|
params: TransportParams,
|
|
306
431
|
**kwargs,
|
|
307
432
|
):
|
|
433
|
+
"""Initialize the Tavus input transport.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
client: The Tavus transport client instance.
|
|
437
|
+
params: Transport configuration parameters.
|
|
438
|
+
**kwargs: Additional arguments passed to parent class.
|
|
439
|
+
"""
|
|
308
440
|
super().__init__(params, **kwargs)
|
|
309
441
|
self._client = client
|
|
310
442
|
self._params = params
|
|
311
|
-
self._resampler = create_default_resampler()
|
|
312
|
-
|
|
313
443
|
# Whether we have seen a StartFrame already.
|
|
314
444
|
self._initialized = False
|
|
315
445
|
|
|
316
446
|
async def setup(self, setup: FrameProcessorSetup):
|
|
447
|
+
"""Setup the input transport.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
setup: The frame processor setup configuration.
|
|
451
|
+
"""
|
|
317
452
|
await super().setup(setup)
|
|
318
453
|
await self._client.setup(setup)
|
|
319
454
|
|
|
320
455
|
async def cleanup(self):
|
|
456
|
+
"""Cleanup input transport resources."""
|
|
321
457
|
await super().cleanup()
|
|
322
458
|
await self._client.cleanup()
|
|
323
459
|
|
|
324
460
|
async def start(self, frame: StartFrame):
|
|
461
|
+
"""Start the input transport.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
frame: The start frame containing initialization parameters.
|
|
465
|
+
"""
|
|
325
466
|
await super().start(frame)
|
|
326
467
|
|
|
327
468
|
if self._initialized:
|
|
@@ -333,14 +474,29 @@ class TavusInputTransport(BaseInputTransport):
|
|
|
333
474
|
await self.set_transport_ready(frame)
|
|
334
475
|
|
|
335
476
|
async def stop(self, frame: EndFrame):
|
|
477
|
+
"""Stop the input transport.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
frame: The end frame signaling transport shutdown.
|
|
481
|
+
"""
|
|
336
482
|
await super().stop(frame)
|
|
337
483
|
await self._client.stop()
|
|
338
484
|
|
|
339
485
|
async def cancel(self, frame: CancelFrame):
|
|
486
|
+
"""Cancel the input transport.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
frame: The cancel frame signaling immediate cancellation.
|
|
490
|
+
"""
|
|
340
491
|
await super().cancel(frame)
|
|
341
492
|
await self._client.stop()
|
|
342
493
|
|
|
343
494
|
async def start_capturing_audio(self, participant):
|
|
495
|
+
"""Start capturing audio from a participant.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
participant: The participant to capture audio from.
|
|
499
|
+
"""
|
|
344
500
|
if self._params.audio_in_enabled:
|
|
345
501
|
logger.info(
|
|
346
502
|
f"TavusTransportClient start capturing audio for participant {participant['id']}"
|
|
@@ -354,6 +510,7 @@ class TavusInputTransport(BaseInputTransport):
|
|
|
354
510
|
async def _on_participant_audio_data(
|
|
355
511
|
self, participant_id: str, audio: AudioData, audio_source: str
|
|
356
512
|
):
|
|
513
|
+
"""Handle received participant audio data."""
|
|
357
514
|
frame = InputAudioRawFrame(
|
|
358
515
|
audio=audio.audio_frames,
|
|
359
516
|
sample_rate=audio.audio_frames,
|
|
@@ -364,12 +521,25 @@ class TavusInputTransport(BaseInputTransport):
|
|
|
364
521
|
|
|
365
522
|
|
|
366
523
|
class TavusOutputTransport(BaseOutputTransport):
|
|
524
|
+
"""Output transport for sending audio and events to Tavus conversations.
|
|
525
|
+
|
|
526
|
+
Handles outgoing audio streams to participants and manages the custom
|
|
527
|
+
audio track expected by the Tavus platform.
|
|
528
|
+
"""
|
|
529
|
+
|
|
367
530
|
def __init__(
|
|
368
531
|
self,
|
|
369
532
|
client: TavusTransportClient,
|
|
370
533
|
params: TransportParams,
|
|
371
534
|
**kwargs,
|
|
372
535
|
):
|
|
536
|
+
"""Initialize the Tavus output transport.
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
client: The Tavus transport client instance.
|
|
540
|
+
params: Transport configuration parameters.
|
|
541
|
+
**kwargs: Additional arguments passed to parent class.
|
|
542
|
+
"""
|
|
373
543
|
super().__init__(params, **kwargs)
|
|
374
544
|
self._client = client
|
|
375
545
|
self._params = params
|
|
@@ -380,14 +550,25 @@ class TavusOutputTransport(BaseOutputTransport):
|
|
|
380
550
|
self._transport_destination: Optional[str] = "stream"
|
|
381
551
|
|
|
382
552
|
async def setup(self, setup: FrameProcessorSetup):
|
|
553
|
+
"""Setup the output transport.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
setup: The frame processor setup configuration.
|
|
557
|
+
"""
|
|
383
558
|
await super().setup(setup)
|
|
384
559
|
await self._client.setup(setup)
|
|
385
560
|
|
|
386
561
|
async def cleanup(self):
|
|
562
|
+
"""Cleanup output transport resources."""
|
|
387
563
|
await super().cleanup()
|
|
388
564
|
await self._client.cleanup()
|
|
389
565
|
|
|
390
566
|
async def start(self, frame: StartFrame):
|
|
567
|
+
"""Start the output transport.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
frame: The start frame containing initialization parameters.
|
|
571
|
+
"""
|
|
391
572
|
await super().start(frame)
|
|
392
573
|
|
|
393
574
|
if self._initialized:
|
|
@@ -403,51 +584,72 @@ class TavusOutputTransport(BaseOutputTransport):
|
|
|
403
584
|
await self.set_transport_ready(frame)
|
|
404
585
|
|
|
405
586
|
async def stop(self, frame: EndFrame):
|
|
587
|
+
"""Stop the output transport.
|
|
588
|
+
|
|
589
|
+
Args:
|
|
590
|
+
frame: The end frame signaling transport shutdown.
|
|
591
|
+
"""
|
|
406
592
|
await super().stop(frame)
|
|
407
593
|
await self._client.stop()
|
|
408
594
|
|
|
409
595
|
async def cancel(self, frame: CancelFrame):
|
|
596
|
+
"""Cancel the output transport.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
frame: The cancel frame signaling immediate cancellation.
|
|
600
|
+
"""
|
|
410
601
|
await super().cancel(frame)
|
|
411
602
|
await self._client.stop()
|
|
412
603
|
|
|
413
604
|
async def send_message(self, frame: TransportMessageFrame | TransportMessageUrgentFrame):
|
|
605
|
+
"""Send a message to participants.
|
|
606
|
+
|
|
607
|
+
Args:
|
|
608
|
+
frame: The message frame to send.
|
|
609
|
+
"""
|
|
414
610
|
logger.info(f"TavusOutputTransport sending message {frame}")
|
|
415
611
|
await self._client.send_message(frame)
|
|
416
612
|
|
|
417
613
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
|
614
|
+
"""Process frames and handle interruptions.
|
|
615
|
+
|
|
616
|
+
Args:
|
|
617
|
+
frame: The frame to process.
|
|
618
|
+
direction: The direction of frame flow in the pipeline.
|
|
619
|
+
"""
|
|
418
620
|
await super().process_frame(frame, direction)
|
|
419
621
|
if isinstance(frame, StartInterruptionFrame):
|
|
420
622
|
await self._handle_interruptions()
|
|
421
623
|
|
|
422
624
|
async def _handle_interruptions(self):
|
|
625
|
+
"""Handle interruption events by sending interrupt message."""
|
|
423
626
|
await self._client.send_interrupt_message()
|
|
424
627
|
|
|
425
628
|
async def write_audio_frame(self, frame: OutputAudioRawFrame):
|
|
629
|
+
"""Write an audio frame to the Tavus transport.
|
|
630
|
+
|
|
631
|
+
Args:
|
|
632
|
+
frame: The audio frame to write.
|
|
633
|
+
"""
|
|
426
634
|
# This is the custom track destination expected by Tavus
|
|
427
635
|
frame.transport_destination = self._transport_destination
|
|
428
636
|
await self._client.write_audio_frame(frame)
|
|
429
637
|
|
|
430
638
|
async def register_audio_destination(self, destination: str):
|
|
639
|
+
"""Register an audio destination.
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
destination: The destination identifier to register.
|
|
643
|
+
"""
|
|
431
644
|
await self._client.register_audio_destination(destination)
|
|
432
645
|
|
|
433
646
|
|
|
434
647
|
class TavusTransport(BaseTransport):
|
|
435
|
-
"""
|
|
436
|
-
Transport implementation for Tavus video calls.
|
|
648
|
+
"""Transport implementation for Tavus video calls.
|
|
437
649
|
|
|
438
650
|
When used, the Pipecat bot joins the same virtual room as the Tavus Avatar and the user.
|
|
439
651
|
This is achieved by using `TavusTransportClient`, which initiates the conversation via
|
|
440
652
|
`TavusApi` and obtains a room URL that all participants connect to.
|
|
441
|
-
|
|
442
|
-
Args:
|
|
443
|
-
bot_name (str): The name of the Pipecat bot.
|
|
444
|
-
session (aiohttp.ClientSession): aiohttp session used for async HTTP requests.
|
|
445
|
-
api_key (str): Tavus API key for authentication.
|
|
446
|
-
replica_id (str): ID of the replica model used for voice generation.
|
|
447
|
-
persona_id (str): ID of the Tavus persona. Defaults to "pipecat-stream" to use the Pipecat TTS voice.
|
|
448
|
-
params (TavusParams): Optional Tavus-specific configuration parameters.
|
|
449
|
-
input_name (Optional[str]): Optional name for the input transport.
|
|
450
|
-
output_name (Optional[str]): Optional name for the output transport.
|
|
451
653
|
"""
|
|
452
654
|
|
|
453
655
|
def __init__(
|
|
@@ -461,6 +663,19 @@ class TavusTransport(BaseTransport):
|
|
|
461
663
|
input_name: Optional[str] = None,
|
|
462
664
|
output_name: Optional[str] = None,
|
|
463
665
|
):
|
|
666
|
+
"""Initialize the Tavus transport.
|
|
667
|
+
|
|
668
|
+
Args:
|
|
669
|
+
bot_name: The name of the Pipecat bot.
|
|
670
|
+
session: aiohttp session used for async HTTP requests.
|
|
671
|
+
api_key: Tavus API key for authentication.
|
|
672
|
+
replica_id: ID of the replica model used for voice generation.
|
|
673
|
+
persona_id: ID of the Tavus persona. Defaults to "pipecat-stream"
|
|
674
|
+
to use the Pipecat TTS voice.
|
|
675
|
+
params: Optional Tavus-specific configuration parameters.
|
|
676
|
+
input_name: Optional name for the input transport.
|
|
677
|
+
output_name: Optional name for the output transport.
|
|
678
|
+
"""
|
|
464
679
|
super().__init__(input_name=input_name, output_name=output_name)
|
|
465
680
|
self._params = params
|
|
466
681
|
|
|
@@ -487,11 +702,13 @@ class TavusTransport(BaseTransport):
|
|
|
487
702
|
self._register_event_handler("on_client_disconnected")
|
|
488
703
|
|
|
489
704
|
async def _on_participant_left(self, participant, reason):
|
|
705
|
+
"""Handle participant left events."""
|
|
490
706
|
persona_name = await self._client.get_persona_name()
|
|
491
707
|
if participant.get("info", {}).get("userName", "") != persona_name:
|
|
492
708
|
await self._on_client_disconnected(participant)
|
|
493
709
|
|
|
494
710
|
async def _on_participant_joined(self, participant):
|
|
711
|
+
"""Handle participant joined events."""
|
|
495
712
|
# get persona, look up persona_name, set this as the bot name to ignore
|
|
496
713
|
persona_name = await self._client.get_persona_name()
|
|
497
714
|
|
|
@@ -513,23 +730,41 @@ class TavusTransport(BaseTransport):
|
|
|
513
730
|
await self._input.start_capturing_audio(participant)
|
|
514
731
|
|
|
515
732
|
async def update_subscriptions(self, participant_settings=None, profile_settings=None):
|
|
733
|
+
"""Update subscription settings for participants.
|
|
734
|
+
|
|
735
|
+
Args:
|
|
736
|
+
participant_settings: Per-participant subscription settings.
|
|
737
|
+
profile_settings: Global subscription profile settings.
|
|
738
|
+
"""
|
|
516
739
|
await self._client.update_subscriptions(
|
|
517
740
|
participant_settings=participant_settings,
|
|
518
741
|
profile_settings=profile_settings,
|
|
519
742
|
)
|
|
520
743
|
|
|
521
744
|
def input(self) -> FrameProcessor:
|
|
745
|
+
"""Get the input transport for receiving media and events.
|
|
746
|
+
|
|
747
|
+
Returns:
|
|
748
|
+
The Tavus input transport instance.
|
|
749
|
+
"""
|
|
522
750
|
if not self._input:
|
|
523
751
|
self._input = TavusInputTransport(client=self._client, params=self._params)
|
|
524
752
|
return self._input
|
|
525
753
|
|
|
526
754
|
def output(self) -> FrameProcessor:
|
|
755
|
+
"""Get the output transport for sending media and events.
|
|
756
|
+
|
|
757
|
+
Returns:
|
|
758
|
+
The Tavus output transport instance.
|
|
759
|
+
"""
|
|
527
760
|
if not self._output:
|
|
528
761
|
self._output = TavusOutputTransport(client=self._client, params=self._params)
|
|
529
762
|
return self._output
|
|
530
763
|
|
|
531
764
|
async def _on_client_connected(self, participant: Any):
|
|
765
|
+
"""Handle client connected events."""
|
|
532
766
|
await self._call_event_handler("on_client_connected", participant)
|
|
533
767
|
|
|
534
768
|
async def _on_client_disconnected(self, participant: Any):
|
|
769
|
+
"""Handle client disconnected events."""
|
|
535
770
|
await self._call_event_handler("on_client_disconnected", participant)
|