[WIP] AWS Nova Sonic service - added TranscriptionFrame

This commit is contained in:
Paul Kompfner
2025-04-25 15:33:45 -04:00
parent 260f7c9b85
commit a38206de9c

View File

@@ -30,12 +30,14 @@ from pipecat.frames.frames import (
LLMFullResponseStartFrame, LLMFullResponseStartFrame,
LLMTextFrame, LLMTextFrame,
StartFrame, StartFrame,
TranscriptionFrame,
TTSAudioRawFrame, TTSAudioRawFrame,
TTSStartedFrame, TTSStartedFrame,
TTSStoppedFrame, TTSStoppedFrame,
) )
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.llm_service import LLMService from pipecat.services.llm_service import LLMService
from pipecat.utils.time import time_now_iso8601
class Role(Enum): class Role(Enum):
@@ -398,19 +400,30 @@ class AWSNovaSonicService(LLMService):
# print("[pk] TTS started") # print("[pk] TTS started")
await self.push_frame(TTSStartedFrame()) await self.push_frame(TTSStartedFrame())
print(f"[pk] content start: {self._content_being_received}") # print(f"[pk] content start: {self._content_being_received}")
async def _handle_text_output_event(self, event_json): async def _handle_text_output_event(self, event_json):
# This should never happen
if not self._content_being_received:
return
text_content = event_json["textOutput"]["content"] text_content = event_json["textOutput"]["content"]
print(f"[pk] text output. content: {text_content}") # print(f"[pk] text output. content: {text_content}")
# Bookkeeping: augment the current content being received with text # Bookkeeping: augment the current content being received with text
# Assumption: only one text content per content block
content = self._content_being_received content = self._content_being_received
content.text_content = text_content content.text_content = text_content
async def _handle_audio_output_event(self, event_json): async def _handle_audio_output_event(self, event_json):
# This should never happen
if not self._content_being_received:
return
# Get audio
audio_content = event_json["audioOutput"]["content"] audio_content = event_json["audioOutput"]["content"]
# print(f"[pk] audio output. content: {len(audio_content)}") # print(f"[pk] audio output. content: {len(audio_content)}")
# Push audio frame # Push audio frame
audio = base64.b64decode(audio_content) audio = base64.b64decode(audio_content)
# TODO: make sample rate + channels (used in multiple places) consts # TODO: make sample rate + channels (used in multiple places) consts
@@ -422,15 +435,19 @@ class AWSNovaSonicService(LLMService):
await self.push_frame(frame) await self.push_frame(frame)
async def _handle_content_end_event(self, event_json): async def _handle_content_end_event(self, event_json):
# This should never happen
if not self._content_being_received:
return
content_end = event_json["contentEnd"] content_end = event_json["contentEnd"]
stop_reason = content_end["stopReason"] stop_reason = content_end["stopReason"]
print(f"[pk] content end: {self._content_being_received}.\n stop_reason: {stop_reason}") # print(f"[pk] content end: {self._content_being_received}.\n stop_reason: {stop_reason}")
# Bookkeeping: clear current content being received # Bookkeeping: clear current content being received
content = self._content_being_received content = self._content_being_received
self._content_being_received = None self._content_being_received = None
if content and content.role == Role.ASSISTANT: if content.role == Role.ASSISTANT:
if content.type == ContentType.AUDIO: if content.type == ContentType.AUDIO:
# We got to the end of a chunk of the assistant's audio. # We got to the end of a chunk of the assistant's audio.
# Report that *equivalent* of TTS (this is a speech-to-speech model) stopped. # Report that *equivalent* of TTS (this is a speech-to-speech model) stopped.
@@ -447,20 +464,30 @@ class AWSNovaSonicService(LLMService):
# - when this is the next text content block after an INTERRUPTED has occurred # - when this is the next text content block after an INTERRUPTED has occurred
# BUT it seems like there's a bug where, if there are multiple assistant text # BUT it seems like there's a bug where, if there are multiple assistant text
# content blocks, the *first* one gets marked END_TURN rather than the last. # content blocks, the *first* one gets marked END_TURN rather than the last.
# print("[pk] LLM full response started") print("[pk] LLM full response started")
self._assistant_is_responding = True self._assistant_is_responding = True
await self.push_frame(LLMFullResponseStartFrame()) await self.push_frame(LLMFullResponseStartFrame())
if self._assistant_is_responding: if self._assistant_is_responding:
# Add text to the ongoing reported assistant response # Add text to the ongoing reported assistant response
# print(f"[pk] LLM text: {content.text_content}") print(f"[pk] LLM text: {content.text_content}")
await self.push_frame(LLMTextFrame(content.text_content)) await self.push_frame(LLMTextFrame(content.text_content))
# Report that the assistant has finished their response. # Report that the assistant has finished their response.
# TODO: kinda busted. see TODO comment above. # TODO: kinda busted. see TODO comment above.
# print("[pk] LLM full response ended") print("[pk] LLM full response ended")
await self.push_frame(LLMFullResponseEndFrame()) await self.push_frame(LLMFullResponseEndFrame())
self._assistant_is_responding = False self._assistant_is_responding = False
elif content.role == Role.USER:
if content.type == ContentType.TEXT:
if content.text_stage == TextStage.FINAL:
# Report a bit of user transcription
print(f"[pk] transcription: {content.text_content}")
await self.push_frame(
TranscriptionFrame(
text=content.text_content, user_id="", timestamp=time_now_iso8601()
)
)
self._content_being_received = False self._content_being_received = False