In active development
Markify is an end-to-end automated grading system for scanned student answer sheets. You upload a marking scheme and a student's scanned PDF; Markify extracts, segments, and grades each answer against the official rubric using a locally-hosted Qwen/Qwen2.5-VL-72B-Instruct-AWQ model served through vLLM.
awarded_marks ≤ max_marks, sum == total_score)React Frontend (port 5173)
│
▼ HTTP
FastAPI Backend (port 8080) ← main.py [single process]
│
├── PyMuPDF (CPU) ← PDF text extraction
├── pdf_to_images (CPU) ← PDF → 1288px PIL images
│
└── HTTP → vLLM Server (port 8000)
└── Qwen/Qwen2.5-VL-72B-Instruct-AWQ (GPU VRAM)
├── /v1/chat/completions (text + vision)
└── /v1/models (health check)
The legacy model_server.py / model_client.py two-process routing layer
has been fully retired. All inference (OCR, segmentation, grading) routes
directly to the single vLLM server.
| Method | Path | Description |
|---|---|---|
GET | /health | vLLM server status + loaded model |
POST | /markdown | Upload marking-scheme PDF → parsed questions + MongoDB save |
POST | /scanned | Upload student answer PDF → OCR → segment → grade |
GET | /get_latest_model | List the 5 most recent model-answer documents |
.
├── backend
│ ├── db.py # MongoDB lifecycle helpers
│ ├── llm_grading.py # Per-question LLM grading
│ ├── llm_marking_scheme.py # LLM marking-scheme extraction
│ ├── llm_segmentation.py # LLM-based document segmentation
│ ├── main.py # FastAPI app ← entry point
│ ├── model_client.py # [RETIRED] legacy model server client
│ ├── model_server.py # [RETIRED] legacy OlmOCREngine process
│ ├── olmocr_grading
│ │ ├── config.py # Rendering / preprocessing config
│ │ ├── image_utils.py # Resize, deskew, denoise helpers
│ │ ├── pdf_utils.py # PDF → PIL image rendering
│ │ └── ...
│ ├── requirements.txt
│ ├── segmentation.py # Regex fallback segmenter
│ └── vlm_client.py # vLLM OpenAI-compatible HTTP client ← NEW
└── frontend
└── ...
| Requirement | Version | Notes |
|---|---|---|
| Python | ≥ 3.10 | |
| CUDA GPU | ≥ 24 GB VRAM | For the 72B AWQ model; A100/H100 recommended |
| MongoDB | any recent | Running locally on default port 27017 |
| Node.js / Bun | any recent | For the React frontend |
All variables are optional (sane defaults shown):
| Variable | Default | Description |
|---|---|---|
VLLM_BASE_URL | http://localhost:8000/v1 | vLLM server endpoint |
VLLM_MODEL | Qwen/Qwen2.5-VL-72B-Instruct-AWQ | Model name on the vLLM server |
VLLM_MAX_TOKENS | 3072 | Max tokens for text-only calls |
VLLM_TIMEOUT | 600 | HTTP timeout (seconds) for inference calls |
MONGODB_URI | mongodb://localhost:27017 | MongoDB connection string |
MONGODB_DB | autoassess | Database name |
MONGODB_SCANNED_COLLECTION | scanned_documents | Collection for student sheets |
MONGODB_MODEL_ANSWERS_COLLECTION | model_answers | Collection for marking schemes |
# macOS (Homebrew)
brew services start mongodb-community
# Ubuntu / WSL
sudo systemctl start mongod
Install vLLM (if not already):
pip install vllm
Serve the model (requires a CUDA GPU with ≥ 24 GB VRAM):
vllm serve Qwen/Qwen2.5-VL-72B-Instruct-AWQ \
--host 0.0.0.0 \
--port 8000 \
--max-model-len 32768 \
--trust-remote-code
Wait until you see INFO: Application startup complete. in the vLLM
logs before starting the backend.
Tip: For multi-GPU setups, add
--tensor-parallel-size <N>to distribute the model across N GPUs.
cd backend
# Create and activate a virtual environment (uv recommended)
uv venv
# macOS / Linux
source .venv/bin/activate
# Windows
.venv\Scripts\activate
# Install dependencies
uv pip install -r requirements.txt
# Development (auto-reload on file changes)
uv run uvicorn main:app --host 0.0.0.0 --port 8080 --reload
# Production
uv run uvicorn main:app --host 0.0.0.0 --port 8080 --workers 2
The FastAPI interactive docs will be available at:
cd frontend
# Using npm
npm install && npm run dev
# Using bun
bun install && bun run dev
The React app will be available at http://localhost:5173.
# Check vLLM server + model status
curl http://localhost:8080/health
# Expected: {"status":"ok","model":"Qwen/Qwen2.5-VL-72B-Instruct-AWQ",...}
# 1. Upload a marking scheme
curl -X POST http://localhost:8080/markdown \
-F "myFile=@marking_scheme.pdf" \
| python -m json.tool
# 2. Grade a student answer sheet (using the model_answer_id from step 1)
curl -X POST http://localhost:8080/scanned \
-F "myFile=@student_answer.pdf" \
-F "model_answer_id=<document_id_from_step_1>" \
| python -m json.tool
If you were previously running the two-process setup:
# OLD (no longer needed — retire these)
uv run uvicorn model_server:app --host 127.0.0.1 --port 8001
uv run uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# NEW — just three processes
mongod # MongoDB
vllm serve Qwen/... # vLLM (port 8000)
uvicorn main:app --port 8080 # FastAPI (port 8080)
model_server.py and model_client.py are kept for reference but are
no longer imported or executed.
In active development
Markify is an end-to-end automated grading system for scanned student answer sheets. You upload a marking scheme and a student's scanned PDF; Markify extracts, segments, and grades each answer against the official rubric using a locally-hosted Qwen/Qwen2.5-VL-72B-Instruct-AWQ model served through vLLM.
awarded_marks ≤ max_marks, sum == total_score)React Frontend (port 5173)
│
▼ HTTP
FastAPI Backend (port 8080) ← main.py [single process]
│
├── PyMuPDF (CPU) ← PDF text extraction
├── pdf_to_images (CPU) ← PDF → 1288px PIL images
│
└── HTTP → vLLM Server (port 8000)
└── Qwen/Qwen2.5-VL-72B-Instruct-AWQ (GPU VRAM)
├── /v1/chat/completions (text + vision)
└── /v1/models (health check)
The legacy model_server.py / model_client.py two-process routing layer
has been fully retired. All inference (OCR, segmentation, grading) routes
directly to the single vLLM server.
| Method | Path | Description |
|---|---|---|
GET | /health | vLLM server status + loaded model |
POST | /markdown | Upload marking-scheme PDF → parsed questions + MongoDB save |
POST | /scanned | Upload student answer PDF → OCR → segment → grade |
GET | /get_latest_model | List the 5 most recent model-answer documents |
.
├── backend
│ ├── db.py # MongoDB lifecycle helpers
│ ├── llm_grading.py # Per-question LLM grading
│ ├── llm_marking_scheme.py # LLM marking-scheme extraction
│ ├── llm_segmentation.py # LLM-based document segmentation
│ ├── main.py # FastAPI app ← entry point
│ ├── model_client.py # [RETIRED] legacy model server client
│ ├── model_server.py # [RETIRED] legacy OlmOCREngine process
│ ├── olmocr_grading
│ │ ├── config.py # Rendering / preprocessing config
│ │ ├── image_utils.py # Resize, deskew, denoise helpers
│ │ ├── pdf_utils.py # PDF → PIL image rendering
│ │ └── ...
│ ├── requirements.txt
│ ├── segmentation.py # Regex fallback segmenter
│ └── vlm_client.py # vLLM OpenAI-compatible HTTP client ← NEW
└── frontend
└── ...
| Requirement | Version | Notes |
|---|---|---|
| Python | ≥ 3.10 | |
| CUDA GPU | ≥ 24 GB VRAM | For the 72B AWQ model; A100/H100 recommended |
| MongoDB | any recent | Running locally on default port 27017 |
| Node.js / Bun | any recent | For the React frontend |
All variables are optional (sane defaults shown):
| Variable | Default | Description |
|---|---|---|
VLLM_BASE_URL | http://localhost:8000/v1 | vLLM server endpoint |
VLLM_MODEL | Qwen/Qwen2.5-VL-72B-Instruct-AWQ | Model name on the vLLM server |
VLLM_MAX_TOKENS | 3072 | Max tokens for text-only calls |
VLLM_TIMEOUT | 600 | HTTP timeout (seconds) for inference calls |
MONGODB_URI | mongodb://localhost:27017 | MongoDB connection string |
MONGODB_DB | autoassess | Database name |
MONGODB_SCANNED_COLLECTION | scanned_documents | Collection for student sheets |
MONGODB_MODEL_ANSWERS_COLLECTION | model_answers | Collection for marking schemes |
# macOS (Homebrew)
brew services start mongodb-community
# Ubuntu / WSL
sudo systemctl start mongod
Install vLLM (if not already):
pip install vllm
Serve the model (requires a CUDA GPU with ≥ 24 GB VRAM):
vllm serve Qwen/Qwen2.5-VL-72B-Instruct-AWQ \
--host 0.0.0.0 \
--port 8000 \
--max-model-len 32768 \
--trust-remote-code
Wait until you see INFO: Application startup complete. in the vLLM
logs before starting the backend.
Tip: For multi-GPU setups, add
--tensor-parallel-size <N>to distribute the model across N GPUs.
cd backend
# Create and activate a virtual environment (uv recommended)
uv venv
# macOS / Linux
source .venv/bin/activate
# Windows
.venv\Scripts\activate
# Install dependencies
uv pip install -r requirements.txt
# Development (auto-reload on file changes)
uv run uvicorn main:app --host 0.0.0.0 --port 8080 --reload
# Production
uv run uvicorn main:app --host 0.0.0.0 --port 8080 --workers 2
The FastAPI interactive docs will be available at:
cd frontend
# Using npm
npm install && npm run dev
# Using bun
bun install && bun run dev
The React app will be available at http://localhost:5173.
# Check vLLM server + model status
curl http://localhost:8080/health
# Expected: {"status":"ok","model":"Qwen/Qwen2.5-VL-72B-Instruct-AWQ",...}
# 1. Upload a marking scheme
curl -X POST http://localhost:8080/markdown \
-F "myFile=@marking_scheme.pdf" \
| python -m json.tool
# 2. Grade a student answer sheet (using the model_answer_id from step 1)
curl -X POST http://localhost:8080/scanned \
-F "myFile=@student_answer.pdf" \
-F "model_answer_id=<document_id_from_step_1>" \
| python -m json.tool
If you were previously running the two-process setup:
# OLD (no longer needed — retire these)
uv run uvicorn model_server:app --host 127.0.0.1 --port 8001
uv run uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# NEW — just three processes
mongod # MongoDB
vllm serve Qwen/... # vLLM (port 8000)
uvicorn main:app --port 8080 # FastAPI (port 8080)
model_server.py and model_client.py are kept for reference but are
no longer imported or executed.