> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dataerai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration reference

> Operator reference for self-hosting Dataerai's AI search: the environment variables behind the local Ollama planner, the CLIP embedding service, and the connection-probe guard. Most have working defaults — a typical deployment overrides only a handful.

This page is for **operators self-hosting Dataerai**. End users and organization admins don't need any of it — they use [AI search](/ai/llm-search), [agent configuration](/ai/agent-configuration), and [AI policy & access](/ai/policy-and-access) from the web UI.

<Note>
  Almost every setting below ships with a working default tuned for the bundled `docker compose` stack. A fresh deployment typically overrides **only a handful** — usually nothing at all if you run the bundled `ollama` and `clip-service` containers. Override a variable only when you're moving a service off its default host or turning on an opt-in lane.
</Note>

## The local planner (Ollama)

AI search translates natural language into the metadata DSL using a **local** model — the unrestricted-tier provider is pinned to `ollama_planner` **in code**, not via an environment variable, so a plain `docker restart` applies it. You point it at a model with these:

| Variable               | Default               | Purpose                                                                                                                                             |
| ---------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OLLAMA_URL`           | `http://ollama:11434` | Base URL of the Ollama server. Defaults to the compose service DNS name — no override needed if you run the bundled container.                      |
| `OLLAMA_PLANNER_MODEL` | `qwen2.5:7b`          | Model tag for NL→DSL translation. Must be pulled on that server.                                                                                    |
| `LLM_TIMEOUT_S`        | `60`                  | Ceiling per planner call. A cold model can spend \~10 s loading into VRAM on the first request after idle; cloud providers answer well within this. |
| `LLM_SEARCH_RATE`      | `10/min`              | Per-user rate limit on the search endpoint.                                                                                                         |

<Note>
  These defaults assume the bundled `ollama` service. To run AI search against a model on hardware you control **without** editing operator env, use [agent configuration](/ai/agent-configuration) from the web UI instead — it overrides the resolved endpoint per organization or per user.
</Note>

## The CLIP embedding service

The optional `semantic_match` lane ranks figures by visual similarity. It embeds query text and figure images into a shared CLIP space.

| Variable                 | Default                    | Purpose                                                                                                                                                           |
| ------------------------ | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SEMANTIC_MATCH_ENABLED` | `false`                    | Master switch for the `semantic_match` DSL op. **Off by default** — flip on only after embeddings have been backfilled for your corpus.                           |
| `SEMANTIC_MATCH_TOP_K`   | `200`                      | ANN neighbours fetched per `semantic_match` leaf. Large enough that AND-narrowing with structured leaves still has headroom while pgvector HNSW stays sub-100 ms. |
| `CLIP_SERVICE_URL`       | `http://clip-service:8000` | Base URL of the embedding service. Defaults to the compose service DNS name.                                                                                      |
| `CLIP_SERVICE_TOKEN`     | `dev-clip-token`           | Bearer token for the CLIP service. **Set a real value in production.**                                                                                            |
| `CLIP_SERVICE_TIMEOUT_S` | `60`                       | Per-request timeout against the CLIP service.                                                                                                                     |
| `CLIP_EMBEDDING_DIM`     | `768`                      | Vector dimension (ViT-L/14 emits 768). Changing the CLIP model **invalidates existing embedding rows** — re-backfill after a model swap.                          |

<Warning>
  `semantic_match` stays dormant until `SEMANTIC_MATCH_ENABLED=true` **and** your images have been embedded. With the flag off, the parser strips the op and topical queries fall back to the structured translation — so turning the flag on before backfilling yields empty visual results, not errors.
</Warning>

## External provider tiers

These are only consulted when an organization has [allowed external AI](/ai/policy-and-access) and a stricter tier serves its requests. Each tier resolves a provider by name; an unconfigured provider safely degrades to the disabled provider rather than leaking to a default.

| Variable                                                                         | Default                        | Purpose                                                                                                                        |
| -------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| `LLM_PROVIDER_ZDR`                                                               | `anthropic_zdr`                | Provider for the Zero-Data-Retention tier.                                                                                     |
| `LLM_PROVIDER_PRIVATE_ENDPOINT`                                                  | `bedrock_private`              | Provider for the private-endpoint tier.                                                                                        |
| `LLM_PROVIDER_SELF_HOSTED`                                                       | *(empty)*                      | Provider name for a self-hosted external tier; empty disables it.                                                              |
| `LLM_MODEL`                                                                      | `claude-haiku-4-5`             | Model for cloud providers.                                                                                                     |
| `LLM_MAX_TOKENS`                                                                 | `1024`                         | Output-token ceiling for cloud providers.                                                                                      |
| `LLM_ANTHROPIC_ZDR_HEADER`                                                       | `zdr-2024-12-01`               | Anthropic beta header value enforcing ZDR. Update to whatever Anthropic publishes for the active cohort.                       |
| `BEDROCK_REGION`, `BEDROCK_ENDPOINT_URL`, `BEDROCK_MODEL_ID`, `BEDROCK_ROLE_ARN` | region `us-east-1`, rest empty | Private-endpoint (PrivateLink) Bedrock adapter. An empty `BEDROCK_ENDPOINT_URL` leaves the provider not-configured → disabled. |
| `OPENAI_COMPATIBLE_URL`, `OPENAI_COMPATIBLE_MODEL`                               | *(empty)*                      | OpenAI-compatible self-hosted adapter (vLLM / Ollama). Empty URL → disabled.                                                   |

## Connection-probe guard

The **Test connection** button in [agent configuration](/ai/agent-configuration) makes the Dataerai server probe the URL an admin entered. To prevent it from being used to reach internal infrastructure (SSRF), public destinations are rejected unless explicitly allowlisted.

| Variable                         | Default   | Purpose                                                                                                                                                                        |
| -------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `INFERENCE_PROBE_HOST_ALLOWLIST` | *(empty)* | Hostnames the probe may reach on the public internet. Private/loopback addresses are handled by the built-in guard; add a host here only to permit a specific public endpoint. |

<Note>
  Saved CLIP service tokens are **encrypted at rest** using the same secret store as other repository secrets. The token is write-only over the API — it is never returned once saved.
</Note>

## Vocabulary discovery

| Variable                  | Default | Purpose                                                                                                                                                                                                                                                                                  |
| ------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LLM_VOCAB_MAX_SCAN_ROWS` | `50000` | Upper bound on accessible-asset rows scanned when building a user's canonicalized metadata vocabulary. Caps both the SQL aggregation and the nested-key Python sample. This is the **fallback** lane the coverage gate drops to when scope profiles don't cover enough of a user's data. |

## Grounded OR-tree search

The lane that designs multi-branch queries from per-scope metadata
statistics ([Grounded OR-tree search](/ai/grounded-search)). Both feature
flags ship **off**; the profile worker and statistics build in the
background regardless, so flipping the read flag on is graceful.

| Variable                      | Default  | Purpose                                                                                                                                                                                                                                                    |
| ----------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LLM_QUERY_TREE_ENABLED`      | `false`  | Master switch for recursive OR-tree design, the evidence-table prompt, branch probes, and the one-pass repair. Off → the legacy single-query lane runs, byte-identical. Safe to flip on without warm profiles (it falls back to the live vocabulary scan). |
| `LLM_VALUE_EMBED_ENABLED`     | `false`  | Enables the distinct-value embedding worker **and** the `value_like` ("like this") matching. Off → `value_like` degrades to a trigram text match. Flip on only after the value-embedding queue has drained for your active concepts.                       |
| `LLM_VALUE_EMBED_TOP_N`       | `64`     | Top redaction-clean values embedded per concept on each profile rebuild.                                                                                                                                                                                   |
| `LLM_EVIDENCE_MIN_COVERAGE`   | `0.8`    | Below this fraction of a user's visible assets covered by scope profiles, the evidence path falls back to the legacy live scan — so a thin-profile user is never served worse evidence than before.                                                        |
| `LLM_EVIDENCE_RESIDUAL_CAP`   | `2000`   | Max directly-granted assets walked exactly to cover the tail the scope profiles miss.                                                                                                                                                                      |
| `LLM_REPAIR_ENABLED`          | `true`   | Whether a designed query with an empty ("certain-zero") branch triggers one corrective model pass. Per-tier kill switch.                                                                                                                                   |
| `LLM_BRANCH_PROBE_TIMEOUT_MS` | `2000`   | Per-branch statement timeout for the match-count probes (each in its own transaction, so a slow branch can't poison the others).                                                                                                                           |
| `LLM_BRANCH_PROBE_MAX`        | `8`      | Max branches probed per request.                                                                                                                                                                                                                           |
| `PROFILE_MIN_SCOPE_ASSETS`    | `1000`   | Collections smaller than this aren't profiled — the residual live walk covers them.                                                                                                                                                                        |
| `PROFILE_FULL_SCAN_MAX`       | `200000` | Scopes at or below this size are profiled exactly; larger ones are ID-anchor **sampled** (so a rebuild's cost is bounded no matter how large the scope).                                                                                                   |
| `PROFILE_DEBOUNCE_S`          | `300`    | A burst of writes to one scope produces at most one rebuild this many seconds out.                                                                                                                                                                         |
| `PROFILE_SIGNALS_ENABLED`     | `true`   | Master switch for the write-path staleness signals that keep profiles fresh.                                                                                                                                                                               |

<Note>
  Profiles are keyed on the collections/grants the access-control layer
  actually grants, and merged at request time from **live** permission
  rows — so revoking a share drops that data from a user's evidence on
  their very next search, with no rebuild. No statistic or value from
  data a user can't see ever enters their prompt, counts, or suggestions.
</Note>

The background worker that builds profiles and value embeddings runs as
its own deployment (`profileWorker` in the Helm values) with a nightly
self-heal job; it is a no-op for the value lane until
`LLM_VALUE_EMBED_ENABLED` is on.

## AI-search (Ask AI) visibility

There are **no build-time frontend flags** for LLM-search — the feature
ships compiled into every build. Visibility of the **Ask AI** button is
controlled at runtime by two policy gates that Dataerai combines in
`acl.llm_search_allowed_for_user`:

| Gate                             | Where it's toggled              | Default for new accounts  |
| -------------------------------- | ------------------------------- | ------------------------- |
| `Organization.allow_external_ai` | Settings → Organizations → Edit | `False` (org opts in)     |
| `User.ai_search_enabled`         | Settings → Profile              | `True` (per-user opt-out) |

Both must be true for the **Ask AI** affordance to render. The user
opt-in is checked first — an opted-out user is hidden from the feature
even if their org permits external AI.

## Search log retention

Each AI-search request is recorded in an internal search log used for observability and usage reporting. The log grows with traffic, so prune it on a schedule with the management command:

```bash theme={null}
python manage.py prune_llm_search_log --days 90
```

It deletes entries older than the retention window (`--days`, default `90`) in bounded batches, leaving recent ones for reporting. Run it from a nightly cron job to keep the table bounded; pass `--dry-run` to preview how many rows would be removed.

## The minimal set

If you run the bundled `docker compose` stack with the built-in `ollama` and `clip-service` containers, the defaults already work. In practice a real deployment touches only:

* `CLIP_SERVICE_TOKEN` — replace the dev token.
* `OLLAMA_PLANNER_MODEL` — pin the model you've pulled (if not `qwen2.5:7b`).
* `SEMANTIC_MATCH_ENABLED` — flip to `true` once you've backfilled embeddings.
* Provider-tier and Bedrock vars — **only** if you offer external AI tiers.
* An org admin flips `Organization.allow_external_ai = true` on each org that should see the feature.

Everything else has a safe default or is pinned in code.

## Next steps

<CardGroup cols={2}>
  <Card title="Agent configuration" icon="server" href="/ai/agent-configuration">
    The web-UI way to redirect inference, no operator env required.
  </Card>

  <Card title="AI policy & access" icon="shield-check" href="/ai/policy-and-access">
    The org-level opt-in that gates external providers.
  </Card>
</CardGroup>
