streamcore (Python)
English | 简体中文
Python SDK for connecting to a streamcore server via WebRTC + WHIP, powered by aiortc.
Requirements
- Python 3.10+
Installation
pip install streamcore
Or install from source:
cd python-sdk
pip install -e .
Quick Start
import asyncio
import numpy as np
import streamcore
async def main():
def on_transcript(entry, all_entries):
print(f"[{entry.role}] {entry.text}")
client = streamcore.Client(
config=streamcore.Config(whip_endpoint="http://localhost:8080/whip"),
events=streamcore.EventHandler(
on_transcript=on_transcript,
on_error=lambda err: print(f"Error: {err}"),
),
)
await client.connect()
# Send a 20 ms frame of silence
pcm = np.zeros(streamcore.FRAME_SIZE, dtype=np.int16)
await client.send_pcm(pcm)
# Receive decoded audio from the agent
audio = await client.recv_pcm() # numpy int16 array
await client.disconnect()
asyncio.run(main())
API
Client(config?, events?)
Creates a new voice agent client.
Config
| Field | Type | Default | Description |
|---|---|---|---|
whip_endpoint |
str |
"http://localhost:8080/whip" |
WHIP signaling endpoint URL |
token |
str |
"" |
JWT sent as Authorization: Bearer on the WHIP request |
token_url |
str |
"" |
Token endpoint; when set, a JWT is fetched before each connection (overrides token) |
api_key |
str |
"" |
Sent as Authorization: Bearer when fetching from token_url |
resource_id |
str |
"" |
Who is on the call, forwarded to an external agent so it can scope memory to the person rather than the call. Sent in the token request body when token_url is set (the server signs it into the token), otherwise as an X-StreamCore-Resource-Id header |
ice_servers |
list[str] |
["stun:stun.l.google.com:19302"] |
ICE server URLs |
EventHandler
All callbacks are optional.
| Callback | Signature | Description |
|---|---|---|
on_status_change |
(status: ConnectionStatus) -> None |
Fired when connection status changes |
on_transcript |
(entry: TranscriptEntry, all: list[TranscriptEntry]) -> None |
Fired on new or updated transcript |
on_agent_state_change |
(state: AgentState) -> None |
Fired when the agent starts listening, thinking, or speaking |
on_timing |
(event: TimingEvent) -> None |
Fired with server-side pipeline timing info |
on_error |
(error: Exception) -> None |
Fired on connection or server errors |
on_data_channel_message |
(msg: DataChannelMessage) -> None |
Fired for every raw DC message |
Methods
| Method | Description |
|---|---|
await client.connect(track?) |
Connect via WHIP. Optionally pass an aiortc audio track. |
await client.disconnect() |
Tear down connection and free resources. |
await client.send_pcm(pcm) |
Send a numpy int16 PCM buffer (mono 48 kHz) to the agent. |
await client.recv_pcm() |
Receive decoded PCM audio as a numpy int16 array. |
client.status |
Current ConnectionStatus. |
client.transcript |
Current conversation as list[TranscriptEntry]. |
client.remote_track |
Inbound audio track from the agent (available after connect). |
Audio Constants
| Constant | Value | Description |
|---|---|---|
SAMPLE_RATE |
48000 |
Audio sample rate in Hz |
CHANNELS |
1 |
Number of channels (mono) |
FRAME_SIZE |
960 |
Samples per 20 ms frame |
Reconnection
A network change mid-call is recovered automatically, and the conversation survives it: the agent still knows who you are and does not replay its greeting.
The other SDKs run a two-phase ladder: an ICE restart first (which keeps the
transport itself alive), falling back to a resume redial once the connection
has failed. Python has only the second phase, and that is a hard aiortc
limit rather than a choice: RTCPeerConnection.createOffer() takes no options,
aioice fixes its ICE credentials at construction, and aiortc has no
disconnected state to act on — it goes straight from connected to failed.
So Python recovers by redialling with a resume token: a brand new peer connection, reattached server-side to the session it was already running. The transport is new; the conversation is not. In practice the difference is a moment of silence where the other SDKs would have had none.
Status goes connected → reconnecting → connected:
import streamcore
client = streamcore.Client(
streamcore.Config(
whip_endpoint="http://localhost:8080/whip",
reconnect_attempts=3,
reconnect_delay=2.0,
),
streamcore.EventHandler(
on_reconnect=lambda e: print(f"redial {e.attempt}/{e.max_attempts}: {e.outcome}"),
),
)
One outcome deserves handling rather than logging:
RECOVERED— reattached; history intact.RECOVERED_WITHOUT_HISTORY— the call works but the agent has forgotten everything. The server had already reaped the session, so the redial started a fresh conversation. Surface this to the user rather than letting them discover it by being asked their name again.FAILED— every attempt failed; status becomesdisconnected.
The window is the server's session_grace_ms (30s by default), measured from
when the connection dropped — not the ~25s ICE deadline the other SDKs work
against. Keep reconnect_attempts × reconnect_delay (doubling each retry)
inside it. Set reconnect_attempts=0 to disable and handle drops yourself.
Two caveats specific to this stack:
- Realtime (speech-to-speech) sessions cannot be resumed. Their history lives inside the provider, so the server issues no token and a redial starts a new conversation.
- A caller-supplied
user_trackis reused across the redial. The built-in track (thesend_pcmpath) is rebuilt automatically; if you pass your own track toconnect(), make sure it is still live, or reconnect yourself.
The wire-format helpers for ICE restart still ship in streamcore.icerestart
for callers driving another WebRTC stack:
from streamcore import whip_restart_ice, ice_fragment_from_sdp, apply_ice_fragment
Audio I/O
The SDK handles aiortc track management, av.AudioFrame construction, and
resampling internally. Callers only deal with raw PCM data as numpy int16
arrays:
# Send microphone audio (960 samples = 20 ms at 48 kHz)
await client.send_pcm(pcm_int16)
# Receive agent audio
audio = await client.recv_pcm()
If you need direct track access (e.g. for a custom aiortc pipeline), pass
your own AudioStreamTrack via client.connect(user_track=my_track).
Dependencies
| Package | Purpose |
|---|---|
aiortc |
WebRTC stack |
aiohttp |
HTTP client for WHIP signaling |
av |
Audio frame encoding/decoding |
numpy |
PCM audio buffers |
License
Apache-2.0
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file streamcore-0.1.4.tar.gz.
File metadata
- Download URL: streamcore-0.1.4.tar.gz
- Upload date:
- Size: 23.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c75c404d5410300bec084de1ecc29e3f5852a585d33b36375699ee677a842c09
|
|
| MD5 |
1e5f2df880004db74bf3c3e6ac103ae9
|
|
| BLAKE2b-256 |
92dd09ed9a40f758b053e3935046df80bab7653c9359b0127a0107b42f188dbe
|
Provenance
The following attestation bundles were made for streamcore-0.1.4.tar.gz:
Publisher:
publish.yml on streamcoreai/python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
streamcore-0.1.4.tar.gz -
Subject digest:
c75c404d5410300bec084de1ecc29e3f5852a585d33b36375699ee677a842c09 - Sigstore transparency entry: 2466638105
- Sigstore integration time:
-
Permalink:
streamcoreai/python-sdk@427de573cc2101098b85ec9c703221d14295c0f5 -
Branch / Tag:
refs/tags/v0.1.4 - Owner: https://github.com/streamcoreai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@427de573cc2101098b85ec9c703221d14295c0f5 -
Trigger Event:
release
-
Statement type:
File details
Details for the file streamcore-0.1.4-py3-none-any.whl.
File metadata
- Download URL: streamcore-0.1.4-py3-none-any.whl
- Upload date:
- Size: 22.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e8bfa81eb28b1178b798f2f609f99f48bf21e3ddde3ae53be44846ec614f69ba
|
|
| MD5 |
c4ff31a5660d57e25ece20a7ec5bf765
|
|
| BLAKE2b-256 |
e7497e0424ca72a9cb043a1f1621fa6e4f2bdc8443703ea33edf34b92355b8b6
|
Provenance
The following attestation bundles were made for streamcore-0.1.4-py3-none-any.whl:
Publisher:
publish.yml on streamcoreai/python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
streamcore-0.1.4-py3-none-any.whl -
Subject digest:
e8bfa81eb28b1178b798f2f609f99f48bf21e3ddde3ae53be44846ec614f69ba - Sigstore transparency entry: 2466638118
- Sigstore integration time:
-
Permalink:
streamcoreai/python-sdk@427de573cc2101098b85ec9c703221d14295c0f5 -
Branch / Tag:
refs/tags/v0.1.4 - Owner: https://github.com/streamcoreai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@427de573cc2101098b85ec9c703221d14295c0f5 -
Trigger Event:
release
-
Statement type: