Part 1 - Scaffold¶
Series: Tutorial index · Part 0 · You are here: Part 1 · Part 2
In this nine-part series you'll build a Document Q&A Assistant: an API that lets users upload PDF documents and ask questions about them using an LLM. By Part 8 the app will have JWT authentication, a RAG pipeline, background workers, rate limiting, and a production-ready deployment.
In Part 1 you'll scaffold the project with the agent preset, explore what was generated, add a custom /status route, and run the dev server.
Prerequisites¶
Complete Part 0 - Prerequisites before continuing. You need:
- Docker services running: PostgreSQL on port 5432, Valkey/Redis on 6379, Qdrant on 6333
- Ollama installed with the tutorial models pulled
- Python 3.11 or later
uvinstalled
1. Install fast-agent-stack¶
Create a working directory and install the package:
Confirm the CLI is available:
fasvsfastagentstack: Both names point to the same CLI. This tutorial usesfasfor brevity.
2. Scaffold with the agent preset¶
The agent preset generates a full AI-ready project: PostgreSQL database, JWT auth, Redis-backed rate limiting, a vector-store client, and an agents module wired to an LLM backend.
The scaffolder runs without further prompts and creates the project immediately. All choices come from the agent preset defaults.
Install the generated dependencies:
3. Explore the structure¶
The scaffolder creates:
.
├── .env.example
├── alembic
│ ├── env.py
│ ├── script.py.mako
│ └── versions
├── docker-compose.yml
├── Dockerfile
├── docqa
│ ├── __init__.py
│ ├── ai/ <- AI module (agents, tools, prompts)
│ │ ├── __init__.py
│ │ ├── agents/
│ │ │ └── __init__.py
│ │ ├── tools/
│ │ │ └── __init__.py
│ │ └── prompts/
│ │ └── __init__.py
│ ├── app.py
│ ├── models.py
│ ├── routes.py
│ ├── schemas.py
│ ├── settings.py
│ └── tasks.py <- background task stubs (Dramatiq)
├── main.py
├── pyproject.toml
└── scripts
├── format.sh
└── lint.sh
A few files worth noting:
ai/agents/is where you define your agent handlers. The scaffolder provides a stub inai/agents/__init__.pyusing the built-in@app.agent()decorator. In Part 5 you'll either extend it or replace it with an external agent framework.ai/tools/is where you put tool functions your agents can call.ai/prompts/is for system prompts and few-shot examples.tasks.pycontains background task stubs. You'll implement async document ingestion in Part 7.docker-compose.ymlandDockerfileare ready for Part 8 when you containerise the app.
For now, focus on the settings and routing layer.
4. Look at settings.py¶
Open docqa/settings.py:
from functools import lru_cache
from pydantic_settings import SettingsConfigDict
from fast_agent_stack.config import BaseSettings
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="DOCQA_")
database_url: str = "postgresql+asyncpg://docqa:docqa@localhost:5432/docqa"
redis_url: str = "redis://localhost:6379/0"
auth_backends: list[str] = ["jwt"]
@lru_cache
def get_settings() -> Settings:
return Settings()
Two things to notice:
database_urldefaults to the PostgreSQL container you started in Part 0. If you changed the credentials indocker-compose.yml, update this value in your.envfile.auth_backends: ["jwt"]enables JWT authentication. You'll configure users and protected routes in Part 3.
Copy the example env file:
The services from Part 0 match the defaults above, so no edits are needed for local development.
5. Add a /status route¶
Open docqa/routes.py and add a /status endpoint:
from fast_agent_stack import __version__
from fastapi import APIRouter, Depends
from .settings import Settings, get_settings
router = APIRouter()
@router.get("/")
async def root(settings: Settings = Depends(get_settings)) -> dict[str, str]:
return {"message": "Hello from docqa!"}
@router.get("/status")
async def status(settings: Settings = Depends(get_settings)) -> dict:
return {
"status": "ok",
"app": settings.app_name,
"debug": settings.debug,
"framework_version": __version__,
}
Depends(get_settings) injects the cached Settings instance into the route. You'll use this same pattern for database sessions and auth in later parts.
6. Run migrations and start the dev server¶
Apply the initial migrations, then start the server:
You should see:
-> main:app on http://127.0.0.1:8000 (development)
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Application startup complete.
fas dev binds to 127.0.0.1 and enables auto-reload. Save a file and the server restarts automatically.
7. Call the API¶
In a second terminal, hit all three endpoints:
curl http://127.0.0.1:8000/
curl http://127.0.0.1:8000/status
curl http://127.0.0.1:8000/health/live
Or with Python:
import httpx
base = "http://127.0.0.1:8000"
print(httpx.get(f"{base}/").json())
print(httpx.get(f"{base}/status").json())
print(httpx.get(f"{base}/health/live").json())
The /health/live endpoint returns {"status": "ok"} as long as the process is alive. In Part 2 you'll also see /health/ready, which checks that the database is reachable.
What you built¶
- A scaffolded project using the
agentpreset: PostgreSQL, Redis, Qdrant, and JWT all configured - A
settings.pysubclass withDOCQA_env-var namespacing and apostgresql+asyncpgdatabase URL - A generated
ai/agents/module wired to an LLM backend (you'll extend it in Part 5) - A
/statusroute that returns app metadata using the publicfast_agent_stackAPI - A running dev server at
http://127.0.0.1:8000with auto-reload
Next steps¶
In Part 2 you'll define the Document SQLAlchemy model, generate an Alembic migration, and add CRUD routes for uploading and listing documents.