AI-Powered Semantic Search for Past Exam Questions
A full-stack web application that uses vector embeddings and semantic search to help students find relevant past exam questions instantly.
MatchMyQuestion transforms how students prepare for exams by leveraging AI to find semantically similar questions from a database of thousands of past exam papers. Instead of keyword matching, it understands the meaning behind queries to deliver the most relevant results.
- Semantic Search: Uses sentence-transformers and Qdrant vector database for intelligent query matching
- Multi-Subject Support: Covers 30+ subjects across various disciplines
- Year Filtering: Filter results by specific exam years
- Instant Answers: View explanations and correct answers for each question
- Fast & Responsive: Built with React + FastAPI for optimal performance
- Redis Caching: Speeds up repeated queries
- React 18 with TypeScript
- Vite for fast development
- Tailwind CSS for styling
- Lucide React for icons
- FastAPI for REST API
- Qdrant as vector database
- Redis for caching
- sentence-transformers (all-MiniLM-L6-v2) for embeddings
- Pydantic for data validation
- slowapi for rate limiting
- BeautifulSoup4 for web scraping
- CLI tool for data collection and Qdrant upload
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Frontend │─────▶│ Backend │─────▶│ Qdrant │
│ (React) │ │ (FastAPI) │ │ (Vectors) │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Redis │
│ (Cache) │
└─────────────┘
┌─────────────┐
│ Scraper │─────▶ Updates Qdrant with new questions
│ (Python) │
└─────────────┘
- Docker and Docker Compose installed
git clone https://github.com/oluwatooki-GA/MatchMyQuestion.git
cd MatchMyQuestion
docker-compose up -d --buildThis starts all services:
- Frontend (port 3000) — React + TypeScript
- Backend (port 8000) — FastAPI
- Qdrant (port 6333) — Vector database
- Redis (port 6379) — Caching
- Frontend: http://localhost:3000
- Backend API: http://localhost:8000
- API Documentation: http://localhost:8000/docs
- Qdrant Dashboard: http://localhost:6333/dashboard
docker-compose downThe scraper collects exam questions from myschool.ng and uploads them to Qdrant for semantic search.
docker-compose run --rm scraper python -m app.main run-allThis will:
- Scrape questions from all subjects and save to JSON
- Upload questions to Qdrant with embeddings
Scrape questions only:
docker-compose run --rm scraper python -m app.main scrape --autoUpload existing data to Qdrant:
docker-compose run --rm scraper python -m app.main upload --input-file final_result.json --collection-name questions --payload-indexes subject --payload-indexes yearProduction environment (uses Qdrant Cloud):
docker-compose run --rm scraper python -m app.main run-all -e prod| Option | Description | Default |
|---|---|---|
--folder |
Where to save scraped results | results |
--environment |
dev or prod |
dev |
--collection-name |
Qdrant collection name | questions |
--payload-indexes |
Metadata fields to index | subject,year |
MatchMyQuestion/
├── backend/ # FastAPI backend
│ ├── app/
│ │ ├── config/ # Settings & configuration
│ │ ├── schemas/ # Pydantic models
│ │ ├── services/ # Business logic (search, embeddings)
│ │ └── main.py # FastAPI application entry
│ ├── requirements.txt
│ └── Dockerfile
│
├── frontend/ # React frontend
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── hooks/ # Custom hooks (useSearchForm)
│ │ ├── lib/ # API client
│ │ ├── types/ # TypeScript types
│ │ └── data/ # Static data (subjects)
│ ├── package.json
│ └── Dockerfile
│
├── scraper/ # Data collection tool
│ ├── app/
│ │ ├── commands/ # CLI commands
│ │ ├── config/ # Scraper configuration
│ │ ├── qdrant/ # Qdrant uploader
│ │ └── services/ # Scraping logic
│ ├── requirements.txt
│ └── Dockerfile
│
├── docker-compose.yml # Service orchestration
├── .env.dev # Development environment
└── .env.prod # Production environment
POST /api/v1/search
Content-Type: application/json
{
"q": "photosynthesis process in plants",
"search_items": [
{"subject": "Biology", "years": ["2023", "2022"]}
]
}Response:
{
"result": [
{
"question": "Which of the following occurs during photosynthesis?",
"options": ["A. Carbon dioxide is released", "B. Oxygen is produced", ...],
"correct_answer": "Correct Answer: Option B",
"correct_answer_letter": "B",
"explanation_html": "<div>...</div>",
"subject": "Biology",
"exam_type": "JAMB",
"year": "2023",
"image_url": "N/A"
}
]
}| Variable | Description | Default |
|---|---|---|
VITE_API_URL |
Frontend API URL | http://localhost:8000 |
QDRANT_URL |
Qdrant instance URL | http://qdrant:6333 |
QDRANT_API_KEY |
Qdrant API key | (empty) |
QDRANT_COLLECTION_NAME |
Collection name | questions |
REDIS_HOST |
Redis host | redis |
REDIS_PORT |
Redis port | 6379 |
CORS_ORIGIN |
Allowed CORS origin | http://localhost:3000 |
- Embedding Generation: Exam questions are converted to 384-dimensional vectors using sentence-transformers
- Vector Storage: Vectors are stored in Qdrant with metadata (subject, year, exam type)
- Semantic Search: User queries are embedded and compared using cosine similarity
- Filtering: Results are filtered by selected subjects and years
- Caching: Redis caches search results for 30 minutes
Accounts, Agricultural Science, Animal Husbandry, Arabic, Biology, Book Keeping, Catering, Chemistry, Christian Religious Knowledge, Civic Education, Commerce, Computer Studies, Data Processing, Economics, English, Fine Arts, French, Further Mathematics, Geography, Government, Hausa, History, Home Economics, Igbo, Insurance, Islamic Religious Knowledge, Literature, Marketing, Mathematics, Music, Office Practice, Physical Education, Physics, Yoruba
MIT License - feel free to use this project for learning or portfolio purposes.
Built with ❤️ by oluwatooki-GA


