Skip to main content

smart-turn-livekit

LiveKit Agents plugin for Smart Turn end-of-turn detection.

PyPI Python License

A LiveKit Agents plugin that runs Smart Turn models to detect when a user has finished speaking based on audio prosody. It handles the mel front-end, the ONNX session, resampling, per-model language gating, and the threading, so any checkpoint plugs into an AgentSession in one line.

Works with upstream's smart-turn-v3, a fine-tune, or your own ONNX file. Fully local — CPU-only ONNX inference, no PyTorch, no inference service, and no network call on the hot path. See Offline and air-gapped.

pip install 'smart-turn-livekit[livekit]'

Quickstart

from livekit.agents import AgentSession
from livekit.plugins import silero
from smart_turn_livekit import SmartTurnDetector

session = AgentSession(
    vad=silero.VAD.load(min_silence_duration=0.25),   # must be >= 0.25
    turn_handling={
        "turn_detection": SmartTurnDetector(),         # smart-turn-v3
        "endpointing": {"min_delay": 0.3, "max_delay": 2.5},
    },
)

Weights download from Hugging Face on first use and are cached. Nothing to configure, nothing bundled in the wheel.

Inspect each decision with on_prediction:

SmartTurnDetector(
    on_prediction=lambda p: print(
        f"end of turn {p.probability:.1%} -> "
        f"{'complete' if p.complete else 'incomplete'} ({p.inference_s * 1000:.0f} ms)"
    )
)
end of turn 1.2%  -> incomplete (55 ms)
end of turn 94.5% -> complete   (58 ms)

Models

SmartTurnDetector()                                  # smart-turn-v3, default
SmartTurnDetector(model="smart-turn-tamil-tiny")     # a fine-tune
SmartTurnDetector(model_path="/opt/my-model.onnx")   # your own
key languages size accuracy
smart-turn-v3 (default) multi 8.7 MB 92.63% on upstream's benchmark
smart-turn-v3-fp32 multi 32 MB unquantised reference graph

Language-specific fine-tunes

key language size accuracy
smart-turn-tamil-tiny ta 8.7 MB 83.71% on real Tamil telephone speech
smart-turn-tamil-base ta 21 MB 86.13% on the same

MODEL_REGISTRY is the only place that knows one model from another, so adding a checkpoint is a dict entry. Anything not listed still runs via model_path=.

Latency

threads p50 p95
1 84.5 ms 91.9 ms
2 53.1 ms 61.7 ms

smart-turn-v3, batch 1, inference only, idle Intel i5-12450H. Measure on your own box — python -m smart_turn_livekit.bench. This number lands directly on the gap between the user finishing and the agent replying, so budget it against min_delay. The bench refuses to print a table without warning you when machine load is high, because a benchmark taken under load once produced a wrong shipping recommendation here.

Gotchas

  • min_silence_duration >= 0.25. LiveKit will not request a prediction below MIN_SILENCE_DURATION_MS + 50 ms and raises at session start if the VAD is faster. A floor, not a ceiling — it fails loudly.
  • The language must be one the model claims, or unknown. supports_language() returns False otherwise, using the languages the model was benchmarked on. This one fails silently — the detector is skipped and the agent falls back to fixed timing. Override with languages=(...).

Threshold

Under LiveKit the threshold picks how long to wait, not whether to end the turn: below it the session takes max_delay instead of min_delay. So a false complete costs a shorter pause rather than an interruption — a softer knob than the same number under Pipecat.

SmartTurnDetector(threshold=0.72)                       # one value
SmartTurnDetector(threshold={"en": 0.6, "ta": 0.85})    # per language
SmartTurnDetector(operating_point="polite")             # if the model ships one

0.5 is the default threshold. Some language-specific fine-tunes offer predefined operating points (like "polite" or "balanced") that are calibrated on held-out evaluation sets to provide a different tradeoff between interruption rate and latency.

A dict is an override table, resolved on exact code, then base language, then the operating point — so {"en-US": 0.6} answers for en-US only, while {"en": 0.6} answers for en, en-US and en-GB alike.

Outside LiveKit

The detector is a thin adapter over a plain class you can call directly:

from smart_turn_livekit import SmartTurn

st = SmartTurn()                  # smart-turn-v3
st.probability(wave_16k)          # P(speaker has finished), 0.0 .. 1.0
st.is_complete(wave_16k)          # bool, against st.threshold

16 kHz mono float, last 8 seconds only. Other sample rates need soxr.

Pipecat needs no adapter — its built-in analyzer takes any Smart Turn ONNX, and this package can still resolve the file for it:

from smart_turn_livekit import resolve_model
from pipecat.audio.turn.smart_turn.local_smart_turn_v3 import LocalSmartTurnAnalyzerV3

analyzer = LocalSmartTurnAnalyzerV3(smart_turn_model_path=str(resolve_model()))

Offline and air-gapped

Inference is entirely local: ONNX Runtime on CPU, in your own process. There is no inference service, no API key, and no network call on the prediction path.

The one network access is fetching the weights, which happens once on first use and is then cached. Any of these removes it:

SmartTurnDetector(model_path="/opt/models/smart-turn.onnx")   # ship the file yourself
SmartTurnDetector(local_only=True)                            # cached weights only, fail otherwise

or SMART_TURN_MODEL=/path/to.onnx, or HF_HUB_OFFLINE=1. Pre-cache the weights in your image build and the running agent never reaches the network — which is what a strict data-residency or air-gapped deployment needs.

An explicit model_path claims no language until you pass languages=(...).

How it works

LiveKit exposes two turn-detector protocols — one reads text, one reads audio — as plain typing.Protocol classes with no base class to import. This package implements the audio one structurally, so it's accepted by duck typing alone; there is nothing to register.

One ONNX code path runs every registered model, because every Smart Turn checkpoint shares the same signature: input_features (batch, 80, 800) in, a logits output that is already a sigmoid out. Applying a second sigmoid to that output gives confident, plausible, wrong answers, so SmartTurn.probability checks the output is in [0, 1] and raises rather than return a silently-broken score.

Inference runs in a single-thread executor so a ~10–30 ms ONNX call never stalls the event loop. Construction resolves LiveKit's event class and runs one dummy inference up front (warmup=True, default) — skipping that would put a ~1.7 s import penalty on the first prediction of the first call, a cost LiveKit's own CLI normally hides by paying it earlier.

The mel front-end matches WhisperFeatureExtractor(chunk_length=8) and is checked against Pipecat's own extractor to <1e-3.

Tests

pip install -e '.[dev,livekit]'
pytest                                  # 26 tests; 3 skip without the research corpus
python -m smart_turn_livekit.bench

Checks features against Pipecat's extractor, the LiveKit Protocols by isinstance, and that every registered model loads and returns a probability — including upstream's, which we do not control.

Limitations

  • Not a VAD. It answers "did they finish?", not "is anyone talking?" You still need Silero upstream.
  • Audio-only, no language token. Nothing stops any of these models running on any language — supports_language() reflects what was measured, not what is possible.
  • Written against livekit-agents 1.7.0. TurnDetectionEvent and both turn detector Protocols are private (_-prefixed) there, so treat a minor-version bump as something to re-check. The test suite fails loudly if the shape moves.

Author

Built and maintained by Santhosh (@santhosh-005).

Licence

This package is BSD-2-Clause. It bundles Whisper's mel filterbank (MIT, OpenAI).

The weights are downloaded, not distributed here, and licensed separately. smart-turn-v3 is BSD-2-Clause by Daily.

Metadata

Release files for smart-turn-livekit 0.4.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 smart-turn-livekit 0.4.0
File Size Uploaded
smart_turn_livekit-0.4.0.tar.gz 29.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for smart-turn-livekit 0.4.0
File Interpreter ABI Platform
smart_turn_livekit-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 55.8 kB

Release files / smart_turn_livekit-0.4.0.tar.gz

Download URL smart_turn_livekit-0.4.0.tar.gz
Size 29.0 kB
Tags Source
SHA-256 checksum
How to use checksums
9f9b314f40c64c0071c6d0d25cad62d8dec248ca3931b755adf1704bbcf2dcc5
BLAKE2b-256 checksum
How to use checksums
ffaf05cff3a3fc4de0248b5ace1be26729adfc8613e56359595d6284b178fe9e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / smart_turn_livekit-0.4.0-py3-none-any.whl

Download URL smart_turn_livekit-0.4.0-py3-none-any.whl
Size 26.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
450fb858149955b6193c20d219e74145cc2b20d16001fc54ef93436d1dc1d016
BLAKE2b-256 checksum
How to use checksums
a07fdd6bd496399f71e3bf15dbb6b8c89757ca793efee9e5636761b2188b8785
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

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