Python SDK

The Python client streams mic and webcam to the hosted server, tagging each frame silent, human-directed, or device-directed, and forwards only device-directed audio.

Overview

The Python client mirrors the browser client for server and embedded apps, and requires Python 3.10+.

# Install
pip install attenlabs-saa

View the SDK on GitHub

Pass your token to the constructor. start() authenticates to the broker, which allocates a backend and returns a per-session WebSocket URL for mic, webcam, and events.

import os, time
from saa import AttentionClient

client = AttentionClient(token=os.environ["SAA_API_KEY"])

@client.on_prediction
def _(event):
    label = {0: "silent", 1: "human", 2: "device"}.get(event.cls, "?")
    print(f"{label}  {event.confidence:.0%}  faces={event.num_faces}")

@client.on_turn_ready
def _(event):
    print(f"turn ready ({event.duration_sec:.2f}s)")
    # event.audio_base64 is ready to forward to your LLM

client.start()
try:
    while True:
        time.sleep(0.1)
except KeyboardInterrupt:
    client.stop()

Quickstart

Install the SDK, export your token, initialize the client, register decorator callbacks, then keep the main thread alive while events stream.

By default the client captures both mic and webcam; pass enable_video=False to disable the camera.

import os
import time
from saa import AttentionClient

client = AttentionClient(token=os.environ["SAA_API_KEY"])

CLASS_LABELS = {0: "silent", 1: "human", 2: "device"}

@client.on_connected
def _():
    print("connected")

@client.on_started
def _():
    print("warmup complete, streaming live")

@client.on_prediction
def _(event):
    label = CLASS_LABELS.get(event.cls, "?")
    print(f"{label:7}  {event.confidence:.0%}  faces={event.num_faces}  src={event.source}")

@client.on_turn_ready
def _(event):
    print(f"turn ready ({event.duration_sec:.2f}s)")
    # Forward event.audio_base64 to your LLM here

@client.on_error
def _(event):
    print(f"ERROR: {event.title}: {event.message}")

client.start()
try:
    while True:
        time.sleep(0.1)
except KeyboardInterrupt:
    client.stop()

on_turn_ready returns the turn's audio as event.audio_base64 (base64 PCM16, 16 kHz mono) or event.audio_pcm16 (NumPy int16 array), ready for your language model.

LLM integration

Call mute() and mark_responding(True) when your LLM starts speaking, then unmute() and mark_responding(False) when it finishes, so the SDK ignores its own audio.

def on_llm_speaking():
    client.mute()
    client.mark_responding(True)

def on_llm_done():
    client.unmute()
    client.mark_responding(False)

AttentionClient (Python)

The constructor accepts a token plus optional config objects and capture toggles. The device-class confidence threshold defaults to 0.7.

from saa import AttentionClient, CameraConfig, MicConfig

client = AttentionClient(
    token="...",                  # auth token (Bearer to broker + WS subprotocol)
    url=None,                     # broker base URL (default https://broker.attentionlabs.ai)
    video=CameraConfig(),         # webcam config
    audio=MicConfig(),            # mic config
    initial_threshold=0.7,        # device-class confidence threshold (0..1)
    enable_audio=True,            # set False to skip mic capture
    enable_video=True,            # set False to skip webcam capture
    server_profile=None,          # override server profile; auto "audio_only" when video off
)

If you pass a ws:// or wss:// URL as url, the SDK treats it as a direct backend and skips the broker step.

MicConfig and CameraConfig

TypeFields
MicConfigdevice (int | str | None, default None for system default), channels (int, default 1).
CameraConfigdevice_index (int, default 0), width (int, default 1920), height (int, default 1080), jpeg_quality (int 0-100, default 50).

Methods

Python method names are snake_case; the Python forms below summarize the full method surface documented on the JavaScript SDK reference.

MethodDescription
start()Acquires mic and camera, opens the WebSocket, starts capture. Non-blocking; raises on handshake failure.
stop()Tears down capture, releases media, closes the WebSocket.
mute()Pauses upstream audio and signals the server to stop VAD.
unmute()Resumes upstream audio.
mark_responding(bool)Tell the server an LLM response is in flight. The server stops emitting predictions while it is True.
set_threshold(value)Update the device-class confidence threshold (0..1). The server acknowledges via a config event.
feed_audio(audio, *, sample_rate=16000)Feed externally captured audio instead of using the built-in mic. See External capture below.
feed_video(frame)Feed an externally captured video frame instead of using the built-in camera. See External capture below.

The current threshold is available on the threshold attribute. Python registers events with decorators, not on()/off(); see below.

Event decorators

Register handlers with decorators on the client. The event set matches the browser client; payloads arrive as snake_case dataclasses, detailed in the table below.

DecoratorPayloadFires when
@client.on_connectednoneThe WebSocket opens.
@client.on_startednoneThe server has loaded the model.
@client.on_warmup_completenoneThe model is warmed up and producing predictions.
@client.on_predictionPredictionEventEach attention prediction.
@client.on_vadVadEventA voice-activity update.
@client.on_stateStateEventA conversation-state transition.
@client.on_turn_readyTurnReadyEventA complete user turn is ready to forward.
@client.on_configConfigEventThe server acknowledges a threshold change.
@client.on_statsStatsEventAbout every 10s, with connection health.
@client.on_interruptInterruptEventThe user is barging in mid-response.
@client.on_interjectionInterjectionEventA proactive volunteer after people go quiet.
@client.on_errorAttentionErrorEventA connection, auth, or server error.
@client.on_disconnectedDisconnectedEventThe WebSocket closes.

Event payloads (Python)

Each callback receives a snake_case dataclass, matching the shared event-types reference. StatsEvent.uptime_s is in seconds; raw_cls and server_turn_ready_ts_ms are JavaScript-only.

TypeFields
PredictionEventcls (int: 0 silent, 1 human-directed, 2 device-directed), confidence (0..1), source ("video" or "audio"), num_faces (faces detected in frame), responding (bool, default False, True while the AI is mid-playback).
VadEventprobability (VAD probability 0..1), is_speech (bool).
StateEventstate: one of "listening", "sending", "cancelled", "idle".
TurnReadyEventaudio_pcm16 (NumPy int16 array, 16 kHz mono), audio_base64 (base64 PCM16 @ 16 kHz mono), duration_sec (seconds), frames (list of TurnFrame, empty unless the server sends per-turn stills), context (str or None, for example "interjection_follow_up").
TurnFramets_offset_s (seconds from listening-start), image_base64 (base64 JPEG, no data: prefix).
ConfigEventmodel_class2_threshold (server-confirmed threshold).
StatsEventrtt_ms (round-trip latency in ms or None), sent_video, skipped_video, sent_audio, uptime_s (seconds).
InterruptEventfade_ms (suggested fade duration in ms before stopping playback), confidence (raw confidence of the firing device-directed prediction).
InterjectionEventreason (why the volunteer fired), audio_pcm16 (NumPy int16 array), audio_base64 (base64 PCM16 @ 16 kHz mono), duration_sec (seconds).
AttentionErrorEventtitle (error category, for example "Auth Failed", "Connection Stalled"), message (human-readable), detail (technical detail or None, default None), code (WebSocket close code, default None).
DisconnectedEventcode (WebSocket close code), reason (close reason), was_clean (True if code is 1000).

Threading

The SDK runs four internal threads. Callbacks fire on saa-ws or saa-heartbeat, so keep them fast and offload heavy work to your own thread.

External capture

Already capturing audio or video elsewhere? Pass enable_audio=False or enable_video=False, then feed frames with feed_audio() or feed_video().

from saa import AttentionClient

# Disable the built-in mic and feed your own audio.
client = AttentionClient(token=os.environ["SAA_API_KEY"], enable_audio=False)
client.start()

# later, from your own capture loop:
client.feed_audio(chunk, sample_rate=16000)

Shared reference

The method surface, events, event types, audio and video framing, errors, and integrations are all shared with the browser client on the JavaScript SDK reference.

Need help integrating? Write to [email protected].