vlm://¶
Provider-portable vision facade. One URI shape that routes to
openai://, anthropic://, ollama://, or vertex:// under the hood
— so you can swap providers via a single param without re-writing your
route. Also handles per-page PDF fan-out so multi-page documents don't
need a separate file:// + split pipeline.
vlm:// does not re-implement vision. It composes the existing
LLM components, so every fix and feature in those components flows
through. Use it when you want one consistent vision URI; use the
underlying components directly when you want provider-specific
features (Anthropic prompt caching, OpenAI structured outputs, etc.).
URI shape¶
When to use what¶
| You want | Reach for |
|---|---|
| Same route works across providers | vlm:// |
| PDF → markdown via a vision LLM (multi-page) | vlm://?splitPDF=true |
| Deterministic table extraction from PDFs | docling:// |
| Full provider-specific feature set (caching, structured outputs, tools) | direct openai:// / anthropic:// / ollama:// / vertex:// |
| OCR for scanned PDFs only (no LLM) | rag-ingest type: ocr backend: docling |
Parameters¶
Provider selection¶
| Param | Type | Default | Description |
|---|---|---|---|
provider |
enum | required | openai / anthropic / ollama / vertex. |
model |
string | required | Vision-capable model id. Provider-specific (gpt-4o, claude-3-5-sonnet-latest, granite3.2-vision, gemini-2.0-flash). |
apiKey |
string | — | Forwarded to the chosen provider. Provider-specific (Ollama doesn't need one). |
baseURL |
string | provider default | Override — use for proxies, on-prem deployments, custom Ollama hosts. |
Generation¶
| Param | Type | Default | Description |
|---|---|---|---|
prompt |
string | — | The LLM / system prompt sent with every call (e.g. "Caption this clinical image"). Per-request override via the HelixLLMSystemPrompt header — header wins. Alias: systemPrompt. |
temperature |
number | 0.7 |
Sampling temperature. 0.0 for deterministic. |
maxTokens |
integer | 4096 |
Response token cap. |
timeout |
duration | 120s |
Per-request HTTP timeout. |
imageDetail |
enum | auto |
auto / high / low. Honoured by OpenAI-shape providers; Anthropic auto-selects from image data; Ollama and Vertex ignore. |
seed |
integer | — | Deterministic-output seed. Critical for reproducible RAG ingestion. Supported on openai, ollama, vertex; Anthropic ignores. Per-request override: HelixLLMSeed header. |
topP |
number | — | Nucleus sampling cutoff. Supported on all four providers. Per-request override: HelixLLMTopP. |
topK |
integer | — | Top-K sampling cutoff. Supported on anthropic, ollama, vertex. OpenAI-shape providers ignore. Per-request override: HelixLLMTopK. |
stop |
string | — | Comma-separated stop sequences. All four providers honour. Per-request override: HelixLLMStop. |
responseFormat |
enum | — | text / json_object / json_schema. JSON mode where supported (openai: response_format; ollama: format: json; vertex: responseMimeType). Anthropic ignores. Per-request override: HelixLLMResponseFormat. |
reasoningEffort |
enum | — | minimal / low / medium / high / xhigh. OpenAI-shape only — required for o-series and GPT-5-family reasoning models. Ignored by Anthropic / Ollama / Vertex and by non-reasoning OpenAI models. Per-request override: HelixLLMReasoningEffort. |
verbosity |
enum | — | low / medium / high. OpenAI-shape only — output-length hint for GPT-5+. Per-request override: HelixLLMVerbosity. |
PDF page-splitting¶
| Param | Type | Default | Description |
|---|---|---|---|
splitPDF |
boolean | false |
When true and the body is a PDF (%PDF- magic), split into per-page PDFs via pdfcpu and dispatch one VLM call per page. |
concurrency |
integer | 4 |
Max concurrent per-page calls. Watch provider rate limits. |
pageRange |
string | all pages | [start, end] pair (1-based). Examples: 1-5, 7 (single page), 1,5. Disjoint multi-ranges not supported. |
pageSeparator |
string | \n\n---\n\n |
Separator inserted between stitched per-page responses. |
PDF→image rendering¶
vlm:// handles PDF rendering automatically when the chosen provider
needs it. The renderer=auto default picks the right path per
provider so you don't have to know which providers accept PDF and
which don't. The route is the same regardless of provider.
| Param | Type | Default | Description |
|---|---|---|---|
renderer |
enum | auto |
auto / native / docling / none. See table below. |
renderDPI |
integer | 150 |
DPI for native rendering. 150 ≈ docling default; 200+ for OCR-sensitive content. Higher = sharper + more tokens. |
renderEndpoint |
string | — | docling-serve base URL when renderer=docling. |
renderAPIKey |
string | — | Bearer token for docling-serve when renderer=docling. |
Renderer modes:
| Mode | Behaviour | When to use |
|---|---|---|
auto (default) |
image-only providers (openai, ollama) → native; PDF-capable (anthropic, vertex) → none. |
The common case. Same URI works across all providers. |
native |
Always render via the embedded PDFium WASM (pure Go, no CGO, ~3 MB binary growth). | Force rendering even for PDF-capable providers (e.g. image tokens cost less than PDF tokens at your tier). |
docling |
Bridge to a running docling-serve at renderEndpoint=. |
You already run docling-serve and want to avoid bundling PDFium twice; needs image_export_mode=embedded support on docling-serve. |
none |
Never render — pass PDF bytes to the provider. | Diagnostic: surfaces provider-side PDF errors clearly. |
Native renderer details:
- Built on
klippa-app/go-pdfiumrunning Google's PDFium engine inside the Wazero pure-Go WASM runtime. - License chain: MIT (wrapper) + Apache 2.0 (PDFium + Wazero). No CGO, no AGPL, no commercial licensing fees.
- First call pays ~3 s for WASM module compile; subsequent renders are sub-millisecond.
- Pool is process-wide, reused across all
vlm://calls.
Headers¶
vlm:// uses the same multimodal header
surface as the underlying LLM components, so a header set on a
vlm:// exchange flows straight through.
| Header | Direction | Purpose |
|---|---|---|
HelixLLMSystemPrompt |
Read | Per-request system prompt; overrides ?prompt=. Legacy systemPrompt still accepted. |
HelixLLMImages |
Read | Extra images (URL list / structured parts). Sent alongside the body text. |
HelixLLMImageDetail |
Read | auto / high / low — overrides ?imageDetail= per request. |
HelixLLMSeed |
Read | Per-request seed override. Header wins over URI ?seed=. |
HelixLLMTopP |
Read | Per-request topP override. |
HelixLLMTopK |
Read | Per-request topK override (anthropic/ollama/vertex only). |
HelixLLMStop |
Read | Per-request stop sequences (CSV) override. |
HelixLLMResponseFormat |
Read | Per-request response-format override (text / json_object / json_schema). |
HelixLLMReasoningEffort |
Read | Per-request reasoning_effort override for GPT-5 / o-series. |
HelixLLMVerbosity |
Read | Per-request verbosity override for GPT-5+. |
HelixVLMProvider |
Written | Which provider actually answered (echo of provider=). |
HelixVLMModel |
Written | Model used (echo of model=). |
HelixVLMPageCount |
Written | Pages processed. 1 unless splitPDF=true. |
HelixVLMRenderer |
Written | Which renderer ran (native / docling / none) when splitPDF=true. Useful for cost telemetry. |
Examples¶
1. Caption a single image — swap providers via one param¶
- id: caption-via-openai
from: direct://caption
steps:
- to: vlm://?provider=openai&model=gpt-4o&apiKey=${env:OPENAI_API_KEY}&prompt=Caption%20this%20image
Same route, different provider:
- to: vlm://?provider=anthropic&model=claude-3-5-sonnet-latest&apiKey=${env:ANTHROPIC_API_KEY}&prompt=Caption%20this%20image
- to: vlm://?provider=ollama&model=granite3.2-vision&baseURL=http://localhost:11434&prompt=Caption%20this%20image
2. PDF page-splitting + stitching — same route, any provider¶
The renderer=auto default makes this work uniformly:
# Anthropic — PDFs sent as-is, no rendering
- to: vlm://?provider=anthropic&model=claude-3-5-sonnet-latest&apiKey=${env:ANTHROPIC_API_KEY}&splitPDF=true&concurrency=4&prompt=Convert%20this%20page
# OpenAI — vlm:// auto-renders each page to PNG via PDFium WASM, then dispatches
- to: vlm://?provider=openai&model=gpt-4o&apiKey=${env:OPENAI_API_KEY}&splitPDF=true&concurrency=4&prompt=Convert%20this%20page
# Ollama — same auto-render path
- to: vlm://?provider=ollama&model=granite3.2-vision&baseURL=http://localhost:11434&splitPDF=true&prompt=Convert%20this%20page
# Vertex — PDF passthrough
- to: vlm://?provider=vertex&model=gemini-2.0-flash&apiKey=${env:GOOGLE_API_KEY}&splitPDF=true&prompt=Convert%20this%20page
2b. Force native rendering for cost reasons¶
# Anthropic accepts PDF, but you've measured that image tokens are
# cheaper for your particular usage. Force native rendering.
- to: vlm://?provider=anthropic&model=claude-3-5-sonnet-latest&apiKey=${env:ANTHROPIC_API_KEY}&splitPDF=true&renderer=native&renderDPI=200
2d. GPT-5 / reasoning models¶
GPT-5 and the o-series have two wire quirks vlm:// handles automatically:
max_tokensis renamed tomax_completion_tokenswhen the model id starts witho1/o3/o4/gpt-5.temperatureis omitted from the wire — OpenAI rejects any value other than the default 1. Helix drops the field; OpenAI's default applies.
You keep writing the same URI; no per-model knowledge required:
- to: vlm://?provider=openai&model=gpt-5&apiKey=${env:OPENAI_API_KEY}
params:
maxTokens: 16384 # → max_completion_tokens on the wire
reasoningEffort: high # minimal | low | medium | high | xhigh
verbosity: medium # low | medium | high
seed: 42 # supported on GPT-5
responseFormat: json_object # supported on GPT-5 / 5.5
prompt: "Extract dosages as JSON."
The Responses API (/v1/responses) is not supported yet —
gpt-5-pro and gpt-5.2-pro may not reach via this chat-completions
path. Use the agent:// component for those, or wait for Phase 2 of
the LLM-params work.
2c. Bridge through an existing docling-serve¶
# You already run docling-serve. Avoid bundling PDFium for this route
# and reuse the docling renderer.
- to: vlm://?provider=openai&model=gpt-4o&apiKey=${env:OPENAI_API_KEY}&splitPDF=true&renderer=docling&renderEndpoint=http://docling:5001
3. Per-request overrides via headers¶
- id: dynamic-prompt
from: direct://caption-with-context
steps:
- set-header:
name: HelixLLMSystemPrompt
starlang: |
"Describe this " ++ headers["doc_type"] ++ " image. Skip decoration."
- set-header:
name: HelixLLMImageDetail
constant: "high"
- to: vlm://?provider=openai&model=gpt-4o&apiKey=${env:OPENAI_API_KEY}
# URI prompt would be overridden by HelixLLMSystemPrompt above; not set here.
4. Mixed text + image URL¶
- set-body: "Is this medication safe with metformin? Use the package image to answer."
- set-header:
name: HelixLLMImages
starlang: |
[ "https://internal.example.com/pills/" ++ payload.med_id ++ ".jpg" ]
- to: vlm://?provider=openai&model=gpt-4o&apiKey=${env:OPENAI_API_KEY}
5. Failover by route choice¶
vlm:// itself doesn't do failover (planned for a follow-up round).
For now use a choice: step with an error handler:
- to: vlm://?provider=openai&model=gpt-4o&apiKey=${env:OPENAI_API_KEY}
on-exception:
- to: vlm://?provider=anthropic&model=claude-3-5-sonnet-latest&apiKey=${env:ANTHROPIC_API_KEY}
Behaviour notes¶
- Composes, does not re-implement. A
vlm://?provider=openaicall lands in the exact same code path as a directopenai://chatcall — same HTTP client, same auth, same parsing. So multimodal fixes shipped on the underlying components flow through automatically. - Body shape conventions follow the underlying LLM components:
[]bytewith image magic → image;string/ non-image bytes → text;*exchange.MultipartContent→ structured pass-through; theHelixLLMImagesheader appends additional images. - No streaming yet. All providers run in non-streaming mode here.
If you need streaming, use the direct component or the
agent://layer. - No tools / function calling. Same caveat as the direct LLM
components — use
agent://for those features. - Concurrency is bounded but not rate-limited. Set
concurrencyto match your provider tier. Excess parallelism leads to 429s, which surface as the first error in the page set. - Stitched output is text only. Per-page responses are
concatenated by
pageSeparator. If you need per-page structured metadata, use the direct component in a custom split/aggregate pipeline.
Source¶
pkg/component/vlm/vlm.go— Component, Endpoint, Describepkg/component/vlm/params.go— URI parser + page-rangepkg/component/vlm/producer.go— dispatch + PDF split/fan-outpkg/component/vlm/headers.go—HelixVLM*response headerspkg/component/vlm/vlm_test.go— 14 httptest-stubbed cases