Skip to main content

VLM Run Logo

VLM Run Python SDK

Website | Platform | Docs | Blog | Discord

PyPI Version PyPI Version PyPI Downloads
License Discord Twitter Follow

The VLM Run Python SDK is the official Python SDK for VLM Run API platform, providing a convenient way to interact with our REST APIs.

🚀 Getting Started

Installation

pip install vlmrun

Installation with Optional Features

The package provides optional features that can be installed based on your needs:

  • Video processing features (numpy, opencv-python):

    pip install "vlmrun[video]"
    
  • Document processing features (pypdfium2):

    pip install "vlmrun[doc]"
    
  • Visualization and notebook helpers (pandas, IPython):

    pip install "vlmrun[all]"
    
  • System One typed decisions (typesafe-sdk):

    pip install "vlmrun[typesafe]"
    
  • All optional features:

    pip install "vlmrun[all]"
    

The CLI and OpenAI-compatible gateway (vlmrun gw chat, vlmrun chat) work out of the box with pip install vlmrun.

System One — typed decisions

vlmrun gw systemone answers named questions about text, JSON, images, PDFs and video with calibrated probabilities. Answers are read off the model in one denoise step, so nothing is generated and nothing is parsed — an answer can never be off-schema. The route speaks TypeSafe's contract, so it is driven by the official typesafe-sdk client (pip install "vlmrun[typesafe]").

vlmrun gw systemone "Invoice #44 was charged twice, I need this fixed today" \
  --noul is_urgent="Is the customer asking for something time-sensitive?" \
  --choice department="billing|technical|sales" \
  --score frustration="Calm|Frustrated|Very angry"

# questions as inline JSON (or @file.json, or - for stdin)
vlmrun gw systemone ticket.txt --json -Q '[
  {"id": "is_urgent", "type": "noul"},
  {"id": "department", "type": "choice", "options": ["billing", "technical", "sales"]}
]'

# images and one PDF ride along; --detail sets the vision budget
vlmrun gw systemone invoice.pdf --detail high --choice kind="invoice|receipt|contract"

From Python:

from vlmrun.client import VLMRun

client = VLMRun()
result = client.gateway.systemone.decide(
    state="Invoice #44 was charged twice, I need this fixed today",
    questions=[
        {"id": "is_urgent", "type": "noul", "instructions": "Is this time-sensitive?"},
        {"id": "department", "type": "choice", "options": ["billing", "technical", "sales"]},
    ],
)
result.nouls["is_urgent"].noul           # 0.91
result.choices["department"].choice      # "billing"
result.choices["department"].confidence  # 0.71

Three flags make a read scriptable:

# --gate sets the exit code: 0 all passed, 1 a gate failed, 2 the request failed
vlmrun gw s1 invoice.pdf --choice kind="invoice|receipt|contract" \
  --gate 'kind==invoice' --gate 'kind.confidence>0.9'

# --repeat sends the same request N times and reports mean and spread
vlmrun gw s1 ticket.txt --noul is_urgent --repeat 5

# --dry-run prints the request body without sending it (pipe it to curl)
vlmrun gw s1 scan.jpg --noul signed --dry-run

Video — a decision per frame

Pass a video and it is sampled into frames, each frame its own read. There is no video model here and no streaming: the timeline is built client-side from single-frame decisions, which is what keeps every frame's answer independent and comparable. Needs the video extra (pip install "vlmrun[video]").

# one read a second by default; --fps sets the rate
vlmrun gw s1 door.mp4 --noul is_open --fps 2

# answers come back as a series over time, not a mean
vlmrun gw s1 door.mp4 --choice state="open|closed|blocked" --fps 1
#   state  closed 0.0s-3.0s · open 4.0s-11.0s · blocked 12.0s-14.0s

# --gate-mode says how a gate reads the timeline:
#   any (default) · all · sustained:N · mean
vlmrun gw s1 door.mp4 --noul is_open --fps 4 \
  --gate 'is_open>0.9' --gate-mode sustained:8   # two seconds, not one bad frame

From Python, sampling and reading are separate pieces that compose. VideoReader.frames(fps=...) is a plain iterator over a video at a given rate, and a stream reads whatever you hand it:

from vlmrun.common.video import VideoReader

with client.gateway.systemone.stream(
    questions=[{"id": "is_open", "type": "noul"}],
    state="Is the door open?",
    concurrency=8,
) as stream, VideoReader("door.mp4") as video:
    # Concurrent: reads overlap, results arrive in frame order.
    for decision in stream.map(video.frames(fps=2)):
        print(decision.timestamp_s, decision.response.nouls["is_open"].noul)
        if decision.timestamp_s > 10:
            break                      # queued reads are dropped

    # One at a time — the shape a camera or a queue wants.
    for frame in video.frames(fps=2):
        response = stream.send(frame)

Because frames() is just an iterator, it composes with the standard library — islice(video.frames(fps=2), 10) for the first ten, or a generator expression to keep only the frames you care about — and map accepts any iterable of paths, URLs, data URLs or RGB arrays, not only frames. A reader is reusable: iterate it twice at different rates off one open decoder.

map pulls lazily and keeps at most concurrency reads in flight, so a long clip costs bounded memory and breaking out early stops the spend; results come back in input order regardless of which answer arrived first. A sampled frame carries its index and timestamp through, so the timeline survives. send() is the blocking single-read form: simpler, but it does not overlap, so a for loop over send() is serial.

The stream is a context manager because it owns a worker pool, released on the way out including when you stop early or raise.

The URLs are derived, not configured separately, so pointing the SDK at another deployment moves everything with it:

from vlmrun.constants import DEFAULT_GATEWAY_URL, gateway_base_url
from vlmrun.client.systemone import typesafe_base_url, typesafe_websocket_url

gateway_base_url()        # 'https://gateway.vlm.run/v1'  (VLMRUN_GATEWAY_BASE_URL wins)
typesafe_base_url()       # 'https://gateway.vlm.run/typesafe'
typesafe_websocket_url()  # 'wss://gateway.vlm.run/typesafe/ws'

gateway_base_url() takes an explicit argument first, then VLMRUN_GATEWAY_BASE_URL, then the older VLMRUN_GATEWAY_URL, then DEFAULT_GATEWAY_URL. TYPESAFE_BASE_URL overrides the /typesafe root on its own.

One session instead of a request per frame

transport="ws" (CLI: --ws) opens a single session on /typesafe/ws rather than a request per read. The questions go once with the handshake and are cached server-side, and reads are pipelined over one socket, correlated by id rather than by arrival order. Needs pip install "vlmrun[ws]".

with client.gateway.systemone.stream(questions=Q, state="...", transport="ws") as stream:
    print(stream.limits["max_inflight"])            # what the server granted
    with VideoReader("door.mp4") as video:
        for decision in stream.map(video.frames(fps=2)):
            ...
print(stream.stats["cost"])                          # session totals, after closing

The surface is identical to the HTTP transport — send, map, the same FrameDecision, the same SystemOneResponse — so nothing above it changes.

One socket avoids the per-request overhead of a read, which shows up as soon as the session is allowed to pipeline. Measured on a 60s clip at 1 fps — 60 reads, best of three:

reads in flight HTTP websocket
4 2458 ms 1525 ms
8 1969 ms 904 ms

Token use and cost are identical either way; the saving is round trips, so it grows with the number of frames.

concurrency is requested as the session's max_inflight during the handshake. This matters: the route's own default is 2, well below this SDK's, so a session that does not ask is throttled to a quarter of the width you asked for. The ack is still authoritative — the server may grant less than requested, and the stream never outruns what it granted — and the route refuses a request above 8.

One genuine difference: the state is session-scoped rather than per-request, so changing it mid-stream drains the reads in flight first.

Every frame is a billed read, so --fps and the clip's length set the bill. --max-frames (default 60) is checked against the duration before anything is sent, so an ask that would cost more than you meant fails for free. --concurrency (default 4) sets how many reads are in flight; results are always returned in timeline order. --json gives every frame, each read tagged with its frame index and timestamp.

Several engines serve the route and there is no catch-all alias — vlmrun gw s1 models lists what your gateway serves. Generative engines can think before answering:

vlmrun gw s1 models

vlmrun gw s1 ticket.txt -m google/gemma-4-26b-a4b-it \
  --reasoning-effort medium --choice dept="billing|tax|technical"

vlmrun gw s1 is a shorthand for vlmrun gw systemone. See vlmrun gw systemone --help for both question dialects, media rules and limits.

Basic Usage

from PIL import Image
from vlmrun.client import VLMRun
from vlmrun.common.utils import remote_image

# Initialize the client
client = VLMRun(api_key="<your-api-key>")

# Process an image using local file or remote URL
image: Image.Image = remote_image("https://storage.googleapis.com/vlm-data-public-prod/hub/examples/document.invoice/invoice_1.jpg")
response = client.image.generate(
    images=[image],
    domain="document.invoice"
)
print(response)

# Or process an image directly from URL
response = client.image.generate(
    urls=["https://storage.googleapis.com/vlm-data-public-prod/hub/examples/document.invoice/invoice_1.jpg"],
    domain="document.invoice"
)
print(response)

OpenAI-Compatible Chat Completions

The VLM Run SDK provides OpenAI-compatible chat completions through the agent endpoint. This allows you to use the familiar OpenAI API with VLM Run's powerful vision-language models.

from vlmrun.client import VLMRun

client = VLMRun(
    api_key="your-key",
    base_url="https://api.vlm.run/v1"
)

response = client.agent.completions.create(
    model="vlmrun-orion-1",
    messages=[
        {"role": "user", "content": "Hello!"}
    ]
)
print(response.choices[0].message.content)

For async support:

import asyncio
from vlmrun.client import VLMRun

client = VLMRun(api_key="your-key", base_url="https://api.vlm.run/v1")

async def main():
    response = await client.agent.async_completions.create(
        model="vlmrun-orion-1",
        messages=[{"role": "user", "content": "Hello!"}]
    )
    print(response.choices[0].message.content)

asyncio.run(main())

CLI Chat with Skills

The vlmrun chat command supports skills — local directories containing a SKILL.md and optional assets that give the agent domain-specific expertise. Skills are sent inline with each request (no server-side upload required).

# Chat with an inline skill
vlmrun chat "Generate a youtube thumbnail for a video using the VLM Run brand colors" -k ./path/to/vlmrun-branding/

# Attach multiple skills (coming soon)
vlmrun chat "Analyze this invoice" -i invoice.pdf -k ./accounting-skills/ -k ./invoice-extraction/

To create a persistent server-side skill, use vlmrun skills upload ./my-skill/.

Claude Code

Install the VLM Run CLI skill directly in Claude Code via the plugin marketplace in the vlm-run/skills repository:

  1. Register the repository as a plugin marketplace:
/plugin marketplace add vlm-run/skills
  1. Install the skill:
/plugin install vlmrun-cli-skill@vlm-run/skills
  1. Configure your API key and base URL using the CLI (get your key from app.vlm.run):
vlmrun config init
vlmrun config set --api-key <your-api-key>
vlmrun config show
  1. Verify the skill is loaded by asking Claude Code (requires restart):
What skills are available in the /vlmrun-cli-skill?

Release files for vlmrun 0.9.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vlmrun 0.9.2
File Size Uploaded
vlmrun-0.9.2.tar.gz 197.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for vlmrun 0.9.2
File Interpreter ABI Platform
vlmrun-0.9.2-py3-none-any.whl Python 3 none any Details

Total release size: 369.5 kB

Release files / vlmrun-0.9.2.tar.gz

Download URL vlmrun-0.9.2.tar.gz
Size 197.9 kB
Tags Source
SHA-256 checksum
How to use checksums
f2b716202e5147edb4ef5cd1301743b4eaa7867aa8d0ed9cc19860719364ca26
BLAKE2b-256 checksum
How to use checksums
f38c387482b1f411bfb661de08a37c8c446205a6347a62eda98bca6913ad5885
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / vlmrun-0.9.2-py3-none-any.whl

Download URL vlmrun-0.9.2-py3-none-any.whl
Size 171.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
30a339ac2fbb6b81e723dba8a094fae4b5db1f42777750c626294249a2e3b22e
BLAKE2b-256 checksum
How to use checksums
91ba90758c36b9205d6625c585c7e6e96a9ba57f3eea87d5e5e8921860270a0b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.9.2 This release

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.8

2 release files

0.7.6

2 release files

0.7.5

2 release files

0.7.4

2 release files

0.7.3

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.5

2 release files

0.6.4

2 release files

0.6.3

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.11

2 release files

0.5.10

2 release files

0.5.9

2 release files

0.5.8

2 release files

0.5.7

2 release files

0.5.6

2 release files

0.5.5

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.10

2 release files

0.3.9

2 release files

0.3.8

2 release files

0.3.7

2 release files

0.3.6

2 release files

0.3.5

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.21

2 release files

0.2.20

2 release files

0.2.18

2 release files

0.2.17

2 release files

0.2.15

2 release files

0.2.11

2 release files

0.2.9

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.16

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page