03. Backend Deep Dive¶
Layout and entrypoints¶
| Path | Role |
|---|---|
backend/app/main.py |
FastAPI app, CORS, lifespan create_all, router mount |
backend/app/config.py |
Pydantic Settings (../.env) |
backend/app/core/ |
Ingestion, extraction, OCR, LLM, audit, sanitize |
backend/app/uc1_factsheet/ |
Factsheet API + phased services |
backend/app/workers/ |
Celery app, async bridge, tasks |
backend/app/prompts/ |
Jinja prompt pack |
backend/app/db/ |
SQLAlchemy models + async session |
Run via Makefile: uvicorn app.main:app from backend/ (default port 8000).
API surface (/api/v1)¶
Documents (core/ingestion/router.py)¶
| Method | Path | Behavior |
|---|---|---|
| POST | /documents |
Upload PDF → MinIO + DB → enqueue pipeline |
| GET | /documents |
List |
| GET | /documents/{id}/status |
Status |
| GET | /documents/{id}/artifacts |
Artifact list |
| GET | /documents/{id}/ocr-texts |
OCR markdown artifacts |
| GET | /documents/{id}/extracted-info |
Merged extraction |
| DELETE | /documents/{id} |
Delete document + related data |
Jobs (core/extraction/router.py)¶
| Method | Path | Behavior |
|---|---|---|
| POST | /jobs/{id}/start |
Start / continue pipeline |
| POST | /jobs/{id}/resume |
Resume after partial failure |
| POST | /jobs/{id}/run-ocr |
Re-run OCR stage |
| POST | /jobs/{id}/run-extraction |
Re-run extraction |
| POST | /jobs/{id}/run-merge |
Re-run merge |
| GET | /jobs |
List |
| GET | /jobs/{id} |
Detail |
| GET | /jobs/{id}/chunks |
Chunk statuses |
Factsheet (uc1_factsheet/router.py)¶
| Method | Path | Behavior |
|---|---|---|
| POST | /factsheet/generate |
202 Accepted; Celery task |
| GET | /factsheet/status/{job_id} |
Celery / record status |
| GET | /factsheet |
List |
| GET | /factsheet/{id} |
Detail |
| DELETE | /factsheet/{id} |
Delete |
Health: GET /health on the app root.
Request lifecycle¶
- Router validates input and opens
AsyncSessionviaDepends(get_db). - Ingestion writes object to S3-compatible storage and creates
Document/DocumentJob. - Celery task runs stages; each stage updates
JobStage/JobChunkand writesArtifactrows. - Clients poll document/job endpoints; UI also polls factsheet status by Celery
task_id.
No auth dependency is attached to these routers (Confirmed).
Pipeline and concurrency¶
workers/tasks/pipeline_tasks.py (~900 LOC) orchestrates:
- OCR — page-range chunks (
OCR_PAGE_CHUNK_SIZE), parallel calls capped byOCR_MAX_PARALLEL_CALLS - EXTRACTION — text-span chunks, parallel LLM extraction
- MERGE — normalize into entities / attributes / relationships
Retries: JOB_CHUNK_MAX_RETRIES. Failed chunks can leave job PARTIAL.
Celery tasks are sync; async SQLAlchemy and LLM calls go through workers/async_runner.py:
OCR engines¶
OCR_ENGINE in settings selects:
| Value | Adapter | Notes |
|---|---|---|
vlm (default) |
PyMuPDF pages → LiteLLM vision | Uses VLLM_MODEL |
sarvam |
Sarvam OCR async API | Poll interval / max wait settings |
layout |
HTTP client to Paddle OCR service | PADDLE_OCR_SERVICE_URL (often local ocr-service) |
Factory lives under core/ocr/. Language default en-IN.
LLM client and prompts¶
core/llm/client.pywraps LiteLLM with timeout / max tokens from settings- Prompts under
prompts/viaprompt_manager.py(OCR, extraction, factsheet) core/text/sanitize.pystrips oversized base64 images before LLM context
UC1 factsheet service¶
| Phase | Module | Role |
|---|---|---|
| 1 | service/phase1.py |
Discriminators / field selection; honor prefilled |
| 2 | service/phase2.py |
Per-doc sub-model extraction; entities-first then OCR fallback when confidence < FACTSHEET_CONFIDENCE_THRESHOLD (0.6) |
| 3 | service/factsheet.py |
Assemble result + provenance |
Supporting assets: factsheet_manifest.json, Pydantic models in uc1_factsheet/models.py.
Configuration highlights¶
From config.py (not exhaustive):
DATABASE_URL,REDIS_URLS3_ENDPOINT_URL,S3_ACCESS_KEY_ID,S3_SECRET_ACCESS_KEY,S3_BUCKET_DOCSLLM_MODEL,VLLM_MODEL,OPENAI_*- Chunking / parallelism knobs
FACTSHEET_PHASE1_MAX_CHARS,FACTSHEET_PHASE2_MAX_CHARSOIDC_*,JWT_SECRET(unused on routes)
.env.example documents MINIO_* names that do not match S3_* settings fields — see 06.
Auth helpers (unused)¶
dependencies.py defines JWT validation, a dev-token bypass, and require_role. Routers do not depend on them. Treat as scaffolding for a future SSO cutover.
Error handling and logging¶
LoggingMiddlewareon all requestssetup_logging()from settingsLOG_LEVEL- Job/chunk
last_errorand factsheeterror_messagepersist failure text - HTTP errors via FastAPI defaults; no global domain exception taxonomy beyond routers
Backend interview Q&A¶
Q: Why Celery instead of asyncio background tasks?
A: Long OCR/LLM work needs process isolation, retries, and horizontal workers. FastAPI stays responsive; Redis brokers durable tasks.
Q: How do you resume a failed job?
A: Stages and chunks are rows with status. resume / run-* endpoints re-enqueue only the needed stage rather than re-uploading the PDF.
Q: How does async code run inside Celery?
A: A process-local event loop in async_runner.py runs coroutines to completion so asyncpg and async LLM clients work from sync task bodies.