Dockerize chat_backend + Ollama LAN + DB file storage (#6) (#7)
Unit Tests / test (push) Successful in 13s

## Summary

Implements [chat_backend#6](#6) Part A:

- **uv** packaging (`pyproject.toml` + `uv.lock`), Docker/compose (dev + prod), entrypoint/validate-env, Gitea unit-test + auto-deploy workflows (mirror `scha`)
- Env-driven Django settings (`DJANGO_*`, `DATABASE_URL`, CSRF/CORS)
- **`OLLAMA_BASE_URL`** wired through all Ollama/LangChain clients (prod → `http://10.0.0.128:11434`)
- **DatabaseStorage** — prompt/document file blobs in Postgres (`StoredFile`), not container FS; RAG materializes temp paths for loaders
- ASGI via `gunicorn` + `UvicornWorker` (HTTP + WebSockets)

Companion server-infra PR registers `app_catalog` / `host_apps` (port **8003**).

## Test plan

- [ ] `uv sync && cd llm_be && SKIP_RAG_INIT=1 uv run python manage.py test`
- [ ] `docker compose build && docker compose up` against bundled Postgres
- [ ] Confirm Ollama calls use `OLLAMA_BASE_URL` (not hardcoded localhost)
- [ ] Upload a document / prompt file → row in `chat_backend_storedfile`, no disk under `media/`
- [ ] After server-infra merge + secret/Postgres/NPM: deploy via `deploy.sh --app chat_backend --env prod`Reviewed-on: #7
This commit was merged in pull request #7.
This commit is contained in:
2026-07-25 05:23:33 -07:00
parent 77d7edd0dc
commit d1660792ad
31 changed files with 6079 additions and 528 deletions
+135 -24
View File
@@ -1,40 +1,151 @@
# Chat Bot Backend
# Chat Backend
## Setup
Django + Channels API for AIML Operations chat (`chatbackend.aimloperations.com`).
Packaging via `uv`; production deploy via `server-infra`.
Clone the repo
```console
git clone http://10.0.0.160:3000/AI_ML_Operations_LLC/Chat_Bot_Backend.git
Companion frontend: [`chat_web_app`](https://git.aimloperations.com/ai_ml_operations/chat_web_app)
(node-static, not Docker).
Ticket: [chat_backend#6](https://git.aimloperations.com/ai_ml_operations/chat_backend/issues/6)
## Layout
```text
chat_backend/ ← repo root (Dockerfile, compose, pyproject, .gitea)
├── llm_be/ ← Django project root (manage.py)
│ ├── manage.py
│ ├── llm_be/ ← settings, urls, asgi/wsgi
│ └── chat_backend/ ← app (models, consumers, services, storage)
├── scripts/
│ ├── docker-entrypoint.sh
│ └── validate-env.sh
├── docker-compose.yml ← local/CI (bundled Postgres)
└── docker-compose.prod.yml ← server-infra (external DATABASE_URL)
```
Go into the repo
```console
cd Chat_Bot_Backend
## Local development
### Prerequisites
- Python 3.12+
- [uv](https://docs.astral.sh/uv/)
- Docker + Docker Compose (optional, recommended)
- Ollama reachable at `OLLAMA_BASE_URL` for LLM features
### uv (host)
```bash
cp .env.example .env
uv sync
cd llm_be
uv run python manage.py migrate
uv run python manage.py runserver 0.0.0.0:8003
```
Create the virtual environment
```console
virtualenv --python=python3.9 venv
Without `DATABASE_URL` / `DB_HOST`, settings fall back to SQLite (`llm_be/db.sqlite3`).
Tests (skip live-Ollama classifier cases):
```bash
cd llm_be
SKIP_RAG_INIT=1 uv run python manage.py test
```
Activate the Virtual Environment
```console
. venv/bin/activate
### Docker (dev, bundled Postgres)
```bash
docker compose up --build
```
Install the requirments
```console
python -m pip install -r requirements.txt
App: http://localhost:8003 — Postgres via bundled `db`
(`postgres://chat_backend:chat_backend@db:5432/chat_backend`).
Compose does **not** read host `DATABASE_URL` (avoids CI/prod leaks); override
with `COMPOSE_DATABASE_URL` if needed.
## Environment variables
| Variable | Dev default | Prod required | Notes |
|----------|-------------|---------------|-------|
| `DJANGO_ENV` | `dev` | `prod` / `beta` | |
| `DJANGO_SECRET_KEY` | insecure default | yes | Must be real in prod/beta |
| `DJANGO_DEBUG` | true when `dev` | `false` | |
| `DJANGO_ALLOWED_HOSTS` | localhost + chat hosts | yes | Comma-separated |
| `DJANGO_CSRF_TRUSTED_ORIGINS` | derived from hosts | optional | Full origins |
| `DATABASE_URL` | SQLite fallback | yes | Shared Postgres in prod |
| `WEB_PORT` | n/a (compose maps 8003) | `8003` | Host port for prod compose |
| `OLLAMA_BASE_URL` | `http://127.0.0.1:11434` | yes | GPU host in prod: `http://10.0.0.128:11434` |
| `OLLAMA_MODEL` / `OLLAMA_EMBED_MODEL` | from `DEBUG` | optional | Override model names |
| `EMAIL_HOST_*` | empty | yes (prod/beta) | SMTP2GO |
| `CAPTCHA_SECRET_KEY` | empty | recommended | |
| `CORS_ALLOWED_ORIGINS` | local + chat FE | set in prod | Frontend origin |
| `USE_TLS_PROXY` | false (dev) | true behind NPM | Sets `SECURE_PROXY_SSL_HEADER` |
| `GUNICORN_WORKERS` / `GUNICORN_BIND` | 2 / `0.0.0.0:8000` | optional | Entrypoint |
| `SKIP_RAG_INIT` | unset | CI/migrate often `1` | Skip Chroma/Ollama boot work |
Templates: `.env.example` (local), `.env.prod.example` (control-node secret).
Control-node secret path (server-infra on ai-server-4080):
```text
~/Documents/secrets/chat_backend/chat_backend_prod.env
```
Pre-populate the database with test data
Validate with:
Run the dev server
```console
python manage.py
```bash
./scripts/validate-env.sh ~/Documents/secrets/chat_backend/chat_backend_prod.env
```
## TODO
If `DATABASE_URL` password contains `$`, escape each as `$$` for Compose.
- [ ] Create inital database with temp data
- [ ] Do a lot of stuff.....
## Ollama
All clients (`ollama.Client`, `OllamaLLM`, `OllamaEmbeddings`, `ChatOllama`) use
`OLLAMA_BASE_URL` — never hardcoded localhost in deployed code.
| Env | Typical URL |
|-----|-------------|
| Local (Ollama on same machine) | `http://127.0.0.1:11434` |
| prod / beta (containers on adama/roslin/ai-server) | `http://10.0.0.128:11434` |
Firewall / Ollama listen on ai-server-4080 must allow `10.0.0.0/24``:11434`.
## File storage
Prompt attachments and workspace documents use **`DatabaseStorage`**
(`chat_backend.StoredFile` BinaryField in Postgres). Blobs are **not** written
to the container filesystem under `media/`.
RAG loaders that need a path materialize a short-lived temp file, then delete it.
Chromas vector index may still use a volume (`chroma_db`); that is embeddings
metadata, not the original upload.
## Production (docker-compose.prod.yml)
- Single `web` service; **no** bundled DB — `DATABASE_URL` → shared Postgres (`10.0.0.230`).
- Host port from `WEB_PORT` (catalog: **8003**; beta reserved **8013**).
- Entrypoint: wait DB → migrate → collectstatic → `gunicorn` + `UvicornWorker`
(ASGI for HTTP **and** WebSockets).
- Active/active on **adama + roslin + ai-server-4080**; NPM balances upstreams.
- Deployed by:
```bash
~/Documents/repos/server-infra/scripts/deploy.sh \
--app chat_backend --env prod --ref <sha>
```
## CI / CD (Gitea Actions)
| Workflow | Trigger | Action |
|----------|---------|--------|
| `unittests.yml` | push + PR → `master` | `uv sync` + `manage.py test` |
| `ci.yml` | PR → `master` | same unit tests |
| `deploy.yml` | after Unit Tests succeeds on `master` **push** | docker build + tests on **ephemeral compose Postgres**`deploy.sh` |
Deploy never runs on PRs.
## Security note
Secrets previously hardcoded in `settings.py` (email password, captcha, Django
secret) must live only in the control-node env file. Rotate anything that was
ever committed; never commit `.env` or `~/Documents/secrets/`.