Skip to main content

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 Predicate trees: 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 != x compare the TOP LEVEL NAME (argmax) while q > x / >= / < / <= compare the EXPECTED ORDINAL. q == x and q.top == x are the same argmax comparison on a choice question.
  • Context — passed to when/onWave handlers (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, firing on_cancel hooks, before tearing down the wire).

Wire behavior (protocol.md §1–§7)

  • wss://.../v1/realtime?model=... + Bearer auth; session.update carries 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.wave boundary preferred when PR-3 lands).
  • An incomplete wave (missing/unknown/null answer) is a ProtocolError: logged loudly and counted in stats.protocol_errors — never silently treated as false/hold.
  • 503/429 back off (5 s→30 s cap, Retry-After honored), 401/404 fatal, session_gone (close 1011) reconnects fresh, buffer_full commits.
  • ctx.cancel(key) / barge-in sends response.cancel; ctx.cancel(key, barge_in=False) cancels background work without preempting the turn (used by run(..., 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/latest accept source= to evaluate against that source's stream. Sources are a client-side concept until the server PR lands — no source field 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 with run() 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/event are 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)

Source distribution for urun-sense 0.1.0
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)

Table of built distributions (wheels) for urun-sense 0.1.0
File Interpreter ABI Platform Actions
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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page