Skip to content

Infrastructure setup

DocStack has three model endpoints:

Endpoint typePurposeUsual backend
paddleocr_vlPaddleOCR-VL OCR pathvllm
glmocr_vlGLM-OCR OCR pathvllm
llmextraction, characterisation, judge, drafter, classifiervllm, ollama, openai, or gemini

The OCR-VLM endpoints are straightforward: run OpenAI-compatible chat-completions servers and point the rows at them. The LLM endpoint is vendor-pluggable and needs the correct backend_kind, base URL, model id, and credential shape.

LLM_URL, LLM_MODEL, LLM_BACKEND_KIND, PADDLEOCR_VL_URL, and GLMOCR_VL_URL are first-boot seeds only. On the first boot of a fresh database, the API writes them into the infra_endpoints table. After any row exists, changing those env vars does not change runtime behavior.

Use one of these runtime surfaces after first boot:

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

Tenant overrides replace the platform default only for that tenant. A tenant that overrides only llm still inherits the platform paddleocr_vl and glmocr_vl rows.

FieldMeaning
backend_kindWire dialect. vllm, ollama, and openai use OpenAI-compatible chat completions. gemini uses Google’s native Gemini SDK and should be used only for llm.
base_urlFor OpenAI-wire backends, the server root. The app appends /v1; a URL that already ends in /v1 is normalized. For Gemini, this is a Gemini host override. Use https://generativelanguage.googleapis.com for the public Developer API.
served_modelExact model id returned by the vendor’s model listing. Validation rejects ids that are not listed.
auth_header / API keyFor vllm, ollama, and openai, this is the full Authorization header value, for example Bearer sk-.... For gemini, this is the raw Google API key with no Bearer prefix.
max_concurrentClient-side semaphore for this endpoint. Match upstream capacity, such as vLLM --max-num-seqs or Ollama OLLAMA_NUM_PARALLEL.
timeout_secondsPer-request model call timeout.
max_model_lenOptional LLM context-window hint when /v1/models does not expose one, commonly with Ollama.

Storing any credential requires CONNECTION_ENCRYPTION_KEYS on both API and worker processes:

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

Keyless local vLLM and local Ollama rows can leave the credential field blank.

Vendorbackend_kindbase_urlserved_modelCredential
vLLMvllmhttp://host:8000vLLM --served-model-nameblank, or Bearer <key> if vLLM auth is enabled
Local Ollamaollamahttp://host:11434pulled tag, for example qwen3.6:35b-a3b-q4_K_Mblank
Ollama Cloudollamahttps://ollama.com/v1model id from /v1/models, for example gpt-oss:120bBearer <OLLAMA_API_KEY>
OpenAIopenaihttps://api.openai.commodel id from /v1/models, for example gpt-5.4-miniBearer <OPENAI_API_KEY>
Geminigeminihttps://generativelanguage.googleapis.comGemini model id, for example gemini-3.1-flash-literaw Gemini API key

Use the model id that the provider lists. For example, Ollama Cloud can accept aliases in a chat call, but DocStack validation checks /v1/models; use gpt-oss:120b if that is what the model list returns, not an unlisted alias such as gpt-oss:120b-cloud.

For local Ollama:

Terminal window
LLM_BACKEND_KIND=ollama
LLM_URL=http://ollama:11434
LLM_MODEL=qwen3.6:35b-a3b-q4_K_M
LLM_REASONING_EFFORT=none

For Gemini:

Terminal window
CONNECTION_ENCRYPTION_KEYS=v1:<base64-32-byte-key>
LLM_BACKEND_KIND=gemini
LLM_URL=https://generativelanguage.googleapis.com
LLM_MODEL=gemini-3.1-flash-lite
LLM_API_KEY=<google-api-key>
LLM_REASONING_EFFORT=none
LLM_GEMINI_SAFETY=block_none

LLM_API_KEY is the env var DocStack reads for Gemini first-boot seeding. GEMINI_API_KEY and GEMINI_MODEL are not read by the runtime config loader unless you map them yourself before starting the API.

If the database already has infra_endpoints rows, use the runtime UI or CLI instead of changing env:

Terminal window
docstack-admin infra set llm \
--backend gemini \
--url https://generativelanguage.googleapis.com \
--model gemini-3.1-flash-lite \
--auth-header "$(cat /run/secrets/gemini_api_key)" \
--timeout 300
Terminal window
docstack-admin infra set llm \
--backend ollama \
--url https://ollama.com/v1 \
--model gpt-oss:120b \
--auth-header "Bearer $(cat /run/secrets/ollama_api_key)" \
--timeout 300

Keep paddleocr_vl and glmocr_vl on OpenAI-compatible chat-completions servers. The bundled model-server path uses vLLM:

Endpointbackend_kindbase_urlserved_model
paddleocr_vlvllmhttp://paddle:8000PaddleOCR-VL-1.6
glmocr_vlvllmhttp://glm:8000glm-ocr

Do not use gemini for OCR-VLM rows. Gemini support is for the text LLM call sites only.

Every save and test runs a server-side probe:

  1. Reachability
  2. Served-model presence
  3. Shape probe

For vllm, ollama, and openai, reachability and model listing use /v1/models. The shape probe uses /v1/chat/completions. For gemini, the same stages run through the native SDK: model listing plus a small generate_content JSON request.

With INFRA_VALIDATION_HARD_GATE=true, a failed probe rejects the save. This is intentional: it prevents a bad endpoint row from being published to workers.

SymptomFix
served_model not in /v1/models responseCopy the exact id from the provider’s model list.
Gemini tries to call an Ollama/vLLM URLSet base_url to https://generativelanguage.googleapis.com when backend_kind=gemini.
OpenAI returns Unrecognized request argument supplied: reasoning_effortUse a model that accepts the configured reasoning field, or change the app config/code so the field is omitted for that model family.
Ollama reasoning model returns empty JSON contentSet LLM_REASONING_EFFORT=none and keep backend_kind=ollama so the app sends max_tokens.
Credential save returns 503Set CONNECTION_ENCRYPTION_KEYS on API and worker, then save the endpoint credential again.
Changing .env does nothingThe row already exists. Use the infrastructure UI or docstack-admin infra set.