> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pipecat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# RTVIProcessor

> Core coordinator for RTVI protocol communication

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 `PipelineWorker`. Access it via `worker.rtvi`:

```python theme={null}
pipeline = Pipeline([
    transport.input(),
    stt,
    # ... other processors ...
    transport.output()
])

worker = PipelineWorker(pipeline)

# Access the processor
rtvi = worker.rtvi
```

To provide a custom processor (e.g., for [Google RTVI](./google-rtvi-observer)):

```python theme={null}
from pipecat.services.google.rtvi import GoogleRTVIProcessor

worker = PipelineWorker(pipeline, rtvi_processor=GoogleRTVIProcessor())
```

To disable RTVI entirely, set `enable_rtvi=False`.

## Properties

### client\_version

Returns the negotiated client protocol version as `[major, minor, patch]`. Defaults to `[0, 0, 0]` until the client sends a `client-ready` message.

```python theme={null}
version = rtvi.client_version
if version[0] == 2:
    # Client is using protocol v2
    pass
```

## Readiness Protocol

### Client Ready State

Clients indicate readiness by sending a `client-ready` message, triggering the `on_client_ready` event in the processor:

```python theme={null}
@rtvi.event_handler("on_client_ready")
async def on_client_ready(rtvi):
    # Handle client ready state
    await rtvi.set_bot_ready()
    # Initialize conversation
    await worker.queue_frames([...])
```

<Note>
  The processor validates the client's RTVI protocol version during the
  handshake. The server protocol version is 2.0.0. Clients with major version 2
  are fully compatible. Clients with major version 1 (deprecated) are still
  supported with the legacy `bot-output` format. 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.
</Note>

### Bot Ready State

The server must mark the bot as ready before it can process client messages:

```python theme={null}
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

```python theme={null}
# 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:

```python theme={null}
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

```python theme={null}
# 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:

```python theme={null}
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:

```python theme={null}

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:

```python theme={null}
# 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:

```python theme={null}
# 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](/client/guides/custom-messaging#requesting-data-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:

```python theme={null}
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)
```

<Note>
  `RTVIServerMessageFrame` is a `SystemFrame`, so it uses the high-priority
  SystemFrame lane, remains ordered with other SystemFrames, and is not
  discarded by interruptions.
</Note>

### 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:

```javascript theme={null}
pcClient.onServerMessage((message) => {
  console.log("Server message:", message);
  // message.data contains whatever you passed on the server
});
```

See [Handling Custom Messages from the Server](/client/guides/custom-messaging#receiving-messages-from-the-server) for more details and examples.

## UI Messaging

The processor handles the [User Interface](/client/rtvi-standard#user-interface) messages that let a server-side agent observe and drive the client GUI. Inbound UI messages from the client are turned into pipeline frames and surfaced via the `on_ui_message` event handler:

| Wire message          | Frame pushed downstream     | `on_ui_message` payload   |
| --------------------- | --------------------------- | ------------------------- |
| `ui-event`            | `RTVIUIEventFrame`          | `UIEventMessage`          |
| `ui-snapshot`         | `RTVIUISnapshotFrame`       | `UISnapshotMessage`       |
| `ui-cancel-job-group` | `RTVIUICancelJobGroupFrame` | `UICancelJobGroupMessage` |

```python theme={null}
@rtvi.event_handler("on_ui_message")
async def on_ui_message(processor, message):
    # message is a UIEventMessage / UISnapshotMessage / UICancelJobGroupMessage
    ...
```

When you use [`UIWorker`](/api-reference/server/workers/ui-worker), `PipelineWorker` wires this up automatically (RTVI is enabled by default): inbound UI messages are republished onto the bus for the worker, and outbound `ui-command` / `ui-job-group` frames are translated by the [RTVI Observer](/api-reference/server/rtvi/rtvi-observer#frame-translation). You only handle `on_ui_message` directly when targeting the wire format from a single-LLM app without a `UIWorker`.
