Files
duocthu/docs-legacy/15-configuration.md
T

6.8 KiB
Raw Blame History

15 — Configuration

Where configuration is defined

apps/ai-service/config.py is the single authority for the Python service: every setting is a field on the Pydantic Settings class, loaded from the environment or from a .env file next to the process, with extra="ignore". get_settings() is @lru_cached, so values are read once per process.

There is no .env.example anywhere in the repository. The only env file is apps/ai-service/.env, which is gitignored and local; production uses apps/ai-service/.env.prod, which is also gitignored and lives only on the EC2 host. A new engineer therefore has no committed template to copy — see 27-technical-debt.md.

ai-service settings

Variable Required Default Purpose Secret
APP_NAME no vsf-duoc-thu-ai-service FastAPI title no
ENVIRONMENT no local Label; sent as deployment.environment on OTel resource no
QDRANT_URL effectively yes http://localhost:6333 Vector store no
QDRANT_COLLECTION no duocthu_v1 Collection name; the manifest sidecar is <name>__manifest no
QDRANT_API_KEY no None Qdrant auth yes
POSTGRES_DSN no postgresql://duoc_thu:duoc_thu@localhost:5432/duoc_thu Traces, turns, feedback. Declared repr=False so it is not echoed yes
EMBEDDING_PROVIDER no cohere-v4 cohere-v4 or disabled. Any other value raises at startup no
EMBEDDING_DIMENSIONS no 1024 Must match the corpus manifest or startup fails no
EVIDENCE_MINIMUM_SCORE no 0.12 Dense-route score floor no
AWS_REGION no us-east-1 Bedrock region no
ANSWER_PROVIDER no disabled disabled | stub | bedrock-converse | bedrock-claude. Chooses the operating mode no
ANSWER_MODEL_ID no deepseek.v3.2 Bedrock model id no
RERANK_ENABLED no false Enables cohere.rerank-v3-5:0 on the fallback route no
METRICS_ENABLED no true Builds the Prometheus exporter no
METRICS_TOKEN no "" Bearer token for GET /metrics; empty = unauthenticated yes
OTEL_ENABLED no false Turns on OTLP export no
OTEL_SERVICE_NAME no ai-service no
OTEL_EXPORTER_OTLP_ENDPOINT no http://localhost:4318/v1/traces OTLP/HTTP traces endpoint no
OTEL_SAMPLE_RATIO no 1.0 (0.01.0) TraceIdRatioBased sampler no
ENTITIES_PATH in the container repo-relative ingestion/data/verified/drug_entities.json Drug alias catalog no
MAX_WALL_CLOCK_MS no 40000 Per-turn budget no
MAX_LLM_CALLS_PER_TURN no 8 Per-turn budget no

ENTITIES_PATH needs an explicit value in the container: config.py's default resolves two parents up from apps/ai-service/config.py, and the image flattens apps/ai-service/ into /app, so the depth is wrong. The Dockerfile bakes the file to ./ingestion_data/drug_entities.json and .env.prod points at it.

Settings that change behaviour, not just tuning

Three values are mode switches rather than knobs:

Setting Effect
EMBEDDING_PROVIDER=disabled build_runtime returns no answer service and no agent. /v1/rag/query answers 503, while /ready still answers 200.
ANSWER_PROVIDER=disabled No RagAgent, no understanding, no multi-turn. Retrieval-only, single-turn, verbatim quotes.
EMBEDDING_DIMENSIONS ≠ manifest Startup raises ManifestMismatch and the process does not come up.

web settings

Variable Required Default Purpose
API_GATEWAY_URL no Preferred upstream; accepts a base URL or a full /v1/rag/... URL
AI_SERVICE_URL no http://localhost:8000 Fallback; set to http://ai-service:8000 in prod Compose

Rate-limit rules are hard-coded constants in middleware.ts, not configuration: /api/chat 12/min and 120/hour; /api/suggest 120/min.

ingestion settings

ingestion takes no environment variables. Everything is a CLI flag (--pdf, --out, --tables, --monographs, --chunks, --provider, --collection, --region, --qdrant-url, --slice-size, --attempts, --embed-only). AWS credentials come from the standard boto3 chain.

Deployment-layer configuration

Layer File Notes
Production Compose infra/docker/docker-compose.prod.yml ai-service reads env_file: ../../apps/ai-service/.env.prod (not in the repo)
Observability overlay infra/docker/docker-compose.observability.yml Sets OTEL_ENABLED=true, OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318/v1/traces, ENVIRONMENT=compose; reads GRAFANA_ADMIN_USER / GRAFANA_ADMIN_PASSWORD from the shell
Helm values.yaml + values-{dev,staging,prod}.yaml Maps to a ConfigMap of the same env vars; POSTGRES_DSN comes from a Secret
CI .github/workflows/deploy.yml Uses EC2_HOST, EC2_SSH_KEY, GRAFANA_ADMIN_PASSWORD GitHub secrets

Helm chart defaults are not production defaults

infra/helm/medical-chatbot/values.yaml ships aiService.config.embeddingProvider: disabled and answerProvider: disabled, i.e. a deployment of the chart as-is answers 503 on /v1/rag/query. It also ships secret.postgresPassword: duoc_thu and secret.grafanaAdminPassword: change-me as literal defaults.

Secrets inventory

Secret Where it lives Committed?
PostgreSQL password docker-compose.prod.yml env (duoc_thu/duoc_thu), Helm secret.postgresPassword Yes — a default credential is in the repository
Grafana admin password GRAFANA_ADMIN_PASSWORD GitHub secret → shell env; Helm default change-me Secret value not committed; the placeholder default is
AWS credentials EC2 instance IAM role No — deliberately; the Compose header comment says so
QDRANT_API_KEY Unset (Qdrant is not exposed) No
METRICS_TOKEN Unset No
EC2 host + SSH key GitHub Actions secrets No

git ls-files shows no .env file tracked, and the two IAM documents under infra/aws/iam/ are policy JSON, not credentials. The one real issue is the PostgreSQL default credential, which is committed in two places — see 16-security.md.

Configuration verified this session

apps/ai-service/.env (local, gitignored) contains:

EMBEDDING_PROVIDER=cohere-v4
ANSWER_PROVIDER=bedrock-converse
ANSWER_MODEL_ID=qwen.qwen3-next-80b-a3b
RERANK_ENABLED=true
AWS_REGION=us-east-1
QDRANT_COLLECTION=duocthu_v1
QDRANT_URL=<local>

Note the drift: the code default for ANSWER_MODEL_ID is deepseek.v3.2, the local .env uses qwen.qwen3-next-80b-a3b, and the production value is unverifiable from the repository because .env.prod is not committed. Any statement about which model production runs would be a guess.