# Deployment Guide ## Prerequisites - **Podman** (or Docker) with compose plugin - **Ollama** running with models: `gpt-oss:20b`, `qwen3-embedding:8b` - At least 8 GB RAM for the marker-api container (model loading) - GPU recommended but not required for marker-pdf OCR ## Quick Start (Podman/Docker Compose) ### 1. Update Environment Variables Create `.env` in the project root: ```env OLLAMA_URL=http://10.0.1.127:11434 GPT_OSS_MODEL=gpt-oss:20b MARKER_API_URL=http://marker-api:8001 DB_HOST=postgres DB_PORT=5432 DB_NAME=research DB_USER=research DB_PASSWORD=research123 VECTOR_DIM=4096 ``` ### 2. Update `podman-compose.yml` `VECTOR_DIM` In `podman-compose.yml`, under `app.environment`, set: ```yaml VECTOR_DIM: 4096 ``` ### 3. Build & Start ```bash # Build all images podman-compose build # Start services (PostgreSQL, marker-api, app) podman-compose up -d # Or with Docker: # docker compose up -d ``` ### 4. Verify Services ```bash # Health check curl http://localhost:8000/health # Should return: # {"status":"ok","db":"connected","ollama":"connected","marker_api":"ok"} ``` ### 5. Upload Documents ```bash curl -X POST http://localhost:8000/api/documents/upload \ -F "file=@document.pdf" ``` ### 6. Run Research ```bash curl -X POST http://localhost:8000/api/research/run \ -H "Content-Type: application/json" \ -d '{"query":"Your question","doc_id":"","skills":["summarizer"]}' ``` --- ## Manual Container Setup (Podman, host networking) Use this if you need direct host network access (e.g., Ollama on `10.0.1.127`): ### PostgreSQL + pgvector ```bash podman run -d --name pv-db --network host \ -e POSTGRES_DB=research \ -e POSTGRES_USER=research \ -e POSTGRES_PASSWORD=research123 \ -v pgdata:/var/lib/postgresql/data \ -v ./migrations/init.sql:/docker-entrypoint-initdb.d/01-init.sql \ docker.io/pgvector/pgvector:pg16 ``` ### Marker OCR API ```bash cd marker-api # Build (slow first time - downloads marker-pdf + surya models) podman build -t marker-api . # Run with model cache volume podman run -d --name marker-api --network host \ -v marker_models:/root/.cache \ -v marker_ws:/workspace \ -e OLLAMA_URL=http://10.0.1.127:11434 \ localhost/marker-api ``` **Model warmup** (required before first use): ```bash podman exec marker-api poetry run python3 -c " from marker.scripts.convert_single import convert_single_cli import sys # Run once to trigger model download sys.argv = ['warmup', '/dev/null', '--output_dir', '/tmp/warmup', '--page_range', '0-0'] try: convert_single_cli(sys.argv[1:], standalone_mode=False) except: pass print('Models downloaded') " ``` Then commit the warm image to persist models: ```bash podman commit marker-api localhost/marker-api:latest ``` ### App Container ```bash podman build -t agentic-app . podman run -d --name agentic-app --network host \ -v app_ws:/workspace \ -e OLLAMA_URL=http://10.0.1.127:11434 \ -e GPT_OSS_MODEL=gpt-oss:20b \ -e MARKER_API_URL=http://localhost:8001 \ -e DB_HOST=localhost \ -e DB_PORT=5432 \ -e DB_NAME=research \ -e DB_USER=research \ -e DB_PASSWORD=research123 \ -e VECTOR_DIM=4096 \ localhost/agentic-app ``` --- ## Kubernetes Deployment ### 1. PostgreSQL StatefulSet ```yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres spec: serviceName: postgres replicas: 1 template: spec: containers: - name: postgres image: pgvector/pgvector:pg16 env: - name: POSTGRES_DB value: research - name: POSTGRES_USER value: research - name: POSTGRES_PASSWORD value: research123 volumeMounts: - name: pgdata mountPath: /var/lib/postgresql/data - name: init-sql mountPath: /docker-entrypoint-initdb.d/01-init.sql subPath: init.sql volumeClaimTemplates: - metadata: name: pgdata spec: accessModes: [ReadWriteOnce] resources: requests: storage: 10Gi --- apiVersion: v1 kind: ConfigMap metadata: name: init-sql data: init.sql: | CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE IF NOT EXISTS documents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), filename TEXT NOT NULL, doc_id TEXT, mime_type TEXT, file_path TEXT, status TEXT DEFAULT 'pending', page_count INTEGER DEFAULT 0, full_text TEXT, metadata JSONB DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS chunks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), doc_id UUID REFERENCES documents(id) ON DELETE CASCADE, content TEXT NOT NULL, vector VECTOR(4096), page_num INTEGER, block_index INTEGER, polygon JSONB, chunk_type TEXT DEFAULT 'text', created_at TIMESTAMPTZ DEFAULT NOW() ); -- (full schema in migrations/init.sql) ``` ### 2. Marker-API Deployment ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: marker-api spec: replicas: 1 template: spec: containers: - name: marker-api image: marker-api:latest ports: - containerPort: 8001 env: - name: OLLAMA_URL value: http://ollama-service:11434 volumeMounts: - name: model-cache mountPath: /root/.cache resources: requests: memory: 4Gi cpu: 2 limits: memory: 8Gi ``` ### 3. App Deployment ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: agentic-app spec: replicas: 1 template: spec: containers: - name: app image: agentic-app:latest ports: - containerPort: 8000 env: - name: OLLAMA_URL value: http://ollama-service:11434 - name: GPT_OSS_MODEL value: gpt-oss:20b - name: MARKER_API_URL value: http://marker-api:8001 - name: DB_HOST value: postgres - name: DB_PORT value: "5432" - name: DB_NAME value: research - name: DB_USER value: research - name: DB_PASSWORD value: research123 - name: VECTOR_DIM value: "4096" --- apiVersion: v1 kind: Service metadata: name: agentic-app spec: ports: - port: 8000 targetPort: 8000 selector: app: agentic-app type: LoadBalancer ``` ### 4. Apply ```bash kubectl apply -f k8s/ # Expose app kubectl port-forward svc/agentic-app 8000:8000 ``` --- ## Architecture ``` ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ Ollama │ │ marker-api │ │ PostgreSQL │ │ Host │◄────┤ (OCR/PDF) │ │ + pgvector │ │ :11434 │ │ :8001 │ │ :5432 │ └─────────────┘ └──────┬───────┘ └──────┬───────┘ │ │ ▼ ▼ ┌──────────────────────────────┐ │ agentic-app (FastAPI) │ │ :8000 │ │ - Upload/chunk documents │ │ - Run LLM research skills │ │ - Vector search (pgvector) │ │ - Pipeline mode (3 stages) │ └──────────────────────────────┘ ``` ## Configuration Reference | Variable | Default | Description | |----------|---------|-------------| | `OLLAMA_URL` | `http://10.0.1.127:11434` | Ollama API endpoint | | `GPT_OSS_MODEL` | `gpt-oss:20b` | LLM for research | | `MARKER_API_URL` | `http://localhost:8001` | Marker OCR service | | `DB_HOST` | `localhost` | PostgreSQL host | | `DB_PORT` | 5432 | PostgreSQL port | | `DB_NAME` | `research` | Database name | | `DB_USER` | `research` | Database user | | `DB_PASSWORD` | `research123` | Database password | | `VECTOR_DIM` | `4096` | Embedding dimension | | `APP_HOST` | `0.0.0.0` | App bind address | | `APP_PORT` | 8000 | App listen port | | `DOC_STORE_DIR` | `/workspace/documents` | Uploaded file storage | ## Troubleshooting | Symptom | Likely Cause | Fix | |---------|-------------|-----| | `vector dimension mismatch 1536 and 4096` | `VECTOR_DIM` env var is `1536` | Set `VECTOR_DIM=4096` in compose file & rebuild | | `'NoneType' object has no attribute 'batch_chunk'` | `db` module not initialized at import time | Restart app container | | `inconsistent types deduced for parameter $1` | UUID vs text type conflict in `upsert_document` | Use `$1::uuid` / `$1::text` casts (fixed in current code) | | `OCR failed: No JSON output` | Marker model not downloaded | Run warmup step, commit image | | `expected str, got list` for vector insert | asyncpg can't serialize Python list to `vector` type | Convert to string `[x,y,z]` with `::vector` cast (fixed in current code) | ## Notes - The marker-api container downloads ~3.3 GB of surya models on first run (layout, text_recognition, table_recognition, text_detection, ocr_error_detection). Commit the container to an image to persist these across restarts. - The app container defaults to reading only pages 0-5 per PDF for fast development. To process full documents, remove `--page_range 0-5` in `marker-api/server.py:58`.