FastAPI AI Scaffold
Generates a runnable, production-shaped FastAPI AI/ML service so you don't
re-derive the same wiring every time. The skeleton encodes the patterns from the
fastapi-ai-patterns skill as working code: nothing here is novel framework
usage, it's the boring-but-correct baseline an inference service needs before it
sees traffic.
Inspired by FastAPI for AI Engineers (AI Engineering Insider, 2026). All
generated code is original.
When to use
- "Start/bootstrap a new FastAPI service for serving a model / LLM / RAG / embeddings"
- "Set up a FastAPI project with clean architecture (router/service/repository)"
- The user wants the production basics pre-wired (lifespan model loading, auth,
DB, probes, tests, Docker) rather than a single-file
main.py
When NOT to use
- The user just wants to understand a pattern or debug an existing app → use
fastapi-ai-patterns
- They're prepping for an interview → use
fastapi-ai-interview-prep
- They already have a project structure and want one endpoint added → don't
regenerate; hand-write the endpoint following
fastapi-ai-patterns
Workflow
1. Generate the project
bash skills/local/fastapi-ai-scaffold/scripts/new-fastapi-ai-service.sh ./my-service
# preview first, write nothing:
bash skills/local/fastapi-ai-scaffold/scripts/new-fastapi-ai-service.sh --dry-run ./my-service
# override the package/metadata slug:
bash skills/local/fastapi-ai-scaffold/scripts/new-fastapi-ai-service.sh --name churn-scorer ./services/churn
The script copies the assets/project/ skeleton, strips each file's .tmpl
suffix, and substitutes the project slug. It prints a JSON summary
(project, slug, path, files, next_steps[]) on stdout.
2. Install and run
cd ./my-service
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
cp .env.example .env # then set JWT_SECRET, DATABASE_URL, MODEL_PATH
uvicorn app.main:app --reload # http://127.0.0.1:8000/docs
pytest -q # the bundled tests should pass immediately
3. Make it yours
Replace the stubs with your real implementation — the structure stays:
app/ml/model.py — swap load_model/Model.predict for your real artifact;
swap stream_llm_tokens for a real provider/vLLM call. Keep predict
synchronous and CPU-bound; the service already offloads it off the event loop.
app/models/, app/schemas/, app/repositories/, app/services/ — add your
domain. Keep HTTP concerns in routers and business logic in services.
- Generate the first migration once models are real:
alembic revision --autogenerate -m "init" then alembic upgrade head.
Review the migration before applying — autogenerate misses renames/data moves.
What the generated project contains
Clean-architecture layout under app/ (router → service → repository), plus:
app/lifespan.py — model handle, shared httpx.AsyncClient, and DB engine
created once at startup and stored on app.state (per-process singletons).
app/api/v1/health.py — cheap /health (liveness) vs dependency-checking
/ready (readiness), so a DB blip removes a pod from rotation instead of
restarting it.
app/api/v1/auth.py + app/core/security.py — bcrypt hashing, JWT with a
pinned algorithm, a get_current_user dependency.
app/api/v1/inference.py — a /predict endpoint (CPU work offloaded via
anyio.to_thread) and a /chat/stream LLM gateway using SSE.
app/ml/guardrails.py — input checks and the generate → validate → retry
loop that turns LLM output into a typed Pydantic value.
tests/ — dependency_overrides + in-memory SQLite, exact-key-set leak
assertions, generic-login-error assertion.
- Ops —
Dockerfile (multi-stage, non-root, HEALTHCHECK), gunicorn_conf.py
(Uvicorn workers), pyproject.toml, .env.example, Alembic env.
Available scripts
scripts/new-fastapi-ai-service.sh <target-dir> — Generate the project.
- Flags:
--name SLUG, --force, --dry-run, --help.
- Output: JSON summary on stdout; progress on stderr.
Bundled assets
assets/project/ — the skeleton. Every file ends in .tmpl; the script strips
the suffix and substitutes PROJECT_SLUG_PLACEHOLDER. Edit these .tmpl files
to change what new projects get.
Gotchas
.tmpl is mandatory on skeleton files. The script only strips .tmpl and
substitutes the slug; a skeleton file without the suffix is copied verbatim
(intentional, e.g. migrations/versions/.gitkeep). Keep real app files .tmpl
so editors don't try to import their (uninstalled) dependencies.
- The generated package is named
app, not the slug. Only project metadata
(pyproject name, README title, .env APP_NAME) uses the slug; imports are
always app.*. Don't rename the package without updating every import.
- The bundled tests use bare
TestClient and skip lifespan on purpose — they
inject app.state doubles + dependency_overrides. If you write a test that
relies on real startup, use with TestClient(app) as client: instead.
- The model and LLM in
app/ml/model.py are deterministic stubs, not real
inference. Tests assert shape/behavior, not model quality. Replace before deploy.
init_db (create_all) runs only in environment=development. Production
schema must go through Alembic; don't rely on create_all in prod.
1---2name: fastapi-ai-scaffold3description: Scaffold a production-grade FastAPI AI/ML service from a bundled, opinionated skeleton. Use when starting a new FastAPI inference/LLM/RAG backend, bootstrapping a FastAPI project with clean architecture, or setting up a model-serving API with the production basics pre-wired: router/service/repository layering, lifespan-loaded model + httpx client, /health + /ready probes, JWT auth, SQLModel + Alembic, an SSE LLM gateway, guardrails + a Pydantic validation loop, tests with dependency_overrides, Docker, and gunicorn.4---56# FastAPI AI Scaffold78Generates a runnable, production-shaped FastAPI AI/ML service so you don't9re-derive the same wiring every time. The skeleton encodes the patterns from the10`fastapi-ai-patterns` skill as working code: nothing here is novel framework11usage, it's the boring-but-correct baseline an inference service needs before it12sees traffic.1314> Inspired by *FastAPI for AI Engineers* (AI Engineering Insider, 2026). All15> generated code is original.1617## When to use1819- "Start/bootstrap a new FastAPI service for serving a model / LLM / RAG / embeddings"20- "Set up a FastAPI project with clean architecture (router/service/repository)"21- The user wants the production basics pre-wired (lifespan model loading, auth,22 DB, probes, tests, Docker) rather than a single-file `main.py`2324## When NOT to use2526- The user just wants to understand a pattern or debug an existing app → use `fastapi-ai-patterns`27- They're prepping for an interview → use `fastapi-ai-interview-prep`28- They already have a project structure and want one endpoint added → don't29 regenerate; hand-write the endpoint following `fastapi-ai-patterns`3031## Workflow3233### 1. Generate the project3435```bash36bash skills/local/fastapi-ai-scaffold/scripts/new-fastapi-ai-service.sh ./my-service37# preview first, write nothing:38bash skills/local/fastapi-ai-scaffold/scripts/new-fastapi-ai-service.sh --dry-run ./my-service39# override the package/metadata slug:40bash skills/local/fastapi-ai-scaffold/scripts/new-fastapi-ai-service.sh --name churn-scorer ./services/churn41```4243The script copies the `assets/project/` skeleton, strips each file's `.tmpl`44suffix, and substitutes the project slug. It prints a JSON summary45(`project`, `slug`, `path`, `files`, `next_steps[]`) on stdout.4647### 2. Install and run4849```bash50cd ./my-service51uv venv && source .venv/bin/activate52uv pip install -e ".[dev]"53cp .env.example .env # then set JWT_SECRET, DATABASE_URL, MODEL_PATH54uvicorn app.main:app --reload # http://127.0.0.1:8000/docs55pytest -q # the bundled tests should pass immediately56```5758### 3. Make it yours5960Replace the stubs with your real implementation — the structure stays:6162- `app/ml/model.py` — swap `load_model`/`Model.predict` for your real artifact;63 swap `stream_llm_tokens` for a real provider/vLLM call. Keep `predict`64 synchronous and CPU-bound; the service already offloads it off the event loop.65- `app/models/`, `app/schemas/`, `app/repositories/`, `app/services/` — add your66 domain. Keep HTTP concerns in routers and business logic in services.67- Generate the first migration once models are real:68 `alembic revision --autogenerate -m "init"` then `alembic upgrade head`.69 Review the migration before applying — autogenerate misses renames/data moves.7071## What the generated project contains7273Clean-architecture layout under `app/` (router → service → repository), plus:7475- **`app/lifespan.py`** — model handle, shared `httpx.AsyncClient`, and DB engine76 created once at startup and stored on `app.state` (per-process singletons).77- **`app/api/v1/health.py`** — cheap `/health` (liveness) vs dependency-checking78 `/ready` (readiness), so a DB blip removes a pod from rotation instead of79 restarting it.80- **`app/api/v1/auth.py` + `app/core/security.py`** — bcrypt hashing, JWT with a81 pinned algorithm, a `get_current_user` dependency.82- **`app/api/v1/inference.py`** — a `/predict` endpoint (CPU work offloaded via83 `anyio.to_thread`) and a `/chat/stream` LLM gateway using SSE.84- **`app/ml/guardrails.py`** — input checks and the generate → validate → retry85 loop that turns LLM output into a typed Pydantic value.86- **`tests/`** — `dependency_overrides` + in-memory SQLite, exact-key-set leak87 assertions, generic-login-error assertion.88- **Ops** — `Dockerfile` (multi-stage, non-root, HEALTHCHECK), `gunicorn_conf.py`89 (Uvicorn workers), `pyproject.toml`, `.env.example`, Alembic env.9091## Available scripts9293- **`scripts/new-fastapi-ai-service.sh <target-dir>`** — Generate the project.94 - Flags: `--name SLUG`, `--force`, `--dry-run`, `--help`.95 - Output: JSON summary on stdout; progress on stderr.9697## Bundled assets9899- `assets/project/` — the skeleton. Every file ends in `.tmpl`; the script strips100 the suffix and substitutes `PROJECT_SLUG_PLACEHOLDER`. Edit these `.tmpl` files101 to change what new projects get.102103## Gotchas104105- **`.tmpl` is mandatory on skeleton files.** The script only strips `.tmpl` and106 substitutes the slug; a skeleton file without the suffix is copied verbatim107 (intentional, e.g. `migrations/versions/.gitkeep`). Keep real app files `.tmpl`108 so editors don't try to import their (uninstalled) dependencies.109- **The generated package is named `app`, not the slug.** Only project *metadata*110 (pyproject name, README title, `.env` `APP_NAME`) uses the slug; imports are111 always `app.*`. Don't rename the package without updating every import.112- **The bundled tests use bare `TestClient` and skip lifespan on purpose** — they113 inject `app.state` doubles + `dependency_overrides`. If you write a test that114 relies on real startup, use `with TestClient(app) as client:` instead.115- **The model and LLM in `app/ml/model.py` are deterministic stubs**, not real116 inference. Tests assert shape/behavior, not model quality. Replace before deploy.117- **`init_db` (create_all) runs only in `environment=development`.** Production118 schema must go through Alembic; don't rely on `create_all` in prod.