Improve docstrings for services and processors (#2087)

This commit is contained in:
Mark Backman
2025-06-28 13:39:45 -04:00
committed by GitHub
parent e1b0db75eb
commit 0ecfa827e6
117 changed files with 5136 additions and 862 deletions

View File

@@ -4,6 +4,13 @@
# SPDX-License-Identifier: BSD 2-Clause License
#
"""RTVI (Real-Time Voice Interface) protocol implementation for Pipecat.
This module provides the RTVI protocol implementation for real-time voice interactions
between clients and AI agents. It includes message handling, action processing,
and frame observation for the RTVI protocol.
"""
import asyncio
import base64
from dataclasses import dataclass
@@ -79,6 +86,12 @@ ActionResult = Union[bool, int, float, str, list, dict]
class RTVIServiceOption(BaseModel):
"""Configuration option for an RTVI service.
Defines a configurable option that can be set for an RTVI service,
including its name, type, and handler function.
"""
name: str
type: Literal["bool", "number", "string", "array", "object"]
handler: Callable[["RTVIProcessor", str, "RTVIServiceOptionConfig"], Awaitable[None]] = Field(
@@ -87,11 +100,18 @@ class RTVIServiceOption(BaseModel):
class RTVIService(BaseModel):
"""An RTVI service definition.
Represents a service that can be configured and used within the RTVI protocol,
containing a name and list of configurable options.
"""
name: str
options: List[RTVIServiceOption]
_options_dict: Dict[str, RTVIServiceOption] = PrivateAttr(default={})
def model_post_init(self, __context: Any) -> None:
"""Initialize the options dictionary after model creation."""
self._options_dict = {}
for option in self.options:
self._options_dict[option.name] = option
@@ -99,16 +119,32 @@ class RTVIService(BaseModel):
class RTVIActionArgumentData(BaseModel):
"""Data for an RTVI action argument.
Contains the name and value of an argument passed to an RTVI action.
"""
name: str
value: Any
class RTVIActionArgument(BaseModel):
"""Definition of an RTVI action argument.
Specifies the name and expected type of an argument for an RTVI action.
"""
name: str
type: Literal["bool", "number", "string", "array", "object"]
class RTVIAction(BaseModel):
"""An RTVI action definition.
Represents an action that can be executed within the RTVI protocol,
including its service, name, arguments, and handler function.
"""
service: str
action: str
arguments: List[RTVIActionArgument] = Field(default_factory=list)
@@ -119,6 +155,7 @@ class RTVIAction(BaseModel):
_arguments_dict: Dict[str, RTVIActionArgument] = PrivateAttr(default={})
def model_post_init(self, __context: Any) -> None:
"""Initialize the arguments dictionary after model creation."""
self._arguments_dict = {}
for arg in self.arguments:
self._arguments_dict[arg.name] = arg
@@ -126,16 +163,31 @@ class RTVIAction(BaseModel):
class RTVIServiceOptionConfig(BaseModel):
"""Configuration value for an RTVI service option.
Contains the name and value to set for a specific service option.
"""
name: str
value: Any
class RTVIServiceConfig(BaseModel):
"""Configuration for an RTVI service.
Contains the service name and list of option configurations to apply.
"""
service: str
options: List[RTVIServiceOptionConfig]
class RTVIConfig(BaseModel):
"""Complete RTVI configuration.
Contains the full configuration for all RTVI services.
"""
config: List[RTVIServiceConfig]
@@ -145,16 +197,31 @@ class RTVIConfig(BaseModel):
class RTVIUpdateConfig(BaseModel):
"""Request to update RTVI configuration.
Contains new configuration settings and whether to interrupt the bot.
"""
config: List[RTVIServiceConfig]
interrupt: bool = False
class RTVIActionRunArgument(BaseModel):
"""Argument for running an RTVI action.
Contains the name and value of an argument to pass to an action.
"""
name: str
value: Any
class RTVIActionRun(BaseModel):
"""Request to run an RTVI action.
Contains the service, action name, and optional arguments.
"""
service: str
action: str
arguments: Optional[List[RTVIActionRunArgument]] = None
@@ -162,11 +229,23 @@ class RTVIActionRun(BaseModel):
@dataclass
class RTVIActionFrame(DataFrame):
"""Frame containing an RTVI action to execute.
Parameters:
rtvi_action_run: The action to execute.
message_id: Optional message ID for response correlation.
"""
rtvi_action_run: RTVIActionRun
message_id: Optional[str] = None
class RTVIMessage(BaseModel):
"""Base RTVI message structure.
Represents the standard format for RTVI protocol messages.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: str
id: str
@@ -179,10 +258,20 @@ class RTVIMessage(BaseModel):
class RTVIErrorResponseData(BaseModel):
"""Data for an RTVI error response.
Contains the error message to send back to the client.
"""
error: str
class RTVIErrorResponse(BaseModel):
"""RTVI error response message.
Sent in response to a client request that resulted in an error.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["error-response"] = "error-response"
id: str
@@ -190,21 +279,41 @@ class RTVIErrorResponse(BaseModel):
class RTVIErrorData(BaseModel):
"""Data for an RTVI error event.
Contains error information including whether it's fatal.
"""
error: str
fatal: bool
class RTVIError(BaseModel):
"""RTVI error event message.
Sent when an error occurs that isn't in response to a specific request.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["error"] = "error"
data: RTVIErrorData
class RTVIDescribeConfigData(BaseModel):
"""Data for describing available RTVI configuration.
Contains the list of available services and their options.
"""
config: List[RTVIService]
class RTVIDescribeConfig(BaseModel):
"""Message describing available RTVI configuration.
Sent in response to a describe-config request.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["config-available"] = "config-available"
id: str
@@ -212,10 +321,20 @@ class RTVIDescribeConfig(BaseModel):
class RTVIDescribeActionsData(BaseModel):
"""Data for describing available RTVI actions.
Contains the list of available actions that can be executed.
"""
actions: List[RTVIAction]
class RTVIDescribeActions(BaseModel):
"""Message describing available RTVI actions.
Sent in response to a describe-actions request.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["actions-available"] = "actions-available"
id: str
@@ -223,6 +342,11 @@ class RTVIDescribeActions(BaseModel):
class RTVIConfigResponse(BaseModel):
"""Response containing current RTVI configuration.
Sent in response to a get-config request.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["config"] = "config"
id: str
@@ -230,10 +354,20 @@ class RTVIConfigResponse(BaseModel):
class RTVIActionResponseData(BaseModel):
"""Data for an RTVI action response.
Contains the result of executing an action.
"""
result: ActionResult
class RTVIActionResponse(BaseModel):
"""Response to an RTVI action execution.
Sent after successfully executing an action.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["action-response"] = "action-response"
id: str
@@ -241,11 +375,21 @@ class RTVIActionResponse(BaseModel):
class RTVIBotReadyData(BaseModel):
"""Data for bot ready notification.
Contains protocol version and initial configuration.
"""
version: str
config: List[RTVIServiceConfig]
class RTVIBotReady(BaseModel):
"""Message indicating bot is ready for interaction.
Sent after bot initialization is complete.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["bot-ready"] = "bot-ready"
id: str
@@ -253,28 +397,53 @@ class RTVIBotReady(BaseModel):
class RTVILLMFunctionCallMessageData(BaseModel):
"""Data for LLM function call notification.
Contains function call details including name, ID, and arguments.
"""
function_name: str
tool_call_id: str
args: Mapping[str, Any]
class RTVILLMFunctionCallMessage(BaseModel):
"""Message notifying of an LLM function call.
Sent when the LLM makes a function call.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["llm-function-call"] = "llm-function-call"
data: RTVILLMFunctionCallMessageData
class RTVILLMFunctionCallStartMessageData(BaseModel):
"""Data for LLM function call start notification.
Contains the function name being called.
"""
function_name: str
class RTVILLMFunctionCallStartMessage(BaseModel):
"""Message notifying that an LLM function call has started.
Sent when the LLM begins a function call.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["llm-function-call-start"] = "llm-function-call-start"
data: RTVILLMFunctionCallStartMessageData
class RTVILLMFunctionCallResultData(BaseModel):
"""Data for LLM function call result.
Contains function call details and result.
"""
function_name: str
tool_call_id: str
arguments: dict
@@ -282,60 +451,103 @@ class RTVILLMFunctionCallResultData(BaseModel):
class RTVIBotLLMStartedMessage(BaseModel):
"""Message indicating bot LLM processing has started."""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["bot-llm-started"] = "bot-llm-started"
class RTVIBotLLMStoppedMessage(BaseModel):
"""Message indicating bot LLM processing has stopped."""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["bot-llm-stopped"] = "bot-llm-stopped"
class RTVIBotTTSStartedMessage(BaseModel):
"""Message indicating bot TTS processing has started."""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["bot-tts-started"] = "bot-tts-started"
class RTVIBotTTSStoppedMessage(BaseModel):
"""Message indicating bot TTS processing has stopped."""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["bot-tts-stopped"] = "bot-tts-stopped"
class RTVITextMessageData(BaseModel):
"""Data for text-based RTVI messages.
Contains text content.
"""
text: str
class RTVIBotTranscriptionMessage(BaseModel):
"""Message containing bot transcription text.
Sent when the bot's speech is transcribed.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["bot-transcription"] = "bot-transcription"
data: RTVITextMessageData
class RTVIBotLLMTextMessage(BaseModel):
"""Message containing bot LLM text output.
Sent when the bot's LLM generates text.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["bot-llm-text"] = "bot-llm-text"
data: RTVITextMessageData
class RTVIBotTTSTextMessage(BaseModel):
"""Message containing bot TTS text output.
Sent when text is being processed by TTS.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["bot-tts-text"] = "bot-tts-text"
data: RTVITextMessageData
class RTVIAudioMessageData(BaseModel):
"""Data for audio-based RTVI messages.
Contains audio data and metadata.
"""
audio: str
sample_rate: int
num_channels: int
class RTVIBotTTSAudioMessage(BaseModel):
"""Message containing bot TTS audio output.
Sent when the bot's TTS generates audio.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["bot-tts-audio"] = "bot-tts-audio"
data: RTVIAudioMessageData
class RTVIUserTranscriptionMessageData(BaseModel):
"""Data for user transcription messages.
Contains transcription text and metadata.
"""
text: str
user_id: str
timestamp: str
@@ -343,44 +555,72 @@ class RTVIUserTranscriptionMessageData(BaseModel):
class RTVIUserTranscriptionMessage(BaseModel):
"""Message containing user transcription.
Sent when user speech is transcribed.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["user-transcription"] = "user-transcription"
data: RTVIUserTranscriptionMessageData
class RTVIUserLLMTextMessage(BaseModel):
"""Message containing user text input for LLM.
Sent when user text is processed by the LLM.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["user-llm-text"] = "user-llm-text"
data: RTVITextMessageData
class RTVIUserStartedSpeakingMessage(BaseModel):
"""Message indicating user has started speaking."""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["user-started-speaking"] = "user-started-speaking"
class RTVIUserStoppedSpeakingMessage(BaseModel):
"""Message indicating user has stopped speaking."""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["user-stopped-speaking"] = "user-stopped-speaking"
class RTVIBotStartedSpeakingMessage(BaseModel):
"""Message indicating bot has started speaking."""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["bot-started-speaking"] = "bot-started-speaking"
class RTVIBotStoppedSpeakingMessage(BaseModel):
"""Message indicating bot has stopped speaking."""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["bot-stopped-speaking"] = "bot-stopped-speaking"
class RTVIMetricsMessage(BaseModel):
"""Message containing performance metrics.
Sent to provide performance and usage metrics.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["metrics"] = "metrics"
data: Mapping[str, Any]
class RTVIServerMessage(BaseModel):
"""Generic server message.
Used for custom server-to-client messages.
"""
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["server-message"] = "server-message"
data: Any
@@ -388,28 +628,32 @@ class RTVIServerMessage(BaseModel):
@dataclass
class RTVIServerMessageFrame(SystemFrame):
"""A frame for sending server messages to the client."""
"""A frame for sending server messages to the client.
Parameters:
data: The message data to send to the client.
"""
data: Any
def __str__(self):
"""String representation of the RTVI server message frame."""
return f"{self.name}(data: {self.data})"
@dataclass
class RTVIObserverParams:
"""
Parameters for configuring RTVI Observer behavior.
"""Parameters for configuring RTVI Observer behavior.
Attributes:
bot_llm_enabled (bool): Indicates if the bot's LLM messages should be sent.
bot_tts_enabled (bool): Indicates if the bot's TTS messages should be sent.
bot_speaking_enabled (bool): Indicates if the bot's started/stopped speaking messages should be sent.
user_llm_enabled (bool): Indicates if the user's LLM input messages should be sent.
user_speaking_enabled (bool): Indicates if the user's started/stopped speaking messages should be sent.
user_transcription_enabled (bool): Indicates if user's transcription messages should be sent.
metrics_enabled (bool): Indicates if metrics messages should be sent.
errors_enabled (bool): Indicates if errors messages should be sent.
Parameters:
bot_llm_enabled: Indicates if the bot's LLM messages should be sent.
bot_tts_enabled: Indicates if the bot's TTS messages should be sent.
bot_speaking_enabled: Indicates if the bot's started/stopped speaking messages should be sent.
user_llm_enabled: Indicates if the user's LLM input messages should be sent.
user_speaking_enabled: Indicates if the user's started/stopped speaking messages should be sent.
user_transcription_enabled: Indicates if user's transcription messages should be sent.
metrics_enabled: Indicates if metrics messages should be sent.
errors_enabled: Indicates if errors messages should be sent.
"""
bot_llm_enabled: bool = True
@@ -432,15 +676,18 @@ class RTVIObserver(BaseObserver):
Note:
This observer only handles outgoing messages. Incoming RTVI client messages
are handled by the RTVIProcessor.
Args:
rtvi (RTVIProcessor): The RTVI processor to push frames to.
params (RTVIObserverParams): Settings to enable/disable specific messages.
"""
def __init__(
self, rtvi: "RTVIProcessor", *, params: Optional[RTVIObserverParams] = None, **kwargs
):
"""Initialize the RTVI observer.
Args:
rtvi: The RTVI processor to push frames to.
params: Settings to enable/disable specific messages.
**kwargs: Additional arguments passed to parent class.
"""
super().__init__(**kwargs)
self._rtvi = rtvi
self._params = params or RTVIObserverParams()
@@ -452,11 +699,7 @@ class RTVIObserver(BaseObserver):
"""Process a frame being pushed through the pipeline.
Args:
src: Source processor pushing the frame
dst: Destination processor receiving the frame
frame: The frame being pushed
direction: Direction of frame flow in pipeline
timestamp: Time when frame was pushed
data: Frame push event data containing source, frame, direction, and timestamp.
"""
src = data.source
frame = data.frame
@@ -517,13 +760,14 @@ class RTVIObserver(BaseObserver):
"""Push an urgent transport message to the RTVI processor.
Args:
model: The message model to send
exclude_none: Whether to exclude None values from the model dump
model: The message model to send.
exclude_none: Whether to exclude None values from the model dump.
"""
frame = TransportMessageUrgentFrame(message=model.model_dump(exclude_none=exclude_none))
await self._rtvi.push_frame(frame)
async def _push_bot_transcription(self):
"""Push accumulated bot transcription as a message."""
if len(self._bot_transcription) > 0:
message = RTVIBotTranscriptionMessage(
data=RTVITextMessageData(text=self._bot_transcription)
@@ -532,6 +776,7 @@ class RTVIObserver(BaseObserver):
self._bot_transcription = ""
async def _handle_interruptions(self, frame: Frame):
"""Handle user speaking interruption frames."""
message = None
if isinstance(frame, UserStartedSpeakingFrame):
message = RTVIUserStartedSpeakingMessage()
@@ -542,6 +787,7 @@ class RTVIObserver(BaseObserver):
await self.push_transport_message_urgent(message)
async def _handle_bot_speaking(self, frame: Frame):
"""Handle bot speaking event frames."""
message = None
if isinstance(frame, BotStartedSpeakingFrame):
message = RTVIBotStartedSpeakingMessage()
@@ -552,6 +798,7 @@ class RTVIObserver(BaseObserver):
await self.push_transport_message_urgent(message)
async def _handle_llm_text_frame(self, frame: LLMTextFrame):
"""Handle LLM text output frames."""
message = RTVIBotLLMTextMessage(data=RTVITextMessageData(text=frame.text))
await self.push_transport_message_urgent(message)
@@ -560,6 +807,7 @@ class RTVIObserver(BaseObserver):
await self._push_bot_transcription()
async def _handle_user_transcriptions(self, frame: Frame):
"""Handle user transcription frames."""
message = None
if isinstance(frame, TranscriptionFrame):
message = RTVIUserTranscriptionMessage(
@@ -608,6 +856,7 @@ class RTVIObserver(BaseObserver):
logger.warning(f"Caught an error while trying to handle context: {e}")
async def _handle_metrics(self, frame: MetricsFrame):
"""Handle metrics frames and convert to RTVI metrics messages."""
metrics = {}
for d in frame.data:
if isinstance(d, TTFBMetricsData):
@@ -632,6 +881,13 @@ class RTVIObserver(BaseObserver):
class RTVIProcessor(FrameProcessor):
"""Main processor for handling RTVI protocol messages and actions.
This processor manages the RTVI protocol communication including client-server
handshaking, configuration management, action execution, and message routing.
It serves as the central hub for RTVI protocol operations.
"""
def __init__(
self,
*,
@@ -639,6 +895,13 @@ class RTVIProcessor(FrameProcessor):
transport: Optional[BaseTransport] = None,
**kwargs,
):
"""Initialize the RTVI processor.
Args:
config: Initial RTVI configuration.
transport: Transport layer for communication.
**kwargs: Additional arguments passed to parent class.
"""
super().__init__(**kwargs)
self._config = config or RTVIConfig(config=[])
@@ -668,34 +931,67 @@ class RTVIProcessor(FrameProcessor):
self._input_transport.enable_audio_in_stream_on_start(False)
def register_action(self, action: RTVIAction):
"""Register an action that can be executed via RTVI.
Args:
action: The action to register.
"""
id = self._action_id(action.service, action.action)
self._registered_actions[id] = action
def register_service(self, service: RTVIService):
"""Register a service that can be configured via RTVI.
Args:
service: The service to register.
"""
self._registered_services[service.name] = service
async def set_client_ready(self):
"""Mark the client as ready and trigger the ready event."""
self._client_ready = True
await self._call_event_handler("on_client_ready")
async def set_bot_ready(self):
"""Mark the bot as ready and send the bot-ready message."""
self._bot_ready = True
await self._update_config(self._config, False)
await self._send_bot_ready()
def set_errors_enabled(self, enabled: bool):
"""Enable or disable error message sending.
Args:
enabled: Whether to send error messages.
"""
self._errors_enabled = enabled
async def interrupt_bot(self):
"""Send a bot interruption frame upstream."""
await self.push_frame(BotInterruptionFrame(), FrameDirection.UPSTREAM)
async def send_error(self, error: str):
"""Send an error message to the client.
Args:
error: The error message to send.
"""
await self._send_error_frame(ErrorFrame(error=error))
async def handle_message(self, message: RTVIMessage):
"""Handle an incoming RTVI message.
Args:
message: The RTVI message to handle.
"""
await self._message_queue.put(message)
async def handle_function_call(self, params: FunctionCallParams):
"""Handle a function call from the LLM.
Args:
params: The function call parameters.
"""
fn = RTVILLMFunctionCallMessageData(
function_name=params.function_name,
tool_call_id=params.tool_call_id,
@@ -707,6 +1003,16 @@ class RTVIProcessor(FrameProcessor):
async def handle_function_call_start(
self, function_name: str, llm: FrameProcessor, context: OpenAILLMContext
):
"""Handle the start of a function call from the LLM.
Args:
function_name: Name of the function being called.
llm: The LLM processor making the call.
context: The LLM context.
Note:
This method is deprecated. Use handle_function_call() instead.
"""
import warnings
with warnings.catch_warnings():
@@ -721,6 +1027,12 @@ class RTVIProcessor(FrameProcessor):
await self._push_transport_message(message, exclude_none=False)
async def process_frame(self, frame: Frame, direction: FrameDirection):
"""Process incoming frames through the RTVI processor.
Args:
frame: The frame to process.
direction: The direction of frame flow.
"""
await super().process_frame(frame, direction)
# Specific system frames
@@ -754,6 +1066,7 @@ class RTVIProcessor(FrameProcessor):
await self.push_frame(frame, direction)
async def _start(self, frame: StartFrame):
"""Start the RTVI processor tasks."""
if not self._action_task:
self._action_queue = WatchdogQueue(self.task_manager)
self._action_task = self.create_task(self._action_task_handler())
@@ -763,12 +1076,15 @@ class RTVIProcessor(FrameProcessor):
await self._call_event_handler("on_bot_started")
async def _stop(self, frame: EndFrame):
"""Stop the RTVI processor tasks."""
await self._cancel_tasks()
async def _cancel(self, frame: CancelFrame):
"""Cancel the RTVI processor tasks."""
await self._cancel_tasks()
async def _cancel_tasks(self):
"""Cancel all running tasks."""
if self._action_task:
await self.cancel_task(self._action_task)
self._action_task = None
@@ -778,22 +1094,26 @@ class RTVIProcessor(FrameProcessor):
self._message_task = None
async def _push_transport_message(self, model: BaseModel, exclude_none: bool = True):
"""Push a transport message frame."""
frame = TransportMessageUrgentFrame(message=model.model_dump(exclude_none=exclude_none))
await self.push_frame(frame)
async def _action_task_handler(self):
"""Handle incoming action frames."""
while True:
frame = await self._action_queue.get()
await self._handle_action(frame.message_id, frame.rtvi_action_run)
self._action_queue.task_done()
async def _message_task_handler(self):
"""Handle incoming transport messages."""
while True:
message = await self._message_queue.get()
await self._handle_message(message)
self._message_queue.task_done()
async def _handle_transport_message(self, frame: TransportMessageUrgentFrame):
"""Handle an incoming transport message frame."""
try:
transport_message = frame.message
if transport_message.get("label") != RTVI_MESSAGE_LABEL:
@@ -806,6 +1126,7 @@ class RTVIProcessor(FrameProcessor):
logger.warning(f"Invalid RTVI transport message: {e}")
async def _handle_message(self, message: RTVIMessage):
"""Handle a parsed RTVI message."""
try:
match message.type:
case "client-ready":
@@ -842,6 +1163,7 @@ class RTVIProcessor(FrameProcessor):
logger.warning(f"Exception processing message: {e}")
async def _handle_client_ready(self, request_id: str):
"""Handle a client-ready message."""
logger.debug("Received client-ready")
if self._input_transport:
await self._input_transport.start_audio_in_streaming()
@@ -850,6 +1172,7 @@ class RTVIProcessor(FrameProcessor):
await self.set_client_ready()
async def _handle_audio_buffer(self, data):
"""Handle incoming audio buffer data."""
if not self._input_transport:
return
@@ -871,20 +1194,24 @@ class RTVIProcessor(FrameProcessor):
logger.error(f"Error processing audio buffer: {e}")
async def _handle_describe_config(self, request_id: str):
"""Handle a describe-config request."""
services = list(self._registered_services.values())
message = RTVIDescribeConfig(id=request_id, data=RTVIDescribeConfigData(config=services))
await self._push_transport_message(message)
async def _handle_describe_actions(self, request_id: str):
"""Handle a describe-actions request."""
actions = list(self._registered_actions.values())
message = RTVIDescribeActions(id=request_id, data=RTVIDescribeActionsData(actions=actions))
await self._push_transport_message(message)
async def _handle_get_config(self, request_id: str):
"""Handle a get-config request."""
message = RTVIConfigResponse(id=request_id, data=self._config)
await self._push_transport_message(message)
def _update_config_option(self, service: str, config: RTVIServiceOptionConfig):
"""Update a specific configuration option."""
for service_config in self._config.config:
if service_config.service == service:
for option_config in service_config.options:
@@ -896,6 +1223,7 @@ class RTVIProcessor(FrameProcessor):
service_config.options.append(config)
async def _update_service_config(self, config: RTVIServiceConfig):
"""Update configuration for a specific service."""
service = self._registered_services[config.service]
for option in config.options:
handler = service._options_dict[option.name].handler
@@ -903,16 +1231,19 @@ class RTVIProcessor(FrameProcessor):
self._update_config_option(service.name, option)
async def _update_config(self, data: RTVIConfig, interrupt: bool):
"""Update the RTVI configuration."""
if interrupt:
await self.interrupt_bot()
for service_config in data.config:
await self._update_service_config(service_config)
async def _handle_update_config(self, request_id: str, data: RTVIUpdateConfig):
"""Handle an update-config request."""
await self._update_config(RTVIConfig(config=data.config), data.interrupt)
await self._handle_get_config(request_id)
async def _handle_function_call_result(self, data):
"""Handle a function call result from the client."""
frame = FunctionCallResultFrame(
function_name=data.function_name,
tool_call_id=data.tool_call_id,
@@ -922,6 +1253,7 @@ class RTVIProcessor(FrameProcessor):
await self.push_frame(frame)
async def _handle_action(self, request_id: Optional[str], data: RTVIActionRun):
"""Handle an action execution request."""
action_id = self._action_id(data.service, data.action)
if action_id not in self._registered_actions:
await self._send_error_response(request_id, f"Action {action_id} not registered")
@@ -939,6 +1271,7 @@ class RTVIProcessor(FrameProcessor):
await self._push_transport_message(message)
async def _send_bot_ready(self):
"""Send the bot-ready message to the client."""
message = RTVIBotReady(
id=self._client_ready_id,
data=RTVIBotReadyData(version=RTVI_PROTOCOL_VERSION, config=self._config.config),
@@ -946,14 +1279,17 @@ class RTVIProcessor(FrameProcessor):
await self._push_transport_message(message)
async def _send_error_frame(self, frame: ErrorFrame):
"""Send an error frame as an RTVI error message."""
if self._errors_enabled:
message = RTVIError(data=RTVIErrorData(error=frame.error, fatal=frame.fatal))
await self._push_transport_message(message)
async def _send_error_response(self, id: str, error: str):
"""Send an error response message."""
if self._errors_enabled:
message = RTVIErrorResponse(id=id, data=RTVIErrorResponseData(error=error))
await self._push_transport_message(message)
def _action_id(self, service: str, action: str) -> str:
"""Generate an action ID from service and action names."""
return f"{service}:{action}"