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
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
+
"""Transcript processing utilities for conversation recording and analysis.
|
|
8
|
+
|
|
9
|
+
This module provides processors that convert speech and text frames into structured
|
|
10
|
+
transcript messages with timestamps, enabling conversation history tracking and analysis.
|
|
11
|
+
"""
|
|
12
|
+
|
|
7
13
|
from typing import List, Optional
|
|
8
14
|
|
|
9
15
|
from loguru import logger
|
|
@@ -30,7 +36,11 @@ class BaseTranscriptProcessor(FrameProcessor):
|
|
|
30
36
|
"""
|
|
31
37
|
|
|
32
38
|
def __init__(self, **kwargs):
|
|
33
|
-
"""Initialize processor with empty message store.
|
|
39
|
+
"""Initialize processor with empty message store.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
**kwargs: Additional arguments passed to parent class.
|
|
43
|
+
"""
|
|
34
44
|
super().__init__(**kwargs)
|
|
35
45
|
self._processed_messages: List[TranscriptionMessage] = []
|
|
36
46
|
self._register_event_handler("on_transcript_update")
|
|
@@ -39,7 +49,7 @@ class BaseTranscriptProcessor(FrameProcessor):
|
|
|
39
49
|
"""Emit transcript updates for new messages.
|
|
40
50
|
|
|
41
51
|
Args:
|
|
42
|
-
messages: New messages to emit in update
|
|
52
|
+
messages: New messages to emit in update.
|
|
43
53
|
"""
|
|
44
54
|
if messages:
|
|
45
55
|
self._processed_messages.extend(messages)
|
|
@@ -55,8 +65,8 @@ class UserTranscriptProcessor(BaseTranscriptProcessor):
|
|
|
55
65
|
"""Process TranscriptionFrames into user conversation messages.
|
|
56
66
|
|
|
57
67
|
Args:
|
|
58
|
-
frame: Input frame to process
|
|
59
|
-
direction: Frame processing direction
|
|
68
|
+
frame: Input frame to process.
|
|
69
|
+
direction: Frame processing direction.
|
|
60
70
|
"""
|
|
61
71
|
await super().process_frame(frame, direction)
|
|
62
72
|
|
|
@@ -74,17 +84,18 @@ class AssistantTranscriptProcessor(BaseTranscriptProcessor):
|
|
|
74
84
|
|
|
75
85
|
This processor aggregates TTS text frames into complete utterances and emits them as
|
|
76
86
|
transcript messages. Utterances are completed when:
|
|
87
|
+
|
|
77
88
|
- The bot stops speaking (BotStoppedSpeakingFrame)
|
|
78
89
|
- The bot is interrupted (StartInterruptionFrame)
|
|
79
90
|
- The pipeline ends (EndFrame)
|
|
80
|
-
|
|
81
|
-
Attributes:
|
|
82
|
-
_current_text_parts: List of text fragments being aggregated for current utterance
|
|
83
|
-
_aggregation_start_time: Timestamp when the current utterance began
|
|
84
91
|
"""
|
|
85
92
|
|
|
86
93
|
def __init__(self, **kwargs):
|
|
87
|
-
"""Initialize processor with aggregation state.
|
|
94
|
+
"""Initialize processor with aggregation state.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
**kwargs: Additional arguments passed to parent class.
|
|
98
|
+
"""
|
|
88
99
|
super().__init__(**kwargs)
|
|
89
100
|
self._current_text_parts: List[str] = []
|
|
90
101
|
self._aggregation_start_time: Optional[str] = None
|
|
@@ -98,42 +109,44 @@ class AssistantTranscriptProcessor(BaseTranscriptProcessor):
|
|
|
98
109
|
TTS services with different formatting patterns.
|
|
99
110
|
|
|
100
111
|
Examples:
|
|
101
|
-
Fragments with embedded spacing (concatenated)
|
|
102
|
-
|
|
112
|
+
Fragments with embedded spacing (concatenated)::
|
|
113
|
+
|
|
103
114
|
TTSTextFrame: ["Hello"]
|
|
104
115
|
TTSTextFrame: [" there"] # Leading space
|
|
105
116
|
TTSTextFrame: ["!"]
|
|
106
117
|
TTSTextFrame: [" How"] # Leading space
|
|
107
118
|
TTSTextFrame: ["'s"]
|
|
108
119
|
TTSTextFrame: [" it"] # Leading space
|
|
109
|
-
|
|
120
|
+
|
|
110
121
|
Result: "Hello there! How's it"
|
|
111
122
|
|
|
112
|
-
Fragments with trailing spaces (concatenated)
|
|
113
|
-
|
|
123
|
+
Fragments with trailing spaces (concatenated)::
|
|
124
|
+
|
|
114
125
|
TTSTextFrame: ["Hel"]
|
|
115
126
|
TTSTextFrame: ["lo "] # Trailing space
|
|
116
127
|
TTSTextFrame: ["to "] # Trailing space
|
|
117
128
|
TTSTextFrame: ["you"]
|
|
118
|
-
|
|
129
|
+
|
|
119
130
|
Result: "Hello to you"
|
|
120
131
|
|
|
121
|
-
Word-by-word fragments without spacing (joined with spaces)
|
|
122
|
-
|
|
132
|
+
Word-by-word fragments without spacing (joined with spaces)::
|
|
133
|
+
|
|
123
134
|
TTSTextFrame: ["Hello"]
|
|
124
135
|
TTSTextFrame: ["there"]
|
|
125
136
|
TTSTextFrame: ["how"]
|
|
126
137
|
TTSTextFrame: ["are"]
|
|
127
138
|
TTSTextFrame: ["you"]
|
|
128
|
-
|
|
139
|
+
|
|
129
140
|
Result: "Hello there how are you"
|
|
130
141
|
"""
|
|
131
142
|
if self._current_text_parts and self._aggregation_start_time:
|
|
143
|
+
# Check specifically for space characters, previously isspace() was used
|
|
144
|
+
# but that includes all whitespace characters (e.g. \n), not just spaces.
|
|
132
145
|
has_leading_spaces = any(
|
|
133
|
-
part and part[0]
|
|
146
|
+
part and part[0] == " " for part in self._current_text_parts[1:]
|
|
134
147
|
)
|
|
135
148
|
has_trailing_spaces = any(
|
|
136
|
-
part and part[-1]
|
|
149
|
+
part and part[-1] == " " for part in self._current_text_parts[:-1]
|
|
137
150
|
)
|
|
138
151
|
|
|
139
152
|
# If there are embedded spaces in the fragments, use direct concatenation
|
|
@@ -169,6 +182,7 @@ class AssistantTranscriptProcessor(BaseTranscriptProcessor):
|
|
|
169
182
|
"""Process frames into assistant conversation messages.
|
|
170
183
|
|
|
171
184
|
Handles different frame types:
|
|
185
|
+
|
|
172
186
|
- TTSTextFrame: Aggregates text for current utterance
|
|
173
187
|
- BotStoppedSpeakingFrame: Completes current utterance
|
|
174
188
|
- StartInterruptionFrame: Completes current utterance due to interruption
|
|
@@ -176,8 +190,8 @@ class AssistantTranscriptProcessor(BaseTranscriptProcessor):
|
|
|
176
190
|
- CancelFrame: Completes current utterance due to cancellation
|
|
177
191
|
|
|
178
192
|
Args:
|
|
179
|
-
frame: Input frame to process
|
|
180
|
-
direction: Frame processing direction
|
|
193
|
+
frame: Input frame to process.
|
|
194
|
+
direction: Frame processing direction.
|
|
181
195
|
"""
|
|
182
196
|
await super().process_frame(frame, direction)
|
|
183
197
|
|
|
@@ -211,8 +225,8 @@ class TranscriptProcessor:
|
|
|
211
225
|
Provides unified access to user and assistant transcript processors
|
|
212
226
|
with shared event handling.
|
|
213
227
|
|
|
214
|
-
Example
|
|
215
|
-
|
|
228
|
+
Example::
|
|
229
|
+
|
|
216
230
|
transcript = TranscriptProcessor()
|
|
217
231
|
|
|
218
232
|
pipeline = Pipeline(
|
|
@@ -232,7 +246,6 @@ class TranscriptProcessor:
|
|
|
232
246
|
@transcript.event_handler("on_transcript_update")
|
|
233
247
|
async def handle_update(processor, frame):
|
|
234
248
|
print(f"New messages: {frame.messages}")
|
|
235
|
-
```
|
|
236
249
|
"""
|
|
237
250
|
|
|
238
251
|
def __init__(self):
|
|
@@ -245,7 +258,10 @@ class TranscriptProcessor:
|
|
|
245
258
|
"""Get the user transcript processor.
|
|
246
259
|
|
|
247
260
|
Args:
|
|
248
|
-
**kwargs: Arguments specific to UserTranscriptProcessor
|
|
261
|
+
**kwargs: Arguments specific to UserTranscriptProcessor.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
The user transcript processor instance.
|
|
249
265
|
"""
|
|
250
266
|
if self._user_processor is None:
|
|
251
267
|
self._user_processor = UserTranscriptProcessor(**kwargs)
|
|
@@ -262,7 +278,10 @@ class TranscriptProcessor:
|
|
|
262
278
|
"""Get the assistant transcript processor.
|
|
263
279
|
|
|
264
280
|
Args:
|
|
265
|
-
**kwargs: Arguments specific to AssistantTranscriptProcessor
|
|
281
|
+
**kwargs: Arguments specific to AssistantTranscriptProcessor.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
The assistant transcript processor instance.
|
|
266
285
|
"""
|
|
267
286
|
if self._assistant_processor is None:
|
|
268
287
|
self._assistant_processor = AssistantTranscriptProcessor(**kwargs)
|
|
@@ -279,10 +298,10 @@ class TranscriptProcessor:
|
|
|
279
298
|
"""Register event handler for both processors.
|
|
280
299
|
|
|
281
300
|
Args:
|
|
282
|
-
event_name: Name of event to handle
|
|
301
|
+
event_name: Name of event to handle.
|
|
283
302
|
|
|
284
303
|
Returns:
|
|
285
|
-
Decorator function that registers handler with both processors
|
|
304
|
+
Decorator function that registers handler with both processors.
|
|
286
305
|
"""
|
|
287
306
|
|
|
288
307
|
def decorator(handler):
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
+
"""User idle detection and timeout handling for Pipecat."""
|
|
8
|
+
|
|
7
9
|
import asyncio
|
|
8
10
|
import inspect
|
|
9
11
|
from typing import Awaitable, Callable, Union
|
|
@@ -21,6 +23,9 @@ from pipecat.frames.frames import (
|
|
|
21
23
|
InputDTMFFrame,
|
|
22
24
|
StartUserIdleProcessorFrame,
|
|
23
25
|
StopUserIdleProcessorFrame,
|
|
26
|
+
FunctionCallInProgressFrame,
|
|
27
|
+
FunctionCallResultFrame,
|
|
28
|
+
StartFrame,
|
|
24
29
|
UserStartedSpeakingFrame,
|
|
25
30
|
UserStoppedSpeakingFrame,
|
|
26
31
|
)
|
|
@@ -30,19 +35,12 @@ from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
|
|
30
35
|
class UserIdleProcessor(FrameProcessor):
|
|
31
36
|
"""Monitors user inactivity and triggers callbacks after timeout periods.
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
This processor tracks user activity and triggers configurable callbacks when
|
|
39
|
+
users become idle. It starts monitoring only after the first conversation
|
|
40
|
+
activity and supports both basic and retry-based callback patterns.
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
callback: Function to call when user is idle. Can be either:
|
|
38
|
-
- Basic callback(processor) -> None
|
|
39
|
-
- Retry callback(processor, retry_count) -> bool
|
|
40
|
-
Return True to continue monitoring for idle events,
|
|
41
|
-
Return False to stop the idle monitoring task
|
|
42
|
-
timeout: Seconds to wait before considering user idle
|
|
43
|
-
**kwargs: Additional arguments passed to FrameProcessor
|
|
42
|
+
Example::
|
|
44
43
|
|
|
45
|
-
Example:
|
|
46
44
|
# Retry callback:
|
|
47
45
|
async def handle_idle(processor: "UserIdleProcessor", retry_count: int) -> bool:
|
|
48
46
|
if retry_count < 3:
|
|
@@ -70,6 +68,16 @@ class UserIdleProcessor(FrameProcessor):
|
|
|
70
68
|
timeout: float,
|
|
71
69
|
**kwargs,
|
|
72
70
|
):
|
|
71
|
+
"""Initialize the user idle processor.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
callback: Function to call when user is idle. Can be either a basic
|
|
75
|
+
callback taking only the processor, or a retry callback taking
|
|
76
|
+
the processor and retry count. Retry callbacks should return
|
|
77
|
+
True to continue monitoring or False to stop.
|
|
78
|
+
timeout: Seconds to wait before considering user idle.
|
|
79
|
+
**kwargs: Additional arguments passed to FrameProcessor.
|
|
80
|
+
"""
|
|
73
81
|
super().__init__(**kwargs)
|
|
74
82
|
self._callback = self._wrap_callback(callback)
|
|
75
83
|
self._timeout = timeout
|
|
@@ -78,7 +86,6 @@ class UserIdleProcessor(FrameProcessor):
|
|
|
78
86
|
self._conversation_started = False
|
|
79
87
|
self._idle_task = None
|
|
80
88
|
self._idle_event = asyncio.Event()
|
|
81
|
-
self._function_call_in_progress = False
|
|
82
89
|
|
|
83
90
|
def _wrap_callback(
|
|
84
91
|
self,
|
|
@@ -116,7 +123,11 @@ class UserIdleProcessor(FrameProcessor):
|
|
|
116
123
|
|
|
117
124
|
@property
|
|
118
125
|
def retry_count(self) -> int:
|
|
119
|
-
"""
|
|
126
|
+
"""Get the current retry count.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
The number of times the idle callback has been triggered.
|
|
130
|
+
"""
|
|
120
131
|
return self._retry_count
|
|
121
132
|
|
|
122
133
|
async def _stop(self) -> None:
|
|
@@ -131,8 +142,8 @@ class UserIdleProcessor(FrameProcessor):
|
|
|
131
142
|
"""Processes incoming frames and manages idle monitoring state.
|
|
132
143
|
|
|
133
144
|
Args:
|
|
134
|
-
frame: The frame to process
|
|
135
|
-
direction: Direction of the frame flow
|
|
145
|
+
frame: The frame to process.
|
|
146
|
+
direction: Direction of the frame flow.
|
|
136
147
|
"""
|
|
137
148
|
await super().process_frame(frame, direction)
|
|
138
149
|
|
|
@@ -168,12 +179,11 @@ class UserIdleProcessor(FrameProcessor):
|
|
|
168
179
|
elif isinstance(frame, InputDTMFFrame):
|
|
169
180
|
self._idle_event.set()
|
|
170
181
|
elif isinstance(frame, FunctionCallInProgressFrame):
|
|
171
|
-
|
|
172
|
-
self.
|
|
182
|
+
# Function calls can take longer than the timeout, so we want to prevent idle callbacks
|
|
183
|
+
self._interrupted = True
|
|
173
184
|
self._idle_event.set()
|
|
174
|
-
elif isinstance(frame,
|
|
175
|
-
self.
|
|
176
|
-
self.logger.debug("Got Function call result")
|
|
185
|
+
elif isinstance(frame, FunctionCallResultFrame):
|
|
186
|
+
self._interrupted = False
|
|
177
187
|
self._idle_event.set()
|
|
178
188
|
elif isinstance(frame, StartUserIdleProcessorFrame):
|
|
179
189
|
if not self._idle_task:
|
|
@@ -200,7 +210,7 @@ class UserIdleProcessor(FrameProcessor):
|
|
|
200
210
|
try:
|
|
201
211
|
await asyncio.wait_for(self._idle_event.wait(), timeout=self._timeout)
|
|
202
212
|
except asyncio.TimeoutError:
|
|
203
|
-
if not self._interrupted
|
|
213
|
+
if not self._interrupted:
|
|
204
214
|
self._retry_count += 1
|
|
205
215
|
should_continue = await self._callback(self, self._retry_count)
|
|
206
216
|
if not should_continue:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Pipecat runner package for local and cloud bot execution."""
|
pipecat/runner/daily.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2024–2025, Daily
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
"""Daily room and token configuration utilities.
|
|
8
|
+
|
|
9
|
+
This module provides helper functions for creating and configuring Daily rooms
|
|
10
|
+
and authentication tokens. It automatically creates temporary rooms for
|
|
11
|
+
development or uses existing rooms specified via environment variables.
|
|
12
|
+
|
|
13
|
+
Environment variables:
|
|
14
|
+
|
|
15
|
+
- DAILY_API_KEY - Daily API key for room/token creation (required)
|
|
16
|
+
- DAILY_SAMPLE_ROOM_URL (optional) - Existing room URL to use. If not provided,
|
|
17
|
+
a temporary room will be created automatically.
|
|
18
|
+
|
|
19
|
+
Example::
|
|
20
|
+
|
|
21
|
+
import aiohttp
|
|
22
|
+
from pipecat.runner.daily import configure
|
|
23
|
+
|
|
24
|
+
async with aiohttp.ClientSession() as session:
|
|
25
|
+
room_url, token = await configure(session)
|
|
26
|
+
# Use room_url and token with DailyTransport
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import os
|
|
30
|
+
import time
|
|
31
|
+
import uuid
|
|
32
|
+
from typing import Tuple
|
|
33
|
+
|
|
34
|
+
import aiohttp
|
|
35
|
+
|
|
36
|
+
from pipecat.transports.services.helpers.daily_rest import (
|
|
37
|
+
DailyRESTHelper,
|
|
38
|
+
DailyRoomParams,
|
|
39
|
+
DailyRoomProperties,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def configure(aiohttp_session: aiohttp.ClientSession) -> Tuple[str, str]:
|
|
44
|
+
"""Configure Daily room URL and token from environment variables.
|
|
45
|
+
|
|
46
|
+
This function will either:
|
|
47
|
+
1. Use an existing room URL from DAILY_SAMPLE_ROOM_URL environment variable
|
|
48
|
+
2. Create a new temporary room automatically if no URL is provided
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
aiohttp_session: HTTP session for making API requests.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Tuple containing the room URL and authentication token.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
Exception: If DAILY_API_KEY is not provided in environment variables.
|
|
58
|
+
"""
|
|
59
|
+
# Check for required API key
|
|
60
|
+
api_key = os.getenv("DAILY_API_KEY")
|
|
61
|
+
if not api_key:
|
|
62
|
+
raise Exception(
|
|
63
|
+
"DAILY_API_KEY environment variable is required. "
|
|
64
|
+
"Get your API key from https://dashboard.daily.co/developers"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Check for existing room URL
|
|
68
|
+
existing_room_url = os.getenv("DAILY_SAMPLE_ROOM_URL")
|
|
69
|
+
|
|
70
|
+
daily_rest_helper = DailyRESTHelper(
|
|
71
|
+
daily_api_key=api_key,
|
|
72
|
+
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
|
73
|
+
aiohttp_session=aiohttp_session,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if existing_room_url:
|
|
77
|
+
# Use existing room
|
|
78
|
+
print(f"Using existing Daily room: {existing_room_url}")
|
|
79
|
+
room_url = existing_room_url
|
|
80
|
+
else:
|
|
81
|
+
# Create a new temporary room
|
|
82
|
+
room_name = f"pipecat-{uuid.uuid4().hex[:8]}"
|
|
83
|
+
print(f"Creating new Daily room: {room_name}")
|
|
84
|
+
|
|
85
|
+
# Calculate expiration time: current time + 2 hours
|
|
86
|
+
expiration_time = time.time() + (2 * 60 * 60) # 2 hours from now
|
|
87
|
+
|
|
88
|
+
# Create room properties with absolute timestamp
|
|
89
|
+
room_properties = DailyRoomProperties(
|
|
90
|
+
exp=expiration_time, # Absolute Unix timestamp
|
|
91
|
+
eject_at_room_exp=True,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Create room parameters
|
|
95
|
+
room_params = DailyRoomParams(name=room_name, properties=room_properties)
|
|
96
|
+
|
|
97
|
+
room_response = await daily_rest_helper.create_room(room_params)
|
|
98
|
+
room_url = room_response.url
|
|
99
|
+
print(f"Created Daily room: {room_url}")
|
|
100
|
+
|
|
101
|
+
# Create a meeting token for the room with an expiration 2 hours in the future
|
|
102
|
+
expiry_time: float = 2 * 60 * 60
|
|
103
|
+
token = await daily_rest_helper.get_token(room_url, expiry_time)
|
|
104
|
+
|
|
105
|
+
return (room_url, token)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# Keep this for backwards compatibility, but mark as deprecated
|
|
109
|
+
async def configure_with_args(aiohttp_session: aiohttp.ClientSession, parser=None):
|
|
110
|
+
"""Configure Daily room with command-line argument parsing.
|
|
111
|
+
|
|
112
|
+
.. deprecated:: 0.0.78
|
|
113
|
+
This function is deprecated. Use configure() instead which uses
|
|
114
|
+
environment variables only.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
aiohttp_session: HTTP session for making API requests.
|
|
118
|
+
parser: Ignored. Kept for backwards compatibility.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Tuple containing room URL, authentication token, and None (for args).
|
|
122
|
+
"""
|
|
123
|
+
import warnings
|
|
124
|
+
|
|
125
|
+
warnings.warn(
|
|
126
|
+
"configure_with_args is deprecated. Use configure() instead.",
|
|
127
|
+
DeprecationWarning,
|
|
128
|
+
stacklevel=2,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
room_url, token = await configure(aiohttp_session)
|
|
132
|
+
return (room_url, token, None)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2024–2025, Daily
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
"""LiveKit room and token configuration utilities.
|
|
8
|
+
|
|
9
|
+
This module provides helper functions for creating and configuring LiveKit
|
|
10
|
+
rooms and authentication tokens. It handles JWT token generation with
|
|
11
|
+
appropriate grants for both regular participants and AI agents.
|
|
12
|
+
|
|
13
|
+
The module supports creating tokens for development and testing, with
|
|
14
|
+
automatic agent detection for proper room permissions.
|
|
15
|
+
|
|
16
|
+
Required environment variables:
|
|
17
|
+
|
|
18
|
+
- LIVEKIT_API_KEY - LiveKit API key
|
|
19
|
+
- LIVEKIT_API_SECRET - LiveKit API secret
|
|
20
|
+
- LIVEKIT_URL - LiveKit server URL
|
|
21
|
+
- LIVEKIT_ROOM_NAME - Room name to join
|
|
22
|
+
|
|
23
|
+
Example::
|
|
24
|
+
|
|
25
|
+
from pipecat.runner.livekit import configure
|
|
26
|
+
|
|
27
|
+
url, token, room_name = await configure()
|
|
28
|
+
# Use with LiveKitTransport
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import os
|
|
33
|
+
from typing import Optional
|
|
34
|
+
|
|
35
|
+
from livekit import api
|
|
36
|
+
from loguru import logger
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def generate_token(room_name: str, participant_name: str, api_key: str, api_secret: str) -> str:
|
|
40
|
+
"""Generate a LiveKit access token for a participant.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
room_name: Name of the LiveKit room.
|
|
44
|
+
participant_name: Name of the participant.
|
|
45
|
+
api_key: LiveKit API key.
|
|
46
|
+
api_secret: LiveKit API secret.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
JWT token string for room access.
|
|
50
|
+
"""
|
|
51
|
+
token = api.AccessToken(api_key, api_secret)
|
|
52
|
+
token.with_identity(participant_name).with_name(participant_name).with_grants(
|
|
53
|
+
api.VideoGrants(
|
|
54
|
+
room_join=True,
|
|
55
|
+
room=room_name,
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return token.to_jwt()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def generate_token_with_agent(
|
|
63
|
+
room_name: str, participant_name: str, api_key: str, api_secret: str
|
|
64
|
+
) -> str:
|
|
65
|
+
"""Generate a LiveKit access token for an agent participant.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
room_name: Name of the LiveKit room.
|
|
69
|
+
participant_name: Name of the participant.
|
|
70
|
+
api_key: LiveKit API key.
|
|
71
|
+
api_secret: LiveKit API secret.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
JWT token string for agent room access.
|
|
75
|
+
"""
|
|
76
|
+
token = api.AccessToken(api_key, api_secret)
|
|
77
|
+
token.with_identity(participant_name).with_name(participant_name).with_grants(
|
|
78
|
+
api.VideoGrants(
|
|
79
|
+
room_join=True,
|
|
80
|
+
room=room_name,
|
|
81
|
+
agent=True, # This makes LiveKit client know agent has joined
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return token.to_jwt()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def configure():
|
|
89
|
+
"""Configure LiveKit room URL and token from arguments or environment.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Tuple containing the server URL, authentication token, and room name.
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
Exception: If required LiveKit configuration is not provided.
|
|
96
|
+
"""
|
|
97
|
+
(url, token, room_name, _) = await configure_with_args()
|
|
98
|
+
return (url, token, room_name)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def configure_with_args(parser: Optional[argparse.ArgumentParser] = None):
|
|
102
|
+
"""Configure LiveKit room with command-line argument parsing.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
parser: Optional argument parser. If None, creates a default one.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Tuple containing server URL, authentication token, room name, and parsed arguments.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
Exception: If required LiveKit configuration is not provided via arguments or environment.
|
|
112
|
+
"""
|
|
113
|
+
if not parser:
|
|
114
|
+
parser = argparse.ArgumentParser(description="LiveKit AI SDK Bot Sample")
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
"-r", "--room", type=str, required=False, help="Name of the LiveKit room to join"
|
|
117
|
+
)
|
|
118
|
+
parser.add_argument("-u", "--url", type=str, required=False, help="URL of the LiveKit server")
|
|
119
|
+
|
|
120
|
+
args, unknown = parser.parse_known_args()
|
|
121
|
+
|
|
122
|
+
room_name = args.room or os.getenv("LIVEKIT_ROOM_NAME")
|
|
123
|
+
url = args.url or os.getenv("LIVEKIT_URL")
|
|
124
|
+
api_key = os.getenv("LIVEKIT_API_KEY")
|
|
125
|
+
api_secret = os.getenv("LIVEKIT_API_SECRET")
|
|
126
|
+
|
|
127
|
+
if not room_name:
|
|
128
|
+
raise Exception(
|
|
129
|
+
"No LiveKit room specified. Use the -r/--room option from the command line, or set LIVEKIT_ROOM_NAME in your environment."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if not url:
|
|
133
|
+
raise Exception(
|
|
134
|
+
"No LiveKit server URL specified. Use the -u/--url option from the command line, or set LIVEKIT_URL in your environment."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if not api_key or not api_secret:
|
|
138
|
+
raise Exception(
|
|
139
|
+
"LIVEKIT_API_KEY and LIVEKIT_API_SECRET must be set in environment variables."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
token = generate_token_with_agent(room_name, "Pipecat Agent", api_key, api_secret)
|
|
143
|
+
|
|
144
|
+
# Generate user token for testing/debugging
|
|
145
|
+
user_token = generate_token(room_name, "User", api_key, api_secret)
|
|
146
|
+
logger.info(f"User token: {user_token}")
|
|
147
|
+
|
|
148
|
+
return (url, token, room_name, args)
|