Configuration
All settings are defined in config/settings.yaml. Environment variables can override specific values. Settings can also be changed at runtime from the web UI under Settings.
Full Configuration Reference
Section titled “Full Configuration Reference”server: host: "0.0.0.0" # Bind address port: 8000 # Internal port (container)
database: path: "/vault/asclepius.sqlite" # SQLite database path
vault: root_path: "/vault" # Root directory for all files inbox_path: "/vault/inbox" # Drop files here for processing patients_path: "/vault/patients" # Organized patient files unclassified_path: "/vault/unclassified" # Docs without a patient
auth: secret_key: "change-me-in-production" # Session signing key session_ttl_hours: 720 # Session lifetime (30 days)
# OIDC / SSO (Authentik, Keycloak, etc.)oidc: enabled: false provider_url: "" # e.g. https://auth.example.com/application/o/asclepius/ client_id: "" client_secret: "" scopes: "openid profile email" auto_create_user: true # Create user on first OIDC login username_claim: "preferred_username" # OIDC claim for username display_name_claim: "name" # OIDC claim for display name
# Shared connections referenced by provider entries via `credential_id`.# Concurrency limits and retry policies live here so models on the same# endpoint share a queue, see admin-guide/llm-configuration.md.credentials: - id: "cred-ollama-main" name: "Ollama (GPU box)" type: "ollama" # ollama | vllm | claude | openai | google_vision | tesseract_remote base_url: "http://ollama:11434" max_concurrent: 1 # Process-wide cap per (credential, kind) max_retries: 3 retry_backoff_seconds: [30, 60, 120]
ocr: # Global defaults (used when no provider-level override is set) language: "eng" # Tesseract language codes, + separated confidence_threshold: 0.7 # Below this, document marked as needs_review max_concurrent_vision_requests: 1 # Legacy, preferred knob is credential.max_concurrent # Ordered provider list, tried in priority order for the OCR+LLM flow. providers: - id: "tesseract-1" type: "tesseract" # tesseract | tesseract_remote | llm_vision | google_vision name: "Tesseract" enabled: true priority: 1 language: "eng+ita+deu" # Local Tesseract has no credential; leave credential_id empty. - id: "llm-vision-1" type: "llm_vision" name: "GLM-OCR" enabled: true priority: 2 credential_id: "cred-ollama-main" llm_provider: "ollama" llm_model: "glm-ocr"
llm: extraction_timeout: 120 # Per-provider timeout (seconds) max_concurrent_requests: 2 # Legacy flat cap, new setups rely on credential.max_concurrent max_retries: 3 # Legacy, replaced by per-credential retry policy retry_backoff_seconds: [30, 60, 120] extraction_max_output_tokens: 16384 # Raise if the LLM keeps hitting the output-token cap classification_max_output_tokens: 4096 canonical_language: "English" # Language for LLM-authored free-form text # Ordered provider list, tried in priority order. providers: - id: "ollama-1" type: "ollama" # ollama | vllm | claude | openai name: "Qwen on Ollama" enabled: true priority: 1 credential_id: "cred-ollama-main" model: "qwen3:14b" timeout: 120 # General-purpose LLM, used for chat, auto-merge, auto-rename, link # suggestion, event extraction, and AI document edits. When empty, those # endpoints return 503. general: credential_id: "" type: "ollama" model: "" timeout: 120
vision: # Alternative to OCR + text-LLM: sends page images to a vision LLM that # returns both OCR text and structured extraction in one call. extraction_timeout: 600 providers: - id: "qwen25vl-1" type: "ollama" # ollama | claude | openai name: "Qwen2.5-VL (local)" enabled: true priority: 1 credential_id: "cred-ollama-main" model: "qwen2.5vl:7b" timeout: 600
pipeline: watch_enabled: true # Auto-process files dropped in inbox poll_interval_seconds: 5 # How often to check for new files retry_interval_seconds: 300 # Wait before retrying failed extractions max_retries: 3 # Max retry attempts for failed documents default_flow: "ocr_llm" # "ocr_llm" or "vision_llm", which path new uploads use
# SMTP transport (used by the doctor-share email-OTP flow)smtp: enabled: false # Master switch — when off, email-OTP shares cannot be created host: "" # smtp.example.com port: 587 # 587 for STARTTLS, 465 for implicit TLS username: "" password: "" # Override with ASCLEPIUS_SMTP_PASSWORD env var use_tls: false # Implicit TLS (port 465) use_starttls: true # STARTTLS upgrade (port 587) from_address: "" # noreply@example.com from_name: "Asclepius" timeout_seconds: 15 # SMTP send wall-clock timeout
# Doctor-share knobs (full list in admin-guide/doctor-shares)share: session_ttl_minutes: 120 otp_ttl_minutes: 10 otp_max_attempts: 5 # Wrong-code attempts on a single OTP share_lockout_after_failed: 3 # Consecutive verify failures → share auto-revoked email_otp_resend_cooldown_seconds: 30 # Min gap between two request-otp calls email_otp_daily_cap: 20 # Max OTP emails per share per rolling 24 h email_otp_subject: "Your access code for medical records" email_otp_body: | Hello {recipient_label},
Your one-time access code is: {code}
This code expires in {expires_minutes} minutes. Enter it on the page your contact at {from_name} shared with you. # Region-translation hardening (caps the blast radius of a successful # prompt-injection via OCR'd content — see admin-guide/doctor-shares). max_translation_chars: 50000 # absolute cap on stored translated_text translation_max_expansion_ratio: 10.0 # reject if len(out) > ratio × max(len(in), 200) translation_audit_enabled: true # write translate_region_done to share audit logProviders and processing flows
Section titled “Providers and processing flows”Asclepius has two mutually exclusive extraction flows:
- OCR + LLM (
pipeline.default_flow: "ocr_llm"): extract text with an OCR engine, then send the text to a language model for classification and structured extraction. - Vision-LLM (
pipeline.default_flow: "vision_llm"): send page images directly to a vision-capable LLM that returns both the transcribed text and the structured extraction in a single call.
Each flow has its own priority-ordered provider list:
| Flow | Config section | Supported types | UI location |
|---|---|---|---|
| OCR | ocr.providers |
tesseract, tesseract_remote, llm_vision, google_vision |
Settings → Document Analysis → Providers (OCR section) |
| Text LLM | llm.providers |
ollama, vllm, claude, openai |
Settings → Document Analysis → Providers (LLM section) |
| Vision LLM | vision.providers |
ollama, claude, openai |
Settings → Document Analysis → Providers (Vision-LLM section) |
| Credentials | credentials |
shared connection definitions referenced by credential_id |
Settings → Document Analysis → Providers (Credentials panel) |
The pipeline tries providers in priority order; on failure it falls through to the next enabled one. A per-document override is available from the document detail page.
pipeline.default_flow only affects new uploads. Existing documents can be reprocessed with any of four modes (OCR+LLM, OCR only, LLM only, Vision-LLM) from the document detail page.
See LLM & OCR Configuration for full details, YAML examples, and recommended models.
Concurrency caps
Section titled “Concurrency caps”Each credential carries a max_concurrent value, the process-wide cap for everything that uses that endpoint. Every LLM/Vision/OCR provider referencing the credential shares a queue, split per kind (llm / ocr / vision). A single Ollama server with max_concurrent: 1 will never run more than one LLM request and one OCR request and one vision request at the same time, matching how the physical GPU actually behaves.
When a provider still uses inline URL + API key (no credential_id), the process falls back to the legacy flat caps:
| Legacy setting | Purpose | Default |
|---|---|---|
ocr.max_concurrent_vision_requests |
OCR+LLM flow, vision-OCR page calls | 1 |
vision.max_concurrent_requests |
Vision-LLM flow, end-to-end page calls | 2 |
llm.max_concurrent_requests |
Text-LLM pipeline calls | 2 |
New deployments should leave these alone and adjust credentials[].max_concurrent instead.
Legacy configuration
Section titled “Legacy configuration”Older settings files used flat llm.provider / ocr.engine fields and a vision_extraction OCR provider type. Asclepius auto-migrates these at startup:
- Flat
llm.*keys are folded intollm.providers[]. - Any OCR provider entry with
type: vision_extractionis moved intovision.providers[]. - Inline
base_url+api_keyvalues on provider entries are promoted tocredentials[]entries and replaced withcredential_id. Per-provider retry / concurrency knobs are preserved as a fallback.
The migrated file is re-written to disk on first run.
OCR Languages
Section titled “OCR Languages”Tesseract language codes are + separated. The Docker image includes these language packs:
| Code | Language |
|---|---|
eng |
English |
ita |
Italian |
deu |
German |
fra |
French |
spa |
Spanish |
To add more languages, extend the Dockerfile with additional tesseract-ocr-* packages.
OIDC / SSO
Section titled “OIDC / SSO”To enable single sign-on with Authentik, Keycloak, or another OIDC provider:
oidc: enabled: true provider_url: "https://auth.example.com/application/o/asclepius/" client_id: "your-client-id" client_secret: "your-client-secret" scopes: "openid profile email" auto_create_user: true username_claim: "preferred_username" display_name_claim: "name"See User Management for details on configuring SSO.
SMTP is opt-in. Without it, every doctor share falls back to manual OTP delivery — the admin reads the code over the phone — and the “Email” delivery option in the share dialog is disabled.
To enable email delivery, configure transport in Settings → Email (or in YAML):
smtp: enabled: true host: "smtp.example.com" port: 587 username: "asclepius@example.com" password: "..." # OR inject via ASCLEPIUS_SMTP_PASSWORD env var use_starttls: true # port 587 # use_tls: true # for implicit TLS on port 465 instead from_address: "noreply@example.com" from_name: "Dr. Smith's Office"Hard requirements:
- TLS is mandatory when
hostis anything other thanlocalhost/127.0.0.1. Pick exactly one ofuse_starttls(port 587, recommended) oruse_tls(port 465). Plaintext sends to a non-local host raisePlaintextRefusedat send time. from_addressmust be set whenenabled: true— it goes into theFrom:header and is what the recipient’s mailbox sees.from_nameis purely cosmetic; it appears alongsidefrom_addressin theFrom:header and is one of the placeholders the OTP email template can interpolate.
After saving SMTP settings, use the Send test email button on the Email tab to verify connectivity. The test endpoint surfaces the underlying exception class on failure (e.g. SMTPAuthenticationError, SMTPConnectError) but never the raw server response — SMTP servers can echo back attacker-controlled bytes, so the audit trail and the toast only carry stable labels.
The email body and subject for the OTP message live under share.email_otp_subject and share.email_otp_body. They support a small set of literal {placeholder} tokens — {code}, {recipient_label}, {expires_minutes}, {share_label}, {from_name}. See Doctor shares → Email template for the full reference.
Security
Section titled “Security”Generate a secure key:
python -c "import secrets; print(secrets.token_urlsafe(32))"Environment Variable Overrides
Section titled “Environment Variable Overrides”These environment variables override settings.yaml values:
| Variable | Overrides |
|---|---|
ASCLEPIUS_SECRET_KEY |
auth.secret_key |
ASCLEPIUS_ENV |
server.environment (development or production) |
ASCLEPIUS_COOKIE_SECURE |
auth.cookie_secure (truthy value enables Secure cookies) |
ASCLEPIUS_CORS_ORIGINS |
server.cors_origins (comma-separated list) |
ASCLEPIUS_VAULT_PATH |
vault.root_path and all sub-paths |
ASCLEPIUS_DB_PATH |
database.path |
ASCLEPIUS_OLLAMA_URL |
base_url on the first llm.providers entry of type ollama (creates one if none exists) |
ASCLEPIUS_ANTHROPIC_API_KEY |
api_key on the first llm.providers entry of type claude (creates one if none exists) |
ASCLEPIUS_GOOGLE_VISION_KEY |
ocr.google_vision_key (also enables cloud_ocr_enabled) |
ASCLEPIUS_CONFIG_PATH |
Path to settings.yaml (default: config/settings.yaml) |
ASCLEPIUS_MODE |
Run mode: core (full app, default) or share (public doctor-share surface only — mounts /api/share/* and /share/... SPA pages, returns 404 for everything else; see Doctor shares → Publishing the share surface) |
ASCLEPIUS_SHARE_PUBLIC_URL |
share.public_base_url — pin every generated share link to this origin (e.g. https://med.example.com). Required for split-host deployments; leave empty when admin and doctor share one hostname. |
ASCLEPIUS_SMTP_PASSWORD |
smtp.password — inject the SMTP password without writing it to settings.yaml. When both are set, the env var wins on every restart. Recommended for container deployments where credentials are managed by an external secrets store. |
FORWARDED_ALLOW_IPS |
uvicorn trust list for X-Forwarded-* headers (* = any). Override to your reverse-proxy IP for stricter defaults. |
TZ |
Container timezone (default: Europe/Zurich) |