Files
duocthu/docs/15-configuration.md
T

127 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_cache`d, 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](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](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.