Simple VLM example#
The simple VLM example is the smallest complete voice-and-vision application in
the repository. It answers spoken or typed questions about each participant’s
latest camera frame and streams the answer to both Pocket TTS and the
vlm.response data topic. Refer to the quickstart to run the sample. This reference owns the
sample’s design and operational details.
Composition#
The orchestrator starts DeviceIOHub and the worker. Passing --capture also
starts passive session capture; capture is disabled by default. The
yaml/models.json profile configures client adapters and shared endpoints for
Parakeet STT, Cosmos3 Nano, and Pocket TTS. Start those endpoints together with
the shared model-server stack.
VoiceAgent owns application readiness, hub transport, voice gating, TTS, signals,
and cleanup. It publishes accepted speech and typed text as a participant-scoped
UserQuery. SimpleVlmAgent selects the participant’s current image with
CurrentFrameTool, passes its opaque reference to
StreamingImageQueryTool, and publishes response chunks to voice output.
Camera bytes remain on the hub path, image locations are redacted from VLM
telemetry, and the sample has no MCP path.
A newer participant turn cancels the superseded vision request and interrupts its voice response. Participant departure releases the sample agent’s cached frames and tasks. This is the reference composition for a single foreground streaming image query.
Source map#
The worker package is under
agent-samples/simple-vlm-example/worker/simple_vlm_example_worker/:
File |
Responsibility |
|---|---|
|
Parses launcher arguments and starts the worker |
|
Composes |
|
Owns participant-scoped vision turns, cancellation, and cleanup |
|
Resolves worker, model, voice-gate, and prompt settings |
|
Defines the default VLM instruction |
Readiness and warmup#
Before announcing readiness, the worker performs a streaming VLM request with a 1280×720 JPEG and consumes the response. This exercises the production multimodal path so the first user query does not pay its initialization cost. The warmup retries failed inference requests until one succeeds. It does not call a health endpoint first. STT and TTS do not gate worker startup; start the shared model-server stack before the sample so speech requests can succeed.
Configuration#
Run and edit the sample from agent-samples/simple-vlm-example/. The
orchestrator always passes yaml/device_io_hub.yaml to DeviceIOHub and
yaml/simple_vlm_example_worker.yaml to the worker. The worker resolves its
models and voice-gate files relative to the worker YAML, so the checked-in
layout works without command-line configuration arguments.
File |
Owns |
|---|---|
|
Frame freshness and wait limits, VAD, idle timeout, and optional prompt overrides |
|
Wake phrases, listening chime, and follow-up window |
|
Model adapters and shared endpoints |
|
LiveKit room and ports, web and token servers, and network behavior |
|
Opt-in media-hub capture, NVENC output, caption layout, and retention |
|
Default VLM instruction |
Edit the owning file, preserve the field’s YAML type, and restart
simple_vlm_example; configuration is loaded only at process startup. For
example, lower silero_threshold in the worker YAML if quieter speech is being
missed, or change magic_phrases in the voice-gate YAML to choose the required
wake phrases. Relative paths in the worker YAML are resolved from yaml/, not
from the shell’s current directory.
Changing an entry in models.json changes only the client adapter or endpoint
that this sample uses. It does not reconfigure or restart the shared server.
For a checkpoint, port, GPU, or model-runtime change, refer to
Customizing model servers, update the shared stack, stop that
persistent stack, and start it again before restarting this sample.
Refer to the generated configuration reference for exact fields, checked-in values, and adjacent YAML comments.
Opt-in session capture#
Capture is disabled by default. Run uv run simple_vlm_example --capture to
start device_io_capture immediately after DeviceIOHub and record normalized
hub media without joining the LiveKit room. Each participant connection then
creates a bundle under
~/.local/share/xr-ai/captures/simple-vlm-example/ containing a canonical raw
bundle plus one derived captioned NVENC H.264 video in a fast-start .mp4 with
timestamp-aligned 48 kHz stereo AAC-LC device/agent audio, retained source H.264 and WAV tracks, exact raw audio
chunks, frame/audio timestamp indexes, a dedicated transcript, optional
frame-linked observations, inbound and outbound data, and a manifest. Text
returned on vlm.response appears in the scrolling data panel; final STT and
text sent to TTS use the larger primary caption. This sample explicitly selects
the demo profile, which invokes the separate capture renderer after the raw
participant-lifetime bundle closes.
Encoding and file writes run in the separate capture process behind bounded
queues. If recording falls behind, it drops pending capture frames rather than
backpressuring the hub or worker. MP4 finalization requires ffmpeg on PATH.
Omit --capture when a deployment must not retain device media.
Wake phrases match at the start of a final transcript or after sentence-final
., ?, or ! punctuation followed by whitespace or a closing quote. Text
before that boundary and the phrase itself are discarded before dispatch.
Relay output#
The worker writes relay-events.jsonl beside worker.log in the per-run log
directory printed at startup. It records runtime publications, receiving-agent
callbacks, the complete simple-vlm.turn lifetime, and nested vision and model
calls. Per-token model marks, incremental voice fragments, and empty stream
terminators are omitted. Voice and STT each emit one semantic scope for a
completed operation. TTS emits one voice.tts scope for each sentence sent
to synthesis. Raw audio is represented only by size, duration, and sample rate.
Image locations appear as <redacted:image>. Prompts, questions, responses,
participant identities, and correlation metadata remain visible and may be
sensitive. Inspect a running sample with:
tail -F /tmp/log_simple-vlm-example_*/relay-events.jsonl