Skip to main content

Documentation Index

Fetch the complete documentation index at: https://daily-docs-pr-4407.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

The RTVIProcessor manages bidirectional communication between clients and your Pipecat application. It processes client messages, handles service configuration, executes actions, and coordinates function calls.

Initialization

RTVIProcessor is automatically added to your pipeline when you create a PipelineTask. Access it via task.rtvi:
pipeline = Pipeline([
    transport.input(),
    stt,
    # ... other processors ...
    transport.output()
])

task = PipelineTask(pipeline)

# Access the processor
rtvi = task.rtvi
To provide a custom processor (e.g., for Google RTVI):
from pipecat.services.google.rtvi import GoogleRTVIProcessor

task = PipelineTask(pipeline, rtvi_processor=GoogleRTVIProcessor())
To disable RTVI entirely, set enable_rtvi=False.

Readiness Protocol

Client Ready State

Clients indicate readiness by sending a client-ready message, triggering the on_client_ready event in the processor:
@rtvi.event_handler("on_client_ready")
async def on_client_ready(rtvi):
    # Handle client ready state
    await rtvi.set_bot_ready()
    # Initialize conversation
    await task.queue_frames([...])
The processor validates the client’s RTVI protocol version during the handshake. If the client’s major version doesn’t match the server’s protocol version, an error response is sent but the connection continues. Check server logs for version compatibility warnings.

Bot Ready State

The server must mark the bot as ready before it can process client messages:
await rtvi.set_bot_ready()
When marked ready, the bot sends a response containing:
  • RTVI protocol version
  • Current service configuration
  • Available actions

Services

Services represent configurable components of your application that clients can interact with.

Registering Services

# 1. Define option handler
async def handle_voice_option(processor, service, option):
    voice_id = option.value
    # Apply configuration change
    logger.info(f"Voice ID updated to: {voice_id}")

# 2. Create RTVIService
voice_service = RTVIService(
    name="voice",
    options=[
        RTVIServiceOption(
            name="voice_id",
            type="string",
            handler=handle_voice_option
        )
    ]
)

# 3. Register with processor
rtvi.register_service(voice_service)

Option Types

Services support multiple data types for configuration:
RTVIServiceOption(
    name="temperature",
    type="number",  # number, string, bool, array, object
    handler=handle_temperature
)
Option handlers receive:
  • The processor instance
  • The service name
  • The option configuration with new value

Actions

Actions are server-side functions that clients can trigger with arguments.

Registering Actions

# 1. Define handler function
async def handle_print_message(processor, service, arguments):
    message = arguments.get("message", "Default message")
    logger.info(f"Print action triggered with message: {message}")
    return True

# 2. Create and register RTVIAction
print_action = RTVIAction(
    service="conversation",
    action="print_message",
    arguments=[
        RTVIActionArgument(name="message", type="string")
    ],
    result="bool",
    handler=handle_print_message
)
rtvi.register_action(print_action)

Action Arguments

Actions can accept typed arguments from clients:
search_action = RTVIAction(
    service="knowledge",
    action="search",
    arguments=[
        RTVIActionArgument(name="query", type="string"),
        RTVIActionArgument(name="limit", type="number")
    ],
    result="array",
    handler=handle_search
)

Function Calls

Handle LLM function calls with client interaction:

await processor.handle_function_call(
    function_name=function_name,
    tool_call_id=tool_call_id,
    arguments=arguments,
)

await processor.handle_function_call(params)
The function call process:
  1. LLM requests a function call
  2. Processor notifies client with llm-function-call message
  3. Client executes function and returns result
  4. Result is passed back to LLM via FunctionCallResultFrame
  5. Conversation continues

Error Handling

Send error messages to clients:
# General error
await processor.send_error("Invalid configuration")

# Request-specific error
await processor._send_error_response(request_id, "Invalid action arguments")
Error categories:
  • Configuration errors
  • Action execution errors
  • Function call errors
  • Protocol errors
  • Fatal and non-fatal errors

Bot Control

Manage bot state and handle interruptions:
# Set bot as ready
await processor.set_bot_ready()

# Handle interruptions
await processor.interrupt_bot()

Custom Messaging

Server messages let you push unsolicited data from the server to the client at any time — notifications, status updates, real-time results, etc. They are distinct from server responses, which reply to a specific client request (see Requesting Information from the Server).

Sending server messages

Any FrameProcessor in the pipeline can push an RTVIServerMessageFrame. The RTVIObserver picks it up and delivers it to the client:
from pipecat.processors.frameworks.rtvi import RTVIServerMessageFrame

class MyProcessor(FrameProcessor):
    async def process_frame(self, frame, direction):
        await super().process_frame(frame, direction)
        if isinstance(frame, SomeEventFrame):
            await self.push_frame(
                RTVIServerMessageFrame(
                    data={"type": "event", "value": frame.value}
                )
            )
        await self.push_frame(frame, direction)
RTVIServerMessageFrame is a SystemFrame, so it uses the high-priority SystemFrame lane, remains ordered with other SystemFrames, and is not discarded by interruptions.

Client-side handling

The message arrives at the client with the wire format { label: "rtvi-ai", type: "server-message", data: ... }. Handle it with the onServerMessage callback:
pcClient.onServerMessage((message) => {
  console.log("Server message:", message);
  // message.data contains whatever you passed on the server
});
See Handling Custom Messages from the Server for more details and examples.

UI Agent Protocol

RTVI 1.3.0+ includes first-class support for the UI Agent Protocol, which lets server-side AI agents observe and drive GUI applications on the client. The protocol covers five message types:
  • ui-event — client → server event message
  • ui-command — server → client command message
  • ui-snapshot — client → server accessibility snapshot
  • ui-cancel-task — client → server task cancellation request
  • ui-task — server → client task lifecycle envelope

Handling UI Messages

The processor automatically handles inbound UI messages from the client and fires the on_ui_message event handler:
@rtvi.event_handler("on_ui_message")
async def on_ui_message(rtvi, message):
    # message is a UIEventMessage, UISnapshotMessage, or UICancelTaskMessage
    if message.type == "ui-event":
        logger.info(f"UI event: {message.data.event}")
    elif message.type == "ui-snapshot":
        # Process accessibility tree
        tree = message.data.tree
        logger.info(f"Snapshot captured at: {tree.captured_at}")
    elif message.type == "ui-cancel-task":
        task_id = message.data.task_id
        logger.info(f"Cancel requested for task: {task_id}")
The processor also pushes corresponding frames downstream for pipeline-level handling:
  • RTVIUIEventFrame — for ui-event messages
  • RTVIUISnapshotFrame — for ui-snapshot messages
  • RTVIUICancelTaskFrame — for ui-cancel-task messages

Sending UI Commands

Push RTVIUICommandFrame to send commands to the client. The observer wraps these in ui-command messages:
from pipecat.processors.frameworks.rtvi import RTVIUICommandFrame
from pipecat.processors.frameworks.rtvi.models import Toast, Navigate

# Send a toast notification
toast = Toast(title="Order confirmed", subtitle="Your order #1234 is on the way")
await self.push_frame(
    RTVIUICommandFrame(command="toast", payload=toast.model_dump())
)

# Navigate to a different view
nav = Navigate(view="order_detail", params={"order_id": "1234"})
await self.push_frame(
    RTVIUICommandFrame(command="navigate", payload=nav.model_dump())
)
Built-in command payload models include: Toast, Navigate, ScrollTo, Highlight, Focus, Click, SetInputValue, and SelectText. These have matching default handlers in @pipecat-ai/client-react.

Sending UI Tasks

Push RTVIUITaskFrame to send task lifecycle updates to the client:
from pipecat.processors.frameworks.rtvi import RTVIUITaskFrame
from pipecat.processors.frameworks.rtvi.models import (
    UITaskGroupStartedData,
    UITaskUpdateData,
    UITaskCompletedData,
    UITaskGroupCompletedData,
)
import time

task_id = "search-123"
timestamp = int(time.time() * 1000)

# Start a task group
await self.push_frame(
    RTVIUITaskFrame(
        data=UITaskGroupStartedData(
            task_id=task_id,
            agents=["search", "summarize"],
            label="Searching knowledge base",
            at=timestamp,
        )
    )
)

# Send progress update
await self.push_frame(
    RTVIUITaskFrame(
        data=UITaskUpdateData(
            task_id=task_id,
            agent_name="search",
            data={"progress": 0.5},
            at=timestamp,
        )
    )
)

# Complete individual task
await self.push_frame(
    RTVIUITaskFrame(
        data=UITaskCompletedData(
            task_id=task_id,
            agent_name="search",
            status="completed",
            response={"results": [...]},
            at=timestamp,
        )
    )
)

# Complete the group
await self.push_frame(
    RTVIUITaskFrame(
        data=UITaskGroupCompletedData(task_id=task_id, at=timestamp)
    )
)