dv-pipecat-ai 0.0.85.dev7__py3-none-any.whl → 0.0.85.dev699__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.85.dev7.dist-info → dv_pipecat_ai-0.0.85.dev699.dist-info}/METADATA +78 -117
- {dv_pipecat_ai-0.0.85.dev7.dist-info → dv_pipecat_ai-0.0.85.dev699.dist-info}/RECORD +158 -122
- pipecat/adapters/base_llm_adapter.py +38 -1
- pipecat/adapters/services/anthropic_adapter.py +9 -14
- pipecat/adapters/services/aws_nova_sonic_adapter.py +5 -0
- pipecat/adapters/services/bedrock_adapter.py +236 -13
- pipecat/adapters/services/gemini_adapter.py +12 -8
- pipecat/adapters/services/open_ai_adapter.py +19 -7
- pipecat/adapters/services/open_ai_realtime_adapter.py +5 -0
- pipecat/audio/filters/krisp_viva_filter.py +193 -0
- pipecat/audio/filters/noisereduce_filter.py +15 -0
- pipecat/audio/turn/base_turn_analyzer.py +9 -1
- pipecat/audio/turn/smart_turn/base_smart_turn.py +14 -8
- pipecat/audio/turn/smart_turn/data/__init__.py +0 -0
- pipecat/audio/turn/smart_turn/data/smart-turn-v3.0.onnx +0 -0
- pipecat/audio/turn/smart_turn/http_smart_turn.py +6 -2
- pipecat/audio/turn/smart_turn/local_smart_turn.py +1 -1
- pipecat/audio/turn/smart_turn/local_smart_turn_v2.py +1 -1
- pipecat/audio/turn/smart_turn/local_smart_turn_v3.py +124 -0
- pipecat/audio/vad/data/README.md +10 -0
- pipecat/audio/vad/vad_analyzer.py +13 -1
- pipecat/extensions/voicemail/voicemail_detector.py +5 -5
- pipecat/frames/frames.py +120 -87
- pipecat/observers/loggers/debug_log_observer.py +3 -3
- pipecat/observers/loggers/llm_log_observer.py +7 -3
- pipecat/observers/loggers/user_bot_latency_log_observer.py +22 -10
- pipecat/pipeline/runner.py +12 -4
- pipecat/pipeline/service_switcher.py +64 -36
- pipecat/pipeline/task.py +85 -24
- pipecat/processors/aggregators/dtmf_aggregator.py +28 -22
- pipecat/processors/aggregators/{gated_openai_llm_context.py → gated_llm_context.py} +9 -9
- pipecat/processors/aggregators/gated_open_ai_llm_context.py +12 -0
- pipecat/processors/aggregators/llm_response.py +6 -7
- pipecat/processors/aggregators/llm_response_universal.py +19 -15
- pipecat/processors/aggregators/user_response.py +6 -6
- pipecat/processors/aggregators/vision_image_frame.py +24 -2
- pipecat/processors/audio/audio_buffer_processor.py +43 -8
- pipecat/processors/filters/stt_mute_filter.py +2 -0
- pipecat/processors/frame_processor.py +103 -17
- pipecat/processors/frameworks/langchain.py +8 -2
- pipecat/processors/frameworks/rtvi.py +209 -68
- pipecat/processors/frameworks/strands_agents.py +170 -0
- pipecat/processors/logger.py +2 -2
- pipecat/processors/transcript_processor.py +4 -4
- pipecat/processors/user_idle_processor.py +3 -6
- pipecat/runner/run.py +270 -50
- pipecat/runner/types.py +2 -0
- pipecat/runner/utils.py +51 -10
- pipecat/serializers/exotel.py +5 -5
- pipecat/serializers/livekit.py +20 -0
- pipecat/serializers/plivo.py +6 -9
- pipecat/serializers/protobuf.py +6 -5
- pipecat/serializers/telnyx.py +2 -2
- pipecat/serializers/twilio.py +43 -23
- pipecat/services/ai_service.py +2 -6
- pipecat/services/anthropic/llm.py +2 -25
- pipecat/services/asyncai/tts.py +2 -3
- pipecat/services/aws/__init__.py +1 -0
- pipecat/services/aws/llm.py +122 -97
- pipecat/services/aws/nova_sonic/__init__.py +0 -0
- pipecat/services/aws/nova_sonic/context.py +367 -0
- pipecat/services/aws/nova_sonic/frames.py +25 -0
- pipecat/services/aws/nova_sonic/llm.py +1155 -0
- pipecat/services/aws/stt.py +1 -3
- pipecat/services/aws_nova_sonic/__init__.py +19 -1
- pipecat/services/aws_nova_sonic/aws.py +11 -1151
- pipecat/services/aws_nova_sonic/context.py +13 -355
- pipecat/services/aws_nova_sonic/frames.py +13 -17
- pipecat/services/azure/realtime/__init__.py +0 -0
- pipecat/services/azure/realtime/llm.py +65 -0
- pipecat/services/azure/stt.py +15 -0
- pipecat/services/cartesia/tts.py +2 -2
- pipecat/services/deepgram/__init__.py +1 -0
- pipecat/services/deepgram/flux/__init__.py +0 -0
- pipecat/services/deepgram/flux/stt.py +636 -0
- pipecat/services/elevenlabs/__init__.py +2 -1
- pipecat/services/elevenlabs/stt.py +254 -276
- pipecat/services/elevenlabs/tts.py +5 -5
- pipecat/services/fish/tts.py +2 -2
- pipecat/services/gemini_multimodal_live/events.py +38 -524
- pipecat/services/gemini_multimodal_live/file_api.py +23 -173
- pipecat/services/gemini_multimodal_live/gemini.py +41 -1403
- pipecat/services/gladia/stt.py +56 -72
- pipecat/services/google/__init__.py +1 -0
- pipecat/services/google/gemini_live/__init__.py +3 -0
- pipecat/services/google/gemini_live/file_api.py +189 -0
- pipecat/services/google/gemini_live/llm.py +1582 -0
- pipecat/services/google/gemini_live/llm_vertex.py +184 -0
- pipecat/services/google/llm.py +15 -11
- pipecat/services/google/llm_openai.py +3 -3
- pipecat/services/google/llm_vertex.py +86 -16
- pipecat/services/google/tts.py +7 -3
- pipecat/services/heygen/api.py +2 -0
- pipecat/services/heygen/client.py +8 -4
- pipecat/services/heygen/video.py +2 -0
- pipecat/services/hume/__init__.py +5 -0
- pipecat/services/hume/tts.py +220 -0
- pipecat/services/inworld/tts.py +6 -6
- pipecat/services/llm_service.py +15 -5
- pipecat/services/lmnt/tts.py +2 -2
- pipecat/services/mcp_service.py +4 -2
- pipecat/services/mem0/memory.py +6 -5
- pipecat/services/mistral/llm.py +29 -8
- pipecat/services/moondream/vision.py +42 -16
- pipecat/services/neuphonic/tts.py +2 -2
- pipecat/services/openai/__init__.py +1 -0
- pipecat/services/openai/base_llm.py +27 -20
- pipecat/services/openai/realtime/__init__.py +0 -0
- pipecat/services/openai/realtime/context.py +272 -0
- pipecat/services/openai/realtime/events.py +1106 -0
- pipecat/services/openai/realtime/frames.py +37 -0
- pipecat/services/openai/realtime/llm.py +829 -0
- pipecat/services/openai/tts.py +16 -8
- pipecat/services/openai_realtime/__init__.py +27 -0
- pipecat/services/openai_realtime/azure.py +21 -0
- pipecat/services/openai_realtime/context.py +21 -0
- pipecat/services/openai_realtime/events.py +21 -0
- pipecat/services/openai_realtime/frames.py +21 -0
- pipecat/services/openai_realtime_beta/azure.py +16 -0
- pipecat/services/openai_realtime_beta/openai.py +17 -5
- pipecat/services/playht/tts.py +31 -4
- pipecat/services/rime/tts.py +3 -4
- pipecat/services/salesforce/__init__.py +9 -0
- pipecat/services/salesforce/llm.py +465 -0
- pipecat/services/sarvam/tts.py +2 -6
- pipecat/services/simli/video.py +2 -2
- pipecat/services/speechmatics/stt.py +1 -7
- pipecat/services/stt_service.py +34 -0
- pipecat/services/tavus/video.py +2 -2
- pipecat/services/tts_service.py +9 -9
- pipecat/services/vision_service.py +7 -6
- pipecat/tests/utils.py +4 -4
- pipecat/transcriptions/language.py +41 -1
- pipecat/transports/base_input.py +17 -42
- pipecat/transports/base_output.py +42 -26
- pipecat/transports/daily/transport.py +199 -26
- pipecat/transports/heygen/__init__.py +0 -0
- pipecat/transports/heygen/transport.py +381 -0
- pipecat/transports/livekit/transport.py +228 -63
- pipecat/transports/local/audio.py +6 -1
- pipecat/transports/local/tk.py +11 -2
- pipecat/transports/network/fastapi_websocket.py +1 -1
- pipecat/transports/smallwebrtc/connection.py +98 -19
- pipecat/transports/smallwebrtc/request_handler.py +204 -0
- pipecat/transports/smallwebrtc/transport.py +65 -23
- pipecat/transports/tavus/transport.py +23 -12
- pipecat/transports/websocket/client.py +41 -5
- pipecat/transports/websocket/fastapi.py +21 -11
- pipecat/transports/websocket/server.py +14 -7
- pipecat/transports/whatsapp/api.py +8 -0
- pipecat/transports/whatsapp/client.py +47 -0
- pipecat/utils/base_object.py +54 -22
- pipecat/utils/string.py +12 -1
- pipecat/utils/tracing/service_decorators.py +21 -21
- {dv_pipecat_ai-0.0.85.dev7.dist-info → dv_pipecat_ai-0.0.85.dev699.dist-info}/WHEEL +0 -0
- {dv_pipecat_ai-0.0.85.dev7.dist-info → dv_pipecat_ai-0.0.85.dev699.dist-info}/licenses/LICENSE +0 -0
- {dv_pipecat_ai-0.0.85.dev7.dist-info → dv_pipecat_ai-0.0.85.dev699.dist-info}/top_level.txt +0 -0
- /pipecat/services/{aws_nova_sonic → aws/nova_sonic}/ready.wav +0 -0
pipecat/frames/frames.py
CHANGED
|
@@ -672,7 +672,7 @@ class TTSSpeakFrame(DataFrame):
|
|
|
672
672
|
|
|
673
673
|
|
|
674
674
|
@dataclass
|
|
675
|
-
class
|
|
675
|
+
class OutputTransportMessageFrame(DataFrame):
|
|
676
676
|
"""Frame containing transport-specific message data.
|
|
677
677
|
|
|
678
678
|
Parameters:
|
|
@@ -685,6 +685,32 @@ class TransportMessageFrame(DataFrame):
|
|
|
685
685
|
return f"{self.name}(message: {self.message})"
|
|
686
686
|
|
|
687
687
|
|
|
688
|
+
@dataclass
|
|
689
|
+
class TransportMessageFrame(OutputTransportMessageFrame):
|
|
690
|
+
"""Frame containing transport-specific message data.
|
|
691
|
+
|
|
692
|
+
.. deprecated:: 0.0.87
|
|
693
|
+
This frame is deprecated and will be removed in a future version.
|
|
694
|
+
Instead, use `OutputTransportMessageFrame`.
|
|
695
|
+
|
|
696
|
+
Parameters:
|
|
697
|
+
message: The transport message payload.
|
|
698
|
+
"""
|
|
699
|
+
|
|
700
|
+
def __post_init__(self):
|
|
701
|
+
super().__post_init__()
|
|
702
|
+
import warnings
|
|
703
|
+
|
|
704
|
+
with warnings.catch_warnings():
|
|
705
|
+
warnings.simplefilter("always")
|
|
706
|
+
warnings.warn(
|
|
707
|
+
"TransportMessageFrame is deprecated and will be removed in a future version. "
|
|
708
|
+
"Instead, use OutputTransportMessageFrame.",
|
|
709
|
+
DeprecationWarning,
|
|
710
|
+
stacklevel=2,
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
|
|
688
714
|
@dataclass
|
|
689
715
|
class DTMFFrame:
|
|
690
716
|
"""Base class for DTMF (Dual-Tone Multi-Frequency) keypad frames.
|
|
@@ -788,43 +814,6 @@ class FatalErrorFrame(ErrorFrame):
|
|
|
788
814
|
fatal: bool = field(default=True, init=False)
|
|
789
815
|
|
|
790
816
|
|
|
791
|
-
@dataclass
|
|
792
|
-
class EndTaskFrame(SystemFrame):
|
|
793
|
-
"""Frame to request graceful pipeline task closure.
|
|
794
|
-
|
|
795
|
-
This is used to notify the pipeline task that the pipeline should be
|
|
796
|
-
closed nicely (flushing all the queued frames) by pushing an EndFrame
|
|
797
|
-
downstream. This frame should be pushed upstream.
|
|
798
|
-
"""
|
|
799
|
-
|
|
800
|
-
pass
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
@dataclass
|
|
804
|
-
class CancelTaskFrame(SystemFrame):
|
|
805
|
-
"""Frame to request immediate pipeline task cancellation.
|
|
806
|
-
|
|
807
|
-
This is used to notify the pipeline task that the pipeline should be
|
|
808
|
-
stopped immediately by pushing a CancelFrame downstream. This frame
|
|
809
|
-
should be pushed upstream.
|
|
810
|
-
"""
|
|
811
|
-
|
|
812
|
-
pass
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
@dataclass
|
|
816
|
-
class StopTaskFrame(SystemFrame):
|
|
817
|
-
"""Frame to request pipeline task stop while keeping processors running.
|
|
818
|
-
|
|
819
|
-
This is used to notify the pipeline task that it should be stopped as
|
|
820
|
-
soon as possible (flushing all the queued frames) but that the pipeline
|
|
821
|
-
processors should be kept in a running state. This frame should be pushed
|
|
822
|
-
upstream.
|
|
823
|
-
"""
|
|
824
|
-
|
|
825
|
-
pass
|
|
826
|
-
|
|
827
|
-
|
|
828
817
|
@dataclass
|
|
829
818
|
class FrameProcessorPauseUrgentFrame(SystemFrame):
|
|
830
819
|
"""Frame to pause frame processing immediately.
|
|
@@ -857,7 +846,7 @@ class FrameProcessorResumeUrgentFrame(SystemFrame):
|
|
|
857
846
|
|
|
858
847
|
|
|
859
848
|
@dataclass
|
|
860
|
-
class
|
|
849
|
+
class InterruptionFrame(SystemFrame):
|
|
861
850
|
"""Frame indicating user started speaking (interruption detected).
|
|
862
851
|
|
|
863
852
|
Emitted by the BaseInputTransport to indicate that a user has started
|
|
@@ -869,6 +858,34 @@ class StartInterruptionFrame(SystemFrame):
|
|
|
869
858
|
pass
|
|
870
859
|
|
|
871
860
|
|
|
861
|
+
@dataclass
|
|
862
|
+
class StartInterruptionFrame(InterruptionFrame):
|
|
863
|
+
"""Frame indicating user started speaking (interruption detected).
|
|
864
|
+
|
|
865
|
+
.. deprecated:: 0.0.85
|
|
866
|
+
This frame is deprecated and will be removed in a future version.
|
|
867
|
+
Instead, use `InterruptionFrame`.
|
|
868
|
+
|
|
869
|
+
Emitted by the BaseInputTransport to indicate that a user has started
|
|
870
|
+
speaking (i.e. is interrupting). This is similar to
|
|
871
|
+
UserStartedSpeakingFrame except that it should be pushed concurrently
|
|
872
|
+
with other frames (so the order is not guaranteed).
|
|
873
|
+
"""
|
|
874
|
+
|
|
875
|
+
def __post_init__(self):
|
|
876
|
+
super().__post_init__()
|
|
877
|
+
import warnings
|
|
878
|
+
|
|
879
|
+
with warnings.catch_warnings():
|
|
880
|
+
warnings.simplefilter("always")
|
|
881
|
+
warnings.warn(
|
|
882
|
+
"StartInterruptionFrame is deprecated and will be removed in a future version. "
|
|
883
|
+
"Instead, use InterruptionFrame.",
|
|
884
|
+
DeprecationWarning,
|
|
885
|
+
stacklevel=2,
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
|
|
872
889
|
@dataclass
|
|
873
890
|
class UserStartedSpeakingFrame(SystemFrame):
|
|
874
891
|
"""Frame indicating user has started speaking.
|
|
@@ -944,20 +961,6 @@ class VADUserStoppedSpeakingFrame(SystemFrame):
|
|
|
944
961
|
pass
|
|
945
962
|
|
|
946
963
|
|
|
947
|
-
@dataclass
|
|
948
|
-
class BotInterruptionFrame(SystemFrame):
|
|
949
|
-
"""Frame indicating the bot should be interrupted.
|
|
950
|
-
|
|
951
|
-
Emitted when the bot should be interrupted. This will mainly cause the
|
|
952
|
-
same actions as if the user interrupted except that the
|
|
953
|
-
UserStartedSpeakingFrame and UserStoppedSpeakingFrame won't be generated.
|
|
954
|
-
This frame should be pushed upstreams. It results in the BaseInputTransport
|
|
955
|
-
starting an interruption by pushing a StartInterruptionFrame downstream.
|
|
956
|
-
"""
|
|
957
|
-
|
|
958
|
-
pass
|
|
959
|
-
|
|
960
|
-
|
|
961
964
|
@dataclass
|
|
962
965
|
class BotStartedSpeakingFrame(SystemFrame):
|
|
963
966
|
"""Frame indicating the bot started speaking.
|
|
@@ -1115,8 +1118,8 @@ class STTMuteFrame(SystemFrame):
|
|
|
1115
1118
|
|
|
1116
1119
|
|
|
1117
1120
|
@dataclass
|
|
1118
|
-
class
|
|
1119
|
-
"""Frame for
|
|
1121
|
+
class InputTransportMessageFrame(SystemFrame):
|
|
1122
|
+
"""Frame for transport messages received from external sources.
|
|
1120
1123
|
|
|
1121
1124
|
Parameters:
|
|
1122
1125
|
message: The urgent transport message payload.
|
|
@@ -1129,20 +1132,69 @@ class TransportMessageUrgentFrame(SystemFrame):
|
|
|
1129
1132
|
|
|
1130
1133
|
|
|
1131
1134
|
@dataclass
|
|
1132
|
-
class InputTransportMessageUrgentFrame(
|
|
1135
|
+
class InputTransportMessageUrgentFrame(InputTransportMessageFrame):
|
|
1133
1136
|
"""Frame for transport messages received from external sources.
|
|
1134
1137
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
TransportMessageFrame while marking the message as having been received
|
|
1139
|
-
rather than generated locally.
|
|
1138
|
+
.. deprecated:: 0.0.87
|
|
1139
|
+
This frame is deprecated and will be removed in a future version.
|
|
1140
|
+
Instead, use `InputTransportMessageFrame`.
|
|
1140
1141
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1142
|
+
Parameters:
|
|
1143
|
+
message: The urgent transport message payload.
|
|
1143
1144
|
"""
|
|
1144
1145
|
|
|
1145
|
-
|
|
1146
|
+
def __post_init__(self):
|
|
1147
|
+
super().__post_init__()
|
|
1148
|
+
import warnings
|
|
1149
|
+
|
|
1150
|
+
with warnings.catch_warnings():
|
|
1151
|
+
warnings.simplefilter("always")
|
|
1152
|
+
warnings.warn(
|
|
1153
|
+
"InputTransportMessageUrgentFrame is deprecated and will be removed in a future version. "
|
|
1154
|
+
"Instead, use InputTransportMessageFrame.",
|
|
1155
|
+
DeprecationWarning,
|
|
1156
|
+
stacklevel=2,
|
|
1157
|
+
)
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
@dataclass
|
|
1161
|
+
class OutputTransportMessageUrgentFrame(SystemFrame):
|
|
1162
|
+
"""Frame for urgent transport messages that need to be sent immediately.
|
|
1163
|
+
|
|
1164
|
+
Parameters:
|
|
1165
|
+
message: The urgent transport message payload.
|
|
1166
|
+
"""
|
|
1167
|
+
|
|
1168
|
+
message: Any
|
|
1169
|
+
|
|
1170
|
+
def __str__(self):
|
|
1171
|
+
return f"{self.name}(message: {self.message})"
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
@dataclass
|
|
1175
|
+
class TransportMessageUrgentFrame(OutputTransportMessageUrgentFrame):
|
|
1176
|
+
"""Frame for urgent transport messages that need to be sent immediately.
|
|
1177
|
+
|
|
1178
|
+
.. deprecated:: 0.0.87
|
|
1179
|
+
This frame is deprecated and will be removed in a future version.
|
|
1180
|
+
Instead, use `OutputTransportMessageUrgentFrame`.
|
|
1181
|
+
|
|
1182
|
+
Parameters:
|
|
1183
|
+
message: The urgent transport message payload.
|
|
1184
|
+
"""
|
|
1185
|
+
|
|
1186
|
+
def __post_init__(self):
|
|
1187
|
+
super().__post_init__()
|
|
1188
|
+
import warnings
|
|
1189
|
+
|
|
1190
|
+
with warnings.catch_warnings():
|
|
1191
|
+
warnings.simplefilter("always")
|
|
1192
|
+
warnings.warn(
|
|
1193
|
+
"TransportMessageUrgentFrame is deprecated and will be removed in a future version. "
|
|
1194
|
+
"Instead, use OutputTransportMessageFrame.",
|
|
1195
|
+
DeprecationWarning,
|
|
1196
|
+
stacklevel=2,
|
|
1197
|
+
)
|
|
1146
1198
|
|
|
1147
1199
|
|
|
1148
1200
|
@dataclass
|
|
@@ -1253,23 +1305,6 @@ class UserImageRawFrame(InputImageRawFrame):
|
|
|
1253
1305
|
return f"{self.name}(pts: {pts}, user: {self.user_id}, source: {self.transport_source}, size: {self.size}, format: {self.format}, request: {self.request})"
|
|
1254
1306
|
|
|
1255
1307
|
|
|
1256
|
-
@dataclass
|
|
1257
|
-
class VisionImageRawFrame(InputImageRawFrame):
|
|
1258
|
-
"""Image frame for vision/image analysis with associated text prompt.
|
|
1259
|
-
|
|
1260
|
-
An image with an associated text to ask for a description of it.
|
|
1261
|
-
|
|
1262
|
-
Parameters:
|
|
1263
|
-
text: Optional text prompt describing what to analyze in the image.
|
|
1264
|
-
"""
|
|
1265
|
-
|
|
1266
|
-
text: Optional[str] = None
|
|
1267
|
-
|
|
1268
|
-
def __str__(self):
|
|
1269
|
-
pts = format_pts(self.pts)
|
|
1270
|
-
return f"{self.name}(pts: {pts}, text: [{self.text}], size: {self.size}, format: {self.format})"
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
1308
|
@dataclass
|
|
1274
1309
|
class InputDTMFFrame(DTMFFrame, SystemFrame):
|
|
1275
1310
|
"""DTMF keypress input frame from transport."""
|
|
@@ -1571,15 +1606,13 @@ class MixerEnableFrame(MixerControlFrame):
|
|
|
1571
1606
|
|
|
1572
1607
|
|
|
1573
1608
|
@dataclass
|
|
1574
|
-
class
|
|
1575
|
-
"""
|
|
1609
|
+
class ServiceSwitcherFrame(ControlFrame):
|
|
1610
|
+
"""A base class for frames that affect ServiceSwitcher behavior."""
|
|
1576
1611
|
|
|
1577
1612
|
|
|
1578
1613
|
@dataclass
|
|
1579
|
-
class
|
|
1580
|
-
"""
|
|
1581
|
-
|
|
1582
|
-
pass
|
|
1614
|
+
class StartUserIdleProcessorFrame(SystemFrame):
|
|
1615
|
+
"""Frame to start the UserIdleProcessor monitoring."""
|
|
1583
1616
|
|
|
1584
1617
|
|
|
1585
1618
|
@dataclass
|
|
@@ -54,7 +54,7 @@ class DebugLogObserver(BaseObserver):
|
|
|
54
54
|
|
|
55
55
|
Log frames with specific source/destination filters::
|
|
56
56
|
|
|
57
|
-
from pipecat.frames.frames import
|
|
57
|
+
from pipecat.frames.frames import InterruptionFrame, UserStartedSpeakingFrame, LLMTextFrame
|
|
58
58
|
from pipecat.observers.loggers.debug_log_observer import DebugLogObserver, FrameEndpoint
|
|
59
59
|
from pipecat.transports.base_output import BaseOutputTransport
|
|
60
60
|
from pipecat.services.stt_service import STTService
|
|
@@ -62,8 +62,8 @@ class DebugLogObserver(BaseObserver):
|
|
|
62
62
|
observers=[
|
|
63
63
|
DebugLogObserver(
|
|
64
64
|
frame_types={
|
|
65
|
-
# Only log
|
|
66
|
-
|
|
65
|
+
# Only log InterruptionFrame when source is BaseOutputTransport
|
|
66
|
+
InterruptionFrame: (BaseOutputTransport, FrameEndpoint.SOURCE),
|
|
67
67
|
# Only log UserStartedSpeakingFrame when destination is STTService
|
|
68
68
|
UserStartedSpeakingFrame: (STTService, FrameEndpoint.DESTINATION),
|
|
69
69
|
# Log LLMTextFrame regardless of source or destination type
|
|
@@ -11,6 +11,7 @@ from loguru import logger
|
|
|
11
11
|
from pipecat.frames.frames import (
|
|
12
12
|
FunctionCallInProgressFrame,
|
|
13
13
|
FunctionCallResultFrame,
|
|
14
|
+
LLMContextFrame,
|
|
14
15
|
LLMFullResponseEndFrame,
|
|
15
16
|
LLMFullResponseStartFrame,
|
|
16
17
|
LLMMessagesFrame,
|
|
@@ -79,10 +80,13 @@ class LLMLogObserver(BaseObserver):
|
|
|
79
80
|
f"🧠 {arrow} {dst} LLM MESSAGES FRAME: {frame.messages} at {time_sec:.2f}s"
|
|
80
81
|
)
|
|
81
82
|
# Log OpenAILLMContextFrame (input)
|
|
82
|
-
elif isinstance(frame, OpenAILLMContextFrame):
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
elif isinstance(frame, (LLMContextFrame, OpenAILLMContextFrame)):
|
|
84
|
+
messages = (
|
|
85
|
+
frame.context.messages
|
|
86
|
+
if isinstance(frame, OpenAILLMContextFrame)
|
|
87
|
+
else frame.context.get_messages()
|
|
85
88
|
)
|
|
89
|
+
logger.debug(f"🧠 {arrow} {dst} LLM CONTEXT FRAME: {messages} at {time_sec:.2f}s")
|
|
86
90
|
# Log function call result (input)
|
|
87
91
|
elif isinstance(frame, FunctionCallResultFrame):
|
|
88
92
|
logger.debug(
|
|
@@ -61,17 +61,29 @@ class UserBotLatencyLogObserver(BaseObserver):
|
|
|
61
61
|
elif isinstance(data.frame, UserStoppedSpeakingFrame):
|
|
62
62
|
self._user_stopped_time = time.time()
|
|
63
63
|
elif isinstance(data.frame, (EndFrame, CancelFrame)):
|
|
64
|
-
|
|
65
|
-
avg_latency = mean(self._latencies)
|
|
66
|
-
min_latency = min(self._latencies)
|
|
67
|
-
max_latency = max(self._latencies)
|
|
68
|
-
logger.info(
|
|
69
|
-
f"⏱️ LATENCY FROM USER STOPPED SPEAKING TO BOT STARTED SPEAKING - Avg: {avg_latency:.3f}s, Min: {min_latency:.3f}s, Max: {max_latency:.3f}s"
|
|
70
|
-
)
|
|
64
|
+
self._log_summary()
|
|
71
65
|
elif isinstance(data.frame, BotStartedSpeakingFrame) and self._user_stopped_time:
|
|
72
66
|
latency = time.time() - self._user_stopped_time
|
|
73
67
|
self._user_stopped_time = 0
|
|
74
68
|
self._latencies.append(latency)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
self._log_latency(latency)
|
|
70
|
+
|
|
71
|
+
def _log_summary(self):
|
|
72
|
+
if not self._latencies:
|
|
73
|
+
return
|
|
74
|
+
avg_latency = mean(self._latencies)
|
|
75
|
+
min_latency = min(self._latencies)
|
|
76
|
+
max_latency = max(self._latencies)
|
|
77
|
+
logger.info(
|
|
78
|
+
f"⏱️ LATENCY FROM USER STOPPED SPEAKING TO BOT STARTED SPEAKING - Avg: {avg_latency:.3f}s, Min: {min_latency:.3f}s, Max: {max_latency:.3f}s"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def _log_latency(self, latency: float):
|
|
82
|
+
"""Log the latency.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
latency: The latency to log.
|
|
86
|
+
"""
|
|
87
|
+
logger.debug(
|
|
88
|
+
f"⏱️ LATENCY FROM USER STOPPED SPEAKING TO BOT STARTED SPEAKING: {latency:.3f}s"
|
|
89
|
+
)
|
pipecat/pipeline/runner.py
CHANGED
|
@@ -106,13 +106,21 @@ class PipelineRunner(BaseObject):
|
|
|
106
106
|
|
|
107
107
|
def _setup_sigint(self):
|
|
108
108
|
"""Set up signal handlers for graceful shutdown."""
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
try:
|
|
110
|
+
loop = asyncio.get_running_loop()
|
|
111
|
+
loop.add_signal_handler(signal.SIGINT, lambda *args: self._sig_handler())
|
|
112
|
+
except NotImplementedError:
|
|
113
|
+
# Windows fallback
|
|
114
|
+
signal.signal(signal.SIGINT, lambda s, f: self._sig_handler())
|
|
111
115
|
|
|
112
116
|
def _setup_sigterm(self):
|
|
113
117
|
"""Set up signal handlers for graceful shutdown."""
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
try:
|
|
119
|
+
loop = asyncio.get_running_loop()
|
|
120
|
+
loop.add_signal_handler(signal.SIGTERM, lambda *args: self._sig_handler())
|
|
121
|
+
except NotImplementedError:
|
|
122
|
+
# Windows fallback
|
|
123
|
+
signal.signal(signal.SIGTERM, lambda s, f: self._sig_handler())
|
|
116
124
|
|
|
117
125
|
def _sig_handler(self):
|
|
118
126
|
"""Handle interrupt signals by cancelling all tasks."""
|
|
@@ -6,9 +6,15 @@
|
|
|
6
6
|
|
|
7
7
|
"""Service switcher for switching between different services at runtime, with different switching strategies."""
|
|
8
8
|
|
|
9
|
+
from dataclasses import dataclass
|
|
9
10
|
from typing import Any, Generic, List, Optional, Type, TypeVar
|
|
10
11
|
|
|
11
|
-
from pipecat.frames.frames import
|
|
12
|
+
from pipecat.frames.frames import (
|
|
13
|
+
ControlFrame,
|
|
14
|
+
Frame,
|
|
15
|
+
ManuallySwitchServiceFrame,
|
|
16
|
+
ServiceSwitcherFrame,
|
|
17
|
+
)
|
|
12
18
|
from pipecat.pipeline.parallel_pipeline import ParallelPipeline
|
|
13
19
|
from pipecat.processors.filters.function_filter import FunctionFilter
|
|
14
20
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
|
@@ -22,19 +28,6 @@ class ServiceSwitcherStrategy:
|
|
|
22
28
|
self.services = services
|
|
23
29
|
self.active_service: Optional[FrameProcessor] = None
|
|
24
30
|
|
|
25
|
-
def is_active(self, service: FrameProcessor) -> bool:
|
|
26
|
-
"""Determine if the given service is the currently active one.
|
|
27
|
-
|
|
28
|
-
This method should be overridden by subclasses to implement specific logic.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
service: The service to check.
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
True if the given service is the active one, False otherwise.
|
|
35
|
-
"""
|
|
36
|
-
raise NotImplementedError("Subclasses must implement this method.")
|
|
37
|
-
|
|
38
31
|
def handle_frame(self, frame: ServiceSwitcherFrame, direction: FrameDirection):
|
|
39
32
|
"""Handle a frame that controls service switching.
|
|
40
33
|
|
|
@@ -60,17 +53,6 @@ class ServiceSwitcherStrategyManual(ServiceSwitcherStrategy):
|
|
|
60
53
|
super().__init__(services)
|
|
61
54
|
self.active_service = services[0] if services else None
|
|
62
55
|
|
|
63
|
-
def is_active(self, service: FrameProcessor) -> bool:
|
|
64
|
-
"""Check if the given service is the currently active one.
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
service: The service to check.
|
|
68
|
-
|
|
69
|
-
Returns:
|
|
70
|
-
True if the given service is the active one, False otherwise.
|
|
71
|
-
"""
|
|
72
|
-
return service == self.active_service
|
|
73
|
-
|
|
74
56
|
def handle_frame(self, frame: ServiceSwitcherFrame, direction: FrameDirection):
|
|
75
57
|
"""Handle a frame that controls service switching.
|
|
76
58
|
|
|
@@ -79,20 +61,21 @@ class ServiceSwitcherStrategyManual(ServiceSwitcherStrategy):
|
|
|
79
61
|
direction: The direction of the frame (upstream or downstream).
|
|
80
62
|
"""
|
|
81
63
|
if isinstance(frame, ManuallySwitchServiceFrame):
|
|
82
|
-
self.
|
|
64
|
+
self._set_active_if_available(frame.service)
|
|
83
65
|
else:
|
|
84
66
|
raise ValueError(f"Unsupported frame type: {type(frame)}")
|
|
85
67
|
|
|
86
|
-
def
|
|
87
|
-
"""Set the active service to the given one.
|
|
68
|
+
def _set_active_if_available(self, service: FrameProcessor):
|
|
69
|
+
"""Set the active service to the given one, if it is in the list of available services.
|
|
70
|
+
|
|
71
|
+
If it's not in the list, the request is ignored, as it may have been
|
|
72
|
+
intended for another ServiceSwitcher in the pipeline.
|
|
88
73
|
|
|
89
74
|
Args:
|
|
90
75
|
service: The service to set as active.
|
|
91
76
|
"""
|
|
92
77
|
if service in self.services:
|
|
93
78
|
self.active_service = service
|
|
94
|
-
else:
|
|
95
|
-
raise ValueError(f"Service {service} is not in the list of available services.")
|
|
96
79
|
|
|
97
80
|
|
|
98
81
|
StrategyType = TypeVar("StrategyType", bound=ServiceSwitcherStrategy)
|
|
@@ -108,6 +91,43 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
|
|
|
108
91
|
self.services = services
|
|
109
92
|
self.strategy = strategy
|
|
110
93
|
|
|
94
|
+
class ServiceSwitcherFilter(FunctionFilter):
|
|
95
|
+
"""An internal filter that allows frames to pass through to the wrapped service only if it's the active service."""
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
wrapped_service: FrameProcessor,
|
|
100
|
+
active_service: FrameProcessor,
|
|
101
|
+
direction: FrameDirection,
|
|
102
|
+
):
|
|
103
|
+
"""Initialize the service switcher filter with a strategy and direction."""
|
|
104
|
+
|
|
105
|
+
async def filter(_: Frame) -> bool:
|
|
106
|
+
return self._wrapped_service == self._active_service
|
|
107
|
+
|
|
108
|
+
super().__init__(filter, direction)
|
|
109
|
+
self._wrapped_service = wrapped_service
|
|
110
|
+
self._active_service = active_service
|
|
111
|
+
|
|
112
|
+
async def process_frame(self, frame, direction):
|
|
113
|
+
"""Process a frame through the filter, handling special internal filter-updating frames."""
|
|
114
|
+
if isinstance(frame, ServiceSwitcher.ServiceSwitcherFilterFrame):
|
|
115
|
+
self._active_service = frame.active_service
|
|
116
|
+
# Two ServiceSwitcherFilters "sandwich" a service. Push the
|
|
117
|
+
# frame only to update the other side of the sandwich, but
|
|
118
|
+
# otherwise don't let it leave the sandwich.
|
|
119
|
+
if direction == self._direction:
|
|
120
|
+
await self.push_frame(frame, direction)
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
await super().process_frame(frame, direction)
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class ServiceSwitcherFilterFrame(ControlFrame):
|
|
127
|
+
"""An internal frame used by ServiceSwitcher to filter frames based on active service."""
|
|
128
|
+
|
|
129
|
+
active_service: FrameProcessor
|
|
130
|
+
|
|
111
131
|
@staticmethod
|
|
112
132
|
def _make_pipeline_definitions(
|
|
113
133
|
services: List[FrameProcessor], strategy: ServiceSwitcherStrategy
|
|
@@ -121,14 +141,18 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
|
|
|
121
141
|
def _make_pipeline_definition(
|
|
122
142
|
service: FrameProcessor, strategy: ServiceSwitcherStrategy
|
|
123
143
|
) -> Any:
|
|
124
|
-
async def filter(frame) -> bool:
|
|
125
|
-
_ = frame
|
|
126
|
-
return strategy.is_active(service)
|
|
127
|
-
|
|
128
144
|
return [
|
|
129
|
-
|
|
145
|
+
ServiceSwitcher.ServiceSwitcherFilter(
|
|
146
|
+
wrapped_service=service,
|
|
147
|
+
active_service=strategy.active_service,
|
|
148
|
+
direction=FrameDirection.DOWNSTREAM,
|
|
149
|
+
),
|
|
130
150
|
service,
|
|
131
|
-
|
|
151
|
+
ServiceSwitcher.ServiceSwitcherFilter(
|
|
152
|
+
wrapped_service=service,
|
|
153
|
+
active_service=strategy.active_service,
|
|
154
|
+
direction=FrameDirection.UPSTREAM,
|
|
155
|
+
),
|
|
132
156
|
]
|
|
133
157
|
|
|
134
158
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
|
@@ -142,3 +166,7 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
|
|
|
142
166
|
|
|
143
167
|
if isinstance(frame, ServiceSwitcherFrame):
|
|
144
168
|
self.strategy.handle_frame(frame, direction)
|
|
169
|
+
service_switcher_filter_frame = ServiceSwitcher.ServiceSwitcherFilterFrame(
|
|
170
|
+
active_service=self.strategy.active_service
|
|
171
|
+
)
|
|
172
|
+
await super().process_frame(service_switcher_filter_frame, direction)
|