Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions fastapi-llm-cookiecutter/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Azure OpenAI
AZURE_OPENAI_ENDPOINT=
AZURE_OPENAI_API_KEY=
AZURE_OPENAI_DEPLOYMENT_CHAT=
AZURE_OPENAI_DEPLOYMENT_EMBED=

# Azure AI Search
AZURE_SEARCH_ENDPOINT=
AZURE_SEARCH_API_KEY=
AZURE_SEARCH_INDEX_NAME=docs

# Storage
AZURE_STORAGE_ACCOUNT_URL=
AZURE_STORAGE_CONTAINER=uploads

# Content Safety
AZURE_CONTENT_SAFETY_ENDPOINT=
AZURE_CONTENT_SAFETY_API_KEY=

# Postgres
DB_URL=postgresql+psycopg://user:pass@postgres:5432/app

# Redis
REDIS_URL=redis://redis:6379/0

# App
APP_ENV=local
APP_PORT=8000
RATE_LIMIT=60/minute
LOG_LEVEL=INFO

# Telemetry
OTEL_EXPORTER_OTLP_ENDPOINT=
APPINSIGHTS_CONNECTION_STRING=
SENTRY_DSN=
25 changes: 25 additions & 0 deletions fastapi-llm-cookiecutter/.github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
lint-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install uv
run: pip install uv
- name: Install dependencies
run: uv pip sync
- name: Run pre-commit
run: |
pip install pre-commit
pre-commit run --all-files
- name: Run tests
run: pytest
27 changes: 27 additions & 0 deletions fastapi-llm-cookiecutter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Python
__pycache__/
*.py[cod]
*.pyo
*.pyd
*.so
*.env
.env
.env.*
!.env.example
venv/
.venv/
build/
dist/
*.egg-info/
.cache/
.mypy_cache/
.pytest_cache/
.coverage
htmlcov/

# IDEs
.vscode/
.idea/

# Misc
.DS_Store
15 changes: 15 additions & 0 deletions fastapi-llm-cookiecutter/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.5
hooks:
- id: ruff
- id: ruff-format
- repo: https://github.com/psf/black
rev: 23.9.1
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.6.1
hooks:
- id: mypy
additional_dependencies: ["pydantic>=2"]
32 changes: 32 additions & 0 deletions fastapi-llm-cookiecutter/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.PHONY: dev up down fmt lint mypy test migrate upgrade seed

dev:
uvicorn app.main:app --reload

up:
docker-compose up -d

down:
docker-compose down

fmt:
black .
ruff --fix .

lint:
ruff .

mypy:
mypy .

test:
pytest

migrate:
alembic revision --autogenerate -m "migration"

upgrade:
alembic upgrade head

seed:
python scripts/seed.py
25 changes: 25 additions & 0 deletions fastapi-llm-cookiecutter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# FastAPI LLM Cookiecutter

A production-ready FastAPI backend scaffold configured for Azure deployments. It includes
SSE streaming chat, Retrieval-Augmented Generation, background ingestion with Celery,
and observability integrations.

## Quickstart

```bash
uv sync
make dev
```

## Features
- SSE chat endpoint backed by Azure OpenAI
- RAG with Azure AI Search
- Background ingestion via Celery and Redis
- Azure Blob Storage for file handling
- Azure Content Safety integration
- Structured logging and OpenTelemetry
- Prometheus metrics
- Rate limiting with Redis
- CI/CD to Azure Container Apps

Refer to [docs/](docs) for operations, security, and observability guides.
Empty file.
13 changes: 13 additions & 0 deletions fastapi-llm-cookiecutter/app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from fastapi import FastAPI

app = FastAPI()


@app.get("/healthz")
async def healthz() -> dict[str, str]:
return {"status": "ok"}


@app.get("/readyz")
async def readyz() -> dict[str, str]:
return {"status": "ready"}
43 changes: 43 additions & 0 deletions fastapi-llm-cookiecutter/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
version: "3.9"
services:
api:
build:
context: .
dockerfile: docker/Dockerfile.api
env_file: .env.example
ports:
- "8000:8000"
depends_on:
- redis
- postgres
- azurite
worker:
build:
context: .
dockerfile: docker/Dockerfile.worker
env_file: .env.example
depends_on:
- redis
- postgres
- azurite
redis:
image: redis:7-alpine
ports:
- "6379:6379"
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
ports:
- "5432:5432"
azurite:
image: mcr.microsoft.com/azure-storage/azurite
command: azurite-blob --blobHost 0.0.0.0
ports:
- "10000:10000"
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
10 changes: 10 additions & 0 deletions fastapi-llm-cookiecutter/docker/Dockerfile.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3.11-slim

WORKDIR /app

COPY pyproject.toml uv.lock ./
RUN pip install --no-cache-dir uv && uv pip sync

COPY . .

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
10 changes: 10 additions & 0 deletions fastapi-llm-cookiecutter/docker/Dockerfile.worker
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3.11-slim

WORKDIR /app

COPY pyproject.toml uv.lock ./
RUN pip install --no-cache-dir uv && uv pip sync

COPY . .

CMD ["celery", "-A", "workers.celery_app", "worker", "-l", "info"]
63 changes: 63 additions & 0 deletions fastapi-llm-cookiecutter/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[project]
name = "fastapi-llm-cookiecutter"
version = "0.1.0"
description = "FastAPI backend scaffold for Azure LLM with RAG and SSE"
requires-python = ">=3.11"
authors = [{name = "Utkarsh"}]
dependencies = [
"fastapi",
"uvicorn[standard]",
"pydantic>=2",
"pydantic-settings",
"openai>=1",
"azure-search-documents",
"azure-ai-contentsafety",
"azure-storage-blob",
"azure-identity",
"celery",
"redis",
"fastapi-limiter",
"opentelemetry-sdk",
"opentelemetry-instrumentation-fastapi",
"prometheus-client",
"sentry-sdk",
"structlog",
"sqlalchemy>=2",
"alembic",
"psycopg[binary]",
"langchain-text-splitters",
"pyjwt",
"httpx",
"python-multipart",
"unstructured",
"pypdf",
]

[project.optional-dependencies]
dev = [
"ruff",
"black",
"mypy",
"pre-commit",
"pytest",
"pytest-asyncio",
"respx",
]

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[tool.black]
line-length = 88
target-version = ["py311"]

[tool.ruff]
line-length = 88
target-version = "py311"
select = ["E", "F", "I", "B"]
ignore = ["E501"]

[tool.mypy]
python_version = "3.11"
strict = true
22 changes: 22 additions & 0 deletions fastapi-llm-cookiecutter/tests/test_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import pytest
from httpx import ASGITransport, AsyncClient

from app.main import app


@pytest.mark.asyncio
async def test_healthz() -> None:
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
resp = await ac.get("/healthz")
assert resp.status_code == 200
assert resp.json() == {"status": "ok"}


@pytest.mark.asyncio
async def test_readyz() -> None:
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
resp = await ac.get("/readyz")
assert resp.status_code == 200
assert resp.json() == {"status": "ready"}
Loading