rtvi: separate specific google RTVI into a GoogleRTVIObserver
This commit is contained in:
15
CHANGELOG.md
15
CHANGELOG.md
@@ -5,10 +5,19 @@ All notable changes to **Pipecat** will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `RTVIObserver` doesn't handle `LLMSearchResponseFrame` frames anymore. For
|
||||||
|
now, to handle those frames you need to create a `GoogleRTVIObserver` instead.
|
||||||
|
|
||||||
## [0.0.56] - 2025-02-06
|
## [0.0.56] - 2025-02-06
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Use `gemini-2.0-flash-001` as the default model for `GoogleLLMSerivce`.
|
||||||
|
|
||||||
- Improved foundational examples 22b, 22c, and 22d to support function calling.
|
- Improved foundational examples 22b, 22c, and 22d to support function calling.
|
||||||
With these base examples, `FunctionCallInProgressFrame` and
|
With these base examples, `FunctionCallInProgressFrame` and
|
||||||
`FunctionCallResultFrame` will no longer be blocked by the gates.
|
`FunctionCallResultFrame` will no longer be blocked by the gates.
|
||||||
@@ -33,10 +42,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
and should be set manually from the serializer constructor if a different
|
and should be set manually from the serializer constructor if a different
|
||||||
value is needed.
|
value is needed.
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Use `gemini-2.0-flash-001` as the default model for `GoogleLLMSerivce`.
|
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
|
|
||||||
- Added a new `sentry-metrics` example.
|
- Added a new `sentry-metrics` example.
|
||||||
@@ -119,7 +124,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- `AudioBufferProcessor.reset_audio_buffers()` has been removed, use
|
- `AudioBufferProcessor.reset_audio_buffers()` has been removed, use
|
||||||
`AudioBufferProcessor.start_recording()` and
|
`AudioBufferProcessor.start_recording()` and
|
||||||
``AudioBufferProcessor.stop_recording()` instead.
|
`AudioBufferProcessor.stop_recording()` instead.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ from pipecat.processors.aggregators.openai_llm_context import (
|
|||||||
OpenAILLMContextFrame,
|
OpenAILLMContextFrame,
|
||||||
)
|
)
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
from pipecat.services.google.frames import LLMSearchOrigin, LLMSearchResponseFrame
|
|
||||||
from pipecat.utils.string import match_endofsentence
|
from pipecat.utils.string import match_endofsentence
|
||||||
|
|
||||||
RTVI_PROTOCOL_VERSION = "0.3.0"
|
RTVI_PROTOCOL_VERSION = "0.3.0"
|
||||||
@@ -296,12 +295,6 @@ class RTVITextMessageData(BaseModel):
|
|||||||
text: str
|
text: str
|
||||||
|
|
||||||
|
|
||||||
class RTVISearchResponseMessageData(BaseModel):
|
|
||||||
search_result: Optional[str]
|
|
||||||
rendered_content: Optional[str]
|
|
||||||
origins: List[LLMSearchOrigin]
|
|
||||||
|
|
||||||
|
|
||||||
class RTVIBotTranscriptionMessage(BaseModel):
|
class RTVIBotTranscriptionMessage(BaseModel):
|
||||||
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
|
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
|
||||||
type: Literal["bot-transcription"] = "bot-transcription"
|
type: Literal["bot-transcription"] = "bot-transcription"
|
||||||
@@ -314,12 +307,6 @@ class RTVIBotLLMTextMessage(BaseModel):
|
|||||||
data: RTVITextMessageData
|
data: RTVITextMessageData
|
||||||
|
|
||||||
|
|
||||||
class RTVIBotLLMSearchResponseMessage(BaseModel):
|
|
||||||
label: Literal["rtvi-ai"] = "rtvi-ai"
|
|
||||||
type: Literal["bot-llm-search-response"] = "bot-llm-search-response"
|
|
||||||
data: RTVISearchResponseMessageData
|
|
||||||
|
|
||||||
|
|
||||||
class RTVIBotTTSTextMessage(BaseModel):
|
class RTVIBotTTSTextMessage(BaseModel):
|
||||||
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
|
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
|
||||||
type: Literal["bot-tts-text"] = "bot-tts-text"
|
type: Literal["bot-tts-text"] = "bot-tts-text"
|
||||||
@@ -618,24 +605,22 @@ class RTVIObserver(BaseObserver):
|
|||||||
elif isinstance(frame, UserStartedSpeakingFrame):
|
elif isinstance(frame, UserStartedSpeakingFrame):
|
||||||
await self._push_bot_transcription()
|
await self._push_bot_transcription()
|
||||||
elif isinstance(frame, LLMFullResponseStartFrame):
|
elif isinstance(frame, LLMFullResponseStartFrame):
|
||||||
await self._push_transport_message_urgent(RTVIBotLLMStartedMessage())
|
await self.push_transport_message_urgent(RTVIBotLLMStartedMessage())
|
||||||
elif isinstance(frame, LLMFullResponseEndFrame):
|
elif isinstance(frame, LLMFullResponseEndFrame):
|
||||||
await self._push_transport_message_urgent(RTVIBotLLMStoppedMessage())
|
await self.push_transport_message_urgent(RTVIBotLLMStoppedMessage())
|
||||||
elif isinstance(frame, LLMTextFrame):
|
elif isinstance(frame, LLMTextFrame):
|
||||||
await self._handle_llm_text_frame(frame)
|
await self._handle_llm_text_frame(frame)
|
||||||
elif isinstance(frame, LLMSearchResponseFrame):
|
|
||||||
await self._handle_llm_search_response_frame(frame)
|
|
||||||
elif isinstance(frame, TTSStartedFrame):
|
elif isinstance(frame, TTSStartedFrame):
|
||||||
await self._push_transport_message_urgent(RTVIBotTTSStartedMessage())
|
await self.push_transport_message_urgent(RTVIBotTTSStartedMessage())
|
||||||
elif isinstance(frame, TTSStoppedFrame):
|
elif isinstance(frame, TTSStoppedFrame):
|
||||||
await self._push_transport_message_urgent(RTVIBotTTSStoppedMessage())
|
await self.push_transport_message_urgent(RTVIBotTTSStoppedMessage())
|
||||||
elif isinstance(frame, TTSTextFrame):
|
elif isinstance(frame, TTSTextFrame):
|
||||||
message = RTVIBotTTSTextMessage(data=RTVITextMessageData(text=frame.text))
|
message = RTVIBotTTSTextMessage(data=RTVITextMessageData(text=frame.text))
|
||||||
await self._push_transport_message_urgent(message)
|
await self.push_transport_message_urgent(message)
|
||||||
elif isinstance(frame, MetricsFrame):
|
elif isinstance(frame, MetricsFrame):
|
||||||
await self._handle_metrics(frame)
|
await self._handle_metrics(frame)
|
||||||
|
|
||||||
async def _push_transport_message_urgent(self, model: BaseModel, exclude_none: bool = True):
|
async def push_transport_message_urgent(self, model: BaseModel, exclude_none: bool = True):
|
||||||
frame = TransportMessageUrgentFrame(message=model.model_dump(exclude_none=exclude_none))
|
frame = TransportMessageUrgentFrame(message=model.model_dump(exclude_none=exclude_none))
|
||||||
await self._rtvi.push_frame(frame)
|
await self._rtvi.push_frame(frame)
|
||||||
|
|
||||||
@@ -644,7 +629,7 @@ class RTVIObserver(BaseObserver):
|
|||||||
message = RTVIBotTranscriptionMessage(
|
message = RTVIBotTranscriptionMessage(
|
||||||
data=RTVITextMessageData(text=self._bot_transcription)
|
data=RTVITextMessageData(text=self._bot_transcription)
|
||||||
)
|
)
|
||||||
await self._push_transport_message_urgent(message)
|
await self.push_transport_message_urgent(message)
|
||||||
self._bot_transcription = ""
|
self._bot_transcription = ""
|
||||||
|
|
||||||
async def _handle_interruptions(self, frame: Frame):
|
async def _handle_interruptions(self, frame: Frame):
|
||||||
@@ -655,7 +640,7 @@ class RTVIObserver(BaseObserver):
|
|||||||
message = RTVIUserStoppedSpeakingMessage()
|
message = RTVIUserStoppedSpeakingMessage()
|
||||||
|
|
||||||
if message:
|
if message:
|
||||||
await self._push_transport_message_urgent(message)
|
await self.push_transport_message_urgent(message)
|
||||||
|
|
||||||
async def _handle_bot_speaking(self, frame: Frame):
|
async def _handle_bot_speaking(self, frame: Frame):
|
||||||
message = None
|
message = None
|
||||||
@@ -665,26 +650,16 @@ class RTVIObserver(BaseObserver):
|
|||||||
message = RTVIBotStoppedSpeakingMessage()
|
message = RTVIBotStoppedSpeakingMessage()
|
||||||
|
|
||||||
if message:
|
if message:
|
||||||
await self._push_transport_message_urgent(message)
|
await self.push_transport_message_urgent(message)
|
||||||
|
|
||||||
async def _handle_llm_text_frame(self, frame: LLMTextFrame):
|
async def _handle_llm_text_frame(self, frame: LLMTextFrame):
|
||||||
message = RTVIBotLLMTextMessage(data=RTVITextMessageData(text=frame.text))
|
message = RTVIBotLLMTextMessage(data=RTVITextMessageData(text=frame.text))
|
||||||
await self._push_transport_message_urgent(message)
|
await self.push_transport_message_urgent(message)
|
||||||
|
|
||||||
self._bot_transcription += frame.text
|
self._bot_transcription += frame.text
|
||||||
if match_endofsentence(self._bot_transcription):
|
if match_endofsentence(self._bot_transcription):
|
||||||
await self._push_bot_transcription()
|
await self._push_bot_transcription()
|
||||||
|
|
||||||
async def _handle_llm_search_response_frame(self, frame: LLMSearchResponseFrame):
|
|
||||||
message = RTVIBotLLMSearchResponseMessage(
|
|
||||||
data=RTVISearchResponseMessageData(
|
|
||||||
search_result=frame.search_result,
|
|
||||||
origins=frame.origins,
|
|
||||||
rendered_content=frame.rendered_content,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
await self._push_transport_message_urgent(message)
|
|
||||||
|
|
||||||
async def _handle_user_transcriptions(self, frame: Frame):
|
async def _handle_user_transcriptions(self, frame: Frame):
|
||||||
message = None
|
message = None
|
||||||
if isinstance(frame, TranscriptionFrame):
|
if isinstance(frame, TranscriptionFrame):
|
||||||
@@ -701,7 +676,7 @@ class RTVIObserver(BaseObserver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if message:
|
if message:
|
||||||
await self._push_transport_message_urgent(message)
|
await self.push_transport_message_urgent(message)
|
||||||
|
|
||||||
async def _handle_context(self, frame: OpenAILLMContextFrame):
|
async def _handle_context(self, frame: OpenAILLMContextFrame):
|
||||||
try:
|
try:
|
||||||
@@ -715,7 +690,7 @@ class RTVIObserver(BaseObserver):
|
|||||||
else:
|
else:
|
||||||
text = content
|
text = content
|
||||||
rtvi_message = RTVIUserLLMTextMessage(data=RTVITextMessageData(text=text))
|
rtvi_message = RTVIUserLLMTextMessage(data=RTVITextMessageData(text=text))
|
||||||
await self._push_transport_message_urgent(rtvi_message)
|
await self.push_transport_message_urgent(rtvi_message)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
logger.warning(f"Caught an error while trying to handle context: {e}")
|
logger.warning(f"Caught an error while trying to handle context: {e}")
|
||||||
|
|
||||||
@@ -740,7 +715,7 @@ class RTVIObserver(BaseObserver):
|
|||||||
metrics["characters"].append(d.model_dump(exclude_none=True))
|
metrics["characters"].append(d.model_dump(exclude_none=True))
|
||||||
|
|
||||||
message = RTVIMetricsMessage(data=metrics)
|
message = RTVIMetricsMessage(data=metrics)
|
||||||
await self._push_transport_message_urgent(message)
|
await self.push_transport_message_urgent(message)
|
||||||
|
|
||||||
|
|
||||||
class RTVIProcessor(FrameProcessor):
|
class RTVIProcessor(FrameProcessor):
|
||||||
|
|||||||
51
src/pipecat/services/google/rtvi.py
Normal file
51
src/pipecat/services/google/rtvi.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
from typing import List, Literal, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from pipecat.frames.frames import Frame
|
||||||
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
|
from pipecat.processors.frameworks.rtvi import RTVIObserver
|
||||||
|
from pipecat.services.google.frames import LLMSearchOrigin, LLMSearchResponseFrame
|
||||||
|
|
||||||
|
|
||||||
|
class RTVISearchResponseMessageData(BaseModel):
|
||||||
|
search_result: Optional[str]
|
||||||
|
rendered_content: Optional[str]
|
||||||
|
origins: List[LLMSearchOrigin]
|
||||||
|
|
||||||
|
|
||||||
|
class RTVIBotLLMSearchResponseMessage(BaseModel):
|
||||||
|
label: Literal["rtvi-ai"] = "rtvi-ai"
|
||||||
|
type: Literal["bot-llm-search-response"] = "bot-llm-search-response"
|
||||||
|
data: RTVISearchResponseMessageData
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleRTVIObserver(RTVIObserver):
|
||||||
|
async def on_push_frame(
|
||||||
|
self,
|
||||||
|
src: FrameProcessor,
|
||||||
|
dst: FrameProcessor,
|
||||||
|
frame: Frame,
|
||||||
|
direction: FrameDirection,
|
||||||
|
timestamp: int,
|
||||||
|
):
|
||||||
|
await super().on_push_frame(src, dst, frame, direction, timestamp)
|
||||||
|
|
||||||
|
if isinstance(frame, LLMSearchResponseFrame):
|
||||||
|
await self._handle_llm_search_response_frame(frame)
|
||||||
|
|
||||||
|
async def _handle_llm_search_response_frame(self, frame: LLMSearchResponseFrame):
|
||||||
|
message = RTVIBotLLMSearchResponseMessage(
|
||||||
|
data=RTVISearchResponseMessageData(
|
||||||
|
search_result=frame.search_result,
|
||||||
|
origins=frame.origins,
|
||||||
|
rendered_content=frame.rendered_content,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await self.push_transport_message_urgent(message)
|
||||||
Reference in New Issue
Block a user