webex_byova.media
BYOVA gRPC media server — async handler API for voice virtual agent streaming.
Optional install
This module requires the media extra:
pip install webex-byova[media]
Overview
webex_byova.media
BYOVA gRPC media server — developer-facing API (requires webex-byova[media]).
MediaServerConfig
Bases: BaseModel
Validated server-wide settings for the BYOVA media server.
chunk_bytes
property
chunk_bytes: int
Raw μ-law bytes per outbound chunk.
from_env
classmethod
from_env() -> MediaServerConfig
Build configuration from WEBEX_MEDIA_* environment variables.
AudioInputEvent
Bases: BaseModel
Inbound caller audio chunk.
BargeInEvent
Bases: BaseModel
Caller interrupted bot playback.
DtmfInputEvent
Bases: BaseModel
Inbound DTMF digits.
ErrorEvent
Bases: BaseModel
Recoverable or fatal handler/server error.
NoInputEvent
Bases: BaseModel
Caller did not provide input within the timeout.
SessionEndEvent
Bases: BaseModel
Session terminated.
SessionStartEvent
Bases: BaseModel
Fired when a new call session begins.
TurnEndedEvent
Bases: BaseModel
Turn stream closed.
TurnStartedEvent
Bases: BaseModel
A new turn stream opened.
DuplicateTurnStreamError
MediaConfigError
MediaServerError
PromptValidationError
ProxyBufferOverflowError
ProxyConnectionError
PromptRequest
Bases: BaseModel
Developer request to play audio or TTS to the caller.
PromptResponse
Bases: BaseModel
Result of a play_prompt operation.
DefaultProxyAdapter
JSON message mapping per websocket-proxy contract.
ProxyAdapter
Bases: Protocol
Map media events to backend messages and vice versa.
WebSocketProxyConnector
Bridge media sessions to an external WebSocket voice AI backend.
attach
attach(server: BYOVAMediaServer) -> None
Attach to a media server instance.
forward_event
async
forward_event(event: MediaEvent, session: MediaSession) -> None
Forward SDK event to backend WebSocket.
disconnect_session
async
disconnect_session(conversation_id: str) -> None
Close backend connection for a session.
BYOVAMediaServer
Developer-hosted BYOVA gRPC media server.
from_env
classmethod
from_env() -> BYOVAMediaServer
Construct server from WEBEX_MEDIA_* environment variables.
on
on(event: str) -> Callable[[Handler], Handler]
Decorator to register an async or sync event handler.
handler
handler(event: str) -> Callable[[Handler], Handler]
Alias for :meth:on.
use_proxy
use_proxy(connector: Any) -> None
Attach a WebSocket proxy connector.
start
async
start() -> None
Bind and start the gRPC server.
stop
async
stop(grace: float = 5.0) -> None
Gracefully stop the server.
serve
async
serve() -> None
Start the server and wait until interrupted.
MediaSession
Long-lived call state keyed by conversation_id.
active_turn
property
active_turn: TurnContext | None
Currently active turn, if any.
bind_turn
bind_turn(turn: TurnContext) -> None
Attach active turn context.
play_prompt
async
play_prompt(*, text: str | None = None, audio: bytes | None = None, ssml: str | None = None, barge_in: bool | None = None) -> PromptResponse
Play prompt on the active turn.
collect_input
async
collect_input(*, mode: str | None = None, timeout: float | None = None) -> AudioInputEvent | DtmfInputEvent
Await caller input on the active turn.
end_session
async
end_session(reason: str = 'completed') -> None
Mark session ending and release resources.
TurnContext
Per-turn view during one bidirectional stream.
turn_id
property
turn_id: str
Unique turn identifier.
turn_number
property
turn_number: int
1-based turn index within the session.
is_active
property
is_active: bool
Whether the turn stream is still open.
is_final
property
is_final: bool
Whether the turn closed with RESPONSE_FINAL.
play_prompt
async
play_prompt(*, text: str | None = None, audio: bytes | None = None, ssml: str | None = None, barge_in: bool | None = None) -> PromptResponse
Encode, chunk, and send outbound prompt audio.
collect_input
async
collect_input(*, mode: str | None = None, timeout: float | None = None) -> AudioInputEvent | DtmfInputEvent
Wait for normalized caller input.
end_turn
async
end_turn() -> None
Send RESPONSE_FINAL and close the turn stream.
BYOVAMediaServer
webex_byova.media.server.BYOVAMediaServer
Developer-hosted BYOVA gRPC media server.
config
instance-attribute
config = config or MediaServerConfig()
_handlers
instance-attribute
_handlers: dict[str, list[Handler]] = defaultdict(list)
_grpc_server
instance-attribute
_grpc_server: Server | None = None
_conversation_store
instance-attribute
_conversation_store = ConversationStore(ttl_seconds=max_session_duration or 3600.0)
_proxy
instance-attribute
_proxy = None
_running
instance-attribute
_running = False
__init__
__init__(config: MediaServerConfig | None = None) -> None
from_env
classmethod
from_env() -> BYOVAMediaServer
Construct server from WEBEX_MEDIA_* environment variables.
on
on(event: str) -> Callable[[Handler], Handler]
Decorator to register an async or sync event handler.
handler
handler(event: str) -> Callable[[Handler], Handler]
Alias for :meth:on.
use_proxy
use_proxy(connector: Any) -> None
Attach a WebSocket proxy connector.
start
async
start() -> None
Bind and start the gRPC server.
stop
async
stop(grace: float = 5.0) -> None
Gracefully stop the server.
serve
async
serve() -> None
Start the server and wait until interrupted.
__aenter__
async
__aenter__() -> BYOVAMediaServer
__aexit__
async
__aexit__(*args: object) -> None
_dispatch_event
async
_dispatch_event(event_name: str, event: MediaEvent, session: MediaSession, turn: TurnContext | None) -> None
_invoke_handler
async
_invoke_handler(fn: Handler, event: MediaEvent, session: MediaSession, turn: TurnContext | None) -> None
_handle_handler_error
async
_handle_handler_error(exc: Exception, session: MediaSession, turn: TurnContext | None) -> None
_release_session
async
_release_session(conversation_id: str, reason: str) -> None
MediaServerConfig
webex_byova.media.config.MediaServerConfig
Bases: BaseModel
Validated server-wide settings for the BYOVA media server.
host
class-attribute
instance-attribute
host: str = '0.0.0.0'
port
class-attribute
instance-attribute
port: int = Field(default=50051, ge=0, le=65535)
tls_cert
class-attribute
instance-attribute
tls_cert: str | None = None
tls_key
class-attribute
instance-attribute
tls_key: str | None = None
audio_mode
class-attribute
instance-attribute
audio_mode: AudioMode = 'chunked'
encoding
class-attribute
instance-attribute
encoding: Literal['mulaw'] = 'mulaw'
sample_rate
class-attribute
instance-attribute
sample_rate: Literal[8000, 16000] = 8000
channels
class-attribute
instance-attribute
channels: int = Field(default=1, ge=1, le=1)
chunk_size_ms
class-attribute
instance-attribute
chunk_size_ms: int = Field(default=20, ge=10, le=100)
input_mode
class-attribute
instance-attribute
input_mode: InputMode = 'voice'
barge_in_enabled
class-attribute
instance-attribute
barge_in_enabled: bool = False
no_input_timeout
class-attribute
instance-attribute
no_input_timeout: float = Field(default=5.0, gt=0)
turn_disconnect_timeout
class-attribute
instance-attribute
turn_disconnect_timeout: float | None = None
end_of_input_silence_ms
class-attribute
instance-attribute
end_of_input_silence_ms: int = Field(default=500, ge=0)
max_session_duration
class-attribute
instance-attribute
max_session_duration: float | None = Field(default=3600.0, gt=0)
verify_tokens
class-attribute
instance-attribute
verify_tokens: bool = True
proxy_buffer_limit
class-attribute
instance-attribute
proxy_buffer_limit: int = Field(default=65536, gt=0)
proxy_overflow_policy
class-attribute
instance-attribute
proxy_overflow_policy: OverflowPolicy = 'disconnect'
log_level
class-attribute
instance-attribute
log_level: str = 'INFO'
chunk_bytes
property
chunk_bytes: int
Raw μ-law bytes per outbound chunk.
_validate_tls_pair
_validate_tls_pair() -> MediaServerConfig
from_env
classmethod
from_env() -> MediaServerConfig
Build configuration from WEBEX_MEDIA_* environment variables.
MediaSession
webex_byova.media.session.MediaSession
Long-lived call state keyed by conversation_id.
conversation_id
instance-attribute
conversation_id = conversation_id
session_id
instance-attribute
session_id = str(uuid4())
metadata
instance-attribute
metadata = metadata or {}
started_at
instance-attribute
started_at = now(utc)
state
instance-attribute
state = ACTIVE
turn_count
instance-attribute
turn_count = 0
_config
instance-attribute
_config = config
_server
instance-attribute
_server = server
_active_turn
instance-attribute
_active_turn: TurnContext | None = None
_input_futures
instance-attribute
_input_futures: list[Future[AudioInputEvent | DtmfInputEvent]] = []
active_turn
property
active_turn: TurnContext | None
Currently active turn, if any.
__init__
__init__(*, conversation_id: str, config: MediaServerConfig, server: BYOVAMediaServer, metadata: dict[str, Any] | None = None) -> None
bind_turn
bind_turn(turn: TurnContext) -> None
Attach active turn context.
play_prompt
async
play_prompt(*, text: str | None = None, audio: bytes | None = None, ssml: str | None = None, barge_in: bool | None = None) -> PromptResponse
Play prompt on the active turn.
collect_input
async
collect_input(*, mode: str | None = None, timeout: float | None = None) -> AudioInputEvent | DtmfInputEvent
Await caller input on the active turn.
end_session
async
end_session(reason: str = 'completed') -> None
Mark session ending and release resources.
_resolve_input
_resolve_input(event: AudioInputEvent | DtmfInputEvent) -> None
_wait_for_input
async
_wait_for_input(*, timeout: float | None) -> AudioInputEvent | DtmfInputEvent
TurnContext
webex_byova.media.session.TurnContext
Per-turn view during one bidirectional stream.
session
instance-attribute
session = session
_config
instance-attribute
_config = config
_send_response
instance-attribute
_send_response = send_response
_close_stream_cb
instance-attribute
_close_stream_cb = close_stream
_runtime
instance-attribute
_runtime = TurnRuntime(turn_number=turn_number)
turn_id
property
turn_id: str
Unique turn identifier.
turn_number
property
turn_number: int
1-based turn index within the session.
is_active
property
is_active: bool
Whether the turn stream is still open.
is_final
property
is_final: bool
Whether the turn closed with RESPONSE_FINAL.
__init__
__init__(*, session: MediaSession, config: MediaServerConfig, send_response: Callable[[Any], Coroutine[Any, Any, None]], close_stream: Callable[[], Coroutine[Any, Any, None]], turn_number: int) -> None
play_prompt
async
play_prompt(*, text: str | None = None, audio: bytes | None = None, ssml: str | None = None, barge_in: bool | None = None) -> PromptResponse
Encode, chunk, and send outbound prompt audio.
collect_input
async
collect_input(*, mode: str | None = None, timeout: float | None = None) -> AudioInputEvent | DtmfInputEvent
Wait for normalized caller input.
end_turn
async
end_turn() -> None
Send RESPONSE_FINAL and close the turn stream.
_emit_turn_ended
async
_emit_turn_ended(reason: str) -> None
_send_platform
async
_send_platform(response: Any) -> None
_close_stream
async
_close_stream() -> None
Events
webex_byova.media.events.SessionStartEvent
Bases: BaseModel
Fired when a new call session begins.
webex_byova.media.events.AudioInputEvent
Bases: BaseModel
Inbound caller audio chunk.
webex_byova.media.events.DtmfInputEvent
Bases: BaseModel
Inbound DTMF digits.
webex_byova.media.events.BargeInEvent
Bases: BaseModel
Caller interrupted bot playback.
webex_byova.media.events.NoInputEvent
Bases: BaseModel
Caller did not provide input within the timeout.
webex_byova.media.events.TurnStartedEvent
Bases: BaseModel
A new turn stream opened.
webex_byova.media.events.TurnEndedEvent
Bases: BaseModel
Turn stream closed.
webex_byova.media.events.SessionEndEvent
Bases: BaseModel
Session terminated.
webex_byova.media.events.ErrorEvent
Bases: BaseModel
Recoverable or fatal handler/server error.
WebSocket proxy
webex_byova.media.proxy.connector.WebSocketProxyConnector
Bridge media sessions to an external WebSocket voice AI backend.
url
instance-attribute
url = url
adapter
instance-attribute
adapter = adapter or DefaultProxyAdapter()
reconnect
instance-attribute
reconnect = reconnect
connect_timeout
instance-attribute
connect_timeout = connect_timeout
_server
instance-attribute
_server: BYOVAMediaServer | None = None
_connections
instance-attribute
_connections: dict[str, Any] = {}
_buffers
instance-attribute
_buffers: dict[str, bytearray] = {}
__init__
__init__(url: str, adapter: ProxyAdapter | None = None, *, reconnect: bool = False, connect_timeout: float = 10.0) -> None
attach
attach(server: BYOVAMediaServer) -> None
Attach to a media server instance.
forward_event
async
forward_event(event: MediaEvent, session: MediaSession) -> None
Forward SDK event to backend WebSocket.
_read_loop
async
_read_loop(ws: Any, session: MediaSession) -> None
disconnect_session
async
disconnect_session(conversation_id: str) -> None
Close backend connection for a session.
webex_byova.media.proxy.adapter.ProxyAdapter
Bases: Protocol
Map media events to backend messages and vice versa.
to_backend
to_backend(event: MediaEvent, session: MediaSession) -> str | bytes
from_backend
from_backend(message: str | bytes, session: MediaSession) -> PromptRequest | None
webex_byova.media.proxy.adapter.DefaultProxyAdapter
JSON message mapping per websocket-proxy contract.
to_backend
to_backend(event: MediaEvent, session: MediaSession) -> str
from_backend
from_backend(message: str | bytes, session: MediaSession) -> PromptRequest | None
Prompts
webex_byova.media.prompts.PromptRequest
Bases: BaseModel
Developer request to play audio or TTS to the caller.
webex_byova.media.prompts.PromptResponse
Bases: BaseModel
Result of a play_prompt operation.