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
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from pipecat.audio.utils import create_default_resampler, pcm_to_ulaw, ulaw_to_pcm
|
|
8
|
+
from pipecat.frames.frames import (
|
|
9
|
+
AudioRawFrame,
|
|
10
|
+
Frame,
|
|
11
|
+
InputAudioRawFrame,
|
|
12
|
+
InputDTMFFrame,
|
|
13
|
+
KeypadEntry,
|
|
14
|
+
StartFrame,
|
|
15
|
+
StartInterruptionFrame,
|
|
16
|
+
TransportMessageFrame,
|
|
17
|
+
TransportMessageUrgentFrame,
|
|
18
|
+
)
|
|
19
|
+
from pipecat.serializers.base_serializer import FrameSerializer, FrameSerializerType
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class GenesysFrameSerializer(FrameSerializer):
|
|
23
|
+
class InputParams(BaseModel):
|
|
24
|
+
genesys_sample_rate: int = 8000 # Default Genesys rate (8kHz)
|
|
25
|
+
sample_rate: Optional[int] = None # Pipeline input rate
|
|
26
|
+
|
|
27
|
+
def __init__(self, session_id: str, params: InputParams = InputParams()):
|
|
28
|
+
self._session_id = session_id
|
|
29
|
+
self._params = params
|
|
30
|
+
self._genesys_sample_rate = self._params.genesys_sample_rate
|
|
31
|
+
self._sample_rate = 0 # Pipeline input rate
|
|
32
|
+
self._resampler = create_default_resampler()
|
|
33
|
+
self._seq = 1 # Sequence number for outgoing messages
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def type(self) -> FrameSerializerType:
|
|
37
|
+
return FrameSerializerType.TEXT
|
|
38
|
+
|
|
39
|
+
async def setup(self, frame: StartFrame):
|
|
40
|
+
self._sample_rate = self._params.sample_rate or frame.audio_in_sample_rate
|
|
41
|
+
|
|
42
|
+
async def serialize(self, frame: Frame) -> str | bytes | None:
|
|
43
|
+
if isinstance(frame, StartInterruptionFrame):
|
|
44
|
+
answer = {
|
|
45
|
+
"version": "2",
|
|
46
|
+
"type": "clearAudio", # Or appropriate event for interruption
|
|
47
|
+
"seq": self._seq,
|
|
48
|
+
"id": self._session_id,
|
|
49
|
+
}
|
|
50
|
+
self._seq += 1
|
|
51
|
+
return json.dumps(answer)
|
|
52
|
+
elif isinstance(frame, AudioRawFrame):
|
|
53
|
+
data = frame.audio
|
|
54
|
+
# Convert PCM to 8kHz μ-law for Genesys
|
|
55
|
+
serialized_data = await pcm_to_ulaw(
|
|
56
|
+
data, frame.sample_rate, self._genesys_sample_rate, self._resampler
|
|
57
|
+
)
|
|
58
|
+
payload = base64.b64encode(serialized_data).decode("utf-8")
|
|
59
|
+
answer = {
|
|
60
|
+
"version": "2",
|
|
61
|
+
"type": "audio",
|
|
62
|
+
"seq": self._seq,
|
|
63
|
+
"id": self._session_id,
|
|
64
|
+
"media": {
|
|
65
|
+
"payload": payload,
|
|
66
|
+
"format": "PCMU",
|
|
67
|
+
"rate": self._genesys_sample_rate,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
self._seq += 1
|
|
71
|
+
return json.dumps(answer)
|
|
72
|
+
elif isinstance(frame, (TransportMessageFrame, TransportMessageUrgentFrame)):
|
|
73
|
+
return json.dumps(frame.message)
|
|
74
|
+
|
|
75
|
+
async def deserialize(self, data: str | bytes) -> Frame | None:
|
|
76
|
+
message = json.loads(data)
|
|
77
|
+
if message.get("type") == "audio":
|
|
78
|
+
payload_base64 = message["media"]["payload"]
|
|
79
|
+
payload = base64.b64decode(payload_base64)
|
|
80
|
+
# Convert Genesys 8kHz μ-law to PCM at pipeline input rate
|
|
81
|
+
deserialized_data = await ulaw_to_pcm(
|
|
82
|
+
payload, self._genesys_sample_rate, self._sample_rate, self._resampler
|
|
83
|
+
)
|
|
84
|
+
audio_frame = InputAudioRawFrame(
|
|
85
|
+
audio=deserialized_data, num_channels=1, sample_rate=self._sample_rate
|
|
86
|
+
)
|
|
87
|
+
return audio_frame
|
|
88
|
+
elif message.get("type") == "dtmf":
|
|
89
|
+
digit = message.get("dtmf", {}).get("digit")
|
|
90
|
+
try:
|
|
91
|
+
return InputDTMFFrame(KeypadEntry(digit))
|
|
92
|
+
except ValueError:
|
|
93
|
+
return None
|
|
94
|
+
else:
|
|
95
|
+
return None
|
pipecat/serializers/livekit.py
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
+
"""LiveKit frame serializer for Pipecat."""
|
|
8
|
+
|
|
7
9
|
import ctypes
|
|
8
10
|
import pickle
|
|
9
11
|
|
|
@@ -21,11 +23,33 @@ except ModuleNotFoundError as e:
|
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
class LivekitFrameSerializer(FrameSerializer):
|
|
26
|
+
"""Serializer for converting between Pipecat frames and LiveKit audio frames.
|
|
27
|
+
|
|
28
|
+
This serializer handles the conversion of Pipecat's OutputAudioRawFrame objects
|
|
29
|
+
to LiveKit AudioFrame objects for transmission, and the reverse conversion
|
|
30
|
+
for received audio data.
|
|
31
|
+
"""
|
|
32
|
+
|
|
24
33
|
@property
|
|
25
34
|
def type(self) -> FrameSerializerType:
|
|
35
|
+
"""Get the serializer type.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
The serializer type indicating binary serialization.
|
|
39
|
+
"""
|
|
26
40
|
return FrameSerializerType.BINARY
|
|
27
41
|
|
|
28
42
|
async def serialize(self, frame: Frame) -> str | bytes | None:
|
|
43
|
+
"""Serialize a Pipecat frame to LiveKit AudioFrame format.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
frame: The Pipecat frame to serialize. Only OutputAudioRawFrame
|
|
47
|
+
instances are supported.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Pickled LiveKit AudioFrame bytes if frame is OutputAudioRawFrame,
|
|
51
|
+
None otherwise.
|
|
52
|
+
"""
|
|
29
53
|
if not isinstance(frame, OutputAudioRawFrame):
|
|
30
54
|
return None
|
|
31
55
|
audio_frame = AudioFrame(
|
|
@@ -37,6 +61,15 @@ class LivekitFrameSerializer(FrameSerializer):
|
|
|
37
61
|
return pickle.dumps(audio_frame)
|
|
38
62
|
|
|
39
63
|
async def deserialize(self, data: str | bytes) -> Frame | None:
|
|
64
|
+
"""Deserialize LiveKit AudioFrame data to a Pipecat frame.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
data: Pickled data containing a LiveKit AudioFrame.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
InputAudioRawFrame containing the deserialized audio data,
|
|
71
|
+
or None if deserialization fails.
|
|
72
|
+
"""
|
|
40
73
|
audio_frame: AudioFrame = pickle.loads(data)["frame"]
|
|
41
74
|
return InputAudioRawFrame(
|
|
42
75
|
audio=bytes(audio_frame.data),
|
pipecat/serializers/plivo.py
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
+
"""Plivo WebSocket frame serializer for audio streaming."""
|
|
8
|
+
|
|
7
9
|
import base64
|
|
8
10
|
import json
|
|
9
11
|
from typing import Optional
|
|
@@ -11,7 +13,7 @@ from typing import Optional
|
|
|
11
13
|
from loguru import logger
|
|
12
14
|
from pydantic import BaseModel
|
|
13
15
|
|
|
14
|
-
from pipecat.audio.utils import
|
|
16
|
+
from pipecat.audio.utils import create_stream_resampler, pcm_to_ulaw, ulaw_to_pcm
|
|
15
17
|
from pipecat.frames.frames import (
|
|
16
18
|
AudioRawFrame,
|
|
17
19
|
CancelFrame,
|
|
@@ -38,22 +40,12 @@ class PlivoFrameSerializer(FrameSerializer):
|
|
|
38
40
|
When auto_hang_up is enabled (default), the serializer will automatically terminate
|
|
39
41
|
the Plivo call when an EndFrame or CancelFrame is processed, but requires Plivo
|
|
40
42
|
credentials to be provided.
|
|
41
|
-
|
|
42
|
-
Attributes:
|
|
43
|
-
_stream_id: The Plivo Stream ID.
|
|
44
|
-
_call_id: The associated Plivo Call ID.
|
|
45
|
-
_auth_id: Plivo auth ID for API access.
|
|
46
|
-
_auth_token: Plivo authentication token for API access.
|
|
47
|
-
_params: Configuration parameters.
|
|
48
|
-
_plivo_sample_rate: Sample rate used by Plivo (typically 8kHz).
|
|
49
|
-
_sample_rate: Input sample rate for the pipeline.
|
|
50
|
-
_resampler: Audio resampler for format conversion.
|
|
51
43
|
"""
|
|
52
44
|
|
|
53
45
|
class InputParams(BaseModel):
|
|
54
46
|
"""Configuration parameters for PlivoFrameSerializer.
|
|
55
47
|
|
|
56
|
-
|
|
48
|
+
Parameters:
|
|
57
49
|
plivo_sample_rate: Sample rate used by Plivo, defaults to 8000 Hz.
|
|
58
50
|
sample_rate: Optional override for pipeline input sample rate.
|
|
59
51
|
auto_hang_up: Whether to automatically terminate call on EndFrame.
|
|
@@ -89,7 +81,8 @@ class PlivoFrameSerializer(FrameSerializer):
|
|
|
89
81
|
self._plivo_sample_rate = self._params.plivo_sample_rate
|
|
90
82
|
self._sample_rate = 0 # Pipeline input rate
|
|
91
83
|
|
|
92
|
-
self.
|
|
84
|
+
self._input_resampler = create_stream_resampler()
|
|
85
|
+
self._output_resampler = create_stream_resampler()
|
|
93
86
|
self._hangup_attempted = False
|
|
94
87
|
|
|
95
88
|
@property
|
|
@@ -137,8 +130,12 @@ class PlivoFrameSerializer(FrameSerializer):
|
|
|
137
130
|
|
|
138
131
|
# Output: Convert PCM at frame's rate to 8kHz μ-law for Plivo
|
|
139
132
|
serialized_data = await pcm_to_ulaw(
|
|
140
|
-
data, frame.sample_rate, self._plivo_sample_rate, self.
|
|
133
|
+
data, frame.sample_rate, self._plivo_sample_rate, self._output_resampler
|
|
141
134
|
)
|
|
135
|
+
if serialized_data is None or len(serialized_data) == 0:
|
|
136
|
+
# Ignoring in case we don't have audio
|
|
137
|
+
return None
|
|
138
|
+
|
|
142
139
|
payload = base64.b64encode(serialized_data).decode("utf-8")
|
|
143
140
|
answer = {
|
|
144
141
|
"event": "playAudio",
|
|
@@ -232,8 +229,12 @@ class PlivoFrameSerializer(FrameSerializer):
|
|
|
232
229
|
|
|
233
230
|
# Input: Convert Plivo's 8kHz μ-law to PCM at pipeline input rate
|
|
234
231
|
deserialized_data = await ulaw_to_pcm(
|
|
235
|
-
payload, self._plivo_sample_rate, self._sample_rate, self.
|
|
232
|
+
payload, self._plivo_sample_rate, self._sample_rate, self._input_resampler
|
|
236
233
|
)
|
|
234
|
+
if deserialized_data is None or len(deserialized_data) == 0:
|
|
235
|
+
# Ignoring in case we don't have audio
|
|
236
|
+
return None
|
|
237
|
+
|
|
237
238
|
audio_frame = InputAudioRawFrame(
|
|
238
239
|
audio=deserialized_data, num_channels=1, sample_rate=self._sample_rate
|
|
239
240
|
)
|
pipecat/serializers/protobuf.py
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
+
"""Protobuf frame serialization for Pipecat."""
|
|
8
|
+
|
|
7
9
|
import dataclasses
|
|
8
10
|
import json
|
|
9
11
|
|
|
@@ -22,13 +24,25 @@ from pipecat.frames.frames import (
|
|
|
22
24
|
from pipecat.serializers.base_serializer import FrameSerializer, FrameSerializerType
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
# Data class for converting transport messages into Protobuf format.
|
|
26
27
|
@dataclasses.dataclass
|
|
27
28
|
class MessageFrame:
|
|
29
|
+
"""Data class for converting transport messages into Protobuf format.
|
|
30
|
+
|
|
31
|
+
Parameters:
|
|
32
|
+
data: JSON-encoded message data for transport.
|
|
33
|
+
"""
|
|
34
|
+
|
|
28
35
|
data: str
|
|
29
36
|
|
|
30
37
|
|
|
31
38
|
class ProtobufFrameSerializer(FrameSerializer):
|
|
39
|
+
"""Serializer for converting Pipecat frames to/from Protocol Buffer format.
|
|
40
|
+
|
|
41
|
+
Provides efficient binary serialization for frame transport over network
|
|
42
|
+
connections. Supports text, audio, transcription, and message frames with
|
|
43
|
+
automatic conversion between transport message types.
|
|
44
|
+
"""
|
|
45
|
+
|
|
32
46
|
SERIALIZABLE_TYPES = {
|
|
33
47
|
TextFrame: "text",
|
|
34
48
|
OutputAudioRawFrame: "audio",
|
|
@@ -46,13 +60,27 @@ class ProtobufFrameSerializer(FrameSerializer):
|
|
|
46
60
|
DESERIALIZABLE_FIELDS = {v: k for k, v in DESERIALIZABLE_TYPES.items()}
|
|
47
61
|
|
|
48
62
|
def __init__(self):
|
|
63
|
+
"""Initialize the Protobuf frame serializer."""
|
|
49
64
|
pass
|
|
50
65
|
|
|
51
66
|
@property
|
|
52
67
|
def type(self) -> FrameSerializerType:
|
|
68
|
+
"""Get the serializer type.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
FrameSerializerType.BINARY indicating binary serialization format.
|
|
72
|
+
"""
|
|
53
73
|
return FrameSerializerType.BINARY
|
|
54
74
|
|
|
55
75
|
async def serialize(self, frame: Frame) -> str | bytes | None:
|
|
76
|
+
"""Serialize a frame to Protocol Buffer binary format.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
frame: The frame to serialize.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Serialized frame as bytes, or None if frame type is not serializable.
|
|
83
|
+
"""
|
|
56
84
|
# Wrapping this messages as a JSONFrame to send
|
|
57
85
|
if isinstance(frame, (TransportMessageFrame, TransportMessageUrgentFrame)):
|
|
58
86
|
frame = MessageFrame(
|
|
@@ -75,6 +103,14 @@ class ProtobufFrameSerializer(FrameSerializer):
|
|
|
75
103
|
return proto_frame.SerializeToString()
|
|
76
104
|
|
|
77
105
|
async def deserialize(self, data: str | bytes) -> Frame | None:
|
|
106
|
+
"""Deserialize Protocol Buffer binary data to a frame.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
data: Binary protobuf data to deserialize.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Deserialized frame instance, or None if deserialization fails.
|
|
113
|
+
"""
|
|
78
114
|
proto = frame_protos.Frame.FromString(data)
|
|
79
115
|
which = proto.WhichOneof("frame")
|
|
80
116
|
if which not in self.DESERIALIZABLE_FIELDS:
|
pipecat/serializers/telnyx.py
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
+
"""Telnyx WebSocket frame serializer for Pipecat."""
|
|
8
|
+
|
|
7
9
|
import base64
|
|
8
10
|
import json
|
|
9
11
|
from typing import Optional
|
|
@@ -14,7 +16,7 @@ from pydantic import BaseModel
|
|
|
14
16
|
|
|
15
17
|
from pipecat.audio.utils import (
|
|
16
18
|
alaw_to_pcm,
|
|
17
|
-
|
|
19
|
+
create_stream_resampler,
|
|
18
20
|
pcm_to_alaw,
|
|
19
21
|
pcm_to_ulaw,
|
|
20
22
|
ulaw_to_pcm,
|
|
@@ -43,22 +45,12 @@ class TelnyxFrameSerializer(FrameSerializer):
|
|
|
43
45
|
When auto_hang_up is enabled (default), the serializer will automatically terminate
|
|
44
46
|
the Telnyx call when an EndFrame or CancelFrame is processed, but requires Telnyx
|
|
45
47
|
credentials to be provided.
|
|
46
|
-
|
|
47
|
-
Attributes:
|
|
48
|
-
_stream_id: The Telnyx Stream ID.
|
|
49
|
-
_call_control_id: The associated Telnyx Call Control ID.
|
|
50
|
-
_api_key: Telnyx API key for API access.
|
|
51
|
-
_params: Configuration parameters.
|
|
52
|
-
_telnyx_sample_rate: Sample rate used by Telnyx (typically 8kHz).
|
|
53
|
-
_sample_rate: Input sample rate for the pipeline.
|
|
54
|
-
_resampler: Audio resampler for format conversion.
|
|
55
|
-
_hangup_attempted: Flag to track if hang-up has been attempted.
|
|
56
48
|
"""
|
|
57
49
|
|
|
58
50
|
class InputParams(BaseModel):
|
|
59
51
|
"""Configuration parameters for TelnyxFrameSerializer.
|
|
60
52
|
|
|
61
|
-
|
|
53
|
+
Parameters:
|
|
62
54
|
telnyx_sample_rate: Sample rate used by Telnyx, defaults to 8000 Hz.
|
|
63
55
|
sample_rate: Optional override for pipeline input sample rate.
|
|
64
56
|
inbound_encoding: Audio encoding for data sent to Telnyx (e.g., "PCMU").
|
|
@@ -101,7 +93,8 @@ class TelnyxFrameSerializer(FrameSerializer):
|
|
|
101
93
|
self._telnyx_sample_rate = self._params.telnyx_sample_rate
|
|
102
94
|
self._sample_rate = 0 # Pipeline input rate
|
|
103
95
|
|
|
104
|
-
self.
|
|
96
|
+
self._input_resampler = create_stream_resampler()
|
|
97
|
+
self._output_resampler = create_stream_resampler()
|
|
105
98
|
self._hangup_attempted = False
|
|
106
99
|
|
|
107
100
|
@property
|
|
@@ -153,15 +146,19 @@ class TelnyxFrameSerializer(FrameSerializer):
|
|
|
153
146
|
# Output: Convert PCM at frame's rate to 8kHz encoded for Telnyx
|
|
154
147
|
if self._params.inbound_encoding == "PCMU":
|
|
155
148
|
serialized_data = await pcm_to_ulaw(
|
|
156
|
-
data, frame.sample_rate, self._telnyx_sample_rate, self.
|
|
149
|
+
data, frame.sample_rate, self._telnyx_sample_rate, self._output_resampler
|
|
157
150
|
)
|
|
158
151
|
elif self._params.inbound_encoding == "PCMA":
|
|
159
152
|
serialized_data = await pcm_to_alaw(
|
|
160
|
-
data, frame.sample_rate, self._telnyx_sample_rate, self.
|
|
153
|
+
data, frame.sample_rate, self._telnyx_sample_rate, self._output_resampler
|
|
161
154
|
)
|
|
162
155
|
else:
|
|
163
156
|
raise ValueError(f"Unsupported encoding: {self._params.inbound_encoding}")
|
|
164
157
|
|
|
158
|
+
if serialized_data is None or len(serialized_data) == 0:
|
|
159
|
+
# Ignoring in case we don't have audio
|
|
160
|
+
return None
|
|
161
|
+
|
|
165
162
|
payload = base64.b64encode(serialized_data).decode("utf-8")
|
|
166
163
|
answer = {
|
|
167
164
|
"event": "media",
|
|
@@ -257,18 +254,22 @@ class TelnyxFrameSerializer(FrameSerializer):
|
|
|
257
254
|
payload,
|
|
258
255
|
self._telnyx_sample_rate,
|
|
259
256
|
self._sample_rate,
|
|
260
|
-
self.
|
|
257
|
+
self._input_resampler,
|
|
261
258
|
)
|
|
262
259
|
elif self._params.outbound_encoding == "PCMA":
|
|
263
260
|
deserialized_data = await alaw_to_pcm(
|
|
264
261
|
payload,
|
|
265
262
|
self._telnyx_sample_rate,
|
|
266
263
|
self._sample_rate,
|
|
267
|
-
self.
|
|
264
|
+
self._input_resampler,
|
|
268
265
|
)
|
|
269
266
|
else:
|
|
270
267
|
raise ValueError(f"Unsupported encoding: {self._params.outbound_encoding}")
|
|
271
268
|
|
|
269
|
+
if deserialized_data is None or len(deserialized_data) == 0:
|
|
270
|
+
# Ignoring in case we don't have audio
|
|
271
|
+
return None
|
|
272
|
+
|
|
272
273
|
audio_frame = InputAudioRawFrame(
|
|
273
274
|
audio=deserialized_data, num_channels=1, sample_rate=self._sample_rate
|
|
274
275
|
)
|
pipecat/serializers/twilio.py
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
+
"""Twilio Media Streams WebSocket protocol serializer for Pipecat."""
|
|
8
|
+
|
|
7
9
|
import base64
|
|
8
10
|
import json
|
|
9
11
|
from typing import Optional
|
|
@@ -11,7 +13,7 @@ from typing import Optional
|
|
|
11
13
|
from loguru import logger
|
|
12
14
|
from pydantic import BaseModel
|
|
13
15
|
|
|
14
|
-
from pipecat.audio.utils import
|
|
16
|
+
from pipecat.audio.utils import create_stream_resampler, pcm_to_ulaw, ulaw_to_pcm
|
|
15
17
|
from pipecat.frames.frames import (
|
|
16
18
|
AudioRawFrame,
|
|
17
19
|
CancelFrame,
|
|
@@ -38,22 +40,12 @@ class TwilioFrameSerializer(FrameSerializer):
|
|
|
38
40
|
When auto_hang_up is enabled (default), the serializer will automatically terminate
|
|
39
41
|
the Twilio call when an EndFrame or CancelFrame is processed, but requires Twilio
|
|
40
42
|
credentials to be provided.
|
|
41
|
-
|
|
42
|
-
Attributes:
|
|
43
|
-
_stream_sid: The Twilio Media Stream SID.
|
|
44
|
-
_call_sid: The associated Twilio Call SID.
|
|
45
|
-
_account_sid: Twilio account SID for API access.
|
|
46
|
-
_auth_token: Twilio authentication token for API access.
|
|
47
|
-
_params: Configuration parameters.
|
|
48
|
-
_twilio_sample_rate: Sample rate used by Twilio (typically 8kHz).
|
|
49
|
-
_sample_rate: Input sample rate for the pipeline.
|
|
50
|
-
_resampler: Audio resampler for format conversion.
|
|
51
43
|
"""
|
|
52
44
|
|
|
53
45
|
class InputParams(BaseModel):
|
|
54
46
|
"""Configuration parameters for TwilioFrameSerializer.
|
|
55
47
|
|
|
56
|
-
|
|
48
|
+
Parameters:
|
|
57
49
|
twilio_sample_rate: Sample rate used by Twilio, defaults to 8000 Hz.
|
|
58
50
|
sample_rate: Optional override for pipeline input sample rate.
|
|
59
51
|
auto_hang_up: Whether to automatically terminate call on EndFrame.
|
|
@@ -89,7 +81,8 @@ class TwilioFrameSerializer(FrameSerializer):
|
|
|
89
81
|
self._twilio_sample_rate = self._params.twilio_sample_rate
|
|
90
82
|
self._sample_rate = 0 # Pipeline input rate
|
|
91
83
|
|
|
92
|
-
self.
|
|
84
|
+
self._input_resampler = create_stream_resampler()
|
|
85
|
+
self._output_resampler = create_stream_resampler()
|
|
93
86
|
self._hangup_attempted = False
|
|
94
87
|
|
|
95
88
|
@property
|
|
@@ -137,11 +130,12 @@ class TwilioFrameSerializer(FrameSerializer):
|
|
|
137
130
|
|
|
138
131
|
# Output: Convert PCM at frame's rate to 8kHz μ-law for Twilio
|
|
139
132
|
serialized_data = await pcm_to_ulaw(
|
|
140
|
-
data, frame.sample_rate, self._twilio_sample_rate, self.
|
|
133
|
+
data, frame.sample_rate, self._twilio_sample_rate, self._output_resampler
|
|
141
134
|
)
|
|
142
135
|
if serialized_data is None or len(serialized_data) == 0:
|
|
143
136
|
# Ignoring in case we don't have audio
|
|
144
137
|
return None
|
|
138
|
+
|
|
145
139
|
payload = base64.b64encode(serialized_data).decode("utf-8")
|
|
146
140
|
answer = {
|
|
147
141
|
"event": "media",
|
|
@@ -195,8 +189,26 @@ class TwilioFrameSerializer(FrameSerializer):
|
|
|
195
189
|
async with session.post(endpoint, auth=auth, data=params) as response:
|
|
196
190
|
if response.status == 200:
|
|
197
191
|
logger.info(f"Successfully terminated Twilio call {call_sid}")
|
|
192
|
+
elif response.status == 404:
|
|
193
|
+
# Handle the case where the call has already ended
|
|
194
|
+
# Error code 20404: "The requested resource was not found"
|
|
195
|
+
# Source: https://www.twilio.com/docs/errors/20404
|
|
196
|
+
try:
|
|
197
|
+
error_data = await response.json()
|
|
198
|
+
if error_data.get("code") == 20404:
|
|
199
|
+
logger.debug(f"Twilio call {call_sid} was already terminated")
|
|
200
|
+
return
|
|
201
|
+
except:
|
|
202
|
+
pass # Fall through to log the raw error
|
|
203
|
+
|
|
204
|
+
# Log other 404 errors
|
|
205
|
+
error_text = await response.text()
|
|
206
|
+
logger.error(
|
|
207
|
+
f"Failed to terminate Twilio call {call_sid}: "
|
|
208
|
+
f"Status {response.status}, Response: {error_text}"
|
|
209
|
+
)
|
|
198
210
|
else:
|
|
199
|
-
#
|
|
211
|
+
# Log other errors
|
|
200
212
|
error_text = await response.text()
|
|
201
213
|
logger.error(
|
|
202
214
|
f"Failed to terminate Twilio call {call_sid}: "
|
|
@@ -225,8 +237,12 @@ class TwilioFrameSerializer(FrameSerializer):
|
|
|
225
237
|
|
|
226
238
|
# Input: Convert Twilio's 8kHz μ-law to PCM at pipeline input rate
|
|
227
239
|
deserialized_data = await ulaw_to_pcm(
|
|
228
|
-
payload, self._twilio_sample_rate, self._sample_rate, self.
|
|
240
|
+
payload, self._twilio_sample_rate, self._sample_rate, self._input_resampler
|
|
229
241
|
)
|
|
242
|
+
if deserialized_data is None or len(deserialized_data) == 0:
|
|
243
|
+
# Ignoring in case we don't have audio
|
|
244
|
+
return None
|
|
245
|
+
|
|
230
246
|
audio_frame = InputAudioRawFrame(
|
|
231
247
|
audio=deserialized_data, num_channels=1, sample_rate=self._sample_rate
|
|
232
248
|
)
|
pipecat/services/ai_service.py
CHANGED
|
@@ -32,12 +32,14 @@ class AIService(FrameProcessor):
|
|
|
32
32
|
settings handling, session properties, and frame processing lifecycle.
|
|
33
33
|
Subclasses should implement specific AI functionality while leveraging
|
|
34
34
|
this base infrastructure.
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
**kwargs: Additional arguments passed to the parent FrameProcessor.
|
|
38
35
|
"""
|
|
39
36
|
|
|
40
37
|
def __init__(self, **kwargs):
|
|
38
|
+
"""Initialize the AI service.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
**kwargs: Additional arguments passed to the parent FrameProcessor.
|
|
42
|
+
"""
|
|
41
43
|
super().__init__(**kwargs)
|
|
42
44
|
self._model_name: str = ""
|
|
43
45
|
self._settings: Dict[str, Any] = {}
|