livekit-plugins-duplexjev
Audio end-of-turn detection for LiveKit Agents, powered by DuplexJev. It listens to how the user speaks, not only what they say, and tells your agent whether the user is finished, still talking, just backchanneling ("mm-hmm") or asking you to wait. The same request can also return emotion, laughter / breathing / coughs / sighs and barge-in.
- Reads the audio, not the transcript. Every answer is one constrained token of a speech LLM: nothing is decoded, so the decision does not wait for STT.
- Fast. Ten decisions about one utterance take 92 ms on one GPU, against 1,978 ms for ASR → LLM with the same LLM.
- Accurate. On the CoDeTT turn-taking benchmark (English, zero-shot), DuplexJev-32B-Turn scores 70.0, against 51.4 for Smart Turn v3. See the paper.
- More than turns, same request. Ask for emotion, non-verbal sounds or your own multiple-choice questions.
- Chinese and English. CPU only on the agent side: the plugin calls an HTTP API.
Tested with livekit-agents 1.8.6. Maintained by Adventists.ai (AI降临派).
Install
pip install livekit-plugins-duplexjev
Use
from livekit.agents import AgentSession
from livekit.plugins import duplexjev, silero
session = AgentSession(
stt=..., llm=..., tts=...,
vad=silero.VAD.load(),
turn_handling={"turn_detection": duplexjev.TurnDetector(
api_url="http://localhost:8420", # your own gateway; omit for the free hosted trial API
extra_questions=["emotion", "sound"],
on_answers=lambda a: print({k: v["answer"] for k, v in a.items()}),
)},
)
Give it the agent's last message (new in 0.2.0). Turn-taking is clearly better when the model knows what the agent had just said. Keep a reference to the detector and attach it to the session once:
detector = duplexjev.TurnDetector(api_url="http://localhost:8420")
session = AgentSession(..., turn_handling={"turn_detection": detector})
detector.attach(session) # follows conversation_item_added; or detector.set_assistant_said(text)
It needs a gateway from duplexjev>=0.6. On LiveKit eot-bench (English, DuplexJev-32B-Para v1.2) this cuts false
cut-offs at a 300 ms budget from 23.5% to 19.7%.
AgentSession(turn_detection=duplexjev.TurnDetector(), ...) works too (LiveKit marks that argument as deprecated).
A complete agent is in examples/agent.py.
A VAD is required. LiveKit ignores a streaming turn detector when the session has no VAD. The plugin buffers the
user's audio, and after about 200 ms of VAD silence LiveKit asks it for a prediction: the last 8 s of the current
user turn go to DuplexJev, and P(finished) becomes LiveKit's end_of_turn_probability. Below unlikely_threshold
the session waits max_delay instead of min_delay before replying. The VAD's min_silence_duration must be at
least 0.25 s (Silero's default is 0.55 s).
STT is optional for the decision. If the session has an STT, LiveKit still waits for its transcript before it commits the turn, as with any turn detector, because the LLM needs the text.
Run the model yourself (recommended for real-time)
The default URL is our free trial API (rate limited to 30 requests per minute; the round trip from outside mainland China adds network latency). For a live agent, serve the model next to it (DuplexJev-4B-Para needs about 10 GB of GPU memory; for the best turn-taking use DuplexJev-32B-Para v1.2 on one 80 GB GPU):
pip install "vllm[audio]>=0.29" duplexjev-vllm "duplexjev[server]>=0.6"
vllm serve adventists-ai/DuplexJev-4B-Para --max-model-len 4096 --port 8000 &
duplexjev gateway --vllm http://localhost:8000/v1 --port 8420
Then pass api_url="http://localhost:8420" (or set DUPLEXJEV_API_URL).
Options
| argument | default | |
|---|---|---|
api_url |
hosted trial API | base URL of a duplexjev gateway (or DUPLEXJEV_API_URL) |
api_key |
None |
bearer key, if your gateway requires one (or DUPLEXJEV_API_KEY) |
lang |
"en" |
"en" or "zh": wording of the questions (the model hears both languages either way) |
unlikely_threshold |
0.5 |
below this P(finished), wait max_delay instead of min_delay |
backchannel_threshold |
None |
if set, also asks "What should the assistant do now?" and reports P(short acknowledgement) as LiveKit's backchannel probability |
extra_questions |
[] |
"emotion", "sound", "barge_in", or dicts {"id", "text", "options"} |
on_answers |
None |
callback with every answer dict; the latest is also on detector.last_answers |
window_s |
8.0 |
seconds of the current user turn sent with each request |
trim_silence |
True |
drop the silence before the user started talking (keeps 0.3 s) |
prediction_timeout |
4.0 hosted, 1.0 own gateway |
how long LiveKit waits for a prediction |
fallback_probability |
1.0 |
P(finished) reported if the gateway fails, so the conversation never blocks |
License
The plugin code is BSD-2-Clause. DuplexJev model weights are CC BY-NC 4.0 (non-commercial); see the model cards. For commercial use, contact jiejin@adventists.ai.
Metadata
Release files for livekit-plugins-duplexjev 0.2.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 | |
|---|---|---|---|
| livekit_plugins_duplexjev-0.2.0.tar.gz | 16.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| livekit_plugins_duplexjev-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.3 kB
Release files / livekit_plugins_duplexjev-0.2.0.tar.gz
| Download URL | livekit_plugins_duplexjev-0.2.0.tar.gz |
|---|---|
| Size | 16.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0405f780386ac0472ae8a5a57fbea1959e8a82474a9c1ac6e9ed3fab06f19c7d
|
|
BLAKE2b-256 checksum How to use checksums |
bf3a095b4844d8f00b52a55335cbf4e41ef03bc0170e78b0143b3ec5559d550c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.12
|
Release files / livekit_plugins_duplexjev-0.2.0-py3-none-any.whl
| Download URL | livekit_plugins_duplexjev-0.2.0-py3-none-any.whl |
|---|---|
| Size | 13.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
827b03ccf075ca15acc5debfcc427f9bc76214d11c9d8cfb577ba1a24f63ed9a
|
|
BLAKE2b-256 checksum How to use checksums |
516d05981d9332b12a5a9e39fb7c66e38a069f97a950146304a1855973821416
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.12
|