urun-sense (Python SDK)
Python SDK for the Sense realtime decision protocol (docs/protocol.md). Dependency:
aiohttp only.
uv add urun-sense # or: pip install urun-sense
Quick start
import asyncio
from sense import Sense
app = Sense(api_key="...") # or SENSE_API_KEY env
finished = app.noul("Did the speaker just finish their turn?", id="finished")
intent = app.choice("What does the user want?", ["support", "sales", "other"], id="intent")
urgency = app.score("How urgent is this?", ["low", "medium", "high"], id="urgency")
s = app.session()
async def main():
async for wave in s.run(audio=mic_stream): # async iterator / callable / iterable
print(
wave.wave_id,
wave.t_ms,
wave.result("finished").value, # bool
wave.result("intent").top, # argmax option
wave.result("urgency").p_at_least("medium"),
)
Flagship demo
The wheel ships the cheese classifier example (sense/examples/cheese_demo.py): it streams your
microphone, classifies what you say, and renders a live ANSI display. Requires Python 3.11+.
pip install "urun-sense[demo]" # adds rich + sounddevice (non-Linux)
SENSE_API_KEY=<key> python -m sense.examples.cheese_demo
PowerShell: $env:SENSE_API_KEY = "<key>" first, then run the python line bare.
Linux drives PipeWire directly (pw-record/pw-dump/wpctl); macOS and Windows need microphone
permission for your terminal. --mic-only runs without a key.
Surface
- Questions — TEXT-FIRST:
app.noul(question, *, id=None, options=None),app.choice(question, options, *, id=None),app.score(question, levels, *, id=None). The natural-language question is the first argument; the id is optional and auto-slugged (lowercase, non-alnum →_, unique) when omitted. Low-level alternative:Session.add_question(qid, kind, ...). - Predicates — questions and accessors compose into
Predicatetrees:finished & (intent == "support"),finished.p >= 0.8,intent.p("support") >= 0.7,urgency.value >= 1.5,urgency.p_at_least("medium") >= 0.6,~q,Predicate.all_of/any_of,Predicate.between(subject, enter, exit)(stateful hysteresis latch). Score comparison rule: on the bare question subject,q == x/q != xcompare the TOP LEVEL NAME (argmax) whileq > x/>=/</<=compare the EXPECTED ORDINAL.q == xandq.top == xare the same argmax comparison on a choice question. - Context — passed to
when/onWavehandlers (and usable standalone for offline replay):ctx.latest(source=None),ctx.waves(coalesce=False)(bounded to the last 512 waves),ctx.when(pred, handler, source=None) -> handle(edge-triggered),ctx.onWave(handler, source=None),ctx.get(q, source=None),ctx.run(key, fn(ctx), on_cancel=..., replace=False),ctx.cancel(key, barge_in=True),ctx.signal(the current run's cancellation event, TS AbortSignal parity),ctx.session. - Inputs —
s.audio.push(pcm_bytes, source="default")(PCM16 24 kHz mono),s.video.push(jpeg_bytes)(≤ 256 KB/frame),s.text.push(text, mode="append"|"replace"|"event"). A text push alone opens and drives a turn — a text-only session works with zero audio. - Session —
s.latest(q, source=None),s.when(..., source=None),s.onWave(handler, coalesce=False, source=None),s.waves(coalesce=False)(live bounded subscription — see below),s.stats(waves/decisions/errors/protocol_errors),s.close()(cancels every tracked ctx task, firingon_cancelhooks, before tearing down the wire).
Wire behavior (protocol.md §1–§7)
wss://.../v1/realtime?model=...+ Bearer auth;session.updatecarries the question envelope ({questions, hz}) as a JSON string.- One commit +
response.create(max_output_tokens=250_000)opens a cadence turn; the empty commit before audio arrives is benign and ignored (empty_buffer). - Decision lines are assembled into waves client-side (§6.3: repeat-qid flush,
turn-end flush,
sense.waveboundary preferred when PR-3 lands). - An incomplete wave (missing/unknown/null answer) is a
ProtocolError: logged loudly and counted instats.protocol_errors— never silently treated as false/hold. 503/429back off (5 s→30 s cap,Retry-Afterhonored),401/404fatal,session_gone(close 1011) reconnects fresh,buffer_fullcommits.ctx.cancel(key)/ barge-in sendsresponse.cancel;ctx.cancel(key, barge_in=False)cancels background work without preempting the turn (used byrun(..., replace=True)supersede).
Documented degradations (awaiting serving PRs)
- Audio sources (PR-2): non-default sources are queued client-side and pushed
round-robin over the single unlabeled audio lane; every wave is labeled with
the input source it was drained for, and
when/onWave/latestacceptsource=to evaluate against that source's stream. Sources are a client-side concept until the server PR lands — nosourcefield is sent on the wire. - Session.waves() vs run():
run()is the primary consumption path;s.waves()is a live bounded subscription (queue of 64, drop-oldest) that never replays history and never competes withrun()delivery.ctx.waves()keeps the last 512 waves for offline replay. - Text replace/event: the conversation transcript is append-only on today's
wire;
replace/eventare client-side bookkeeping with documented degradation.
Tests
uv run --project . pytest tests ../conformance/python_runner.py
Metadata
Release files for urun-sense 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| urun_sense-0.1.0.tar.gz | 598.2 kB | Without provenance | Details for urun_sense-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| urun_sense-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for urun_sense-0.1.0-py3-none-any.whl |
Total release size: 1.1 MB
Release files / urun_sense-0.1.0.tar.gz
| Download URL | urun_sense-0.1.0.tar.gz |
|---|---|
| Size | 598.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c2117bdd28eee9dd2ef9510a421c0632dfd304a1e8aeb3b55f2b0a484a6562d6
|
|
BLAKE2b-256 checksum How to use checksums |
aeb45afac33bf98501e873ff58c2a235411b23ac20c686918fed4342dd31ba11
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|
Release files / urun_sense-0.1.0-py3-none-any.whl
| Download URL | urun_sense-0.1.0-py3-none-any.whl |
|---|---|
| Size | 490.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
40a40569a4656b39f7eaf5419446e6e8e453b9109ce41c3813e63fbc76d69569
|
|
BLAKE2b-256 checksum How to use checksums |
cf9bbdad60e12eacf2400a5c3f9c5133e0ca0cffbf3dd2066cc439754d83bcce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|
Security for urun-sense 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.