Why: - the slice had no operator documentation: no tuning guidance, no failure procedure, no statement of the proxy timeout requirement. Changes: - add docs/runbook.md: startup, deployment steps, provisioning, the INGESTION_* tuning table with each bound's status code, the proxy read-timeout rule, /healthz vs /readyz, failure investigation by real event name plus job/event SQL, retry semantics, and alert thresholds - link it from the README and note provisioning there - mark plan 001 Phase 6 done and refresh CLAUDE.md's status paragraph Impact: - documentation only Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
95 lines
3.0 KiB
Markdown
95 lines
3.0 KiB
Markdown
# Talie chatbot service
|
|
|
|
Architecture decisions live in [`docs/adr`](docs/adr). The first implementation
|
|
milestone is documented in the [ingestion vertical-slice plan](docs/plans/001-ingestion-vertical-slice.md).
|
|
Day-to-day operation — tuning the ingestion bounds, the proxy timeout
|
|
requirement, and how to investigate or retry a failed upload — is the
|
|
[operator runbook](docs/runbook.md).
|
|
|
|
## Provisioning the datastores
|
|
|
|
Both schema steps run as explicit deployment steps. The application performs no
|
|
DDL at startup — not for Postgres (ADR-0009) and not for Qdrant (ADR-0001,
|
|
"Collection provisioning").
|
|
|
|
```bash
|
|
docker compose up -d # Postgres, MinIO, Qdrant
|
|
uv run alembic upgrade head # Postgres schema
|
|
uv run python -m src.cli.qdrant_bootstrap # the `chunks` collection
|
|
uv run fastapi dev src/main.py
|
|
```
|
|
|
|
Nothing over HTTP can create the first tenant — every `/v1` route needs an API
|
|
key, and a key cannot exist before its tenant. One command issues both, plus any
|
|
domains, printing the key once (only its hash is stored):
|
|
|
|
```bash
|
|
uv run python -m src.cli.provision_tenant --slug acme --domain fire
|
|
```
|
|
|
|
Before a tenant can upload, its domains must be registered — `POST /v1/files`
|
|
rejects an unregistered or disabled `domain` with `400`. The calling backend
|
|
manages them over `/v1/domains` using a key with the `domains:write` scope:
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8000/v1/domains \
|
|
-H "Authorization: Bearer $API_KEY" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"domain": "fire", "display_name": "Fire insurance"}'
|
|
```
|
|
|
|
Both bootstrap commands are idempotent and safe to re-run. `qdrant_bootstrap` verifies an
|
|
existing collection against the pinned schema and exits non-zero on a mismatch,
|
|
rather than leaving a silently degraded sparse index in place.
|
|
|
|
`./scripts/smoke.sh` verifies the whole path — Compose up, both deployment
|
|
steps, provisioning, an upload through the running web process to indexed Qdrant
|
|
points. See the [runbook](docs/runbook.md#12-verifying-a-deployment).
|
|
|
|
## Local Langfuse
|
|
|
|
This repo includes a root-level development Compose file for Langfuse:
|
|
|
|
- [`docker-compose.langfuse.yml`](docker-compose.langfuse.yml)
|
|
- [`.env.langfuse.example`](.env.langfuse.example)
|
|
|
|
Start Langfuse locally:
|
|
|
|
```bash
|
|
cp .env.langfuse.example .env.langfuse
|
|
# edit .env.langfuse and replace CHANGE_ME values
|
|
|
|
docker compose --env-file .env.langfuse -f docker-compose.langfuse.yml up -d
|
|
```
|
|
|
|
Open:
|
|
|
|
```text
|
|
http://localhost:3000
|
|
```
|
|
|
|
If the chatbot app runs on your host machine, configure it with:
|
|
|
|
```env
|
|
LANGFUSE_HOST=http://localhost:3000
|
|
```
|
|
|
|
If the chatbot app later runs inside the same Compose project/network as
|
|
Langfuse, configure it with:
|
|
|
|
```env
|
|
LANGFUSE_HOST=http://langfuse-web:3000
|
|
```
|
|
|
|
A future app stack can be launched together with Langfuse using multiple Compose
|
|
files:
|
|
|
|
```bash
|
|
docker compose \
|
|
-f docker-compose.yml \
|
|
-f docker-compose.langfuse.yml \
|
|
--env-file .env \
|
|
--env-file .env.langfuse \
|
|
up -d
|
|
```
|