| # ๐๏ธ Architecture Documentation - AI Quiz Bot |
|
|
| System design, API specifications, and data structures. |
|
|
| ## ๐ Table of Contents |
|
|
| - [System Architecture](#system-architecture) |
| - [API Specifications](#api-specifications) |
| - [Data Structures](#data-structures) |
| - [Request/Response Examples](#requestresponse-examples) |
| - [Error Handling](#error-handling) |
| - [Scalability Considerations](#scalability-considerations) |
|
|
| ## ๐๏ธ System Architecture |
|
|
| ### High-Level Overview |
|
|
| ``` |
| โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ |
| โ Telegram Users โ |
| โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ |
| โ |
| โ |
| โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ |
| โ Telegram Bot API (python-telegram-bot) โ |
| โ - Polling for messages โ |
| โ - Sending polls and messages โ |
| โ - File download capability โ |
| โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ |
| โ |
| โ |
| โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ |
| โ AI Quiz Bot (bot.py) โ |
| โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ |
| โ โ Message Handlers โ โ |
| โ โ - handle_file() [Images/PDFs] โ โ |
| โ โ - handle_text() [Text messages] โ โ |
| โ โ - Commands (/start, /help) โ โ |
| โ โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ |
| โ โ โ |
| โ โโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ |
| โ โ Processing Layer โ โ |
| โ โ - extract_text_from_image() โ โ |
| โ โ - extract_text_from_pdf() โ โ |
| โ โ - parse_num_questions() โ โ |
| โ โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ |
| โ โ โ |
| โ โโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ |
| โ โ Generation Layer โ โ |
| โ โ - generate_quiz_questions() โ โ |
| โ โ - Quiz JSON parsing โ โ |
| โ โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ |
| โ โ โ |
| โ โโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ |
| โ โ Delivery Layer โ โ |
| โ โ - send_quiz_poll() โ โ |
| โ โ - Completion messages โ โ |
| โ โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ |
| โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ |
| โ |
| โโโโโโโโโโโโดโโโโโโโโโโโ |
| โ โ |
| โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ |
| โ Ollama Cloud API โ โ External APIs โ |
| โ (REST HTTP/HTTPS)โ โ Telegram API โ |
| โ โ โ โ |
| โ - /api/chat โ โ (https) โ |
| โ Vision Model โ โ โ |
| โ Chat Model โ โ โ |
| โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ |
| ``` |
|
|
| ### Component Layers |
|
|
| #### 1. **Telegram Integration Layer** |
| - Receives messages via polling |
| - Handles file downloads |
| - Sends messages and polls |
| - Manages message edits |
|
|
| #### 2. **Message Handler Layer** |
| - Routes incoming messages to appropriate handlers |
| - Validates message type (file vs text vs command) |
| - Extracts metadata (chat ID, user ID, caption, etc.) |
| - Shows loading messages |
|
|
| #### 3. **Processing Layer** |
| - Extracts text from images (Vision API) |
| - Extracts text from PDFs (PyPDF2) |
| - Parses user input (question count) |
| - Handles errors and retries |
|
|
| #### 4. **Generation Layer** |
| - Sends text to Chat API |
| - Manages prompts and system instructions |
| - Parses JSON responses |
| - Validates question format |
|
|
| #### 5. **Delivery Layer** |
| - Sends each question as a Telegram poll |
| - Manages rate limiting (1-second delays) |
| - Sends completion messages |
| - Logs delivery success/failure |
|
|
| ## ๐ API Specifications |
|
|
| ### Ollama Cloud API |
|
|
| **Base URL**: `https://ollama.com` |
|
|
| **Authentication**: Bearer Token (in Authorization header) |
|
|
| #### Endpoint: POST /api/chat |
|
|
| **Purpose**: Vision analysis, chat completion, JSON generation |
|
|
| **Request Headers**: |
| ``` |
| Authorization: Bearer {OLLAMA_API_KEY} |
| Content-Type: application/json |
| ``` |
|
|
| **Request Body**: |
| ```json |
| { |
| "model": "ministral-3:8b", |
| "messages": [ |
| { |
| "role": "user", |
| "content": "Text or prompt", |
| "images": ["base64_encoded_image"] // Optional |
| } |
| ], |
| "stream": false, |
| "format": "json" // Optional, ensures JSON output |
| } |
| ``` |
|
|
| **Response**: |
| ```json |
| { |
| "model": "ministral-3:8b", |
| "created_at": "2026-02-01T10:00:00Z", |
| "message": { |
| "role": "assistant", |
| "content": "Response text or JSON" |
| }, |
| "done": true, |
| "total_duration": 1234567, |
| "load_duration": 567, |
| "prompt_eval_count": 50, |
| "eval_count": 100, |
| "eval_duration": 100000 |
| } |
| ``` |
|
|
| **Parameters**: |
| | Parameter | Type | Required | Description | |
| |-----------|------|----------|-------------| |
| | model | string | Yes | Model name (e.g., "ministral-3:14b") | |
| | messages | array | Yes | Array of message objects | |
| | stream | boolean | No | Default: false (we don't use streaming) | |
| | format | string | No | "json" to ensure JSON output | |
|
|
| **Message Object**: |
| ```json |
| { |
| "role": "user" | "assistant" | "system", |
| "content": "Text content", |
| "images": ["base64_string"] // Optional, only for vision models |
| } |
| ``` |
|
|
| ### Telegram Bot API |
|
|
| **Used Methods**: |
|
|
| #### 1. send_poll |
| ```python |
| await bot.send_poll( |
| chat_id=12345, |
| question="Question text?", |
| options=["A", "B", "C", "D"], |
| type="quiz", |
| correct_option_id=1, |
| is_anonymous=False |
| ) |
| ``` |
| |
| #### 2. send_message |
| ```python |
| await bot.send_message( |
| chat_id=12345, |
| text="Message text", |
| reply_to_message_id=67890 |
| ) |
| ``` |
| |
| #### 3. edit_message_text |
| ```python |
| await message.edit_text("Updated text") |
| ``` |
|
|
| #### 4. get_file |
| ```python |
| file = await bot.get_file(file_id) |
| file_bytes = await file.download_as_bytearray() |
| ``` |
| |
| ## ๐ Data Structures |
| |
| ### Question Object (JSON) |
| |
| ```json |
| { |
| "question": "What is the capital of France?", |
| "options": ["London", "Berlin", "Paris", "Madrid"], |
| "correct_option_id": 2 |
| } |
| ``` |
| |
| **Fields**: |
| - `question` (string): Question text (max 300 chars in Telegram) |
| - `options` (array): Array of option strings (max 10 options in Telegram) |
| - `correct_option_id` (number): Index of correct option (0-based) |
| |
| ### Questions Array |
| |
| ```json |
| [ |
| { |
| "question": "Question 1?", |
| "options": ["A", "B", "C", "D"], |
| "correct_option_id": 0 |
| }, |
| { |
| "question": "Question 2?", |
| "options": ["A", "B", "C", "D"], |
| "correct_option_id": 1 |
| } |
| ] |
| ``` |
| |
| ### Image Object |
|
|
| **Format**: Base64-encoded JPEG/PNG/WebP |
|
|
| **Usage in API**: |
| ```json |
| { |
| "role": "user", |
| "content": "Describe this image", |
| "images": ["iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="] |
| } |
| ``` |
|
|
| ## ๐ค Request/Response Examples |
|
|
| ### Example 1: Image Analysis |
|
|
| **User Action**: Send image with caption "5" |
|
|
| **Request to Ollama**: |
| ```bash |
| POST https://ollama.com/api/chat |
| Authorization: Bearer 830f2fd6fedc491ba49c91a0709c94d9.4Z84I1pRSwd8iPDT1J4Tfu6c |
| Content-Type: application/json |
| |
| { |
| "model": "ministral-3:8b", |
| "messages": [ |
| { |
| "role": "user", |
| "content": "Just transcribe all the text in the image in details. Output only the text, nothing else.", |
| "images": ["base64_encoded_image_here..."] |
| } |
| ], |
| "stream": false |
| } |
| ``` |
|
|
| **Response from Ollama**: |
| ```json |
| { |
| "message": { |
| "role": "assistant", |
| "content": "The extracted text from the image goes here...\nThis is the text the bot analyzed." |
| }, |
| "done": true |
| } |
| ``` |
|
|
| **Bot Processing**: |
| 1. Receives extracted text |
| 2. Parses caption: 5 questions |
| 3. Calls quiz generation |
|
|
| ### Example 2: Quiz Generation |
|
|
| **Request to Ollama**: |
| ```bash |
| POST https://ollama.com/api/chat |
| Authorization: Bearer 830f2fd6fedc491ba49c91a0709c94d9.4Z84I1pRSwd8iPDT1J4Tfu6c |
| Content-Type: application/json |
| |
| { |
| "model": "ministral-3:14b", |
| "messages": [ |
| { |
| "role": "system", |
| "content": "You are a strict Quiz Generator.\nRules:\n1. Output ONLY the questions and answers.\n2. Do NOT provide any conversational text..." |
| }, |
| { |
| "role": "user", |
| "content": "Based on the text I provide, generate 5 multiple-choice quiz questions.\n\nYou must output valid JSON only...\n\nText to analyze:\nThe extracted text goes here..." |
| } |
| ], |
| "stream": false, |
| "format": "json" |
| } |
| ``` |
|
|
| **Response from Ollama**: |
| ```json |
| { |
| "message": { |
| "role": "assistant", |
| "content": "[{\"question\":\"Q1?\",\"options\":[\"A\",\"B\",\"C\",\"D\"],\"correct_option_id\":0},{\"question\":\"Q2?\",\"options\":[\"A\",\"B\",\"C\",\"D\"],\"correct_option_id\":1}]" |
| }, |
| "done": true |
| } |
| ``` |
|
|
| **Bot Processing**: |
| 1. Receives JSON string |
| 2. Cleans markdown if present |
| 3. Parses JSON |
| 4. Validates each question |
| 5. Sends as Telegram polls |
|
|
| ### Example 3: Send Quiz Poll to Telegram |
|
|
| **Request to Telegram**: |
| ```bash |
| POST https://api.telegram.org/bot7319290683:AAFpfIEGCZGOky3OOB2V-xcn8rq8pc7TEUQ/sendPoll |
| Content-Type: application/json |
| |
| { |
| "chat_id": 123456789, |
| "question": "What is the capital of France?", |
| "options": ["London", "Berlin", "Paris", "Madrid"], |
| "type": "quiz", |
| "correct_option_id": 2, |
| "is_anonymous": false |
| } |
| ``` |
|
|
| **Response from Telegram**: |
| ```json |
| { |
| "ok": true, |
| "result": { |
| "message_id": 12345, |
| "chat": {"id": 123456789}, |
| "poll": { |
| "id": "poll123", |
| "question": "What is the capital of France?", |
| "options": [ |
| {"text": "London", "voter_count": 0}, |
| {"text": "Berlin", "voter_count": 0}, |
| {"text": "Paris", "voter_count": 0}, |
| {"text": "Madrid", "voter_count": 0} |
| ], |
| "type": "quiz", |
| "total_voter_count": 0, |
| "is_closed": false, |
| "is_anonymous": false, |
| "allows_multiple_answers": false, |
| "correct_option_id": 2 |
| } |
| } |
| } |
| ``` |
|
|
| ## ๐ก๏ธ Error Handling |
|
|
| ### API Error Types |
|
|
| | Error | HTTP Code | Cause | Bot Response | |
| |-------|-----------|-------|-------------| |
| | Invalid API Key | 401 | Wrong/expired OLLAMA_API_KEY | "ุญุตู ู
ุดููุฉ" + retry | |
| | Model Not Found | 404 | Invalid VISION_MODEL or CHAT_MODEL | "Model not found" error | |
| | Rate Limited | 429 | Too many requests | Retry with backoff | |
| | Timeout | 504 | API slow | "API timeout" message | |
| | DNS Error | N/A | Wrong OLLAMA_HOST | Network error | |
| |
| ### Error Flow |
| |
| ``` |
| Error Occurs |
| โ |
| Logger.error() called with details |
| โ |
| Exception raised/caught |
| โ |
| IF API Error: Notify user with friendly message |
| ELSE IF Validation Error: Show specific error |
| ELSE: Generic "ุญุตู ู
ุดููุฉ" message |
| โ |
| Message edit/delete if needed |
| โ |
| Continue waiting for next user input |
| ``` |
| |
| ### User-Facing Error Messages |
| |
| ```python |
| # Network errors |
| "โ ููุน ู
ูู ู
ุด ู
ุฏุนูู
!" |
| |
| # Processing errors |
| "โ ู
ุด ูุงุฏุฑ ุงุณุชุฎุฑุฌ ูุต ูุงูู" |
| |
| # Generation errors |
| "โ ู
ุด ูุงุฏุฑ ุงุนู
ู ุงุณุฆูุฉ ู
ู ุงููุต ุฏู" |
| |
| # Validation errors |
| "โ ุงููุต ูุตูุฑ ุดููุฉ!" |
| |
| # Generic errors |
| "โ ุญุตู ู
ุดููุฉ: {error_details}" |
| ``` |
| |
| ## ๐ Scalability Considerations |
| |
| ### Current Limitations |
| |
| 1. **Sequential Processing** |
| - Each question sent with 1-second delay |
| - Multiple users processed sequentially |
| - No concurrent request batching |
| |
| 2. **Memory Usage** |
| - Entire image/PDF loaded into memory |
| - Full text stored in variables |
| - No streaming or chunking |
| |
| 3. **Rate Limiting** |
| - No rate limiting per user |
| - Telegram has built-in limits |
| - Ollama API has unknown limits |
| |
| ### Scaling Improvements |
| |
| #### 1. Add Request Queuing |
| ```python |
| import asyncio |
| from asyncio import Queue |
|
|
| class BotQueue: |
| def __init__(self, max_concurrent=3): |
| self.queue = Queue() |
| self.processing = set() |
| |
| async def process(self, user_id, request): |
| await self.queue.put((user_id, request)) |
| await self._process_queue() |
| ``` |
| |
| #### 2. Add Redis Caching |
| ```python |
| import redis |
| |
| cache = redis.Redis(host='localhost', port=6379) |
| |
| async def generate_quiz_questions_cached(text, num_questions): |
| cache_key = f"quiz:{hash(text)}:{num_questions}" |
| cached = cache.get(cache_key) |
| if cached: |
| return json.loads(cached) |
| |
| # Generate new questions |
| questions = await generate_quiz_questions(text, num_questions) |
| cache.setex(cache_key, 3600, json.dumps(questions)) # 1 hour TTL |
| return questions |
| ``` |
|
|
| #### 3. Add Database Logging |
| ```python |
| import sqlite3 |
| |
| def log_request(user_id, request_type, status): |
| conn = sqlite3.connect('bot.db') |
| c = conn.cursor() |
| c.execute('''INSERT INTO requests (user_id, type, status, timestamp) |
| VALUES (?, ?, ?, ?)''', |
| (user_id, request_type, status, datetime.now())) |
| conn.commit() |
| ``` |
|
|
| #### 4. Concurrent File Processing |
| ```python |
| # For multiple images/PDFs |
| async def process_multiple_files(files): |
| tasks = [extract_text_from_image(f) for f in files] |
| results = await asyncio.gather(*tasks) |
| return results |
| ``` |
|
|
| ### Performance Metrics |
|
|
| **Current Performance**: |
| - Image analysis: 5-15 seconds |
| - PDF extraction: 1-5 seconds |
| - Quiz generation: 10-30 seconds |
| - Total time: 20-50 seconds |
|
|
| **Bottlenecks**: |
| 1. Ollama API response time (50-80%) |
| 2. Image base64 encoding (5-10%) |
| 3. Network latency (10-20%) |
|
|
| ## ๐ Deployment Architecture |
|
|
| ### Development |
| ``` |
| Local PC |
| โโโ Python 3.9+ |
| โโโ bot.py |
| โโโ .env (with test credentials) |
| โโโ Internet connection to Ollama Cloud |
| ``` |
|
|
| ### Production |
| ``` |
| Server/Cloud |
| โโโ Python 3.9+ |
| โโโ bot.py |
| โโโ .env (with production credentials) |
| โโโ Systemd/Docker service |
| โโโ Error logging |
| โโโ Database backup |
| ``` |
|
|
| ### Docker Deployment |
|
|
| ```dockerfile |
| FROM python:3.9-slim |
| |
| WORKDIR /app |
| COPY requirements.txt . |
| RUN pip install -r requirements.txt |
| |
| COPY bot.py . |
| COPY .env . |
| |
| CMD ["python", "bot.py"] |
| ``` |
|
|
| ```bash |
| docker build -t aiquiz-bot . |
| docker run -d --name aiquiz-bot aiquiz-bot |
| ``` |
|
|
| --- |
|
|
| **Last Updated**: February 2026 |
| **Architecture Version**: 1.0 |
| **API Version**: Ollama Cloud API v1 |
|
|