Zeli AvatarDeveloper docs

Python SDK / Configuration

Configuration

Three dataclasses shape a session: AvatarConfig (the persona), ClientOptions (where the server is), and SessionOptions (per-session media settings).

from zeli import AvatarConfig, ClientOptions, SessionOptions

AvatarConfig

Describes the avatar persona to bring to life.

FieldTypeDefaultDescription
avatar_idstr | NoneNonePrepared avatar to render (uploaded files are keyed by filename stem). If omitted, the server default is used.
namestr | NoneNoneHuman-friendly persona label.
voice_idstr | NoneNoneVoice to speak with.
llm_idstr | NoneNoneModel to answer this session's turns with. Sent on the connection body; a model the server will not run raises ModelRefusedError rather than falling back.
system_promptstr | NoneNoneSystem prompt priming the model.
language_codestr | NoneNoneBCP-47 language, e.g. "en".
emotion_responsiveboolFalseReserved. Never transmitted; the server matches tone clips on its own. See Emotions.
enhanceboolFalseRegion-limited mouth restoration for sharper lips/teeth.
enhance_strengthfloat | NoneNoneBlend factor for enhance in [0, 1].
loop_mode"boomerang" | "forward" | NoneNoneHow the idle loop repeats while speaking.
max_session_length_secondsint | NoneNoneSeconds this session may run before the server ends it. Sent on the control gateway's upgrade URL. Omit for the server's own ceiling; pass 0 to ask for none.

Four of these fields reach the server and the other seven do not.

On the wire: avatar_id and llm_id travel on the connection body, voice_id on every control gateway command, and max_session_length_seconds on the control gateway's upgrade URL.

The remaining seven are reserved: they are never transmitted, so setting one has no effect and no error, only a warning on the zeli logger naming it. Configure those on the server instead, through its /api/settings.

Two fields have moved off that list since it was written, because the server grew the thing each was asking for: max_session_length_seconds once a session ceiling existed, and llm_id once a session could choose its own model. A list of dead fields that outlives the death is worse than no list, because it teaches you not to use something that works.

ClientOptions

Client-wide settings: where the server is and how to reach it.

FieldTypeDefaultDescription
server_urlstr"http://localhost:8080"Base URL of the avatar server.
connect_pathstr"/connect"Persistent WebRTC offer/answer path.
ice_serverslist[dict] | NoneGoogle STUNICE servers for NAT traversal.
connect_timeoutfloat30.0Seconds to wait for the media connection.
client_labelstr | NoneNoneOptional label attached to requests for observability.

SessionOptions

Per-session settings passed to connect().

FieldTypeDefaultDescription
video_quality"high" | "auto""high""auto" lets the server adapt to bandwidth.
receive_videoboolTrueSubscribe to the inbound video track.
receive_audioboolTrueSubscribe to the inbound audio track.

Example

client = ZeliClient(
    avatar_config=AvatarConfig(
        avatar_id="01-presenter-male__confident",
        voice_id="your-voice-id",
        llm_id="au.anthropic.claude-opus-5-5",
        max_session_length_seconds=900,
    ),
    options=ClientOptions(
        server_url="https://avatar.zelibot.xyz",
        connect_timeout=20.0,
    ),
)
 
async with client.connect(SessionOptions(video_quality="auto")) as session:
    ...
Zeli Avatar · real-time avatars over WebRTC · self-hostable · AU data residency · source