dv-pipecat-ai 0.0.82.dev857__py3-none-any.whl → 0.0.85.dev837__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 (195) hide show
  1. {dv_pipecat_ai-0.0.82.dev857.dist-info → dv_pipecat_ai-0.0.85.dev837.dist-info}/METADATA +98 -130
  2. {dv_pipecat_ai-0.0.82.dev857.dist-info → dv_pipecat_ai-0.0.85.dev837.dist-info}/RECORD +192 -140
  3. pipecat/adapters/base_llm_adapter.py +38 -1
  4. pipecat/adapters/services/anthropic_adapter.py +9 -14
  5. pipecat/adapters/services/aws_nova_sonic_adapter.py +120 -5
  6. pipecat/adapters/services/bedrock_adapter.py +236 -13
  7. pipecat/adapters/services/gemini_adapter.py +12 -8
  8. pipecat/adapters/services/open_ai_adapter.py +19 -7
  9. pipecat/adapters/services/open_ai_realtime_adapter.py +5 -0
  10. pipecat/audio/dtmf/dtmf-0.wav +0 -0
  11. pipecat/audio/dtmf/dtmf-1.wav +0 -0
  12. pipecat/audio/dtmf/dtmf-2.wav +0 -0
  13. pipecat/audio/dtmf/dtmf-3.wav +0 -0
  14. pipecat/audio/dtmf/dtmf-4.wav +0 -0
  15. pipecat/audio/dtmf/dtmf-5.wav +0 -0
  16. pipecat/audio/dtmf/dtmf-6.wav +0 -0
  17. pipecat/audio/dtmf/dtmf-7.wav +0 -0
  18. pipecat/audio/dtmf/dtmf-8.wav +0 -0
  19. pipecat/audio/dtmf/dtmf-9.wav +0 -0
  20. pipecat/audio/dtmf/dtmf-pound.wav +0 -0
  21. pipecat/audio/dtmf/dtmf-star.wav +0 -0
  22. pipecat/audio/filters/krisp_viva_filter.py +193 -0
  23. pipecat/audio/filters/noisereduce_filter.py +15 -0
  24. pipecat/audio/turn/base_turn_analyzer.py +9 -1
  25. pipecat/audio/turn/smart_turn/base_smart_turn.py +14 -8
  26. pipecat/audio/turn/smart_turn/data/__init__.py +0 -0
  27. pipecat/audio/turn/smart_turn/data/smart-turn-v3.0.onnx +0 -0
  28. pipecat/audio/turn/smart_turn/http_smart_turn.py +6 -2
  29. pipecat/audio/turn/smart_turn/local_smart_turn.py +1 -1
  30. pipecat/audio/turn/smart_turn/local_smart_turn_v2.py +1 -1
  31. pipecat/audio/turn/smart_turn/local_smart_turn_v3.py +124 -0
  32. pipecat/audio/vad/data/README.md +10 -0
  33. pipecat/audio/vad/data/silero_vad_v2.onnx +0 -0
  34. pipecat/audio/vad/silero.py +9 -3
  35. pipecat/audio/vad/vad_analyzer.py +13 -1
  36. pipecat/extensions/voicemail/voicemail_detector.py +5 -5
  37. pipecat/frames/frames.py +277 -86
  38. pipecat/observers/loggers/debug_log_observer.py +3 -3
  39. pipecat/observers/loggers/llm_log_observer.py +7 -3
  40. pipecat/observers/loggers/user_bot_latency_log_observer.py +22 -10
  41. pipecat/pipeline/runner.py +18 -6
  42. pipecat/pipeline/service_switcher.py +64 -36
  43. pipecat/pipeline/task.py +125 -79
  44. pipecat/pipeline/tts_switcher.py +30 -0
  45. pipecat/processors/aggregators/dtmf_aggregator.py +2 -3
  46. pipecat/processors/aggregators/{gated_openai_llm_context.py → gated_llm_context.py} +9 -9
  47. pipecat/processors/aggregators/gated_open_ai_llm_context.py +12 -0
  48. pipecat/processors/aggregators/llm_context.py +40 -2
  49. pipecat/processors/aggregators/llm_response.py +32 -15
  50. pipecat/processors/aggregators/llm_response_universal.py +19 -15
  51. pipecat/processors/aggregators/user_response.py +6 -6
  52. pipecat/processors/aggregators/vision_image_frame.py +24 -2
  53. pipecat/processors/audio/audio_buffer_processor.py +43 -8
  54. pipecat/processors/dtmf_aggregator.py +174 -77
  55. pipecat/processors/filters/stt_mute_filter.py +17 -0
  56. pipecat/processors/frame_processor.py +110 -24
  57. pipecat/processors/frameworks/langchain.py +8 -2
  58. pipecat/processors/frameworks/rtvi.py +210 -68
  59. pipecat/processors/frameworks/strands_agents.py +170 -0
  60. pipecat/processors/logger.py +2 -2
  61. pipecat/processors/transcript_processor.py +26 -5
  62. pipecat/processors/user_idle_processor.py +35 -11
  63. pipecat/runner/daily.py +59 -20
  64. pipecat/runner/run.py +395 -93
  65. pipecat/runner/types.py +6 -4
  66. pipecat/runner/utils.py +51 -10
  67. pipecat/serializers/__init__.py +5 -1
  68. pipecat/serializers/asterisk.py +16 -2
  69. pipecat/serializers/convox.py +41 -4
  70. pipecat/serializers/custom.py +257 -0
  71. pipecat/serializers/exotel.py +5 -5
  72. pipecat/serializers/livekit.py +20 -0
  73. pipecat/serializers/plivo.py +5 -5
  74. pipecat/serializers/protobuf.py +6 -5
  75. pipecat/serializers/telnyx.py +2 -2
  76. pipecat/serializers/twilio.py +43 -23
  77. pipecat/serializers/vi.py +324 -0
  78. pipecat/services/ai_service.py +2 -6
  79. pipecat/services/anthropic/llm.py +2 -25
  80. pipecat/services/assemblyai/models.py +6 -0
  81. pipecat/services/assemblyai/stt.py +13 -5
  82. pipecat/services/asyncai/tts.py +5 -3
  83. pipecat/services/aws/__init__.py +1 -0
  84. pipecat/services/aws/llm.py +147 -105
  85. pipecat/services/aws/nova_sonic/__init__.py +0 -0
  86. pipecat/services/aws/nova_sonic/context.py +436 -0
  87. pipecat/services/aws/nova_sonic/frames.py +25 -0
  88. pipecat/services/aws/nova_sonic/llm.py +1265 -0
  89. pipecat/services/aws/stt.py +3 -3
  90. pipecat/services/aws_nova_sonic/__init__.py +19 -1
  91. pipecat/services/aws_nova_sonic/aws.py +11 -1151
  92. pipecat/services/aws_nova_sonic/context.py +8 -354
  93. pipecat/services/aws_nova_sonic/frames.py +13 -17
  94. pipecat/services/azure/llm.py +51 -1
  95. pipecat/services/azure/realtime/__init__.py +0 -0
  96. pipecat/services/azure/realtime/llm.py +65 -0
  97. pipecat/services/azure/stt.py +15 -0
  98. pipecat/services/cartesia/stt.py +77 -70
  99. pipecat/services/cartesia/tts.py +80 -13
  100. pipecat/services/deepgram/__init__.py +1 -0
  101. pipecat/services/deepgram/flux/__init__.py +0 -0
  102. pipecat/services/deepgram/flux/stt.py +640 -0
  103. pipecat/services/elevenlabs/__init__.py +4 -1
  104. pipecat/services/elevenlabs/stt.py +339 -0
  105. pipecat/services/elevenlabs/tts.py +87 -46
  106. pipecat/services/fish/tts.py +5 -2
  107. pipecat/services/gemini_multimodal_live/events.py +38 -524
  108. pipecat/services/gemini_multimodal_live/file_api.py +23 -173
  109. pipecat/services/gemini_multimodal_live/gemini.py +41 -1403
  110. pipecat/services/gladia/stt.py +56 -72
  111. pipecat/services/google/__init__.py +1 -0
  112. pipecat/services/google/gemini_live/__init__.py +3 -0
  113. pipecat/services/google/gemini_live/file_api.py +189 -0
  114. pipecat/services/google/gemini_live/llm.py +1582 -0
  115. pipecat/services/google/gemini_live/llm_vertex.py +184 -0
  116. pipecat/services/google/llm.py +15 -11
  117. pipecat/services/google/llm_openai.py +3 -3
  118. pipecat/services/google/llm_vertex.py +86 -16
  119. pipecat/services/google/stt.py +4 -0
  120. pipecat/services/google/tts.py +7 -3
  121. pipecat/services/heygen/api.py +2 -0
  122. pipecat/services/heygen/client.py +8 -4
  123. pipecat/services/heygen/video.py +2 -0
  124. pipecat/services/hume/__init__.py +5 -0
  125. pipecat/services/hume/tts.py +220 -0
  126. pipecat/services/inworld/tts.py +6 -6
  127. pipecat/services/llm_service.py +15 -5
  128. pipecat/services/lmnt/tts.py +4 -2
  129. pipecat/services/mcp_service.py +4 -2
  130. pipecat/services/mem0/memory.py +6 -5
  131. pipecat/services/mistral/llm.py +29 -8
  132. pipecat/services/moondream/vision.py +42 -16
  133. pipecat/services/neuphonic/tts.py +5 -2
  134. pipecat/services/openai/__init__.py +1 -0
  135. pipecat/services/openai/base_llm.py +27 -20
  136. pipecat/services/openai/realtime/__init__.py +0 -0
  137. pipecat/services/openai/realtime/context.py +272 -0
  138. pipecat/services/openai/realtime/events.py +1106 -0
  139. pipecat/services/openai/realtime/frames.py +37 -0
  140. pipecat/services/openai/realtime/llm.py +829 -0
  141. pipecat/services/openai/tts.py +49 -10
  142. pipecat/services/openai_realtime/__init__.py +27 -0
  143. pipecat/services/openai_realtime/azure.py +21 -0
  144. pipecat/services/openai_realtime/context.py +21 -0
  145. pipecat/services/openai_realtime/events.py +21 -0
  146. pipecat/services/openai_realtime/frames.py +21 -0
  147. pipecat/services/openai_realtime_beta/azure.py +16 -0
  148. pipecat/services/openai_realtime_beta/openai.py +17 -5
  149. pipecat/services/piper/tts.py +7 -9
  150. pipecat/services/playht/tts.py +34 -4
  151. pipecat/services/rime/tts.py +12 -12
  152. pipecat/services/riva/stt.py +3 -1
  153. pipecat/services/salesforce/__init__.py +9 -0
  154. pipecat/services/salesforce/llm.py +700 -0
  155. pipecat/services/sarvam/__init__.py +7 -0
  156. pipecat/services/sarvam/stt.py +540 -0
  157. pipecat/services/sarvam/tts.py +97 -13
  158. pipecat/services/simli/video.py +2 -2
  159. pipecat/services/speechmatics/stt.py +22 -10
  160. pipecat/services/stt_service.py +47 -0
  161. pipecat/services/tavus/video.py +2 -2
  162. pipecat/services/tts_service.py +75 -22
  163. pipecat/services/vision_service.py +7 -6
  164. pipecat/services/vistaar/llm.py +51 -9
  165. pipecat/tests/utils.py +4 -4
  166. pipecat/transcriptions/language.py +41 -1
  167. pipecat/transports/base_input.py +13 -34
  168. pipecat/transports/base_output.py +140 -104
  169. pipecat/transports/daily/transport.py +199 -26
  170. pipecat/transports/heygen/__init__.py +0 -0
  171. pipecat/transports/heygen/transport.py +381 -0
  172. pipecat/transports/livekit/transport.py +228 -63
  173. pipecat/transports/local/audio.py +6 -1
  174. pipecat/transports/local/tk.py +11 -2
  175. pipecat/transports/network/fastapi_websocket.py +1 -1
  176. pipecat/transports/smallwebrtc/connection.py +103 -19
  177. pipecat/transports/smallwebrtc/request_handler.py +246 -0
  178. pipecat/transports/smallwebrtc/transport.py +65 -23
  179. pipecat/transports/tavus/transport.py +23 -12
  180. pipecat/transports/websocket/client.py +41 -5
  181. pipecat/transports/websocket/fastapi.py +21 -11
  182. pipecat/transports/websocket/server.py +14 -7
  183. pipecat/transports/whatsapp/api.py +8 -0
  184. pipecat/transports/whatsapp/client.py +47 -0
  185. pipecat/utils/base_object.py +54 -22
  186. pipecat/utils/redis.py +58 -0
  187. pipecat/utils/string.py +13 -1
  188. pipecat/utils/tracing/service_decorators.py +21 -21
  189. pipecat/serializers/genesys.py +0 -95
  190. pipecat/services/google/test-google-chirp.py +0 -45
  191. pipecat/services/openai.py +0 -698
  192. {dv_pipecat_ai-0.0.82.dev857.dist-info → dv_pipecat_ai-0.0.85.dev837.dist-info}/WHEEL +0 -0
  193. {dv_pipecat_ai-0.0.82.dev857.dist-info → dv_pipecat_ai-0.0.85.dev837.dist-info}/licenses/LICENSE +0 -0
  194. {dv_pipecat_ai-0.0.82.dev857.dist-info → dv_pipecat_ai-0.0.85.dev837.dist-info}/top_level.txt +0 -0
  195. /pipecat/services/{aws_nova_sonic → aws/nova_sonic}/ready.wav +0 -0
@@ -0,0 +1,436 @@
1
+ #
2
+ # Copyright (c) 2025, Daily
3
+ #
4
+ # SPDX-License-Identifier: BSD 2-Clause License
5
+ #
6
+
7
+ """Context management for AWS Nova Sonic LLM service.
8
+
9
+ This module provides specialized context aggregators and message handling for AWS Nova Sonic,
10
+ including conversation history management and role-specific message processing.
11
+
12
+ .. deprecated:: 0.0.91
13
+ AWS Nova Sonic no longer uses types from this module under the hood.
14
+ It now uses `LLMContext` and `LLMContextAggregatorPair`.
15
+ Using the new patterns should allow you to not need types from this module.
16
+
17
+ BEFORE:
18
+ ```
19
+ # Setup
20
+ context = OpenAILLMContext(messages, tools)
21
+ context_aggregator = llm.create_context_aggregator(context)
22
+
23
+ # Context frame type
24
+ frame: OpenAILLMContextFrame
25
+
26
+ # Context type
27
+ context: AWSNovaSonicLLMContext
28
+ # or
29
+ context: OpenAILLMContext
30
+ ```
31
+
32
+ AFTER:
33
+ ```
34
+ # Setup
35
+ context = LLMContext(messages, tools)
36
+ context_aggregator = LLMContextAggregatorPair(context)
37
+
38
+ # Context frame type
39
+ frame: LLMContextFrame
40
+
41
+ # Context type
42
+ context: LLMContext
43
+ ```
44
+ """
45
+
46
+ import warnings
47
+
48
+ with warnings.catch_warnings():
49
+ warnings.simplefilter("always")
50
+ warnings.warn(
51
+ "Types in pipecat.services.aws.nova_sonic.context (or "
52
+ "pipecat.services.aws_nova_sonic.context) are deprecated. \n"
53
+ "AWS Nova Sonic no longer uses types from this module under the hood. \n"
54
+ "It now uses `LLMContext` and `LLMContextAggregatorPair`. \n"
55
+ "Using the new patterns should allow you to not need types from this module.\n\n"
56
+ "BEFORE:\n"
57
+ "```\n"
58
+ "# Setup\n"
59
+ "context = OpenAILLMContext(messages, tools)\n"
60
+ "context_aggregator = llm.create_context_aggregator(context)\n\n"
61
+ "# Context frame type\n"
62
+ "frame: OpenAILLMContextFrame\n\n"
63
+ "# Context type\n"
64
+ "context: AWSNovaSonicLLMContext\n"
65
+ "# or\n"
66
+ "context: OpenAILLMContext\n\n"
67
+ "```\n\n"
68
+ "AFTER:\n"
69
+ "```\n"
70
+ "# Setup\n"
71
+ "context = LLMContext(messages, tools)\n"
72
+ "context_aggregator = LLMContextAggregatorPair(context)\n\n"
73
+ "# Context frame type\n"
74
+ "frame: LLMContextFrame\n\n"
75
+ "# Context type\n"
76
+ "context: LLMContext\n\n"
77
+ "```",
78
+ DeprecationWarning,
79
+ stacklevel=2,
80
+ )
81
+
82
+ import copy
83
+ from dataclasses import dataclass, field
84
+ from enum import Enum
85
+
86
+ from loguru import logger
87
+
88
+ from pipecat.frames.frames import (
89
+ BotStoppedSpeakingFrame,
90
+ DataFrame,
91
+ Frame,
92
+ FunctionCallResultFrame,
93
+ InterruptionFrame,
94
+ LLMFullResponseEndFrame,
95
+ LLMFullResponseStartFrame,
96
+ LLMMessagesAppendFrame,
97
+ LLMMessagesUpdateFrame,
98
+ LLMSetToolChoiceFrame,
99
+ LLMSetToolsFrame,
100
+ TextFrame,
101
+ UserImageRawFrame,
102
+ )
103
+ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
104
+ from pipecat.processors.frame_processor import FrameDirection
105
+ from pipecat.services.aws.nova_sonic.frames import AWSNovaSonicFunctionCallResultFrame
106
+ from pipecat.services.openai.llm import (
107
+ OpenAIAssistantContextAggregator,
108
+ OpenAIUserContextAggregator,
109
+ )
110
+
111
+
112
+ class Role(Enum):
113
+ """Roles supported in AWS Nova Sonic conversations.
114
+
115
+ Parameters:
116
+ SYSTEM: System-level messages (not used in conversation history).
117
+ USER: Messages sent by the user.
118
+ ASSISTANT: Messages sent by the assistant.
119
+ TOOL: Messages sent by tools (not used in conversation history).
120
+ """
121
+
122
+ SYSTEM = "SYSTEM"
123
+ USER = "USER"
124
+ ASSISTANT = "ASSISTANT"
125
+ TOOL = "TOOL"
126
+
127
+
128
+ @dataclass
129
+ class AWSNovaSonicConversationHistoryMessage:
130
+ """A single message in AWS Nova Sonic conversation history.
131
+
132
+ Parameters:
133
+ role: The role of the message sender (USER or ASSISTANT only).
134
+ text: The text content of the message.
135
+ """
136
+
137
+ role: Role # only USER and ASSISTANT
138
+ text: str
139
+
140
+
141
+ @dataclass
142
+ class AWSNovaSonicConversationHistory:
143
+ """Complete conversation history for AWS Nova Sonic initialization.
144
+
145
+ Parameters:
146
+ system_instruction: System-level instruction for the conversation.
147
+ messages: List of conversation messages between user and assistant.
148
+ """
149
+
150
+ system_instruction: str = None
151
+ messages: list[AWSNovaSonicConversationHistoryMessage] = field(default_factory=list)
152
+
153
+
154
+ class AWSNovaSonicLLMContext(OpenAILLMContext):
155
+ """Specialized LLM context for AWS Nova Sonic service.
156
+
157
+ Extends OpenAI context with Nova Sonic-specific message handling,
158
+ conversation history management, and text buffering capabilities.
159
+ """
160
+
161
+ def __init__(self, messages=None, tools=None, **kwargs):
162
+ """Initialize AWS Nova Sonic LLM context.
163
+
164
+ Args:
165
+ messages: Initial messages for the context.
166
+ tools: Available tools for the context.
167
+ **kwargs: Additional arguments passed to parent class.
168
+ """
169
+ super().__init__(messages=messages, tools=tools, **kwargs)
170
+ self.__setup_local()
171
+
172
+ def __setup_local(self, system_instruction: str = ""):
173
+ self._assistant_text = ""
174
+ self._user_text = ""
175
+ self._system_instruction = system_instruction
176
+
177
+ @staticmethod
178
+ def upgrade_to_nova_sonic(
179
+ obj: OpenAILLMContext, system_instruction: str
180
+ ) -> "AWSNovaSonicLLMContext":
181
+ """Upgrade an OpenAI context to AWS Nova Sonic context.
182
+
183
+ Args:
184
+ obj: The OpenAI context to upgrade.
185
+ system_instruction: System instruction for the context.
186
+
187
+ Returns:
188
+ The upgraded AWS Nova Sonic context.
189
+ """
190
+ if isinstance(obj, OpenAILLMContext) and not isinstance(obj, AWSNovaSonicLLMContext):
191
+ obj.__class__ = AWSNovaSonicLLMContext
192
+ obj.__setup_local(system_instruction)
193
+ return obj
194
+
195
+ # NOTE: this method has the side-effect of updating _system_instruction from messages
196
+ def get_messages_for_initializing_history(self) -> AWSNovaSonicConversationHistory:
197
+ """Get conversation history for initializing AWS Nova Sonic session.
198
+
199
+ Processes stored messages and extracts system instruction and conversation
200
+ history in the format expected by AWS Nova Sonic.
201
+
202
+ Returns:
203
+ Formatted conversation history with system instruction and messages.
204
+ """
205
+ history = AWSNovaSonicConversationHistory(system_instruction=self._system_instruction)
206
+
207
+ # Bail if there are no messages
208
+ if not self.messages:
209
+ return history
210
+
211
+ messages = copy.deepcopy(self.messages)
212
+
213
+ # If we have a "system" message as our first message, let's pull that out into "instruction"
214
+ if messages[0].get("role") == "system":
215
+ system = messages.pop(0)
216
+ content = system.get("content")
217
+ if isinstance(content, str):
218
+ history.system_instruction = content
219
+ elif isinstance(content, list):
220
+ history.system_instruction = content[0].get("text")
221
+ if history.system_instruction:
222
+ self._system_instruction = history.system_instruction
223
+
224
+ # Process remaining messages to fill out conversation history.
225
+ # Nova Sonic supports "user" and "assistant" messages in history.
226
+ for message in messages:
227
+ history_message = self.from_standard_message(message)
228
+ if history_message:
229
+ history.messages.append(history_message)
230
+
231
+ return history
232
+
233
+ def get_messages_for_persistent_storage(self):
234
+ """Get messages formatted for persistent storage.
235
+
236
+ Returns:
237
+ List of messages including system instruction if present.
238
+ """
239
+ messages = super().get_messages_for_persistent_storage()
240
+ # If we have a system instruction and messages doesn't already contain it, add it
241
+ if self._system_instruction and not (messages and messages[0].get("role") == "system"):
242
+ messages.insert(0, {"role": "system", "content": self._system_instruction})
243
+ return messages
244
+
245
+ def from_standard_message(self, message) -> AWSNovaSonicConversationHistoryMessage:
246
+ """Convert standard message format to Nova Sonic format.
247
+
248
+ Args:
249
+ message: Standard message dictionary to convert.
250
+
251
+ Returns:
252
+ Nova Sonic conversation history message, or None if not convertible.
253
+ """
254
+ role = message.get("role")
255
+ if message.get("role") == "user" or message.get("role") == "assistant":
256
+ content = message.get("content")
257
+ if isinstance(message.get("content"), list):
258
+ content = ""
259
+ for c in message.get("content"):
260
+ if c.get("type") == "text":
261
+ content += " " + c.get("text")
262
+ else:
263
+ logger.error(
264
+ f"Unhandled content type in context message: {c.get('type')} - {message}"
265
+ )
266
+ # There won't be content if this is an assistant tool call entry.
267
+ # We're ignoring those since they can't be loaded into AWS Nova Sonic conversation
268
+ # history
269
+ if content:
270
+ return AWSNovaSonicConversationHistoryMessage(role=Role[role.upper()], text=content)
271
+ # NOTE: we're ignoring messages with role "tool" since they can't be loaded into AWS Nova
272
+ # Sonic conversation history
273
+
274
+ def buffer_user_text(self, text):
275
+ """Buffer user text for later flushing to context.
276
+
277
+ Args:
278
+ text: User text to buffer.
279
+ """
280
+ self._user_text += f" {text}" if self._user_text else text
281
+ # logger.debug(f"User text buffered: {self._user_text}")
282
+
283
+ def flush_aggregated_user_text(self) -> str:
284
+ """Flush buffered user text to context as a complete message.
285
+
286
+ Returns:
287
+ The flushed user text, or empty string if no text was buffered.
288
+ """
289
+ if not self._user_text:
290
+ return ""
291
+ user_text = self._user_text
292
+ message = {
293
+ "role": "user",
294
+ "content": [{"type": "text", "text": user_text}],
295
+ }
296
+ self._user_text = ""
297
+ self.add_message(message)
298
+ # logger.debug(f"Context updated (user): {self.get_messages_for_logging()}")
299
+ return user_text
300
+
301
+ def buffer_assistant_text(self, text):
302
+ """Buffer assistant text for later flushing to context.
303
+
304
+ Args:
305
+ text: Assistant text to buffer.
306
+ """
307
+ self._assistant_text += text
308
+ # logger.debug(f"Assistant text buffered: {self._assistant_text}")
309
+
310
+ def flush_aggregated_assistant_text(self):
311
+ """Flush buffered assistant text to context as a complete message."""
312
+ if not self._assistant_text:
313
+ return
314
+ message = {
315
+ "role": "assistant",
316
+ "content": [{"type": "text", "text": self._assistant_text}],
317
+ }
318
+ self._assistant_text = ""
319
+ self.add_message(message)
320
+ # logger.debug(f"Context updated (assistant): {self.get_messages_for_logging()}")
321
+
322
+
323
+ @dataclass
324
+ class AWSNovaSonicMessagesUpdateFrame(DataFrame):
325
+ """Frame containing updated AWS Nova Sonic context.
326
+
327
+ Parameters:
328
+ context: The updated AWS Nova Sonic LLM context.
329
+ """
330
+
331
+ context: AWSNovaSonicLLMContext
332
+
333
+
334
+ class AWSNovaSonicUserContextAggregator(OpenAIUserContextAggregator):
335
+ """Context aggregator for user messages in AWS Nova Sonic conversations.
336
+
337
+ Extends the OpenAI user context aggregator to emit Nova Sonic-specific
338
+ context update frames.
339
+ """
340
+
341
+ async def process_frame(
342
+ self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM
343
+ ):
344
+ """Process frames and emit Nova Sonic-specific context updates.
345
+
346
+ Args:
347
+ frame: The frame to process.
348
+ direction: The direction the frame is traveling.
349
+ """
350
+ await super().process_frame(frame, direction)
351
+
352
+ # Parent does not push LLMMessagesUpdateFrame
353
+ if isinstance(frame, LLMMessagesUpdateFrame):
354
+ await self.push_frame(AWSNovaSonicMessagesUpdateFrame(context=self._context))
355
+
356
+
357
+ class AWSNovaSonicAssistantContextAggregator(OpenAIAssistantContextAggregator):
358
+ """Context aggregator for assistant messages in AWS Nova Sonic conversations.
359
+
360
+ Provides specialized handling for assistant responses and function calls
361
+ in AWS Nova Sonic context, with custom frame processing logic.
362
+ """
363
+
364
+ async def process_frame(self, frame: Frame, direction: FrameDirection):
365
+ """Process frames with Nova Sonic-specific logic.
366
+
367
+ Args:
368
+ frame: The frame to process.
369
+ direction: The direction the frame is traveling.
370
+ """
371
+ # HACK: For now, disable the context aggregator by making it just pass through all frames
372
+ # that the parent handles (except the function call stuff, which we still need).
373
+ # For an explanation of this hack, see
374
+ # AWSNovaSonicLLMService._report_assistant_response_text_added.
375
+ if isinstance(
376
+ frame,
377
+ (
378
+ InterruptionFrame,
379
+ LLMFullResponseStartFrame,
380
+ LLMFullResponseEndFrame,
381
+ TextFrame,
382
+ LLMMessagesAppendFrame,
383
+ LLMMessagesUpdateFrame,
384
+ LLMSetToolsFrame,
385
+ LLMSetToolChoiceFrame,
386
+ UserImageRawFrame,
387
+ BotStoppedSpeakingFrame,
388
+ ),
389
+ ):
390
+ await self.push_frame(frame, direction)
391
+ else:
392
+ await super().process_frame(frame, direction)
393
+
394
+ async def handle_function_call_result(self, frame: FunctionCallResultFrame):
395
+ """Handle function call results for AWS Nova Sonic.
396
+
397
+ Args:
398
+ frame: The function call result frame to handle.
399
+ """
400
+ await super().handle_function_call_result(frame)
401
+
402
+ # The standard function callback code path pushes the FunctionCallResultFrame from the LLM
403
+ # itself, so we didn't have a chance to add the result to the AWS Nova Sonic server-side
404
+ # context. Let's push a special frame to do that.
405
+ await self.push_frame(
406
+ AWSNovaSonicFunctionCallResultFrame(result_frame=frame), FrameDirection.UPSTREAM
407
+ )
408
+
409
+
410
+ @dataclass
411
+ class AWSNovaSonicContextAggregatorPair:
412
+ """Pair of user and assistant context aggregators for AWS Nova Sonic.
413
+
414
+ Parameters:
415
+ _user: The user context aggregator.
416
+ _assistant: The assistant context aggregator.
417
+ """
418
+
419
+ _user: AWSNovaSonicUserContextAggregator
420
+ _assistant: AWSNovaSonicAssistantContextAggregator
421
+
422
+ def user(self) -> AWSNovaSonicUserContextAggregator:
423
+ """Get the user context aggregator.
424
+
425
+ Returns:
426
+ The user context aggregator instance.
427
+ """
428
+ return self._user
429
+
430
+ def assistant(self) -> AWSNovaSonicAssistantContextAggregator:
431
+ """Get the assistant context aggregator.
432
+
433
+ Returns:
434
+ The assistant context aggregator instance.
435
+ """
436
+ return self._assistant
@@ -0,0 +1,25 @@
1
+ #
2
+ # Copyright (c) 2025, Daily
3
+ #
4
+ # SPDX-License-Identifier: BSD 2-Clause License
5
+ #
6
+
7
+ """Custom frames for AWS Nova Sonic LLM service."""
8
+
9
+ from dataclasses import dataclass
10
+
11
+ from pipecat.frames.frames import DataFrame, FunctionCallResultFrame
12
+
13
+
14
+ @dataclass
15
+ class AWSNovaSonicFunctionCallResultFrame(DataFrame):
16
+ """Frame containing function call result for AWS Nova Sonic processing.
17
+
18
+ This frame wraps a standard function call result frame to enable
19
+ AWS Nova Sonic-specific handling and context updates.
20
+
21
+ Parameters:
22
+ result_frame: The underlying function call result frame.
23
+ """
24
+
25
+ result_frame: FunctionCallResultFrame