Settings reference#
Every environment variable the app reads. Settings are loaded by pydantic-settings at startup; missing required variables cause the app to fail fast.
Tip: rather than editing this table by hand, you can
uv run python -c "from qfa.settings import AppSettings; import json; print(AppSettings.model_json_schema())"to dump the live schema.
LLM (LLM_*)#
Variable |
Required |
Default |
Notes |
|---|---|---|---|
|
no |
|
Routed by LiteLLM based on the prefix ( |
|
yes |
— |
Provider API key. Stored as |
|
only some providers |
|
E.g. |
|
only some providers |
|
API version where the provider expects one. |
|
no |
|
Per-attempt LLM-call timeout. A single call retries transient failures (timeout, rate-limit) up to |
|
no |
|
Token budget guard. Estimated as |
|
no |
|
Conversion ratio used by the token budget guard. |
Judge LLM (JUDGE_LLM_*)#
An optional second LLM connection used only for the LLM-as-judge quality
scores, so the model that produces an analysis or summary is not the one that
grades it. It applies to four call sites: the analyze judge, the hierarchical
leaf judges, and the judges in summarize and summarize_aggregate. The
per-level judge inside assign_codes stays on the primary connection.
Every variable below is optional, and an unset one inherits the matching
LLM_* value — including LLM_API_KEY. That inheritance is the whole point
of the block: enabling a judge model needs no new Key Vault secret and no
credential provisioning, only non-secret variables.
JUDGE_LLM_MODEL is the switch. While it is unset (the default), no judge
client is built and judge calls run on the primary connection exactly as they
did before this block existed. Setting it alone is a complete, valid
configuration — the app will not fail to start for want of a matching key or
base URL.
Variable |
Required |
Default |
Notes |
|---|---|---|---|
|
no |
unset locally; deployed environments set |
Enables the separate judge connection. Same LiteLLM prefix routing as |
|
no |
inherits |
Only needed for a judge on a different Azure resource or provider. Stored as |
|
no |
inherits |
Needed when the judge sits on a different provider route — e.g. |
|
no |
inherits |
API version where the judge provider expects a different one. |
There are no judge-side equivalents of LLM_TIMEOUT_SECONDS,
LLM_MAX_TOTAL_TOKENS or LLM_CHARS_PER_TOKEN: both connections always share
the primary values, so the two clients cannot drift apart on timeout or token
accounting.
Minimum configuration — a judge on the same provider route as generation, one variable:
JUDGE_LLM_MODEL=azure/<some-azure-openai-deployment>
A judge on the other provider route of the same Azure Foundry resource needs two, both non-secret; the API key is still inherited:
JUDGE_LLM_MODEL=azure_ai/mistral-medium-3-5
JUDGE_LLM_API_BASE=https://<resource>.services.ai.azure.com/models
Cost and usage are recorded for judge calls exactly as for generation calls:
the judge client is wrapped in the same TrackingLLMAdapter, and each
llm_calls row carries the model that actually served the call, so a run with
two models in play remains attributable per model.
Embedding (EMBEDDING_*)#
Only consumed by mode=hierarchical. The path defaults to empty at the
settings layer, but the official Docker image bakes the default ONNX embedder
in (multilingual-e5-base) and sets the ENV to it (see the builder stage in
Dockerfile), so a deployed image serves hierarchical out of the box. The 502
analysis_unavailable response only applies where the model is genuinely
absent — a bare local run, or a deployment that strips these vars. Running a
different family (e.g. BGE-M3) in production means adding a fetch step to the
Dockerfile model stage and overriding the EMBEDDING_* env — it is not
baked in by default.
Two model families are supported, selected by EMBEDDING_MODEL_KIND; the
family fixes the adapter’s output handling (pooling + query prefix) while the
dimension and token cap are per-artifact knobs (EMBEDDING_DENSE_DIM /
EMBEDDING_MAX_TOKENS):
e5(default, multilingual-e5-base, 768-d) — mean-pools token vectors and prepends thequery:prefix. Smaller and faster than BGE-M3 for a modest cross-lingual quality trade. e5-small (384-d) is the samekind.bge-m3(1024-d) — takes the model’s already-pooleddense_vecshead as-is. The strongest cross-lingual model; select it when quality matters more than latency. Not baked into the image: add a fetch step to theDockerfilemodel stage (fetch_embedding_model.py --model bge-m3) and point theEMBEDDING_*env at it (see theDockerfilecomment).
For local development, fetch an artifact and get the matching env lines
with uv run python scripts/fetch_embedding_model.py (defaults to e5-base;
--model bge-m3 / --model e5-small for the others). It downloads to a
gitignored .models/ and prints every EMBEDDING_* value to paste, including
EMBEDDING_MODEL_KIND and EMBEDDING_DENSE_DIM.
Variable |
Required |
Default |
Notes |
|---|---|---|---|
|
for hierarchical |
|
Path to the mirrored ONNX graph. Never a HuggingFace URL in production. |
|
for hierarchical |
|
Path to the mirrored tokenizer file. Defaults to |
|
for hierarchical |
|
Pinned artifact revision/content hash. |
|
no |
|
Model family: |
|
no |
|
Expected output dimensionality, validated per batch so a mismatched artifact/config fails loud. e5-base 768; e5-small 384; BGE-M3 1024. |
|
no |
family default |
Tokenizer truncation cap. Unset → the family’s natural context (8192 for |
|
no |
core count |
onnxruntime intra-op threads for the batched encode. |
|
no |
|
Records encoded per onnxruntime batch. The corpus is embedded in sequential batches of this size to bound peak memory on large inputs (padding is per-batch). Lower it if the embedder is memory-pressured; raise it for throughput on roomy hosts. |
Orchestrator (ORCHESTRATOR_*)#
Cross-cutting wiring shared by every use-case service (retry policy, token-budget estimation, metadata allow-list). Endpoint-specific tuning lives in its own settings group (see Analyze below) so the per-endpoint service split (ADR-011, ADR-017) didn’t require renaming environment variables in production.
Variable |
Required |
Default |
Notes |
|---|---|---|---|
|
no |
|
JSON list. Metadata keys allowed to reach the LLM. |
Analyze (ANALYZE_*)#
Configuration specific to POST /v1/analyze (both mode=single_pass
and mode=hierarchical). The coding-trend knobs apply to both modes;
the clustering knobs are only consulted on the hierarchical path.
Variable |
Required |
Default |
Notes |
|---|---|---|---|
|
no |
|
HDBSCAN |
|
no |
|
HDBSCAN distance metric ( |
|
no |
|
Max map-step chunks analysed concurrently ( |
|
no |
|
Target chunk size in estimated tokens — the chunking granularity knob ( |
|
no |
|
JSON list. Metadata keys holding coding labels (comma-separated strings). |
|
no |
|
Server-side default granularity for the coding-trend table ( |
Auth (AUTH_*)#
Variable |
Required |
Default |
Notes |
|---|---|---|---|
|
yes |
— |
JSON array of |
Database (DB_*)#
Usage tracking is always on, so a database connection is mandatory: the app
fails to start unless either DB_URL or the host/user/name parts below are
provided (see DatabaseSettings).
Variable |
Required |
Default |
Notes |
|---|---|---|---|
|
only if host/user/name not split |
|
Full asyncpg URL. Used when supplied; otherwise built from the next four. |
|
only if |
|
|
|
no |
|
|
|
only if |
|
|
|
only if |
|
For |
|
only when |
— |
Stored as |
|
no |
|
|
|
no |
|
AAD scope for the access token (Entra mode only). |
Logging (LOG_*)#
Variable |
Required |
Default |
Notes |
|---|---|---|---|
|
no |
|
Level for the |
|
no |
|
Level for third-party libraries. |