Search stack replicates the Hamilton site pattern with pg_orrery-specific additions: - FastAPI REST API (chat SSE streaming, semantic search, health check) - FastMCP server at /mcp with doc search and live SQL query tools - pgvector + pgai vectorizer for 1024-dim document embeddings - Hybrid search (semantic cosine + text ILIKE with pg_trgm GIN) - Dual LLM backend: self-hosted qwen3 via GPU gateway or Anthropic Claude - Live read-only pg_orrery SQL execution with safety guardrails (SELECT-only validation, read-only transaction, 5s timeout, 100-row cap) - Convenience MCP tools: planet_position, sky_survey, satellite_pass - MDX content ingestion from docs/src/content/docs/ (50 pages) - Docker Compose: pg_orrery+pgvector DB, pgai, vectorizer-worker, API - Alembic async migrations, Makefile, .env.example
135 lines
3.9 KiB
YAML
135 lines
3.9 KiB
YAML
services:
|
|
db:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.db
|
|
environment:
|
|
POSTGRES_DB: orrery_search
|
|
POSTGRES_USER: ${POSTGRES_USER:-orrery}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
volumes:
|
|
- pg-data:/var/lib/postgresql/data
|
|
networks:
|
|
- internal
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U orrery -d orrery_search"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
restart: unless-stopped
|
|
|
|
pgai-install:
|
|
image: timescale/pgai-vectorizer-worker:latest
|
|
entrypoint: ["python", "-m", "pgai", "install", "-d", "postgresql://${POSTGRES_USER:-orrery}:${POSTGRES_PASSWORD}@db:5432/orrery_search"]
|
|
networks:
|
|
- internal
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
restart: "no"
|
|
|
|
vectorizer-worker:
|
|
image: timescale/pgai-vectorizer-worker:latest
|
|
environment:
|
|
PGAI_VECTORIZER_WORKER_DB_URL: postgresql://${POSTGRES_USER:-orrery}:${POSTGRES_PASSWORD}@db:5432/orrery_search
|
|
OPENAI_BASE_URL: ${GPU_BASE_URL:-https://orrery-search.gpu.supported.systems/v1}
|
|
OPENAI_API_KEY: ${GPU_API_KEY}
|
|
command: ["--poll-interval", "5s"]
|
|
networks:
|
|
- internal
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
pgai-install:
|
|
condition: service_completed_successfully
|
|
restart: unless-stopped
|
|
|
|
api-dev:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
target: dev
|
|
profiles: ["dev"]
|
|
env_file: .env
|
|
volumes:
|
|
- ./src:/app/src
|
|
- ./alembic:/app/alembic
|
|
- ../docs/src/content/docs:/data/content:ro
|
|
networks:
|
|
- internal
|
|
- caddy
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
pgai-install:
|
|
condition: service_completed_successfully
|
|
labels:
|
|
caddy: ${DOMAIN:-pg-orrery.warehack.ing}
|
|
caddy.handle: /api/search*
|
|
caddy.handle.0_reverse_proxy: "{{upstreams 8000}}"
|
|
caddy.handle_1: /health
|
|
caddy.handle_1.0_reverse_proxy: "{{upstreams 8000}}"
|
|
caddy.handle_2: /mcp*
|
|
caddy.handle_2.0_reverse_proxy: "{{upstreams 8000}}"
|
|
caddy.handle_3: /api/chat*
|
|
caddy.handle_3.0_reverse_proxy: "{{upstreams 8000}}"
|
|
caddy.handle_3.0_reverse_proxy.flush_interval: "-1"
|
|
caddy.handle_3.0_reverse_proxy.transport: "http"
|
|
caddy.handle_3.0_reverse_proxy.transport.read_timeout: "0"
|
|
caddy.handle_3.0_reverse_proxy.transport.write_timeout: "0"
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 15s
|
|
restart: unless-stopped
|
|
|
|
api-prod:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
target: prod
|
|
profiles: ["prod"]
|
|
env_file: .env
|
|
volumes:
|
|
- ../docs/src/content/docs:/data/content:ro
|
|
networks:
|
|
- internal
|
|
- caddy
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
pgai-install:
|
|
condition: service_completed_successfully
|
|
labels:
|
|
caddy: ${DOMAIN:-pg-orrery.warehack.ing}
|
|
caddy.handle: /api/search*
|
|
caddy.handle.0_reverse_proxy: "{{upstreams 8000}}"
|
|
caddy.handle_1: /health
|
|
caddy.handle_1.0_reverse_proxy: "{{upstreams 8000}}"
|
|
caddy.handle_2: /mcp*
|
|
caddy.handle_2.0_reverse_proxy: "{{upstreams 8000}}"
|
|
caddy.handle_3: /api/chat*
|
|
caddy.handle_3.0_reverse_proxy: "{{upstreams 8000}}"
|
|
caddy.handle_3.0_reverse_proxy.flush_interval: "-1"
|
|
caddy.handle_3.0_reverse_proxy.transport: "http"
|
|
caddy.handle_3.0_reverse_proxy.transport.read_timeout: "0"
|
|
caddy.handle_3.0_reverse_proxy.transport.write_timeout: "0"
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 15s
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
pg-data:
|
|
|
|
networks:
|
|
internal:
|
|
caddy:
|
|
external: true
|