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.

Files changed (244) hide show
  1. {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/METADATA +137 -93
  2. dv_pipecat_ai-0.0.82.dev776.dist-info/RECORD +340 -0
  3. pipecat/__init__.py +17 -0
  4. pipecat/adapters/base_llm_adapter.py +36 -1
  5. pipecat/adapters/schemas/direct_function.py +296 -0
  6. pipecat/adapters/schemas/function_schema.py +15 -6
  7. pipecat/adapters/schemas/tools_schema.py +55 -7
  8. pipecat/adapters/services/anthropic_adapter.py +22 -3
  9. pipecat/adapters/services/aws_nova_sonic_adapter.py +23 -3
  10. pipecat/adapters/services/bedrock_adapter.py +22 -3
  11. pipecat/adapters/services/gemini_adapter.py +16 -3
  12. pipecat/adapters/services/open_ai_adapter.py +17 -2
  13. pipecat/adapters/services/open_ai_realtime_adapter.py +23 -3
  14. pipecat/audio/filters/base_audio_filter.py +30 -6
  15. pipecat/audio/filters/koala_filter.py +37 -2
  16. pipecat/audio/filters/krisp_filter.py +59 -6
  17. pipecat/audio/filters/noisereduce_filter.py +37 -0
  18. pipecat/audio/interruptions/base_interruption_strategy.py +25 -5
  19. pipecat/audio/interruptions/min_words_interruption_strategy.py +21 -4
  20. pipecat/audio/mixers/base_audio_mixer.py +30 -7
  21. pipecat/audio/mixers/soundfile_mixer.py +53 -6
  22. pipecat/audio/resamplers/base_audio_resampler.py +17 -9
  23. pipecat/audio/resamplers/resampy_resampler.py +26 -1
  24. pipecat/audio/resamplers/soxr_resampler.py +32 -1
  25. pipecat/audio/resamplers/soxr_stream_resampler.py +101 -0
  26. pipecat/audio/utils.py +194 -1
  27. pipecat/audio/vad/silero.py +60 -3
  28. pipecat/audio/vad/vad_analyzer.py +114 -30
  29. pipecat/clocks/base_clock.py +19 -0
  30. pipecat/clocks/system_clock.py +25 -0
  31. pipecat/extensions/voicemail/__init__.py +0 -0
  32. pipecat/extensions/voicemail/voicemail_detector.py +707 -0
  33. pipecat/frames/frames.py +590 -156
  34. pipecat/metrics/metrics.py +64 -1
  35. pipecat/observers/base_observer.py +58 -19
  36. pipecat/observers/loggers/debug_log_observer.py +56 -64
  37. pipecat/observers/loggers/llm_log_observer.py +8 -1
  38. pipecat/observers/loggers/transcription_log_observer.py +19 -7
  39. pipecat/observers/loggers/user_bot_latency_log_observer.py +32 -5
  40. pipecat/observers/turn_tracking_observer.py +26 -1
  41. pipecat/pipeline/base_pipeline.py +5 -7
  42. pipecat/pipeline/base_task.py +52 -9
  43. pipecat/pipeline/parallel_pipeline.py +121 -177
  44. pipecat/pipeline/pipeline.py +129 -20
  45. pipecat/pipeline/runner.py +50 -1
  46. pipecat/pipeline/sync_parallel_pipeline.py +132 -32
  47. pipecat/pipeline/task.py +263 -280
  48. pipecat/pipeline/task_observer.py +85 -34
  49. pipecat/pipeline/to_be_updated/merge_pipeline.py +32 -2
  50. pipecat/processors/aggregators/dtmf_aggregator.py +29 -22
  51. pipecat/processors/aggregators/gated.py +25 -24
  52. pipecat/processors/aggregators/gated_openai_llm_context.py +22 -2
  53. pipecat/processors/aggregators/llm_response.py +398 -89
  54. pipecat/processors/aggregators/openai_llm_context.py +161 -13
  55. pipecat/processors/aggregators/sentence.py +25 -14
  56. pipecat/processors/aggregators/user_response.py +28 -3
  57. pipecat/processors/aggregators/vision_image_frame.py +24 -14
  58. pipecat/processors/async_generator.py +28 -0
  59. pipecat/processors/audio/audio_buffer_processor.py +78 -37
  60. pipecat/processors/consumer_processor.py +25 -6
  61. pipecat/processors/filters/frame_filter.py +23 -0
  62. pipecat/processors/filters/function_filter.py +30 -0
  63. pipecat/processors/filters/identity_filter.py +17 -2
  64. pipecat/processors/filters/null_filter.py +24 -1
  65. pipecat/processors/filters/stt_mute_filter.py +56 -21
  66. pipecat/processors/filters/wake_check_filter.py +46 -3
  67. pipecat/processors/filters/wake_notifier_filter.py +21 -3
  68. pipecat/processors/frame_processor.py +488 -131
  69. pipecat/processors/frameworks/langchain.py +38 -3
  70. pipecat/processors/frameworks/rtvi.py +719 -34
  71. pipecat/processors/gstreamer/pipeline_source.py +41 -0
  72. pipecat/processors/idle_frame_processor.py +26 -3
  73. pipecat/processors/logger.py +23 -0
  74. pipecat/processors/metrics/frame_processor_metrics.py +77 -4
  75. pipecat/processors/metrics/sentry.py +42 -4
  76. pipecat/processors/producer_processor.py +34 -14
  77. pipecat/processors/text_transformer.py +22 -10
  78. pipecat/processors/transcript_processor.py +48 -29
  79. pipecat/processors/user_idle_processor.py +31 -21
  80. pipecat/runner/__init__.py +1 -0
  81. pipecat/runner/daily.py +132 -0
  82. pipecat/runner/livekit.py +148 -0
  83. pipecat/runner/run.py +543 -0
  84. pipecat/runner/types.py +67 -0
  85. pipecat/runner/utils.py +515 -0
  86. pipecat/serializers/base_serializer.py +42 -0
  87. pipecat/serializers/exotel.py +17 -6
  88. pipecat/serializers/genesys.py +95 -0
  89. pipecat/serializers/livekit.py +33 -0
  90. pipecat/serializers/plivo.py +16 -15
  91. pipecat/serializers/protobuf.py +37 -1
  92. pipecat/serializers/telnyx.py +18 -17
  93. pipecat/serializers/twilio.py +32 -16
  94. pipecat/services/ai_service.py +5 -3
  95. pipecat/services/anthropic/llm.py +113 -43
  96. pipecat/services/assemblyai/models.py +63 -5
  97. pipecat/services/assemblyai/stt.py +64 -11
  98. pipecat/services/asyncai/__init__.py +0 -0
  99. pipecat/services/asyncai/tts.py +501 -0
  100. pipecat/services/aws/llm.py +185 -111
  101. pipecat/services/aws/stt.py +217 -23
  102. pipecat/services/aws/tts.py +118 -52
  103. pipecat/services/aws/utils.py +101 -5
  104. pipecat/services/aws_nova_sonic/aws.py +82 -64
  105. pipecat/services/aws_nova_sonic/context.py +15 -6
  106. pipecat/services/azure/common.py +10 -2
  107. pipecat/services/azure/image.py +32 -0
  108. pipecat/services/azure/llm.py +9 -7
  109. pipecat/services/azure/stt.py +65 -2
  110. pipecat/services/azure/tts.py +154 -23
  111. pipecat/services/cartesia/stt.py +125 -8
  112. pipecat/services/cartesia/tts.py +102 -38
  113. pipecat/services/cerebras/llm.py +15 -23
  114. pipecat/services/deepgram/stt.py +19 -11
  115. pipecat/services/deepgram/tts.py +36 -0
  116. pipecat/services/deepseek/llm.py +14 -23
  117. pipecat/services/elevenlabs/tts.py +330 -64
  118. pipecat/services/fal/image.py +43 -0
  119. pipecat/services/fal/stt.py +48 -10
  120. pipecat/services/fireworks/llm.py +14 -21
  121. pipecat/services/fish/tts.py +109 -9
  122. pipecat/services/gemini_multimodal_live/__init__.py +1 -0
  123. pipecat/services/gemini_multimodal_live/events.py +83 -2
  124. pipecat/services/gemini_multimodal_live/file_api.py +189 -0
  125. pipecat/services/gemini_multimodal_live/gemini.py +218 -21
  126. pipecat/services/gladia/config.py +17 -10
  127. pipecat/services/gladia/stt.py +82 -36
  128. pipecat/services/google/frames.py +40 -0
  129. pipecat/services/google/google.py +2 -0
  130. pipecat/services/google/image.py +39 -2
  131. pipecat/services/google/llm.py +176 -58
  132. pipecat/services/google/llm_openai.py +26 -4
  133. pipecat/services/google/llm_vertex.py +37 -15
  134. pipecat/services/google/rtvi.py +41 -0
  135. pipecat/services/google/stt.py +65 -17
  136. pipecat/services/google/test-google-chirp.py +45 -0
  137. pipecat/services/google/tts.py +390 -19
  138. pipecat/services/grok/llm.py +8 -6
  139. pipecat/services/groq/llm.py +8 -6
  140. pipecat/services/groq/stt.py +13 -9
  141. pipecat/services/groq/tts.py +40 -0
  142. pipecat/services/hamsa/__init__.py +9 -0
  143. pipecat/services/hamsa/stt.py +241 -0
  144. pipecat/services/heygen/__init__.py +5 -0
  145. pipecat/services/heygen/api.py +281 -0
  146. pipecat/services/heygen/client.py +620 -0
  147. pipecat/services/heygen/video.py +338 -0
  148. pipecat/services/image_service.py +5 -3
  149. pipecat/services/inworld/__init__.py +1 -0
  150. pipecat/services/inworld/tts.py +592 -0
  151. pipecat/services/llm_service.py +127 -45
  152. pipecat/services/lmnt/tts.py +80 -7
  153. pipecat/services/mcp_service.py +85 -44
  154. pipecat/services/mem0/memory.py +42 -13
  155. pipecat/services/minimax/tts.py +74 -15
  156. pipecat/services/mistral/__init__.py +0 -0
  157. pipecat/services/mistral/llm.py +185 -0
  158. pipecat/services/moondream/vision.py +55 -10
  159. pipecat/services/neuphonic/tts.py +275 -48
  160. pipecat/services/nim/llm.py +8 -6
  161. pipecat/services/ollama/llm.py +27 -7
  162. pipecat/services/openai/base_llm.py +54 -16
  163. pipecat/services/openai/image.py +30 -0
  164. pipecat/services/openai/llm.py +7 -5
  165. pipecat/services/openai/stt.py +13 -9
  166. pipecat/services/openai/tts.py +42 -10
  167. pipecat/services/openai_realtime_beta/azure.py +11 -9
  168. pipecat/services/openai_realtime_beta/context.py +7 -5
  169. pipecat/services/openai_realtime_beta/events.py +10 -7
  170. pipecat/services/openai_realtime_beta/openai.py +37 -18
  171. pipecat/services/openpipe/llm.py +30 -24
  172. pipecat/services/openrouter/llm.py +9 -7
  173. pipecat/services/perplexity/llm.py +15 -19
  174. pipecat/services/piper/tts.py +26 -12
  175. pipecat/services/playht/tts.py +227 -65
  176. pipecat/services/qwen/llm.py +8 -6
  177. pipecat/services/rime/tts.py +128 -17
  178. pipecat/services/riva/stt.py +160 -22
  179. pipecat/services/riva/tts.py +67 -2
  180. pipecat/services/sambanova/llm.py +19 -17
  181. pipecat/services/sambanova/stt.py +14 -8
  182. pipecat/services/sarvam/tts.py +60 -13
  183. pipecat/services/simli/video.py +82 -21
  184. pipecat/services/soniox/__init__.py +0 -0
  185. pipecat/services/soniox/stt.py +398 -0
  186. pipecat/services/speechmatics/stt.py +29 -17
  187. pipecat/services/stt_service.py +47 -11
  188. pipecat/services/tavus/video.py +94 -25
  189. pipecat/services/together/llm.py +8 -6
  190. pipecat/services/tts_service.py +77 -53
  191. pipecat/services/ultravox/stt.py +46 -43
  192. pipecat/services/vision_service.py +5 -3
  193. pipecat/services/websocket_service.py +12 -11
  194. pipecat/services/whisper/base_stt.py +58 -12
  195. pipecat/services/whisper/stt.py +69 -58
  196. pipecat/services/xtts/tts.py +59 -2
  197. pipecat/sync/base_notifier.py +19 -0
  198. pipecat/sync/event_notifier.py +24 -0
  199. pipecat/tests/utils.py +73 -5
  200. pipecat/transcriptions/language.py +24 -0
  201. pipecat/transports/base_input.py +112 -8
  202. pipecat/transports/base_output.py +235 -13
  203. pipecat/transports/base_transport.py +119 -0
  204. pipecat/transports/local/audio.py +76 -0
  205. pipecat/transports/local/tk.py +84 -0
  206. pipecat/transports/network/fastapi_websocket.py +174 -15
  207. pipecat/transports/network/small_webrtc.py +383 -39
  208. pipecat/transports/network/webrtc_connection.py +214 -8
  209. pipecat/transports/network/websocket_client.py +171 -1
  210. pipecat/transports/network/websocket_server.py +147 -9
  211. pipecat/transports/services/daily.py +792 -70
  212. pipecat/transports/services/helpers/daily_rest.py +122 -129
  213. pipecat/transports/services/livekit.py +339 -4
  214. pipecat/transports/services/tavus.py +273 -38
  215. pipecat/utils/asyncio/task_manager.py +92 -186
  216. pipecat/utils/base_object.py +83 -1
  217. pipecat/utils/network.py +2 -0
  218. pipecat/utils/string.py +114 -58
  219. pipecat/utils/text/base_text_aggregator.py +44 -13
  220. pipecat/utils/text/base_text_filter.py +46 -0
  221. pipecat/utils/text/markdown_text_filter.py +70 -14
  222. pipecat/utils/text/pattern_pair_aggregator.py +18 -14
  223. pipecat/utils/text/simple_text_aggregator.py +43 -2
  224. pipecat/utils/text/skip_tags_aggregator.py +21 -13
  225. pipecat/utils/time.py +36 -0
  226. pipecat/utils/tracing/class_decorators.py +32 -7
  227. pipecat/utils/tracing/conversation_context_provider.py +12 -2
  228. pipecat/utils/tracing/service_attributes.py +80 -64
  229. pipecat/utils/tracing/service_decorators.py +48 -21
  230. pipecat/utils/tracing/setup.py +13 -7
  231. pipecat/utils/tracing/turn_context_provider.py +12 -2
  232. pipecat/utils/tracing/turn_trace_observer.py +27 -0
  233. pipecat/utils/utils.py +14 -14
  234. dv_pipecat_ai-0.0.74.dev770.dist-info/RECORD +0 -319
  235. pipecat/examples/daily_runner.py +0 -64
  236. pipecat/examples/run.py +0 -265
  237. pipecat/utils/asyncio/watchdog_async_iterator.py +0 -72
  238. pipecat/utils/asyncio/watchdog_event.py +0 -42
  239. pipecat/utils/asyncio/watchdog_priority_queue.py +0 -48
  240. pipecat/utils/asyncio/watchdog_queue.py +0 -48
  241. {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/WHEEL +0 -0
  242. {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/licenses/LICENSE +0 -0
  243. {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/top_level.txt +0 -0
  244. /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].isspace() for part in self._current_text_parts[1:]
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].isspace() for part in self._current_text_parts[:-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
- ```python
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
- Starts monitoring only after the first conversation activity (UserStartedSpeaking
34
- or BotSpeaking).
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
- Args:
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
- """Returns the current retry count."""
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
- self._function_call_in_progress = True
172
- self.logger.debug("Function call in progress")
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, (FunctionCallResultFrame, FunctionCallCancelFrame)):
175
- self._function_call_in_progress = False
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 and not self._function_call_in_progress:
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."""
@@ -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)