Typed decisions from text and images, in one forward pass. Give the model a state (text, images, or both) and questions — is this statement true? which of these? how much on this scale? — and it returns calibrated probabilities for every option. Nothing is generated.
It is a re-creation of the Jev API's shape on an open base
(Qwen3.5-4B), with images added. This repository is the inference side: the model code, a
Jev-compatible HTTP API, and a web console. The weights are on the Hub:
yah01/vjev-vision (the earlier 300-step pilot:
yah01/vjev-vision-pilot).
pip install git+https://github.com/BubbleCal/vjev-serve
vjev-serve # yah01/vjev-vision; downloads ~9 GB the first timeThen open http://localhost:8800: drop an image, write questions, see the distributions.
CUDA (bf16, ~9 GB), Apple silicon (fp16, ~10 GB) and CPU (fp32, slow) all work; the device
is picked automatically (--device to force one). --stub serves the API and the console
with fake answers, for trying the interface without a model.
POST /v1/systemone:
{"state": [{"type": "text", "text": "Frame from the warehouse camera, 12:40."},
{"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": "..."}}],
"questions": {
"person": {"type": "noul", "instructions": "There is a person in this image."},
"where": {"type": "choice", "instructions": "Where is the forklift?",
"criteria": {"left": "left half", "right": "right half", "none": "no forklift"}},
"busy": {"type": "score", "instructions": "How cluttered is the scene?",
"criteria": ["empty", "sparse", "busy", "crowded"]},
"tags": {"type": "multilabel", "instructions": "Which of these describe the scene?",
"criteria": ["indoors", "daylight", "crowded", "hazard"]}}}{"answers": {
"person": {"type": "noul", "noul": 0.93},
"where": {"type": "choice", "choice": "left", "confidence": 0.71,
"probabilities": {"left": 0.80, "right": 0.14, "none": 0.06}},
"busy": {"type": "score", "score": 1.4, "legend": {"0": "empty", "1": "sparse", "2": "busy", "3": "crowded"},
"probabilities": {"0": 0.12, "1": 0.45, "2": 0.34, "3": 0.09}},
"tags": {"type": "multilabel", "labels": ["indoors", "daylight"],
"probabilities": {"indoors": 0.91, "daylight": 0.77, "crowded": 0.22, "hazard": 0.08}}},
"usage": {"input_tokens": 231, "tokens_processed": 312, "prefix_cache": "miss"},
"latency_ms": 640}statemay also be a plain string, or a JSON object/array (serialised, as Jev does). Images: up to 8, as content blocks (base64source, or adata:URL); each is resized to a 512 px longer side, the training resolution. Several images are numbered — a question can say "the person in picture 1".- noul — one probability that the statement holds. choice — one softmax over the
options: they compete. score — the levels of one ordered scale;
scoreis the expected level. multilabel — each label scored on its own, nothing competes (internally one noul per label, or a score per label if you passlevels). To rate several independent things, use multilabel or several nouls, not one choice. - A choice sent without
criteriais matched to a preset option set (colours, objects, sentiment, emotion, scene, weather, topic, language —GET /v1/presets): the model itself reads the question and picks the preset and whether several answers can hold."@colors"pins one. - Questions never see each other; options within a question do, and are read in the order given (reordering them moves probabilities by ~0.03 TVD on average).
confidenceis Jev's(p_max - 1/n) / (1 - 1/n), kept for compatibility;probabilitiesis the output.
Other routes: GET /v1/models, GET /v1/presets, GET /health.
from vjev import load
from vjev.server import ModelEngine # or drive the pieces yourself, as server.py does
L = load("yah01/vjev-vision") # -> model, processor, config, meta, devicevjev/render.py turns a question into the sequence the model reads, vjev/model.py is the
listwise head over Qwen3.5's trunk, vjev/prefix.py encodes what a request's questions share
once and keeps the state across requests.
Every option of a question lives in one sequence, and every option's score is read at a
slot after the whole list (Answer: (A) (B) …), so options can see each other — a
pointwise scorer, one option per pass, cannot reproduce the option interactions the Jev API
shows. The base's vocabulary projection is replaced by one shared linear head; there is no
decoding. A request's shared prefix (the images and the state) is encoded once and its
cache broadcast to every question, then kept for the next request about the same state.
yah01/vjev-vision is step 600 of the full vision run. On held-out images: COCO geometry
choice accuracy 0.735, VQAv2 0.706; absent objects called present 6.1% of the time on
POPE-adversarial (the pilot: 13.8%). Spatial answers are over-confident (ECE 0.18): trust the
ranking more than the probabilities. Trained on single images. Details on the model card.
Apache-2.0. The base model, Qwen3.5-4B, is Apache-2.0.