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:
| Role | Required behavior | Allowed adapters |
|---|---|---|
paddleocr_vl | full Paddle lane with the pinned PaddleOCR-VL-1.6 contract | vllm, openai |
granite_docling_vl | full Docling lane with Granite Docling and DocTags | local, vllm, ollama, openai |
glmocr_vl | targeted GLM-OCR text/table/formula recognition | vllm, ollama, openai |
llm | extraction, characterisation, judge, drafter, classifier | vllm, 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.
Source of truth and hot reload
Section titled “Source of truth and hot reload”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.
Field meanings
Section titled “Field meanings”| Field | Meaning |
|---|---|
backend_kind | Role-allowed protocol adapter from the table above. local means the digest-pinned Granite model baked into its isolated service. |
base_url | Exact 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_model | Provider deployment/routing alias returned by model listing. This is separate from the lane’s pinned semantic model contract. |
auth_header_name | Approved credential header such as Authorization, Azure api-key, or x-api-key. Hop-by-hop/request-smuggling headers are rejected. |
auth_header | Write-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_revision | Compare-and-swap fence copied from the displayed row. A stale value returns HTTP 409. |
max_concurrent | Per-binding client semaphore. Match provider capacity such as vLLM --max-num-seqs. |
timeout_seconds | Per-provider-call wall-clock limit. |
max_model_len | Optional extraction-LLM context-window hint. |
Credentials require CONNECTION_ENCRYPTION_KEYS on API and worker processes:
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 examples
Section titled “Provider examples”| Provider pattern | backend_kind | base_url | Header |
|---|---|---|---|
| vLLM | vllm | http://host:8000 | blank or Authorization: Bearer … |
| Local Ollama | ollama | http://host:11434 | blank |
| Managed OpenAI-compatible API | openai | provider’s exact API root | provider-defined approved header |
| Azure AI Foundry v1 | openai | resource base ending in /openai or /openai/v1 | api-key, or Authorization with an Entra bearer token |
| Gemini extraction LLM | gemini | https://generativelanguage.googleapis.com | raw key in the configured credential field |
| Anthropic extraction LLM | anthropic | https://api.anthropic.com | x-api-key |
Example runtime updates:
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 300Docling 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.
Validation and model identity
Section titled “Validation and model identity”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.
Network security
Section titled “Network security”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.
Common failures
Section titled “Common failures”| Symptom | Fix |
|---|---|
served_model not in /v1/models response | Copy the provider’s exact deployment/routing alias. |
| HTTP 409 on Apply | Another editor applied a revision; refresh and reapply intentionally. |
| Credential save returns 503 | Configure identical CONNECTION_ENCRYPTION_KEYS on API and workers. |
| Tenant URL is rejected as unsafe | Use an approved public/provider host or explicitly governed internal CIDR; do not bypass SSRF policy. |
| Remote visual service cannot connect | Enable only the required egress CIDRs/ports after network approval. |
Changing .env does nothing | The active row already exists; use the infrastructure console, CLI, or API. |