Skip to content

Model infrastructure

DocStack requires specific model behavior for its load-bearing visual lanes, but it does not require one hosting vendor. Four logical roles separate model semantics from infrastructure:

RoleRequired behaviorAllowed adapters
paddleocr_vlfull Paddle lane with the pinned PaddleOCR-VL-1.6 contractvllm, openai
granite_docling_vlfull Docling lane with Granite Docling and DocTagslocal, vllm, ollama, openai
glmocr_vltargeted GLM-OCR text/table/formula recognitionvllm, ollama, openai
llmextraction, characterisation, judge, drafter, classifiervllm, ollama, openai, gemini, anthropic

The adapter names describe protocols, not commercial vendors. An Azure AI Foundry deployment, approved self-hosted service, or other compatible API can supply a role without changing Paddle/Docling orchestration, prompts, codecs, or the required model revision.

LLM_URL, LLM_MODEL, LLM_BACKEND_KIND, PADDLEOCR_VL_URL, and GLMOCR_VL_URL seed missing platform rows on first boot only. Granite/Docling is initially local://baked. Restarting with different environment values does not overwrite active rows.

Use these runtime surfaces:

  • Platform console: /admin/platform/infrastructure
  • Tenant console: /admin/infrastructure, when overrides are enabled
  • CLI: docstack-admin infra set ...
  • API: /v1/admin/platform/infra/{role} or its tenant equivalent

Each console exposes two operations:

  • Test draft probes the unsaved form and does not change active state.
  • Apply repeats validation, compare-and-swaps the displayed revision, commits and audits the next revision, and eagerly invalidates worker caches.

Every new execution plan performs a fresh effective-binding resolution and freezes the result. A successful Apply therefore affects the next planned job without restarting any process. Already planned or in-flight jobs finish on their prior immutable revision. A missed Redis notification cannot leave the worker stale indefinitely because next-job resolution also checks the binding fingerprint.

Tenant overrides replace the platform default for that tenant only. A tenant that overrides llm still inherits platform bindings for the other three roles. The UI shows the effective source and revision and can reset an override to platform fallback.

FieldMeaning
backend_kindRole-allowed protocol adapter from the table above. local means the digest-pinned Granite model baked into its isolated service.
base_urlExact remote API root. A trailing /v1 is normalized while another reverse-proxy prefix is preserved. URL credentials, query strings, parameters, fragments, whitespace, and control characters are rejected. The only local value is local://baked.
served_modelProvider deployment/routing alias returned by model listing. This is separate from the lane’s pinned semantic model contract.
auth_header_nameApproved credential header such as Authorization, Azure api-key, or x-api-key. Hop-by-hop/request-smuggling headers are rejected.
auth_headerWrite-only printable-ASCII credential value. null keeps the effective encrypted value; "" clears it; any other value replaces it. Local baked bindings cannot carry a credential.
expected_revisionCompare-and-swap fence copied from the displayed row. A stale value returns HTTP 409.
max_concurrentPer-binding client semaphore. Match provider capacity such as vLLM --max-num-seqs.
timeout_secondsPer-provider-call wall-clock limit.
max_model_lenOptional extraction-LLM context-window hint.

Credentials require CONNECTION_ENCRYPTION_KEYS on API and worker processes:

Terminal window
python -c "import base64,secrets; print('v1:' + base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())"

The API returns has_auth_header and the non-secret header name, never the plaintext or ciphertext. Execution plans retain binding IDs, revisions, aliases, and public fingerprints, but never credentials.

Provider patternbackend_kindbase_urlHeader
vLLMvllmhttp://host:8000blank or Authorization: Bearer …
Local Ollamaollamahttp://host:11434blank
Managed OpenAI-compatible APIopenaiprovider’s exact API rootprovider-defined approved header
Azure AI Foundry v1openairesource base ending in /openai or /openai/v1api-key, or Authorization with an Entra bearer token
Gemini extraction LLMgeminihttps://generativelanguage.googleapis.comraw key in the configured credential field
Anthropic extraction LLManthropichttps://api.anthropic.comx-api-key

Example runtime updates:

Terminal window
docstack-admin infra set granite_docling_vl \
--backend openai \
--url https://models.internal.example/v1 \
--model granite-docling-prod \
--auth-header-name Authorization \
--auth-header "Bearer $(cat /run/secrets/model_token)" \
--timeout 300
docstack-admin infra set llm \
--backend openai \
--url https://my-resource.openai.azure.com/openai/v1 \
--model extraction-prod \
--auth-header-name api-key \
--auth-header "$(cat /run/secrets/azure_api_key)" \
--timeout 300

Docling and the direct GLM adapter forward the configured approved header. Paddle’s official remote-pipeline option accepts one API-key input with bearer semantics and cannot directly emit Azure’s api-key header. Use an Entra bearer token for Paddle on Azure or an approved internal header-normalizing gateway; never put a secret in the URL.

Remote draft Test and Apply perform reachability, served-alias presence, and a role-specific response-shape probe. local://baked validates its pinned local attestation without network access. Apply is rejected on validation failure when INFRA_VALIDATION_HARD_GATE=true.

A successful probe proves connectivity and the advertised response contract. It cannot independently prove that a managed provider mapped its mutable alias to a claimed weight revision. The isolated service separately rejects a binding whose required model contract/revision differs from its pinned configuration. Before a remote binding gains production authority, retain provider deployment evidence and run conformance, corpus, shadow, performance, security, and rollback gates.

Tenant-authored remote hosts pass DNS/IP/CIDR SSRF validation before Test or Apply. Kubernetes visual-service egress is default-deny; opt in only to explicit provider or gateway CIDRs and ports through vision.networkPolicy.providerEgress. Compose keeps its base vision network internal; add docker-compose.vision-provider-egress.yaml only after a host or gateway allowlist exists. See the vision operations runbook for the exact configuration and rollback procedure.

SymptomFix
served_model not in /v1/models responseCopy the provider’s exact deployment/routing alias.
HTTP 409 on ApplyAnother editor applied a revision; refresh and reapply intentionally.
Credential save returns 503Configure identical CONNECTION_ENCRYPTION_KEYS on API and workers.
Tenant URL is rejected as unsafeUse an approved public/provider host or explicitly governed internal CIDR; do not bypass SSRF policy.
Remote visual service cannot connectEnable only the required egress CIDRs/ports after network approval.
Changing .env does nothingThe active row already exists; use the infrastructure console, CLI, or API.