tuner-stt-observer
STT observability for Tuner. Hooks into your existing STT provider's native event system to capture user transcript, turn timing, and STT latency — your WebSocket/streaming loop is completely untouched. This is a pure observer: it never intercepts audio or makes decisions that affect the call (e.g. barge-in/VAD) — that belongs in your own application.
Supported adapters
| Provider | Adapter | Status |
|---|---|---|
| Deepgram | DeepgramAdapter |
✅ Supported |
| Speechmatics | SpeechmaticsAdapter |
✅ Supported |
| AssemblyAI | — | SOON |
Need a provider that isn't listed? Extend BaseSTTAdapter — see
Custom providers below.
Installation
pip install tuner-core tuner-stt-observer
Each provider adapter lives behind an optional extra so you only pull in the SDKs you actually use:
pip install "tuner-stt-observer[deepgram]" # DeepgramAdapter
pip install "tuner-stt-observer[speechmatics]" # SpeechmaticsAdapter
Usage — Deepgram
DeepgramAdapter registers on Open, Transcript, and UtteranceEnd.
speech_final transcript fragments are buffered internally and flushed as a
single joined utterance when UtteranceEnd fires, so you get one complete
user turn per utterance instead of one per fragment.
from tuner_core import TunerConfig, TunerSession
from tuner_stt_observer import DeepgramAdapter
dg_connection = deepgram_client.listen.asyncwebsocket.v("1")
session = TunerSession(config=TunerConfig.from_env(), call_id=call_id)
adapter = DeepgramAdapter(connection=dg_connection)
adapter.on_utterance = lambda text: transcript_queue.put_nowait(text)
session.attach(adapter)
await dg_connection.start(options) # requires utterance_end_ms + vad_events=True
# In the audio receive loop, send to Deepgram as you normally would —
# the adapter only listens on the connection's events, it never touches audio:
async for chunk in websocket.iter_bytes():
await dg_connection.send(chunk)
await session.flush() # at call end
LiveOptions must set utterance_end_ms (e.g. "1000") and
vad_events=True — without them UtteranceEnd never fires and the fragment
buffer never flushes.
Need barge-in detection? That's call-affecting logic, not observation — build
it in your own application (e.g. a local VAD in your audio loop, or
Deepgram's own SpeechStarted event), not in this adapter.
Usage — Speechmatics
SpeechmaticsAdapter registers on ADD_TRANSCRIPT and END_OF_UTTERANCE.
Speechmatics-rt stacks event handlers rather than replacing them, so this
coexists safely with any handlers you register yourself (e.g. for barge-in
via ADD_PARTIAL_TRANSCRIPT).
from tuner_stt_observer import SpeechmaticsAdapter
adapter = SpeechmaticsAdapter(client=stt_client)
session.attach(adapter)
# Your own handlers still work — Speechmatics-rt stacks, not replaces:
stt_client.on(SMEvent.ADD_PARTIAL_TRANSCRIPT)(my_barge_in_handler)
Speechmatics reports times already relative to session start, so no timing
anchor is needed (unlike Deepgram's stream-relative start field).
Custom providers
For providers other than Deepgram/Speechmatics, extend BaseSTTAdapter from
tuner_stt_observer — its docstring documents the full contract
(_record_user_turn(), _record_stt_usage(), timestamp conversion) with a
worked example.
What gets captured automatically
| Signal | Deepgram | Speechmatics |
|---|---|---|
| Complete user utterance text | ✓ (fragments joined on UtteranceEnd) |
✓ (fragments joined on END_OF_UTTERANCE) |
| Turn start timestamp | ✓ | ✓ |
| Turn duration | ✓ | ✓ |
| STT latency | ✓ (last fragment's audio end → Transcript arrival) |
✓ (END_OF_UTTERANCE → flush) |
| STT audio usage | ✓ | ✓ |
Development
uv sync
uv run pytest
uv run ruff check .
uv run mypy
Metadata
Release files for tuner-stt-observer 0.1.1
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 |
|---|---|---|---|---|
| tuner_stt_observer-0.1.1.tar.gz | 15.2 kB | Without provenance | Details for tuner_stt_observer-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| tuner_stt_observer-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for tuner_stt_observer-0.1.1-py3-none-any.whl |
Total release size: 27.6 kB
Release files / tuner_stt_observer-0.1.1.tar.gz
| Download URL | tuner_stt_observer-0.1.1.tar.gz |
|---|---|
| Size | 15.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
32f49d055d2234a718e60004682938bec888ddd90d7fbb7ef96c95076439e27c
|
|
BLAKE2b-256 checksum How to use checksums |
e7e23f8065c71a5c5f4185563eca30cdde1b20e58e73aa321e51dfde1e6416d4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.7
|
Release files / tuner_stt_observer-0.1.1-py3-none-any.whl
| Download URL | tuner_stt_observer-0.1.1-py3-none-any.whl |
|---|---|
| Size | 12.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dc72dc7509f9f01cdabe7dfbc5b95c7c4264cbfaf3515d15b130df6afd24a243
|
|
BLAKE2b-256 checksum How to use checksums |
997d207a126272ee484518f281554b2b62a519c4608298977596470077b69d97
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.7
|
Security for tuner-stt-observer 0.1.1
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.