diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e82badd --- /dev/null +++ b/.env.example @@ -0,0 +1,38 @@ +# ── PostgreSQL (used by docker-compose.yml and the backend) ───────────── +POSTGRES_USER=postgres +POSTGRES_PASSWORD=password +POSTGRES_DB=main +POSTGRES_HOST=127.0.0.1 +POSTGRES_PORT=5432 + +# ── LLM Provider (OpenAI-compatible API) ────────────────────────────── +LLM_PROVIDER_TYPE=openai +# Set your API key and base URL. Examples: +# LLM_BASE_URL=https://api.openai.com/v1 # OpenAI +# LLM_BASE_URL=https://api.deepseek.com # DeepSeek +# LLM_BASE_URL=https://openrouter.ai/api/v1 # OpenRouter +LLM_BASE_URL="https://api.openai.com/v1" +LLM_API_KEY="sk-..." +LLM_MODEL="gpt-4o" +LLM_MAX_TOKENS=4096 + +# ── Embedding Provider (OpenAI-compatible) ──────────────────────────── +# Typically uses OpenRouter or OpenAI: +# EMBEDDING_MODEL="text-embedding-3-large" # OpenAI +# EMBEDDING_MODEL="qwen/qwen3-embedding-8b" # OpenRouter +EMBEDDING_MODEL="text-embedding-3-small" +EMBEDDING_BASE_URL="https://openrouter.ai/api/v1" +EMBEDDING_API_KEY="sk-or-..." +EMBEDDING_DIMENSIONS=768 + +# ── Public Demo Abuse Prevention (optional) ─────────────────────────── +# Uncomment to disable the daily token budget (useful for local dev). +# DEMO_DISABLE_BUDGET=true +# DEMO_DAILY_BUDGET_TOKENS=1000000 +# DEMO_BUDGET_FILE=/tmp/demo-budget.json +# DEMO_MAX_QUERY_LENGTH=500 +# DEMO_MAX_USER_MESSAGES=50 + +# ── Cross-Environment Copy (optional — for scripts/copy_corpus_data.py) ── +# DEV_DATABASE_URL=postgresql+asyncpg://postgres:password@127.0.0.1:5432/main +# SUPABASE_DIRECT_URL=postgresql+asyncpg://user:password@host:5432/postgres?ssl=require diff --git a/README.md b/README.md index 1f4baa2..fb97416 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,19 @@ # Multi-Agent RAG +> **Live demo:** [multi-agent-rag.olegedly.com](https://multi-agent-rag.olegedly.com/) A config-driven multi-agent research system with pgvector RAG, MCP tool servers, and a LangChain agent pipeline. Answers are grounded in curated knowledge bases (corpora), each scoped to a single corpus with route-based isolation. ## Quick Start +First, create your config: + +```bash +cp .env.example .env +# Then edit .env with your API keys +``` + +Then start the dev environment: + ```bash ./dev.sh ``` @@ -25,6 +35,8 @@ SolidJS SPA → AG-UI/SSE → FastAPI → LangChain Agent Pipeline └── pgvector RAG (corpus-filtered) ``` +![Dashboard screenshot](docs/dashboard-screenshot.png) + The system is **corpus-scoped**: each conversation binds to one corpus from start to finish. Every retrieval carries the active `corpus_id`. Adding a new knowledge base is an ingestion + config task. ### Key modules @@ -75,6 +87,8 @@ make test_backend # backend only make test_frontend # frontend only ``` +> All **422 tests pass** (223 backend + 199 frontend) with 0 pyright/TypeScript errors. + Pure unit tests — no Docker or DB dependency for business logic. The `create_app()` DI makes mocking straightforward. ## Deployment diff --git a/backend/main.py b/backend/main.py index ad4b0e8..ef649db 100644 --- a/backend/main.py +++ b/backend/main.py @@ -19,6 +19,7 @@ from backend.config import Settings, get_settings from backend.corpus_config import CorporaConfig from backend.middleware import ChatGuard +from backend.db import migrate_db def create_app( @@ -48,6 +49,13 @@ def create_app( @asynccontextmanager async def _lifespan(app: FastAPI) -> AsyncIterator[None]: + # Auto-migrate DB schema on startup (idempotent — no-op if tables exist). + # Skip when no DB credentials are configured (e.g. in tests). + if settings.postgres_user and settings.postgres_password: + try: + await migrate_db(settings.database_url) + except Exception: + pass # Non-fatal — RAG tools will surface connection errors at query time. yield app = FastAPI(title=settings.app_name, lifespan=_lifespan) diff --git a/dev.sh b/dev.sh index a3f5ddf..c826204 100755 --- a/dev.sh +++ b/dev.sh @@ -4,7 +4,7 @@ set -e cleanup() { echo "" echo "Shutting down..." - docker compose -f docker-compose.base.yml -f docker-compose.dev-override.yml down + docker compose down [ -n "$BACKEND_PID" ] && kill "$BACKEND_PID" 2>/dev/null [ -n "$PGWEB_PID" ] && kill "$PGWEB_PID" 2>/dev/null [ -n "$MCP_PID" ] && kill "$MCP_PID" 2>/dev/null @@ -18,10 +18,10 @@ trap cleanup SIGINT SIGTERM # ── Database (Docker) ────────────────────────────────────── echo "Starting PostgreSQL (pgvector)..." -docker compose -f docker-compose.base.yml -f docker-compose.dev-override.yml up -d db +docker compose up -d echo "Waiting for PostgreSQL to be healthy..." -until docker compose -f docker-compose.base.yml -f docker-compose.dev-override.yml exec db pg_isready -U "$POSTGRES_USER" --quiet 2>/dev/null; do +until docker compose exec db pg_isready -U "$POSTGRES_USER" --quiet 2>/dev/null; do sleep 1 done echo "PostgreSQL is ready." diff --git a/docker-compose.base.yml b/docker-compose.base.yml deleted file mode 100644 index 8eb8a32..0000000 --- a/docker-compose.base.yml +++ /dev/null @@ -1,25 +0,0 @@ -services: - backend: - build: - context: . - dockerfile: backend/Dockerfile - env_file: ".env" - expose: - - "8000" - volumes: - - demo_budget:/data - - frontend: - profiles: ["production"] - build: - context: ./frontend - dockerfile: Dockerfile - ports: - - "80:80" - - "443:443" - volumes: - - caddy_data:/data - -volumes: - caddy_data: - demo_budget: diff --git a/docker-compose.dev-override.yml b/docker-compose.yml similarity index 55% rename from docker-compose.dev-override.yml rename to docker-compose.yml index f30c003..ad66acb 100644 --- a/docker-compose.dev-override.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ services: db: image: pgvector/pgvector:pg18 container_name: "pgvector-db" - env_file: .env + env_file: ".env" ports: - "5432:5432" volumes: @@ -13,15 +13,5 @@ services: timeout: 5s retries: 5 - backend: - ports: - - "8000:8000" - volumes: - - ./backend:/app/backend - command: ["fastapi", "dev", "backend/main.py", "--port", "8000", "--host", "0.0.0.0"] - depends_on: - db: - condition: service_healthy - volumes: pgdata: diff --git a/docs/dashboard-screenshot.png b/docs/dashboard-screenshot.png new file mode 100644 index 0000000..fcf8731 Binary files /dev/null and b/docs/dashboard-screenshot.png differ diff --git a/docs/grills/GRILL-ME-27.md b/docs/grills/GRILL-ME-27.md new file mode 100644 index 0000000..fb2038c --- /dev/null +++ b/docs/grills/GRILL-ME-27.md @@ -0,0 +1,126 @@ +# Grill Me Results + +Generated: 2026-07-01T15:59:49.108Z + +## Plan + +let's carefully plan the changes. so I've found that @docker-compose.coolify.yml is purely a note for how to deploy on coolify, I copy/paste it into the coolify web ui, there's no other usage. and then we have 2 files: @docker-compose.base.yml and @docker-compose.dev-override.yml ; these are safe to merge into one, because we don't in fact ever use them separately. so the way @dev.sh calls them needs to become the baseline docker-compose.yml ; clear? + +## Shared Understanding + +AC #8 (docker compose up from scratch) redesigned via /grill-me interview. The plan: merge docker-compose files into a single docker-compose.yml with only the db service, delete the unused base.yml/dev-override.yml, create .env.example, add migrate_db() to backend startup lifespan, update dev.sh to call plain 'docker compose up -d', and update the AC text and tick box in the issue. + +## Questions and Answers + +### 1. What's the target mode for the merged docker-compose.yml — dev (native-like hot-reload) or "from scratch" production-like (everything in containers)? + +**Recommended answer:** We need to merge base.yml + dev-override.yml into one docker-compose.yml that works for both: dev.sh would just run `docker compose up -d db` (backend/frontend native), AND `docker compose up --profile production` would start all containers for the "clone → .env → docker compose up" experience. But the AC #8 demands the latter work seamlessly. + +**User answer:** _(not recorded)_ + +**Status:** open + +**Notes:** Tension: dev.sh uses containers only for DB (backend/frontend native), but AC #8 requires everything in containers from scratch. The merged compose needs to serve both. + +### 2. Are the docker-compose files used for building deployable Docker images? + +**Recommended answer:** No. The CI/CD workflow at .github/workflows/deploy.yml builds images directly from Dockerfiles (backend/Dockerfile, frontend/Dockerfile) and pushes to GHCR. The compose files are purely for local development (dev.sh spins up the DB container) and the coolify.yml is a copy-paste note for the Coolify web UI. + +**User answer:** No — compose files are not used for building deployable images; CI/CD uses Dockerfiles directly. + +**Status:** resolved + +### 3. Should AC #8 change to reflect the actual dev workflow instead of the strict "docker compose up from scratch"? + +**Recommended answer:** Yes. Change it to: "Developer setup works: clone → copy .env.example → ./dev.sh starts DB in Docker, backend (hot-reload) and frontend (Vite HMR) natively. No manual steps beyond configuring .env." + +**User answer:** Yeah, let's make the AC reflect what we already do in practice + +**Status:** resolved + +**Notes:** Compose files are merged into one docker-compose.yml (base + dev-override combined). dev.sh uses it the same way, just without -f flags. The criterion tracks the real dev workflow. + +### 4. The merged docker-compose.yml will have db, backend, and frontend services. dev.sh starts only `db`. But a fresh clone also needs DB tables and seeded corpus data to be useful. Should the backend auto-migrate and auto-seed on startup, or are those separate concerns from this AC? + +**Recommended answer:** They're separate concerns. The AC says "docker compose up from scratch works with only .env file." The merged compose + dev.sh handles the container/native startup. DB migration and seeding are part of the seed workflow (scripts/seed_knowledge_base.py), not the compose refactor. But we should mention in the README that after first ./dev.sh, you run `uv run scripts/seed_knowledge_base.py --all`. + +**User answer:** _(not recorded)_ + +**Status:** needs-codebase-check + +**Notes:** Need to check if the backend startup currently fails gracefully when DB tables don't exist, or if it crashes. If it crashes, we may need to at least call migrate_db() on startup. + +### 5. Should merged compose + .env.example also add auto-migration (calling migrate_db() on backend startup via the lifespan), or is that a separate concern? + +**Recommended answer:** Separate concern. The AC is about startup mechanics (merge compose files, create .env.example). DB migration and seeding remain the existing workflow: `uv run scripts/seed_knowledge_base.py --all`. However, to make the "from scratch" experience not immediately fail, we could add migrate_db() to the backend lifespan — it's idempotent and takes <100ms. Worth adding as a good-faith step. + +**User answer:** _(not recorded)_ + +**Status:** open + +**Notes:** Backend starts fine without tables (lazy DB connections via RAG tools). First chat query would error on missing tables. migrate_db() is safe to call on every startup (idempotent). + +### 6. Should migrate_db() be called on every backend startup via the lifespan? + +**Recommended answer:** Yes. migrate_db() is fully idempotent (~100ms no-op when tables exist). Adding it to the backend lifespan means a fresh clone with an empty DB gets tables created automatically, preventing SQL errors from leaking to the agent on first chat. Data seeding remains a separate manual step. + +**User answer:** Yes, sounds reasonable — add migrate_db() to backend startup lifespan. + +**Status:** resolved + +### 7. The merged docker-compose.yml will contain db, backend, and frontend services. dev.sh only starts db. What should be the backend command in the merged file — the production CMD from the Dockerfile ("fastapi run"), the dev override CMD ("fastapi dev"), or no command (let Dockerfile CMD apply)? + +**Recommended answer:** No command in the compose file — let the Dockerfile's CMD ("fastapi run" — production mode) be the default. If someone runs the backend in a container they get production behavior. dev.sh never starts the backend container anyway, so it doesn't matter for the dev workflow. + +**User answer:** _(not recorded)_ + +**Status:** open + +**Notes:** dev.sh only starts 'db' — backend/frontend start natively. The backend CMD in compose only applies if someone runs all containers. + +### 8. Should docker-compose.yml contain only the db service, and dev.sh call `docker compose up -d` without any service name? + +**Recommended answer:** Yes. Single service (db) in docker-compose.yml, so `docker compose up -d` starts only the database. dev.sh loses the -f flags and service argument. Delete base.yml and dev-override.yml. Keep coolify.yml as deploy reference. + +**User answer:** _(not recorded)_ + +**Status:** open + +**Notes:** Backend/frontend service definitions in compose are unused — dev.sh never starts them in containers. Pruning them eliminates maintenance burden and the confusing -f flag dance. + +### 9. Create .env.example with all env vars, default values, and inline comments for each section? + +**Recommended answer:** Yes. Write .env.example with: PostgreSQL (matching compose defaults), LLM (OpenAI-compatible placeholder), Embeddings (OpenRouter default), optional demo budget settings, optional cross-env copy vars. User will manually review and finalize. + +**User answer:** Create the placeholder .env.example and I'll manually edit it for correctness. + +**Status:** resolved + +**Notes:** File is written to .env.example. Covers all Settings fields from config.py. + +### 10. What should docker-compose.yml contain? + +**Recommended answer:** Single db service (pgvector/pgvector:pg18 with healthcheck, port, volume, env_file). No backend or frontend services. `docker compose up -d` starts just the database. dev.sh calls `docker compose up -d` without -f flags. + +**User answer:** Only the db service, so `docker compose up` starts just the database. + +**Status:** resolved + +**Notes:** base.yml and dev-override.yml can be deleted. coolify.yml retained as deploy reference. + +## Agreed Decisions + +- Compose files are not used for building deployable images (CI/CD uses Dockerfiles directly). +- Merge base.yml + dev-override.yml into one docker-compose.yml with only the db service — backend/frontend services removed since nothing uses them. +- Delete docker-compose.base.yml and docker-compose.dev-override.yml. Keep coolify.yml as deploy reference. +- dev.sh becomes: docker compose up -d (no -f flags, no service argument). +- Add migrate_db() to backend startup lifespan so tables auto-create on fresh clone (~100ms idempotent no-op). +- Create .env.example with all env vars, sensible defaults, and inline comments. +- Update AC #8 in the issue to reflect the real dev workflow (clone → cp .env.example .env → ./dev.sh → seed). +- Tick AC #8 in the issue after changes are made. + +## Open Risks + +- User still needs to manually run uv run scripts/seed_knowledge_base.py --all after first startup to get corpus data — not automated. +- The .env.example needs user review for correctness before being committed. +- If the backend starts before DB is healthy, migrate_db() might fail — need depends_on or retry logic.