This file says "every key, no values" at the top and was not that, which cost
real time twice on lin001 in one day.
- CUBEJS_DB_* was absent entirely, while ai-compose.yml states that Cube's
database settings come from api.env. Deploying Cube meant reconstructing
what it needed from compose comments and db/003_roles.sql. Added, with the
pg-ai.env names its credentials come from, and a note that Phase 4 turns
the block into an mssql connection against imh.
Also recorded NEGATIVELY: not CUBEJS_EXT_DB_*. Cube v1 refuses Postgres as
an external pre-aggregation store, so someone reading the older compose
file will otherwise try to supply keys for a setting that must not exist.
- NO_LLM_STUB, which is new and defaults to false here for the same reason it
defaults to false in config.py.
- The Langfuse keys were listed but not explained, and every way of getting
them wrong is SILENT:
absent -> _langfuse() returns None, every question untraced
mismatched pair -> a client is built, Langfuse rejects it, and main.py
swallows the exception by design
Neither logs anything, in an answer path that is deliberately built never
to break on observability. The symptom is identical - no traces - so
correcting one cause while the other is still present looks like no
progress at all. That is exactly what happened: a placeholder secret was
pasted alongside a public key from a different pair, and the fix looked
like it had not worked.
So the file now says: they are PROJECT keys from the UI, not the server's
own SALT/NEXTAUTH_SECRET in langfuse.env; they must be a matched pair; the
secret is shown once and stored hashed, so it cannot be read back; and
traces must be confirmed as ARRIVING rather than inferred from config.
No secrets here, including the masked tail of a real key - the example suffix
is invented.
The Langfuse fix itself is not in this commit and cannot be: it was two lines
in ~/ai/api.env, which .gitignore excludes on purpose.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
134 lines
6.1 KiB
Text
134 lines
6.1 KiB
Text
# =============================================================================
|
|
# .env.example — every key, no values. Committed deliberately.
|
|
#
|
|
# On lin001 these live as TWO 0600 files under ~/ai/, never in Git:
|
|
# ~/ai/pg-ai.env the POSTGRES_* / AGENT_DB_* block
|
|
# ~/ai/api.env everything else
|
|
# Follow the ~/authelia/authelia.env precedent: chmod 0600, owned by azureuser.
|
|
# =============================================================================
|
|
|
|
# --- pg-ai (~/ai/pg-ai.env) --------------------------------------------------
|
|
POSTGRES_PASSWORD=
|
|
AGENT_DB_USER=agent_ro
|
|
AGENT_DB_PASSWORD=
|
|
|
|
# --- imh (PENDING — leave blank until Phase 4) -------------------------------
|
|
IMH_HOST=yau-sls-poc-imh
|
|
IMH_PORT=1433
|
|
IMH_DB=
|
|
IMH_USER=svc_agent_ro
|
|
IMH_PASSWORD=
|
|
USE_FIXTURES=true # flip to false when imh is live
|
|
|
|
# --- local Postgres ----------------------------------------------------------
|
|
PGHOST=pg-ai
|
|
PGPORT=5432
|
|
PGDATABASE=plant
|
|
PGUSER=agent_ro
|
|
PGPASSWORD=
|
|
|
|
# --- Azure OpenAI ------------------------------------------------------------
|
|
AZURE_OPENAI_ENDPOINT=
|
|
AZURE_OPENAI_API_KEY=
|
|
AZURE_OPENAI_API_VERSION=
|
|
CHAT_DEPLOYMENT= # flagship — final prose only
|
|
CHEAP_DEPLOYMENT= # nano/mini — classifier, entities, tool selection
|
|
EMBED_DEPLOYMENT= # text-embedding-3-small
|
|
|
|
# --- no-LLM stub mode --------------------------------------------------------
|
|
# OFF for anything real. With it on, no model is called: the class comes from
|
|
# the caller instead of the classifier and the prose is a fixed placeholder.
|
|
# Every answer carries stub_mode: true and a banner. See api/stub.py for what
|
|
# it proves and what it does not.
|
|
NO_LLM_STUB=false
|
|
|
|
# --- behaviour ---------------------------------------------------------------
|
|
CLASSIFIER_CONFIDENCE_THRESHOLD=0.7
|
|
SITE_TIMEZONE=Australia/Sydney # storage UTC; convert once, in Cube
|
|
MAX_ROWS_RETURNED=5000
|
|
QUERY_TIMEOUT_SECONDS=30
|
|
MAX_OUTPUT_TOKENS=1200
|
|
|
|
# --- Cube --------------------------------------------------------------------
|
|
CUBEJS_API_SECRET=
|
|
CUBEJS_API_URL=http://cube:4000/cubejs-api/v1
|
|
|
|
# The upstream source Cube reads. ai-compose.yml says these come from this
|
|
# file, and this file did not list them - so the first person to deploy Cube
|
|
# had to work out from the compose comments and db/003_roles.sql what the
|
|
# service actually needed. While USE_FIXTURES=true the fixtures live in pg-ai,
|
|
# so this points at pg-ai with the cube_rw credentials from pg-ai.env.
|
|
# At Phase 4 the whole block becomes CUBEJS_DB_TYPE=mssql against imh.
|
|
CUBEJS_DB_TYPE=postgres
|
|
CUBEJS_DB_HOST=pg-ai
|
|
CUBEJS_DB_PORT=5432
|
|
CUBEJS_DB_NAME=plant
|
|
CUBEJS_DB_USER=cube_rw # CUBE_DB_USER in pg-ai.env
|
|
CUBEJS_DB_PASS= # CUBE_DB_PASSWORD in pg-ai.env
|
|
|
|
# NOT CUBEJS_EXT_DB_*. Cube v1 will not use Postgres as an external
|
|
# pre-aggregation store; cubestore does that job and needs no keys here.
|
|
# See the note above the cube service in compose/ai-compose.yml.
|
|
|
|
# --- Langfuse ----------------------------------------------------------------
|
|
# PROJECT keys, created in the Langfuse UI - not the server's own secrets
|
|
# (SALT, NEXTAUTH_SECRET), which live in langfuse.env and are a different
|
|
# thing. The two must be a MATCHED PAIR from the same key: a public key from
|
|
# one pair with a secret from another authenticates as neither.
|
|
#
|
|
# Langfuse shows the secret ONCE, at creation, and stores only a hash - the
|
|
# database keeps a masked form (sk-lf-...abcd) and nothing can recover it. If
|
|
# it is lost, generate a new pair; there is no way to read the old one back.
|
|
#
|
|
# Getting this wrong is silent in both directions. Keys absent -> _langfuse()
|
|
# returns None and every question is simply untraced. Keys present but wrong ->
|
|
# a client is built, Langfuse rejects it, and main.py swallows the exception on
|
|
# purpose, because observability must never break the answer path. Neither case
|
|
# logs anything. Confirm traces are ARRIVING; do not infer it from config.
|
|
LANGFUSE_HOST=http://langfuse:3000
|
|
LANGFUSE_PUBLIC_KEY=
|
|
LANGFUSE_SECRET_KEY=
|
|
LANGFUSE_SALT=
|
|
LANGFUSE_NEXTAUTH_SECRET=
|
|
LANGFUSE_DB_PASSWORD=
|
|
|
|
# --- ingest ------------------------------------------------------------------
|
|
AI_DOCS_ROOT=/datadisk/ai-docs
|
|
CHUNK_TOKEN_TARGET=800
|
|
|
|
# --- document upload (Phase 9) -----------------------------------------------
|
|
# Two more roles, because the component reachable from the internet must not be
|
|
# the component that can write doc_chunks. See db/004_doc_uploads.sql.
|
|
UPLOADS_DB_USER=uploads_rw # ai-api: the queue only, never doc_chunks
|
|
UPLOADS_DB_PASSWORD=
|
|
INGEST_DB_USER=ingest_rw # ai-docs-worker AND the ai-ingest CLI:
|
|
# doc_chunks + the queue. PGUSER is agent_ro
|
|
# and cannot INSERT - see BUILD-AI-CONTAINERS §16.1.
|
|
INGEST_DB_PASSWORD=
|
|
|
|
AI_DOCS_INBOX=/datadisk/ai-docs-inbox # writable staging; NOT the ingest root
|
|
AI_DOCS_WITHDRAWN=/datadisk/ai-docs-withdrawn # withdrawn files are moved, not deleted
|
|
MAX_UPLOAD_MB=50
|
|
UPLOAD_DISK_LIMIT_PCT=90 # refuse uploads above this on /datadisk
|
|
ALLOWED_UPLOAD_EXTENSIONS=.pdf,.docx,.md,.txt
|
|
|
|
# DIRECT membership only — Authelia does not resolve nested groups.
|
|
DOC_PUBLISHER_GROUP=AI_DocPublishers
|
|
# Who may curate the retrieval pool and run trimmed-pool demos. Defaults to the
|
|
# publisher group; point it at a narrower AD group if that should be separate.
|
|
DOC_ADMIN_GROUP=AI_DocPublishers
|
|
ALLOW_SELF_APPROVAL=false # uploader approving their own document
|
|
# Withdrawal (superseded = TRUE) is always available to the publisher group and
|
|
# is reversible. PURGE deletes chunks and is not. Leave it off unless there is a
|
|
# document that must not be in the database at all.
|
|
ALLOW_PURGE=false
|
|
|
|
# Retrieval pool. pool_enabled is orthogonal to superseded - see db/006_doc_pool.sql.
|
|
# Below this share of documents enabled, retrieval drops to an exact scan: the
|
|
# HNSW index is built over ALL embeddings and filters afterwards, so a heavily
|
|
# trimmed pool can return almost nothing. This bites in exactly the demo that
|
|
# trims the pool. Rehearse it.
|
|
POOL_EXACT_SCAN_BELOW_PCT=50
|
|
|
|
WORKER_POLL_SECONDS=10
|
|
WORKER_LEASE_MINUTES=30 # an `ingesting` row older than this is a dead worker
|