Skip to content

Start locally

OpenJev Multimodal turns text and images into typed decisions on your Mac. You need an Apple Silicon Mac, Python 3.11 or later, and enough memory for your chosen model. Start with the 4B balanced profile on a 16 GB or larger Mac; use fast for a smaller footprint, and quality or max on a Mac with more memory. These are practical starting points, not measured minimum-memory guarantees.

Install and run

bash
brew install uv llama.cpp
git clone https://github.com/jev-skills/openjev-multimodal.git
cd openjev-multimodal
uv sync --frozen
uv run openjev serve

The first run downloads the pinned model and vision projector. Subsequent runs use the local Hugging Face cache. Open localhost:8000/playground to try text and images. The interactive API reference is at localhost:8000/docs.

llama.cpp build b9670 is the verified baseline. Use that build or a newer version with /props.media_marker and post_sampling_probs. Update Homebrew if these features are missing.

Your first decision

bash
curl http://127.0.0.1:8000/v1/systemone \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "jev-latest",
    "state": "I was charged twice. Please refund the duplicate.",
    "questions": {
      "refund": {
        "type": "noul",
        "instructions": "Does the customer request a refund?"
      },
      "team": {
        "type": "choice",
        "instructions": "Which team should handle this?",
        "criteria": {
          "billing": "Payments and refunds",
          "technical": "Software bugs"
        }
      }
    }
  }'

The response contains answers.refund.noul, answers.team.choice, the full choice distribution and usage. Two questions consume two output tokens. Input processing still costs time; images and long contexts take longer.

Choose a profile

bash
uv run openjev serve --profile fast      # Qwen3.5-0.8B Q4_K_M
uv run openjev serve --profile balanced  # Qwen3.5-4B Q4_K_M (default)
uv run openjev serve --profile quality   # Qwen3.6-35B-A3B UD-Q4_K_XL
uv run openjev serve --profile max       # Qwen3.8-27B UD-Q4_K_XL

Run one profile at a time. The server uses one inference slot and four CPU threads by default. --threads 2 further limits CPU work. Stop with Ctrl+C; the CLI shuts down its own backend.

Download faster

bash
uv run openjev download --profile max --source modelscope

Weights come from Hugging Face by default. --source modelscope fetches the same files from ModelScope, which is often far faster from mainland China; the other hub is the fallback. Downloads run as parallel, resumable ranges (--connections, default 8), are checked against the SHA-256 pinned for each profile, and land in the Hugging Face cache, so later runs start offline. Set OPENJEV_MODEL_SOURCE=modelscope to make it the default. --quant Q8_0 selects the 8-bit weights of the max profile.

Existing weights or backend

bash
uv run openjev serve --profile quality \
  --model-file /path/to/model.gguf \
  --mmproj-file /path/to/mmproj.gguf

uv run openjev serve --connect http://127.0.0.1:18081

Use a matching projector. An existing backend must expose native llama.cpp endpoints, support post-sampling probabilities, disable thinking, use one slot, and load a projector for vision. Configure its context and image-token limits consistently with --context and --image-tokens.

Backends

bash
uv run openjev serve --backend llama.cpp

--backend (or OPENJEV_BACKEND) chooses the inference backend at startup; every backend serves the same API. llama.cpp is built in and the default. Other backends are Python packages registered under the openjev.backends entry point: install one into the same environment and pass its name. openjev serve --backend NAME --help lists its options, openjev doctor shows what is installed and /health reports the backend in use. Contract →

Configuration

SettingDefaultPurpose
--backendllama.cppInference backend (OPENJEV_BACKEND)
--port8000API and local playground
--backend-port18081Private localhost inference server
--context8192Per-question context limit
--image-tokens512Backend image-token budget per image
--threads4CPU and prompt-processing threads
OPENJEV_API_KEYunsetBearer authentication on /v1/*
OPENJEV_REQUEST_TIMEOUT120Evaluation deadline including queue time
OPENJEV_MAX_CONCURRENT_REQUESTS4Active/queued evaluations; inference stays serial
OPENJEV_IMAGE_MAX_EDGE1024Longest image edge after resizing
OPENJEV_IMAGE_ALIGN32Visual-token edge in pixels; oversized images are resized once, straight to the encoder's size (0 turns this off)
OPENJEV_PRIME_SHARED_PREFIXtrueRead the shared state once for multi-question requests
OPENJEV_PRIME_REPEATED_STATEtrueKeep a state that consecutive requests repeat, so each reads only its question
OPENJEV_TEMPLATE_CACHEtrueReuse a chat-template skeleton verified against the backend
OPENJEV_COMPACT_JSONtrueSend JSON states without spaces: about a fifth fewer tokens
OPENJEV_CHECKPOINT_EVERY512Checkpoint prompts at this token interval, so a follow-up on a similar state resumes where the two differ (OpenJev's llama.cpp build)
OPENJEV_RESPONSE_TIMINGtrueAdd the timing object to responses; headers always carry it

Use environment variables or a local .env. Keep credentials out of Git. The API binds to 127.0.0.1. For an intentional network deployment, use authentication and a TLS reverse proxy.

bash
uv run openjev doctor
curl http://127.0.0.1:8000/health
uv run openjev schema > openapi.json

GitHub Pages hosts this documentation. Your Mac hosts the inference API.

Open models. Local inference. Measured claims.