processors(rtvi): make sure to send bot-ready when transport is joined

This commit is contained in:
Aleix Conchillo Flaqué
2024-08-13 13:25:15 -07:00
parent 49ca16d125
commit 574df4ba3d
2 changed files with 48 additions and 27 deletions

View File

@@ -23,9 +23,11 @@ from pipecat.frames.frames import (
FunctionCallResultFrame, FunctionCallResultFrame,
UserStoppedSpeakingFrame) UserStoppedSpeakingFrame)
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
from pipecat.transports.base_transport import BaseTransport
from loguru import logger from loguru import logger
RTVI_PROTOCOL_VERSION = "0.1" RTVI_PROTOCOL_VERSION = "0.1"
ActionResult = Union[bool, int, float, str, list, dict] ActionResult = Union[bool, int, float, str, list, dict]
@@ -241,13 +243,24 @@ class RTVIUserStoppedSpeakingMessage(BaseModel):
type: Literal["user-stopped-speaking"] = "user-stopped-speaking" type: Literal["user-stopped-speaking"] = "user-stopped-speaking"
class RTVIProcessorParams(BaseModel):
send_bot_ready: bool = True
class RTVIProcessor(FrameProcessor): class RTVIProcessor(FrameProcessor):
def __init__(self, config: RTVIConfig): def __init__(self,
*,
transport: BaseTransport,
config: RTVIConfig = RTVIConfig(config=[]),
params: RTVIProcessorParams = RTVIProcessorParams()):
super().__init__() super().__init__()
self._config = config self._config = config
self._params = params
self._pipeline: FrameProcessor | None = None self._pipeline: FrameProcessor | None = None
self._pipeline_started = False
self._transport_joined = False
self._registered_actions: Dict[str, RTVIAction] = {} self._registered_actions: Dict[str, RTVIAction] = {}
self._registered_services: Dict[str, RTVIService] = {} self._registered_services: Dict[str, RTVIService] = {}
@@ -255,6 +268,10 @@ class RTVIProcessor(FrameProcessor):
self._frame_handler_task = self.get_event_loop().create_task(self._frame_handler()) self._frame_handler_task = self.get_event_loop().create_task(self._frame_handler())
self._frame_queue = asyncio.Queue() self._frame_queue = asyncio.Queue()
# TODO(aleix): This is very Daily specific. There should be a generic
# way to do this.
transport.add_event_handler("on_joined", self._transport_on_joined)
def register_action(self, action: RTVIAction): def register_action(self, action: RTVIAction):
id = self._action_id(action.service, action.action) id = self._action_id(action.service, action.action)
self._registered_actions[id] = action self._registered_actions[id] = action
@@ -334,8 +351,9 @@ class RTVIProcessor(FrameProcessor):
await self._pipeline.cleanup() await self._pipeline.cleanup()
async def _start(self, frame: StartFrame): async def _start(self, frame: StartFrame):
self._pipeline_started = True
await self._update_config(self._config) await self._update_config(self._config)
await self._send_bot_ready() await self._maybe_send_bot_ready()
async def _stop(self, frame: EndFrame): async def _stop(self, frame: EndFrame):
await self._frame_handler_task await self._frame_handler_task
@@ -503,7 +521,17 @@ class RTVIProcessor(FrameProcessor):
frame = TransportMessageFrame(message=message.model_dump(exclude_none=True)) frame = TransportMessageFrame(message=message.model_dump(exclude_none=True))
await self.push_frame(frame) await self.push_frame(frame)
async def _transport_on_joined(self, transport, participant):
self._transport_joined = True
async def _maybe_send_bot_ready(self):
if self._pipeline_started and self._transport_joined:
await self._send_bot_ready()
async def _send_bot_ready(self): async def _send_bot_ready(self):
if not self._params.send_bot_ready:
return
message = RTVIBotReady( message = RTVIBotReady(
data=RTVIBotReadyData( data=RTVIBotReadyData(
version=RTVI_PROTOCOL_VERSION, version=RTVI_PROTOCOL_VERSION,

View File

@@ -8,7 +8,6 @@ import aiohttp
import base64 import base64
import io import io
import json import json
from anthropic.types import tool_use_block
import httpx import httpx
from dataclasses import dataclass from dataclasses import dataclass
@@ -25,7 +24,6 @@ from pipecat.frames.frames import (
LLMFullResponseStartFrame, LLMFullResponseStartFrame,
LLMMessagesFrame, LLMMessagesFrame,
LLMModelUpdateFrame, LLMModelUpdateFrame,
MetricsFrame,
TextFrame, TextFrame,
URLImageRawFrame, URLImageRawFrame,
VisionImageRawFrame, VisionImageRawFrame,
@@ -48,12 +46,7 @@ from pipecat.services.ai_services import (
try: try:
from openai import AsyncOpenAI, AsyncStream, DefaultAsyncHttpxClient, BadRequestError from openai import AsyncOpenAI, AsyncStream, DefaultAsyncHttpxClient, BadRequestError
from openai.types.chat import ( from openai.types.chat import ChatCompletionChunk, ChatCompletionMessageParam
ChatCompletionChunk,
ChatCompletionFunctionMessageParam,
ChatCompletionMessageParam,
ChatCompletionToolParam
)
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
logger.error(f"Exception: {e}") logger.error(f"Exception: {e}")
logger.error( logger.error(
@@ -65,7 +58,6 @@ class OpenAIUnhandledFunctionException(Exception):
pass pass
class BaseOpenAILLMService(LLMService): class BaseOpenAILLMService(LLMService):
"""This is the base for all services that use the AsyncOpenAI client. """This is the base for all services that use the AsyncOpenAI client.
@@ -94,8 +86,6 @@ class BaseOpenAILLMService(LLMService):
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
return True return True
async def get_chat_completions( async def get_chat_completions(
self, self,
context: OpenAILLMContext, context: OpenAILLMContext,
@@ -208,7 +198,6 @@ class BaseOpenAILLMService(LLMService):
arguments=arguments arguments=arguments
) )
async def process_frame(self, frame: Frame, direction: FrameDirection): async def process_frame(self, frame: Frame, direction: FrameDirection):
await super().process_frame(frame, direction) await super().process_frame(frame, direction)
@@ -232,6 +221,7 @@ class BaseOpenAILLMService(LLMService):
await self.stop_processing_metrics() await self.stop_processing_metrics()
await self.push_frame(LLMFullResponseEndFrame()) await self.push_frame(LLMFullResponseEndFrame())
@dataclass @dataclass
class OpenAIContextAggregatorPair: class OpenAIContextAggregatorPair:
_user: 'OpenAIUserContextAggregator' _user: 'OpenAIUserContextAggregator'
@@ -243,12 +233,13 @@ class OpenAIContextAggregatorPair:
def assistant(self) -> str: def assistant(self) -> str:
return self._assistant return self._assistant
class OpenAILLMService(BaseOpenAILLMService): class OpenAILLMService(BaseOpenAILLMService):
def __init__(self, *, model: str = "gpt-4o", **kwargs): def __init__(self, *, model: str = "gpt-4o", **kwargs):
super().__init__(model=model, **kwargs) super().__init__(model=model, **kwargs)
@ staticmethod @staticmethod
def create_context_aggregator(context: OpenAILLMContext) -> OpenAIContextAggregatorPair: def create_context_aggregator(context: OpenAILLMContext) -> OpenAIContextAggregatorPair:
user = OpenAIUserContextAggregator(context) user = OpenAIUserContextAggregator(context)
assistant = OpenAIAssistantContextAggregator(user) assistant = OpenAIAssistantContextAggregator(user)
@@ -256,6 +247,8 @@ class OpenAILLMService(BaseOpenAILLMService):
_user=user, _user=user,
_assistant=assistant _assistant=assistant
) )
class OpenAIImageGenService(ImageGenService): class OpenAIImageGenService(ImageGenService):
def __init__( def __init__(
@@ -357,6 +350,7 @@ class OpenAITTSService(TTSService):
except BadRequestError as e: except BadRequestError as e:
logger.exception(f"{self} error generating TTS: {e}") logger.exception(f"{self} error generating TTS: {e}")
class OpenAIUserContextAggregator(LLMUserContextAggregator): class OpenAIUserContextAggregator(LLMUserContextAggregator):
def __init__(self, context: OpenAILLMContext): def __init__(self, context: OpenAILLMContext):
super().__init__(context=context) super().__init__(context=context)
@@ -393,7 +387,6 @@ class OpenAIAssistantContextAggregator(LLMAssistantContextAggregator):
self._function_call_in_progress = None self._function_call_in_progress = None
self._function_call_result = None self._function_call_result = None
def add_message(self, message): def add_message(self, message):
self._user_context_aggregator.add_message(message) self._user_context_aggregator.add_message(message)