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— swapload_model/Model.predictfor your real artifact; swapstream_llm_tokensfor a real provider/vLLM call. Keeppredictsynchronous 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"thenalembic 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, sharedhttpx.AsyncClient, and DB engine created once at startup and stored onapp.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, aget_current_userdependency.app/api/v1/inference.py— a/predictendpoint (CPU work offloaded viaanyio.to_thread) and a/chat/streamLLM 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.
- Flags:
Bundled assets
assets/project/— the skeleton. Every file ends in.tmpl; the script strips the suffix and substitutesPROJECT_SLUG_PLACEHOLDER. Edit these.tmplfiles to change what new projects get.
Gotchas
.tmplis mandatory on skeleton files. The script only strips.tmpland substitutes the slug; a skeleton file without the suffix is copied verbatim (intentional, e.g.migrations/versions/.gitkeep). Keep real app files.tmplso 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,.envAPP_NAME) uses the slug; imports are alwaysapp.*. Don't rename the package without updating every import. - The bundled tests use bare
TestClientand skip lifespan on purpose — they injectapp.statedoubles +dependency_overrides. If you write a test that relies on real startup, usewith TestClient(app) as client:instead. - The model and LLM in
app/ml/model.pyare deterministic stubs, not real inference. Tests assert shape/behavior, not model quality. Replace before deploy. init_db(create_all) runs only inenvironment=development. Production schema must go through Alembic; don't rely oncreate_allin prod.