Skip to content

Architecture

QuOptuna ships as a single Python package, src/quoptuna/, that bundles four cooperating layers: a core optimization engine (backend/), a FastAPI server (server/), a command-line interface (cli.py), and a pre-built Next.js static UI (web/). A legacy Streamlit dashboard (frontend/app.py) is also included as a fallback. This page explains how those pieces relate and how a request flows through them.

The engine is the heart of the system: an Optuna wrapper that builds a search space, runs trials, scores them, and records results. It knows nothing about HTTP.

The server is a thin FastAPI application (quoptuna.server.main:app, titled “QuOptuna Next API”). It exposes REST routers, orchestrates runs as background tasks, and manages persistence. It also mounts the compiled Next.js UI at / and serves interactive API docs at /api/docs.

The web UI is a Next.js 15 / React 19 App Router application. The top-level frontend/ directory holds its dev source; make build_package runs npm run build and copies frontend/out into src/quoptuna/web, so a plain uvx quoptuna ships the fully-built UI.

The CLI drives the same engine headlessly for scripted or reproducible runs.

flowchart TD
Browser["Browser (Next.js UI)"] -->|REST| API["FastAPI server"]
CLI["CLI"] --> Engine["Optimization engine (Optuna)"]
API --> Engine
Engine --> Study[("Optuna study (SQLite in db/, or PostgreSQL)")]
API --> Stores[("App database: runs, datasets, analysis tables")]
API -->|mounts| Browser

In packaged mode, one uvicorn process serves both the UI and the API on a single port (default 8000) — same origin, no CORS gymnastics for the user. This is what end users get from uvx quoptuna.

In dev mode, two processes run side by side: FastAPI on :8000 and the Next.js dev server on :3000. The server is CORS-enabled so the separate frontend origin can call it during development.

Starting a run is asynchronous. The browser issues POST /api/v1/optimize, and the server launches the optimization as a FastAPI background task so the request returns immediately. The engine then writes trials into an Optuna study. The UI polls GET /api/v1/optimize/{id}/trials for live progress rather than holding a long connection open. The CLI skips HTTP and runs the same workflow in-process (server/services/headless.py).

Server-side state lives in one SQLModel application database (DATABASE_URL, default sqlite:///./db/quoptuna_app.db), accessed through three service modules:

  • run_store — durable optimization-run records (quoptuna_runs). On startup it marks any stale running/pending runs as interrupted, which enables crash rehydration: a restarted server can recover and reconcile in-flight work.
  • analysis_store — analysis snapshots, revisions, jobs, reports and artifacts (SHAP, confusion matrices, LLM reports). Analysis jobs left pending/running by a restart are marked failed on startup. Figure artifacts are written to ARTIFACT_ROOT (default db/analysis) or to S3 when ARTIFACT_STORAGE=s3.
  • dataset_registry — maps each dataset_id (quoptuna_datasets) to its persisted CSV path.

These are distinct from the Optuna study storage. The stores track orchestration and results metadata; the Optuna studies own the trial-level truth.

Authentication is optional and handled by Auth0 (the auth0-server-python SDK). Sessions live in encrypted, httponly cookies — there is no server-side session store to manage. Enforcement is a no-op when the AUTH0_* environment variables are unset, so local, dev, and test runs work unauthenticated with zero configuration.