Signed-off-by: lishunyang12 <125541396+lishunyang12@users.noreply.github.com> Signed-off-by: Yueqian Lin <linyueqian@outlook.com> Signed-off-by: lishunyang <lishunyang12@163.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com> Co-authored-by: Yueqian Lin <linyueqian@outlook.com>
193 lines
9.1 KiB
Markdown
193 lines
9.1 KiB
Markdown
# JoyAI-VL-Interaction
|
|
|
|
> Real-time streaming video-language interaction (proactive speak / silence / delegate)
|
|
|
|
## Summary
|
|
|
|
- Vendor: JD (Joy Future Academy)
|
|
- Model: [`jdopensource/JoyAI-VL-Interaction-Preview`](https://huggingface.co/jdopensource/JoyAI-VL-Interaction-Preview) (8B, vanilla Qwen3-VL weights)
|
|
- Task: Per-tick proactive interaction over a live video stream — the model decides
|
|
on its own each second to speak, stay silent, or delegate a hard question
|
|
- Mode: Online serving — an OpenAI-compatible interaction orchestrator in front of
|
|
a plain `vllm serve` backend
|
|
- Maintainer: Community
|
|
|
|
## When to use this recipe
|
|
|
|
Use this to stand up the streaming-interaction serving layer (`vllm_omni/experimental/fullduplex/`).
|
|
The model is served unchanged by `vllm serve`; this layer adds session state, 3-tier
|
|
summary memory, the per-tick decision, and pluggable ASR / TTS / delegation. For the
|
|
framework internals and how to add another full-duplex model, see
|
|
[`vllm_omni/experimental/fullduplex/README.md`](../../vllm_omni/experimental/fullduplex/README.md).
|
|
|
|
## Environment
|
|
|
|
- OS: Linux
|
|
- Python: 3.10+
|
|
- Hardware: 1x GPU. The default `T_s=100` frame window + `--max-model-len 131072` wants
|
|
≈48GB+; for ~24GB, lower `--chunk-frames`, `--max-model-len`, and the image limit together
|
|
- vLLM / vLLM-Omni: versions from your current checkout
|
|
|
|
## Start server
|
|
|
|
From repository root:
|
|
|
|
```bash
|
|
# 1. Serve the model (plain `vllm serve`, NOT --omni; it is vanilla Qwen3-VL).
|
|
# The image limit must cover the short-term frame window (chunk_frames, default 100 = T_s);
|
|
# prefix caching keeps the accumulating window cheap. Lower both for smaller GPUs.
|
|
vllm serve jdopensource/JoyAI-VL-Interaction-Preview \
|
|
--served-model-name JoyAI-VL-Interaction-Preview --port 8061 \
|
|
--max-model-len 131072 --enable-prefix-caching \
|
|
--limit-mm-per-prompt '{"image":256,"video":1}'
|
|
|
|
# 2. Interaction orchestrator (OpenAI-compatible, :8070)
|
|
python -m vllm_omni.experimental.fullduplex.joyvl.serving.server --port 8070 \
|
|
--main-backend-url http://127.0.0.1:8061/v1 --main-model JoyAI-VL-Interaction-Preview
|
|
```
|
|
|
|
Optional one-shot launch (model + orchestrator + JD webui + ASR/TTS/background,
|
|
all env-configurable). The JD webui frontend is external — set `WEBUI_DIR` to point at it:
|
|
|
|
```bash
|
|
bash examples/online_serving/joyvl_interaction/scripts/start_all.sh
|
|
```
|
|
|
|
## Using the model
|
|
|
|
The orchestrator is OpenAI-compatible: send **one user turn per video frame** (~1 fps) to
|
|
`/v1/chat/completions` with an `x-session-id` header, and attach an optional **standing
|
|
instruction** as a text part. Each reply carries an `interaction` block — `action` is
|
|
`silence` / `response` / `delegate` and `text` is what to say:
|
|
|
|
```bash
|
|
curl -s http://127.0.0.1:8070/v1/chat/completions \
|
|
-H 'x-session-id: s1' -H 'content-type: application/json' -d '{
|
|
"messages": [{"role": "user", "content": [
|
|
{"type": "text", "text": "Alert me if a fire breaks out"},
|
|
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}}
|
|
]}]}' | jq .interaction
|
|
```
|
|
|
|
Send the standing instruction once (it persists for the session); subsequent turns send
|
|
just the frame. Ready-made headless client: `cli/run_cli_demo.py`. Reset a session with
|
|
`POST /reset {"session_id": "..."}`.
|
|
|
|
**What to ask it** — give a standing task and let it act on its own each second:
|
|
|
|
- **Proactive alerting** — "tell me when someone enters", "alert me if a fire breaks out"
|
|
- **Streaming Q&A** — ask about what's on screen; it answers once it has the evidence
|
|
- **Time & memory** — "how many people have walked past?", "what did you see earlier?"
|
|
(the 3-tier summary memory lets it recall beyond the live frame window)
|
|
- **Read & translate** — "translate the on-screen text as it appears"
|
|
- **Delegate hard questions** — handed to a background brain (see below)
|
|
- **Live commentary** — `--persona talkative` for continuous, danmaku-style narration
|
|
|
|
**Personas** (`--persona`): `default` (speak on meaningful events or to answer), `silent`
|
|
(answer only when asked, never delegate), `talkative` (proactively narrate).
|
|
|
|
**Tuning:** `--chunk-frames` (short-term window `T_s`), `--response-dedup-threshold`
|
|
(lower drops more near-duplicate narration), and `--no-memory` to disable the 3-tier
|
|
summaries. The model stays silent until the first instruction arrives
|
|
(`force_silence_before_query`, on by default).
|
|
|
|
## Delegation (background brain)
|
|
|
|
When the model judges a question too hard, it emits `</delegation> <question>` and the
|
|
orchestrator hands it to a **background brain** — any OpenAI-compatible endpoint you
|
|
self-host. Enable it by pointing the orchestrator at one:
|
|
|
|
```bash
|
|
python -m vllm_omni.experimental.fullduplex.joyvl.serving.server --port 8070 \
|
|
--main-backend-url http://127.0.0.1:8061/v1 --main-model JoyAI-VL-Interaction-Preview \
|
|
--delegation-backend-url <brain-endpoint>/v1 \
|
|
--delegation-model <brain-model> --delegation-kind chat
|
|
```
|
|
|
|
`--delegation-kind` picks the bridge:
|
|
|
|
- `chat` — a stronger text/VL model answers (`/chat/completions`)
|
|
- `image` — a text-to-image model generates a picture (`/images/generations`, e.g. Qwen-Image)
|
|
- `edit` — an image-edit model restyles the current frame (e.g. Qwen-Image-Edit)
|
|
- `router` — dispatch each request to chat / image / edit by inspecting it (set
|
|
`--delegation-image-url` / `--delegation-edit-url` for the latter two)
|
|
- `stub` — canned answers for tests/demos only (no backend needed)
|
|
|
|
`chat`/`image`/`edit`/`router` each need a backend URL — **without one, delegation stays
|
|
off** (the model's delegate note is still spoken, but nothing is folded back). The brain is
|
|
**bring-your-own**: a larger vLLM you serve, or any OpenAI-compatible API (e.g.
|
|
`--delegation-backend-url https://api.anthropic.com/v1/ --delegation-model claude-...
|
|
--delegation-api-key …`). The reference deployment instead drives the `codex` CLI as the
|
|
brain via a separate background-agent service; that agent runs with its own credentials
|
|
and bypasses its sandbox, so it is **not bundled here** — self-host a plain
|
|
OpenAI-compatible endpoint instead.
|
|
|
|
## Host the WebUI demo
|
|
|
|
For the full browser experience — live webcam / RTSP input, voice (ASR/TTS), and the
|
|
per-tick decision stream — run JD's official WebUI in front of the orchestrator. Clone the
|
|
model repo and start its WebUI pointed at the orchestrator (`:8070`):
|
|
|
|
```bash
|
|
git clone https://github.com/jd-opensource/JoyAI-VL-Interaction.git
|
|
cd JoyAI-VL-Interaction/services/webui
|
|
uv venv && uv pip install -e .
|
|
bash scripts/start_server.sh --api-base http://127.0.0.1:8070/v1
|
|
```
|
|
|
|
Open the printed HTTPS URL, allow the camera (or enter an RTSP URL), and give a standing
|
|
instruction. `examples/online_serving/joyvl_interaction/scripts/start_all.sh` can launch
|
|
the model + orchestrator + WebUI together — point `WEBUI_DIR` at your clone.
|
|
|
|
## Verification
|
|
|
|
```bash
|
|
# headless: stream a clip and print the per-tick decision timeline
|
|
# (the CLI reads video frames via OpenCV: `uv pip install opencv-python`)
|
|
python examples/online_serving/joyvl_interaction/cli/run_cli_demo.py \
|
|
path/to/video.mp4 --query "Alert me if a fire breaks out"
|
|
|
|
pytest tests/fullduplex # framework + JoyVL unit tests
|
|
```
|
|
|
|
## Testing with an RTSP stream (optional)
|
|
|
|
RTSP is a **webui-side input** — the browser pulls the stream and feeds frames to the
|
|
orchestrator over the normal API; no serving-layer code is involved. To simulate an RTSP
|
|
camera from a local video file (no physical IP camera needed), use the helper scripts in
|
|
[`examples/online_serving/joyvl_interaction/rtsp/`](../../examples/online_serving/joyvl_interaction/rtsp/),
|
|
which wrap [MediaMTX](https://github.com/bluenviron/mediamtx/releases) + `ffmpeg`:
|
|
|
|
```bash
|
|
cd examples/online_serving/joyvl_interaction/rtsp
|
|
|
|
# 1. Local RTSP server (MediaMTX, listens on :8554)
|
|
bash ./mediamtx.sh
|
|
|
|
# 2. Push a local video file as an RTSP stream (another terminal)
|
|
bash ./rtsp.sh ./videos/example.mp4 rtsp://127.0.0.1:8554/fire1
|
|
|
|
# 3. In the WebUI RTSP box, enter: rtsp://127.0.0.1:8554/fire1
|
|
# (replace 127.0.0.1 with the MediaMTX host IP if the webui runs on another machine)
|
|
```
|
|
|
|
See the directory's `README.md` for streaming a whole video folder (`rtsp_all.sh`) and
|
|
the audio-track caveat.
|
|
|
|
## Notes
|
|
|
|
- `--omni` is **not** used: the model is standard Qwen3-VL, so stock `vllm serve`
|
|
runs the forward pass; this recipe only adds the interaction/serving layer.
|
|
- On a host without `nvcc` / `ninja`, `vllm serve` of the 8B can crash engine-core in the
|
|
FlashInfer sampler JIT (`FileNotFoundError: 'ninja'`) during `profile_run`. Set
|
|
`VLLM_USE_FLASHINFER_SAMPLER=0` (or install `ninja`) to work around it.
|
|
- `force_silence_before_query` is on by default — the model stays silent until an
|
|
instruction arrives; give a standing task (e.g. "translate the on-screen text")
|
|
to arm proactive output.
|
|
- Speech is external and pluggable: point `ASR_URL` / `TTS_URL` at the bridges in
|
|
`examples/online_serving/joyvl_interaction/bridges/` or any compatible service.
|
|
- The decision prompts, sampling, and 3-tier summary memory (`T_s=100`, mid→long every
|
|
5 chunks, `key_frames=0` = summarize all chunk frames) are aligned to the JoyVL
|
|
reference adapter so per-tick behavior matches the released model; the framework only
|
|
supplies the serving structure.
|