iitmbs24f commited on
Commit
2f95553
·
verified ·
1 Parent(s): c205fdb

Upload 37 files

Browse files
.dockerignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .Python
6
+ *.so
7
+ *.egg
8
+ *.egg-info
9
+ dist
10
+ build
11
+ .git
12
+ .gitignore
13
+ .env
14
+ .venv
15
+ venv/
16
+ ENV/
17
+ *.log
18
+ .DS_Store
19
+ .vscode
20
+ .idea
21
+ *.swp
22
+ *.swo
23
+ *~
24
+
.gitignore ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual environments
24
+ venv/
25
+ ENV/
26
+ env/
27
+ .venv
28
+
29
+ # IDE
30
+ .vscode/
31
+ .idea/
32
+ *.swp
33
+ *.swo
34
+ *~
35
+
36
+ # Environment variables
37
+ .env
38
+ .env.local
39
+
40
+ # Logs
41
+ *.log
42
+ logs/
43
+
44
+ # OS
45
+ .DS_Store
46
+ Thumbs.db
47
+
48
+ # Playwright
49
+ .playwright/
50
+ ms-playwright/
51
+
52
+ # Testing
53
+ .pytest_cache/
54
+ .coverage
55
+ htmlcov/
56
+
57
+ # Jupyter
58
+ .ipynb_checkpoints
59
+
60
+ # Project specific
61
+ *.pdf
62
+ *.csv
63
+ *.xlsx
64
+ temp/
65
+ tmp/
66
+
DEBUG_ERROR.md ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Debugging the D1_TYPE_ERROR
2
+
3
+ ## Understanding the Error
4
+
5
+ The error `"Internal error: D1_TYPE_ERROR: Type 'object' not supported for value '[object Object]'"` is coming from the **quiz page backend** (likely Cloudflare D1 database), not from our server.
6
+
7
+ This suggests:
8
+ 1. ✅ Our server is working correctly
9
+ 2. ✅ The request is reaching the quiz page
10
+ 3. ❌ The quiz page backend is rejecting our answer format
11
+
12
+ ## Possible Causes
13
+
14
+ ### 1. Wrong URL
15
+ You're using: `https://tds-llm-analysis.s-anand.net/demo`
16
+
17
+ This is a **demo endpoint**, not a quiz page. You need to:
18
+ - Go to the actual quiz page first
19
+ - Get the quiz page URL
20
+ - Use that URL in your request
21
+
22
+ ### 2. Answer Format Issue
23
+ The quiz page might expect:
24
+ - A simple string instead of a JSON object
25
+ - A specific JSON structure
26
+ - A different data type
27
+
28
+ ### 3. Quiz Page Backend Issue
29
+ The quiz page's database (D1) might have issues with:
30
+ - Complex nested objects
31
+ - Certain data types
32
+ - The way we're serializing the answer
33
+
34
+ ## How to Debug
35
+
36
+ ### Step 1: Check Server Logs
37
+
38
+ When you run the server, you should see detailed logs:
39
+ ```powershell
40
+ python -m app.main
41
+ ```
42
+
43
+ Look for:
44
+ - "Submitting answer to: ..."
45
+ - "Payload: ..."
46
+ - "Response status: ..."
47
+ - "Error response: ..."
48
+
49
+ ### Step 2: Test with a Simple Answer
50
+
51
+ Try submitting a simple string answer first:
52
+
53
+ ```powershell
54
+ # Test with simple answer
55
+ $body = @{
56
+ email = "24f2005753@ds.study.iitm.ac.in"
57
+ secret = "EasyQuiz"
58
+ url = "https://tds-llm-analysis.s-anand.net/demo"
59
+ } | ConvertTo-Json
60
+
61
+ # But wait - you need the actual quiz page URL, not the demo endpoint!
62
+ ```
63
+
64
+ ### Step 3: Verify the Quiz Page URL
65
+
66
+ The URL should be:
67
+ - A quiz page (like `https://tds-llm-analysis.s-anand.net/quiz/123`)
68
+ - NOT the demo endpoint (`/demo`)
69
+
70
+ ### Step 4: Check What the Quiz Page Expects
71
+
72
+ 1. Open the quiz page in a browser
73
+ 2. Look at the page source
74
+ 3. Find the submit URL
75
+ 4. Check what format the answer should be in
76
+
77
+ ## Quick Fixes to Try
78
+
79
+ ### Fix 1: Use Correct URL
80
+ ```powershell
81
+ # Get the actual quiz page URL first
82
+ # Then use that URL, not the demo endpoint
83
+ $body = @{
84
+ email = "24f2005753@ds.study.iitm.ac.in"
85
+ secret = "EasyQuiz"
86
+ url = "https://tds-llm-analysis.s-anand.net/actual-quiz-page" # Change this!
87
+ } | ConvertTo-Json
88
+ ```
89
+
90
+ ### Fix 2: Check Server Response
91
+ The server now logs more details. Check the terminal where `python -m app.main` is running to see:
92
+ - What payload is being sent
93
+ - What response is received
94
+ - Any error details
95
+
96
+ ### Fix 3: Test with curl for Better Error Messages
97
+ ```powershell
98
+ curl.exe -X POST http://127.0.0.1:8000/demo `
99
+ -H "Content-Type: application/json" `
100
+ -d '{\"email\":\"24f2005753@ds.study.iitm.ac.in\",\"secret\":\"EasyQuiz\",\"url\":\"https://tds-llm-analysis.s-anand.net/demo\"}' `
101
+ -v
102
+ ```
103
+
104
+ The `-v` flag shows verbose output including headers and response details.
105
+
106
+ ## What I've Fixed
107
+
108
+ I've updated the code to:
109
+ 1. ✅ Better error handling in `_submit_answer`
110
+ 2. ✅ More detailed logging
111
+ 3. ✅ Better JSON serialization handling
112
+ 4. ✅ Logs the full payload and response
113
+
114
+ ## Next Steps
115
+
116
+ 1. **Check the server logs** when you make the request
117
+ 2. **Verify you're using the correct quiz page URL** (not the demo endpoint)
118
+ 3. **Look at the detailed error in the logs** to see what the quiz page is rejecting
119
+ 4. **Try with a simpler answer format** if the logs show serialization issues
120
+
121
+ ## Expected Behavior
122
+
123
+ When working correctly:
124
+ 1. Server loads the quiz page
125
+ 2. Extracts the question
126
+ 3. Solves it
127
+ 4. Finds the submit URL
128
+ 5. Submits the answer
129
+ 6. Returns the response
130
+
131
+ The error you're seeing happens at step 5 - the quiz page backend is rejecting our submission.
132
+
Dockerfile ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python 3.10 slim image
2
+ FROM python:3.10-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ wget \
10
+ gnupg \
11
+ ca-certificates \
12
+ fonts-liberation \
13
+ libasound2 \
14
+ libatk-bridge2.0-0 \
15
+ libatk1.0-0 \
16
+ libatspi2.0-0 \
17
+ libcups2 \
18
+ libdbus-1-3 \
19
+ libdrm2 \
20
+ libgbm1 \
21
+ libgtk-3-0 \
22
+ libnspr4 \
23
+ libnss3 \
24
+ libxcomposite1 \
25
+ libxdamage1 \
26
+ libxfixes3 \
27
+ libxkbcommon0 \
28
+ libxrandr2 \
29
+ xdg-utils \
30
+ && rm -rf /var/lib/apt/lists/*
31
+
32
+ # Copy requirements first for better caching
33
+ COPY requirements.txt .
34
+
35
+ # Install Python dependencies
36
+ RUN pip install --no-cache-dir -r requirements.txt
37
+
38
+ # Install Playwright browsers
39
+ RUN playwright install chromium
40
+ RUN playwright install-deps chromium
41
+
42
+ # Copy application code
43
+ COPY app/ ./app/
44
+
45
+ # Set environment variables
46
+ ENV PYTHONUNBUFFERED=1
47
+ ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
48
+
49
+ # Expose port
50
+ EXPOSE 8000
51
+
52
+ # Health check
53
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
54
+ CMD python -c "import requests; requests.get('http://localhost:8000/health')"
55
+
56
+ # Run the application
57
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
58
+
ENV_SETUP.md ADDED
@@ -0,0 +1,316 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment Variables Setup Guide
2
+
3
+ This guide explains how to set the required environment variables for the IITM LLM Quiz Solver.
4
+
5
+ ## Required Variables
6
+
7
+ - `QUIZ_SECRET`: Secret key for API authentication (required)
8
+ - `OPENAI_API_KEY`: OpenAI API key for LLM features (optional)
9
+ - `OPENROUTER_API_KEY`: OpenRouter key (e.g., GPT-5-nano) – optional fallback or alternative
10
+
11
+ ---
12
+
13
+ ## Method 1: Windows (PowerShell)
14
+
15
+ ### Temporary (Current Session Only)
16
+
17
+ Open PowerShell and run:
18
+
19
+ ```powershell
20
+ $env:QUIZ_SECRET = "EasyQuiZ"
21
+ $env:OPENAI_API_KEY = "sk-your-openai-api-key-here"
22
+ $env:OPENROUTER_API_KEY = "sk-or-your-openrouter-key"
23
+ ```
24
+
25
+ ### Permanent (System-Wide)
26
+
27
+ 1. **Using PowerShell (Admin):**
28
+ ```powershell
29
+ [System.Environment]::SetEnvironmentVariable("QUIZ_SECRET", "your_secret_key_here", "User")
30
+ [System.Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "sk-your-openai-api-key-here", "User")
31
+ [System.Environment]::SetEnvironmentVariable("OPENROUTER_API_KEY", "sk-or-your-openrouter-key", "User")
32
+ ```
33
+
34
+ 2. **Using GUI:**
35
+ - Press `Win + R`, type `sysdm.cpl`, press Enter
36
+ - Go to "Advanced" tab → Click "Environment Variables"
37
+ - Under "User variables", click "New"
38
+ - Add `QUIZ_SECRET` with your value
39
+ - Add `OPENAI_API_KEY` with your value
40
+ - Click OK to save
41
+
42
+ ### Using .env File (Recommended for Development)
43
+
44
+ 1. Create a `.env` file in the project root:
45
+ ```env
46
+ QUIZ_SECRET=your_secret_key_here
47
+ OPENAI_API_KEY=sk-your-openai-api-key-here
48
+ OPENROUTER_API_KEY=sk-or-your-openrouter-key
49
+ ```
50
+
51
+ 2. Install python-dotenv:
52
+ ```bash
53
+ pip install python-dotenv
54
+ ```
55
+
56
+ 3. Update `app/main.py` to load .env file (add at the top):
57
+ ```python
58
+ from dotenv import load_dotenv
59
+ load_dotenv()
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Method 2: Linux/Mac (Bash)
65
+
66
+ ### Temporary (Current Session Only)
67
+
68
+ Open terminal and run:
69
+
70
+ ```bash
71
+ export QUIZ_SECRET="your_secret_key_here"
72
+ export OPENAI_API_KEY="sk-your-openai-api-key-here"
73
+ export OPENROUTER_API_KEY="sk-or-your-openrouter-key"
74
+ ```
75
+
76
+ ### Permanent (Add to Shell Profile)
77
+
78
+ Add to `~/.bashrc` or `~/.zshrc`:
79
+
80
+ ```bash
81
+ echo 'export QUIZ_SECRET="your_secret_key_here"' >> ~/.bashrc
82
+ echo 'export OPENAI_API_KEY="sk-your-openai-api-key-here"' >> ~/.bashrc
83
+ echo 'export OPENROUTER_API_KEY="sk-or-your-openrouter-key"' >> ~/.bashrc
84
+ source ~/.bashrc
85
+ ```
86
+
87
+ ### Using .env File (Recommended for Development)
88
+
89
+ Same as Windows method above.
90
+
91
+ ---
92
+
93
+ ## Method 3: Hugging Face Spaces
94
+
95
+ ### Step-by-Step Instructions
96
+
97
+ 1. **Go to your Space:**
98
+ - Navigate to https://huggingface.co/spaces
99
+ - Open your Space
100
+
101
+ 2. **Access Settings:**
102
+ - Click on "Settings" in the top menu
103
+ - Select "Variables and secrets" from the left sidebar
104
+
105
+ 3. **Add Variables:**
106
+ - Click "New variable" or "New secret"
107
+ - For `QUIZ_SECRET`:
108
+ - Key: `QUIZ_SECRET`
109
+ - Value: Your secret key
110
+ - Type: Variable (or Secret if you want it hidden)
111
+ - For `OPENAI_API_KEY`:
112
+ - Key: `OPENAI_API_KEY`
113
+ - Value: Your OpenAI API key (starts with `sk-`)
114
+ - Type: Secret (recommended for API keys)
115
+
116
+ 4. **Save:**
117
+ - Click "Save" or "Add variable"
118
+ - The Space will automatically rebuild with new variables
119
+
120
+ ### Important Notes for HF Spaces:
121
+ - Variables are available to your Docker container
122
+ - Secrets are hidden from logs but accessible in code
123
+ - Changes require a rebuild (automatic)
124
+ - `PORT` is usually set automatically by HF Spaces
125
+
126
+ ---
127
+
128
+ ## Method 4: Docker (Local)
129
+
130
+ ### Using docker run:
131
+
132
+ ```bash
133
+ docker run -e QUIZ_SECRET="your_secret" -e OPENAI_API_KEY="sk-your-key" -p 8000:8000 your-image
134
+ ```
135
+
136
+ ### Using docker-compose.yml:
137
+
138
+ Create `docker-compose.yml`:
139
+
140
+ ```yaml
141
+ version: '3.8'
142
+ services:
143
+ app:
144
+ build: .
145
+ ports:
146
+ - "8000:8000"
147
+ environment:
148
+ - QUIZ_SECRET=${QUIZ_SECRET}
149
+ - OPENAI_API_KEY=${OPENAI_API_KEY}
150
+ env_file:
151
+ - .env # Optional: load from .env file
152
+ ```
153
+
154
+ Then run:
155
+ ```bash
156
+ docker-compose up
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Verification
162
+
163
+ ### Method 1: Using the Check Script (Easiest)
164
+
165
+ Run the included verification script:
166
+
167
+ ```bash
168
+ python check_env.py
169
+ ```
170
+
171
+ This will show:
172
+ - ✓/✗ status for each variable
173
+ - Masked values (for security)
174
+ - Warnings if values look incorrect
175
+ - Next steps
176
+
177
+ ### Method 2: Command Line
178
+
179
+ **Windows (PowerShell):**
180
+ ```powershell
181
+ # Check if set (shows value - be careful!)
182
+ echo $env:QUIZ_SECRET
183
+ echo $env:OPENAI_API_KEY
184
+
185
+ # Check if set (just true/false)
186
+ if ($env:QUIZ_SECRET) { Write-Host "QUIZ_SECRET is set" } else { Write-Host "QUIZ_SECRET is NOT set" }
187
+ ```
188
+
189
+ **Linux/Mac:**
190
+ ```bash
191
+ # Check if set (shows value - be careful!)
192
+ echo $QUIZ_SECRET
193
+ echo $OPENAI_API_KEY
194
+
195
+ # Check if set (just true/false)
196
+ [ -n "$QUIZ_SECRET" ] && echo "QUIZ_SECRET is set" || echo "QUIZ_SECRET is NOT set"
197
+ [ -n "$OPENAI_API_KEY" ] && echo "OPENAI_API_KEY is set" || echo "OPENAI_API_KEY is NOT set"
198
+ ```
199
+
200
+ ### Method 3: Python Script
201
+
202
+ **Quick check:**
203
+ ```python
204
+ import os
205
+ print("QUIZ_SECRET:", "✓ Set" if os.getenv("QUIZ_SECRET") else "✗ Not set")
206
+ print("OPENAI_API_KEY:", "✓ Set" if os.getenv("OPENAI_API_KEY") else "✗ Not set (optional)")
207
+ ```
208
+
209
+ **Detailed check:**
210
+ ```python
211
+ import os
212
+
213
+ quiz_secret = os.getenv("QUIZ_SECRET")
214
+ openai_key = os.getenv("OPENAI_API_KEY")
215
+
216
+ if quiz_secret:
217
+ print(f"✓ QUIZ_SECRET: Set (length: {len(quiz_secret)})")
218
+ else:
219
+ print("✗ QUIZ_SECRET: NOT SET")
220
+
221
+ if openai_key:
222
+ print(f"✓ OPENAI_API_KEY: Set (starts with: {openai_key[:3]})")
223
+ else:
224
+ print("✗ OPENAI_API_KEY: NOT SET (optional)")
225
+ ```
226
+
227
+ ### Method 4: Test the API
228
+
229
+ Start the server and test:
230
+
231
+ ```bash
232
+ # Start server
233
+ python -m app.main
234
+
235
+ # In another terminal, test health endpoint
236
+ curl http://localhost:8000/health
237
+
238
+ # Test with invalid secret (should return 403)
239
+ curl -X POST http://localhost:8000/solve \
240
+ -H "Content-Type: application/json" \
241
+ -d '{"email":"test@test.com","secret":"wrong","url":"https://example.com"}'
242
+ ```
243
+
244
+ If you get `{"error":"forbidden"}`, the secret validation is working!
245
+
246
+ ---
247
+
248
+ ## Security Best Practices
249
+
250
+ 1. **Never commit secrets to Git:**
251
+ - Add `.env` to `.gitignore` (already included)
252
+ - Use environment variables or secrets management
253
+
254
+ 2. **Use strong secrets:**
255
+ - `QUIZ_SECRET`: Use a long random string (32+ characters)
256
+ - Generate with: `python -c "import secrets; print(secrets.token_urlsafe(32))"`
257
+
258
+ 3. **Rotate keys regularly:**
259
+ - Change `QUIZ_SECRET` periodically
260
+ - Regenerate OpenAI API keys if compromised
261
+
262
+ 4. **For production:**
263
+ - Use secret management services (AWS Secrets Manager, Azure Key Vault, etc.)
264
+ - Never hardcode secrets in code
265
+ - Use different secrets for dev/staging/production
266
+
267
+ ---
268
+
269
+ ## Quick Start Script
270
+
271
+ Create `setup_env.sh` (Linux/Mac) or `setup_env.ps1` (Windows):
272
+
273
+ **setup_env.sh:**
274
+ ```bash
275
+ #!/bin/bash
276
+ export QUIZ_SECRET="your_secret_key_here"
277
+ export OPENAI_API_KEY="sk-your-openai-api-key-here"
278
+ python -m app.main
279
+ ```
280
+
281
+ **setup_env.ps1:**
282
+ ```powershell
283
+ $env:QUIZ_SECRET = "your_secret_key_here"
284
+ $env:OPENAI_API_KEY = "sk-your-openai-api-key-here"
285
+ python -m app.main
286
+ ```
287
+
288
+ ---
289
+
290
+ ## Troubleshooting
291
+
292
+ ### Variable not found?
293
+ - Restart your terminal/IDE after setting variables
294
+ - Check spelling (case-sensitive)
295
+ - Verify with `echo $VARIABLE_NAME` or `echo $env:VARIABLE_NAME`
296
+
297
+ ### HF Spaces not picking up variables?
298
+ - Ensure variables are saved in Settings
299
+ - Check Space logs for errors
300
+ - Rebuild the Space manually if needed
301
+
302
+ ### Docker not using variables?
303
+ - Check `docker-compose.yml` syntax
304
+ - Use `docker run -e` for single container
305
+ - Verify with `docker exec container env`
306
+
307
+ ---
308
+
309
+ ## Need Help?
310
+
311
+ If you encounter issues:
312
+ 1. Check the application logs
313
+ 2. Verify variable names match exactly
314
+ 3. Ensure no extra spaces in values
315
+ 4. Test with a simple `print(os.getenv("VAR"))` in Python
316
+
EXPLAIN_OUTPUT.md ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Understanding the PowerShell Setup Output
2
+
3
+ ## What Happened (Lines 26-41)
4
+
5
+ Let me break down what you saw:
6
+
7
+ ### Line 26-28: QUIZ_SECRET Setup
8
+ ```
9
+ QUIZ_SECRET is not set
10
+ Enter your QUIZ_SECRET (or press Enter to generate one): EasyQuiz
11
+ QUIZ_SECRET set for this session
12
+ ```
13
+
14
+ **What this means:**
15
+ - ✅ The script detected `QUIZ_SECRET` wasn't set
16
+ - ✅ You entered `EasyQuiz` as your secret
17
+ - ✅ The variable is now set **for this PowerShell session only**
18
+
19
+ ### Line 29: OPENAI_API_KEY
20
+ ```
21
+ OPENAI_API_KEY is already set
22
+ ```
23
+
24
+ **What this means:**
25
+ - ✅ Your OpenAI API key was already configured (probably from a previous session or system-wide)
26
+
27
+ ### Line 31: Success Message
28
+ ```
29
+ Environment variables configured!
30
+ ```
31
+
32
+ **What this means:**
33
+ - ✅ Both variables are now available in your current PowerShell window
34
+
35
+ ### Lines 33-35: Permanent Setup Commands
36
+ ```
37
+ To make these permanent, run:
38
+ [System.Environment]::SetEnvironmentVariable("QUIZ_SECRET", " + EasyQuiz + ", "User")
39
+ [System.Environment]::SetEnvironmentVariable("OPENAI_API_KEY", " + sk-proj-... + ", "User")
40
+ ```
41
+
42
+ **⚠️ IMPORTANT:** There's a display bug here - the output shows string concatenation instead of the actual command. The correct commands should be:
43
+
44
+ ```powershell
45
+ [System.Environment]::SetEnvironmentVariable("QUIZ_SECRET", "EasyQuiz", "User")
46
+ [System.Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "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", "User")
47
+ ```
48
+
49
+ ## What "Set for this session" Means
50
+
51
+ **Current Status:**
52
+ - ✅ Variables are set **only in this PowerShell window**
53
+ - ✅ You can run `python -m app.main` right now and it will work
54
+ - ❌ If you close this window, the variables will be lost
55
+ - ❌ If you open a new PowerShell window, variables won't be there
56
+
57
+ ## What You Should Do
58
+
59
+ ### Option 1: Use It Now (Temporary - Current Session Only)
60
+
61
+ Just run the server in the **same PowerShell window**:
62
+ ```powershell
63
+ python -m app.main
64
+ ```
65
+
66
+ This will work until you close the window.
67
+
68
+ ### Option 2: Make It Permanent (Recommended)
69
+
70
+ Run these commands to make variables permanent:
71
+
72
+ ```powershell
73
+ [System.Environment]::SetEnvironmentVariable("QUIZ_SECRET", "EasyQuiz", "User")
74
+ [System.Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "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", "User")
75
+ ```
76
+
77
+ **After running these:**
78
+ - ✅ Variables will be available in ALL new PowerShell windows
79
+ - ✅ You need to close and reopen PowerShell for changes to take effect
80
+ - ✅ Variables persist even after restarting your computer
81
+
82
+ ### Option 3: Verify It's Working
83
+
84
+ Run the check script:
85
+ ```powershell
86
+ python check_env.py
87
+ ```
88
+
89
+ You should see:
90
+ ```
91
+ ✓ QUIZ_SECRET: Set (Easy...Quiz)
92
+ ✓ OPENAI_API_KEY: Set (sk-...4UA)
93
+ ✓ Status: Ready to run
94
+ ```
95
+
96
+ ## Quick Summary
97
+
98
+ | Status | Meaning | Action Needed |
99
+ |--------|---------|---------------|
100
+ | ✅ "set for this session" | Works in current window only | Run server now, or make permanent |
101
+ | ✅ "already set" | Variable exists | Nothing needed |
102
+ | ⚠️ Lines 33-35 | Display bug (shows wrong format) | Use corrected commands above |
103
+
104
+ ## Next Steps
105
+
106
+ 1. **Test it now** (in the same window):
107
+ ```powershell
108
+ python check_env.py
109
+ python -m app.main
110
+ ```
111
+
112
+ 2. **Make it permanent** (optional but recommended):
113
+ ```powershell
114
+ [System.Environment]::SetEnvironmentVariable("QUIZ_SECRET", "EasyQuiz", "User")
115
+ [System.Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "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", "User")
116
+ ```
117
+
118
+ 3. **Close and reopen PowerShell** (if you made it permanent)
119
+
FIX_SERVER.md ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fix: Server "Not Found" Error
2
+
3
+ ## The Problem
4
+
5
+ You have a server running on port 8000, but it's **NOT our FastAPI app**. It's returning "Not Found" for all endpoints.
6
+
7
+ ## Solution: Stop and Restart
8
+
9
+ ### Step 1: Stop the Wrong Server
10
+
11
+ Run this to stop whatever is on port 8000:
12
+
13
+ ```powershell
14
+ # Find and stop process on port 8000
15
+ $portInfo = netstat -ano | findstr :8000 | findstr LISTENING
16
+ if ($portInfo) {
17
+ $pid = ($portInfo -split '\s+')[-1]
18
+ Stop-Process -Id $pid -Force
19
+ Write-Host "Stopped process $pid"
20
+ Start-Sleep -Seconds 2
21
+ }
22
+ ```
23
+
24
+ ### Step 2: Start the Correct Server
25
+
26
+ In a **NEW PowerShell terminal**, run:
27
+
28
+ ```powershell
29
+ # Navigate to project
30
+ cd C:\Users\tarun\IITMTdsPrj2
31
+
32
+ # Set environment variables
33
+ $env:QUIZ_SECRET = "EasyQuiz"
34
+ $env:OPENAI_API_KEY = "eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6IjI0ZjIwMDU3NTNAZHMuc3R1ZHkuaWl0bS5hYy5pbiJ9.eji7L5I62M9YHoeEKE8ao6eTw8dFjgDIMP9C3lOUXc4"
35
+
36
+ # Start the server
37
+ python -m app.main
38
+ ```
39
+
40
+ You should see:
41
+ ```
42
+ INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
43
+ ```
44
+
45
+ **Keep this terminal open!**
46
+
47
+ ### Step 3: Test in Another Terminal
48
+
49
+ Open a **NEW PowerShell window** and test:
50
+
51
+ ```powershell
52
+ # Test health endpoint
53
+ Invoke-RestMethod -Uri "http://127.0.0.1:8000/health"
54
+
55
+ # Test demo endpoint
56
+ $body = @{
57
+ email = "24f2005753@ds.study.iitm.ac.in"
58
+ secret = "EasyQuiz"
59
+ url = "https://tds-llm-analysis.s-anand.net/demo"
60
+ } | ConvertTo-Json
61
+
62
+ Invoke-RestMethod -Uri "http://127.0.0.1:8000/demo" -Method POST -ContentType "application/json" -Body $body
63
+ ```
64
+
65
+ ## Quick One-Liner to Stop Server
66
+
67
+ ```powershell
68
+ Get-NetTCPConnection -LocalPort 8000 -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess | ForEach-Object { Stop-Process -Id $_ -Force }; Write-Host "Stopped all processes on port 8000"
69
+ ```
70
+
71
+ ## Verify Server is Running
72
+
73
+ After starting, test:
74
+
75
+ ```powershell
76
+ # Should return: {"status":"healthy"}
77
+ Invoke-RestMethod -Uri "http://127.0.0.1:8000/health"
78
+ ```
79
+
80
+ If you get "Not Found", the wrong server is still running.
81
+
82
+ ## Summary
83
+
84
+ 1. ✅ Stop whatever is on port 8000
85
+ 2. ✅ Start our FastAPI server: `python -m app.main`
86
+ 3. ✅ Test endpoints in a new terminal
87
+ 4. ✅ Keep server terminal open while testing
88
+
89
+
LICENSE ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 IITM LLM Quiz Solver
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
QUICK_CHECK.md ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Quick Check Guide - PowerShell
2
+
3
+ ## Issue: "Not Found" Error
4
+
5
+ The error `{"detail":"Not Found"}` means:
6
+ - ❌ Server is not running, OR
7
+ - ❌ Server needs to be restarted to see the new `/env-check` endpoint
8
+
9
+ ## Issue: QUIZ_SECRET Not Set
10
+
11
+ The test shows `QUIZ_SECRET` is `false` because:
12
+ - ❌ Environment variable was only set in a previous PowerShell session
13
+ - ❌ You need to set it again in the current session
14
+
15
+ ## Solution: Step-by-Step
16
+
17
+ ### Step 1: Set Environment Variables (Current Session)
18
+
19
+ ```powershell
20
+ $env:QUIZ_SECRET = "EasyQuiz"
21
+ $env:OPENAI_API_KEY = "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"
22
+ ```
23
+
24
+ ### Step 2: Verify Variables Are Set
25
+
26
+ ```powershell
27
+ python test_env.json.py
28
+ ```
29
+
30
+ **Expected Output:**
31
+ ```json
32
+ {
33
+ "status": "ok",
34
+ "variables": {
35
+ "QUIZ_SECRET": {
36
+ "set": true,
37
+ "length": 8,
38
+ "preview": "Easy...Quiz"
39
+ },
40
+ ...
41
+ },
42
+ "ready": true
43
+ }
44
+ ```
45
+
46
+ ### Step 3: Start the Server
47
+
48
+ ```powershell
49
+ python -m app.main
50
+ ```
51
+
52
+ Wait for: `Uvicorn running on http://0.0.0.0:8000`
53
+
54
+ ### Step 4: Test the API (In a NEW PowerShell Window)
55
+
56
+ **Important:** Open a NEW PowerShell window (keep server running in the first one)
57
+
58
+ ```powershell
59
+ # Set variables again in the new window
60
+ $env:QUIZ_SECRET = "EasyQuiz"
61
+
62
+ # Test the endpoint
63
+ Invoke-RestMethod -Uri http://localhost:8000/env-check | ConvertTo-Json
64
+ ```
65
+
66
+ **Or use curl (PowerShell alias):**
67
+ ```powershell
68
+ curl http://localhost:8000/env-check
69
+ ```
70
+
71
+ ## Alternative: Use the Test Script (No Server Needed)
72
+
73
+ Instead of starting the server, just run:
74
+
75
+ ```powershell
76
+ # Set variables
77
+ $env:QUIZ_SECRET = "EasyQuiz"
78
+
79
+ # Check (no server needed)
80
+ python test_env.json.py
81
+ ```
82
+
83
+ This gives you JSON output without needing the server running.
84
+
85
+ ## Make Variables Permanent (Recommended)
86
+
87
+ To avoid setting variables every time:
88
+
89
+ ```powershell
90
+ [System.Environment]::SetEnvironmentVariable("QUIZ_SECRET", "EasyQuiz", "User")
91
+ [System.Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "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", "User")
92
+ ```
93
+
94
+ Then **close and reopen PowerShell** for changes to take effect.
95
+
96
+ ## Quick Test Commands
97
+
98
+ ### All-in-One Test (No Server)
99
+ ```powershell
100
+ $env:QUIZ_SECRET = "EasyQuiz"
101
+ python test_env.json.py
102
+ ```
103
+
104
+ ### Test with Server Running
105
+ ```powershell
106
+ # Terminal 1: Start server
107
+ $env:QUIZ_SECRET = "EasyQuiz"
108
+ python -m app.main
109
+
110
+ # Terminal 2: Test API
111
+ $env:QUIZ_SECRET = "EasyQuiz"
112
+ Invoke-RestMethod -Uri http://localhost:8000/env-check | ConvertTo-Json
113
+ ```
114
+
115
+ ## Troubleshooting
116
+
117
+ ### "Not Found" Error
118
+ - ✅ Make sure server is running
119
+ - ✅ Restart server after code changes
120
+ - ✅ Check URL is correct: `http://localhost:8000/env-check`
121
+
122
+ ### "QUIZ_SECRET is required"
123
+ - ✅ Set variable: `$env:QUIZ_SECRET = "EasyQuiz"`
124
+ - ✅ Verify: `python test_env.json.py`
125
+ - ✅ Make permanent to avoid setting every time
126
+
127
+ ### Server Won't Start
128
+ - ✅ Check if port 8000 is available
129
+ - ✅ Install dependencies: `pip install -r requirements.txt`
130
+ - ✅ Check for errors in the terminal
131
+
QUICK_START.md ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Quick Start Guide
2
+
3
+ ## 1. Install Dependencies
4
+
5
+ ```bash
6
+ pip install -r requirements.txt
7
+ playwright install chromium
8
+ ```
9
+
10
+ ## 2. Set Environment Variables
11
+
12
+ ### Option A: Use Setup Scripts (Recommended)
13
+
14
+ **Windows:**
15
+ ```powershell
16
+ .\setup_env.ps1
17
+ ```
18
+
19
+ **Linux/Mac:**
20
+ ```bash
21
+ source setup_env.sh
22
+ ```
23
+
24
+ ### Option B: Manual Setup
25
+
26
+ **Windows PowerShell:**
27
+ ```powershell
28
+ $env:QUIZ_SECRET = "your_secret_here"
29
+ $env:OPENAI_API_KEY = "sk-your-key-here" # Optional
30
+ ```
31
+
32
+ **Linux/Mac:**
33
+ ```bash
34
+ export QUIZ_SECRET="your_secret_here"
35
+ export OPENAI_API_KEY="sk-your-key-here" # Optional
36
+ ```
37
+
38
+ ### Option C: Use .env File
39
+
40
+ Create `.env` file:
41
+ ```env
42
+ QUIZ_SECRET=your_secret_here
43
+ OPENAI_API_KEY=sk-your-key-here
44
+ ```
45
+
46
+ ## 3. Verify Setup
47
+
48
+ ```bash
49
+ python check_env.py
50
+ ```
51
+
52
+ You should see:
53
+ ```
54
+ ✓ QUIZ_SECRET: Set (xxxx...xxxx)
55
+ ✓ OPENAI_API_KEY: Set (sk-...xxxx) [if set]
56
+ ✓ Status: Ready to run
57
+ ```
58
+
59
+ ## 4. Start the Server
60
+
61
+ ```bash
62
+ python -m app.main
63
+ ```
64
+
65
+ Or:
66
+ ```bash
67
+ uvicorn app.main:app --host 0.0.0.0 --port 8000
68
+ ```
69
+
70
+ ## 5. Test the API
71
+
72
+ **Health Check:**
73
+ ```bash
74
+ curl http://localhost:8000/health
75
+ ```
76
+
77
+ **Test Quiz Solving:**
78
+ ```bash
79
+ curl -X POST http://localhost:8000/demo \
80
+ -H "Content-Type: application/json" \
81
+ -d '{
82
+ "email": "test@example.com",
83
+ "secret": "your_secret_here",
84
+ "url": "https://example.com/quiz"
85
+ }'
86
+ ```
87
+
88
+ ## Troubleshooting
89
+
90
+ ### "QUIZ_SECRET is NOT SET"
91
+ - Run the setup script again
92
+ - Or manually set: `export QUIZ_SECRET="your_secret"` (Linux/Mac)
93
+ - Or: `$env:QUIZ_SECRET = "your_secret"` (Windows)
94
+
95
+ ### "Module not found"
96
+ - Run: `pip install -r requirements.txt`
97
+
98
+ ### "Playwright browser not found"
99
+ - Run: `playwright install chromium`
100
+
101
+ ### Server won't start
102
+ - Check if port 8000 is available
103
+ - Change port: `export PORT=8001` then restart
104
+
105
+ ## Next Steps
106
+
107
+ - See [ENV_SETUP.md](ENV_SETUP.md) for detailed environment setup
108
+ - See [README.md](README.md) for full documentation
109
+ - Deploy to Hugging Face Spaces (see README.md)
110
+
README.md CHANGED
@@ -1,11 +1,265 @@
1
- ---
2
- title: Prj2
3
- emoji: 😻
4
- colorFrom: pink
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # IITM LLM Quiz Solver
2
+
3
+ A complete Python project with FastAPI that acts as an API endpoint to automatically solve dynamic quiz tasks using a headless browser and optional LLM reasoning.
4
+
5
+ ## Features
6
+
7
+ - 🚀 FastAPI-based REST API
8
+ - 🌐 Playwright for headless browser automation
9
+ - 🤖 OpenAI GPT integration for complex reasoning
10
+ - 📊 Data processing (CSV, JSON, PDF, etc.)
11
+ - 🔄 Recursive quiz solving
12
+ - ⚡ Async/await for performance
13
+ - 🐳 Docker support for easy deployment
14
+
15
+ ## Project Structure
16
+
17
+ ```
18
+ /app
19
+ - main.py # FastAPI server
20
+ - solver.py # Quiz solving logic
21
+ - browser.py # Playwright helper
22
+ - llm.py # GPT helper
23
+ - utils.py # Utility functions
24
+ /Dockerfile
25
+ /requirements.txt
26
+ /README.md
27
+ /LICENSE
28
+ ```
29
+
30
+ ## Installation
31
+
32
+ ### Local Development
33
+
34
+ 1. Clone the repository:
35
+ ```bash
36
+ git clone <repository-url>
37
+ cd IITMTdsPrj2
38
+ ```
39
+
40
+ 2. Install Python dependencies:
41
+ ```bash
42
+ pip install -r requirements.txt
43
+ ```
44
+
45
+ 3. Install Playwright browsers:
46
+ ```bash
47
+ playwright install chromium
48
+ ```
49
+
50
+ 4. Set environment variables:
51
+
52
+ **Quick Setup (Windows PowerShell):**
53
+ ```powershell
54
+ .\setup_env.ps1
55
+ ```
56
+
57
+ **Quick Setup (Linux/Mac):**
58
+ ```bash
59
+ source setup_env.sh
60
+ ```
61
+
62
+ **Manual Setup (choose whichever LLM provider you prefer):**
63
+ ```bash
64
+ # Windows PowerShell
65
+ $env:QUIZ_SECRET = "your_secret_key"
66
+ $env:OPENAI_API_KEY = "sk-your-openai-api-key" # Optional - OpenAI
67
+ $env:OPENROUTER_API_KEY = "sk-or-your-openrouter" # Optional - OpenRouter GPT-5-nano
68
+
69
+ # Linux/Mac
70
+ export QUIZ_SECRET="your_secret_key"
71
+ export OPENAI_API_KEY="sk-your-openai-api-key" # Optional
72
+ export OPENROUTER_API_KEY="sk-or-your-openrouter" # Optional
73
+ ```
74
+
75
+ **Or use .env file:**
76
+ - Copy `.env.example` to `.env` (if available)
77
+ - Fill in your values
78
+ - The app will automatically load it
79
+
80
+ 📖 **See [ENV_SETUP.md](ENV_SETUP.md) for detailed instructions**
81
+
82
+ 5. Run the server:
83
+ ```bash
84
+ python -m app.main
85
+ # or
86
+ uvicorn app.main:app --host 0.0.0.0 --port 8000
87
+ ```
88
+
89
+ ## API Endpoints
90
+
91
+ ### POST /solve
92
+
93
+ Main endpoint to solve a quiz.
94
+
95
+ **Request Body:**
96
+ ```json
97
+ {
98
+ "email": "user@example.com",
99
+ "secret": "your_secret",
100
+ "url": "https://example.com/quiz"
101
+ }
102
+ ```
103
+
104
+ **Response:**
105
+ - `200 OK`: Quiz solved successfully
106
+ - `400 Bad Request`: Invalid request format
107
+ - `403 Forbidden`: Invalid secret
108
+ - `500 Internal Server Error`: Server error
109
+ - `504 Gateway Timeout`: Request timeout (>3 minutes)
110
+
111
+ ### POST /demo
112
+
113
+ Demo endpoint for testing (same as `/solve` but with more lenient error handling).
114
+
115
+ **Request Body:** Same as `/solve`
116
+
117
+ ### GET /health
118
+
119
+ Health check endpoint.
120
+
121
+ **Response:**
122
+ ```json
123
+ {
124
+ "status": "healthy"
125
+ }
126
+ ```
127
+
128
+ ## Deployment on Hugging Face Spaces
129
+
130
+ ### Method 1: Using Dockerfile (Recommended)
131
+
132
+ 1. **Create a new Space on Hugging Face:**
133
+ - Go to https://huggingface.co/spaces
134
+ - Create a new Space
135
+ - Select "Docker" as the SDK
136
+
137
+ 2. **Upload your files:**
138
+ - Upload all project files to your Space
139
+ - Ensure `Dockerfile` is in the root directory
140
+
141
+ 3. **Set Environment Variables:**
142
+ - Go to Space Settings → Variables and secrets
143
+ - Add the following:
144
+ - `QUIZ_SECRET`: Your secret key for authentication
145
+ - `OPENAI_API_KEY`: Your OpenAI API key (optional)
146
+ - `OPENROUTER_API_KEY`: Your OpenRouter key (e.g., GPT-5-nano)
147
+ - `PORT`: 8000 (usually set automatically)
148
+
149
+ 4. **Deploy:**
150
+ - Hugging Face will automatically build and deploy your Docker container
151
+ - The API will be available at: `https://<your-username>-<space-name>.hf.space`
152
+
153
+ ### Method 2: Using Docker Compose (Alternative)
154
+
155
+ If you need more control, you can use `docker-compose.yml`:
156
+
157
+ ```yaml
158
+ version: '3.8'
159
+ services:
160
+ app:
161
+ build: .
162
+ ports:
163
+ - "8000:8000"
164
+ environment:
165
+ - QUIZ_SECRET=${QUIZ_SECRET}
166
+ - OPENAI_API_KEY=${OPENAI_API_KEY}
167
+ ```
168
+
169
+ ## Environment Variables
170
+
171
+ | Variable | Description | Required | Default |
172
+ |----------|-------------|----------|---------|
173
+ | `QUIZ_SECRET` | Secret key for API authentication | Yes | `default_secret_change_me` |
174
+ | `OPENAI_API_KEY` | OpenAI API key for LLM features | No | - |
175
+ | `OPENROUTER_API_KEY` | OpenRouter key (e.g., GPT-5-nano) | No | - |
176
+ | `OPENROUTER_MODEL` | Override OpenRouter model (default gpt-5-nano) | No | `gpt-5-nano` |
177
+ | `PORT` | Server port | No | `8000` |
178
+
179
+ ## Testing
180
+
181
+ ### Test with curl:
182
+
183
+ ```bash
184
+ curl -X POST "https://tds-llm-analysis.s-anand.net/demo" \
185
+ -H "Content-Type: application/json" \
186
+ -d '{
187
+ "email": "test@example.com",
188
+ "secret": "your_secret",
189
+ "url": "https://example.com/quiz"
190
+ }'
191
+ ```
192
+
193
+ ### Test with Python:
194
+
195
+ ```python
196
+ import requests
197
+
198
+ response = requests.post(
199
+ "https://tds-llm-analysis.s-anand.net/demo",
200
+ json={
201
+ "email": "test@example.com",
202
+ "secret": "your_secret",
203
+ "url": "https://example.com/quiz"
204
+ }
205
+ )
206
+
207
+ print(response.json())
208
+ ```
209
+
210
+ ## How It Works
211
+
212
+ 1. **Request Validation**: Validates email, secret, and URL format
213
+ 2. **Secret Authentication**: Checks secret against expected value (403 if wrong)
214
+ 3. **Page Loading**: Uses Playwright to load and render the quiz page
215
+ 4. **Content Extraction**: Extracts all text, HTML, links, and images
216
+ 5. **Submit URL Detection**: Automatically finds the submit URL from page content
217
+ 6. **Question Solving**:
218
+ - Extracts question text
219
+ - Tries multiple strategies:
220
+ - Check if answer is in page
221
+ - Download and process data files (CSV, JSON, PDF)
222
+ - Use LLM for complex reasoning
223
+ 7. **Answer Submission**: Submits answer to detected submit URL
224
+ 8. **Recursive Solving**: If response contains next URL, solves recursively
225
+ 9. **Response**: Returns final result
226
+
227
+ ## Solver Strategies
228
+
229
+ The solver uses multiple strategies in order:
230
+
231
+ 1. **Direct Answer Extraction**: Checks if answer is already in page
232
+ 2. **Data File Processing**: Downloads and processes CSV, JSON, PDF files
233
+ 3. **LLM Reasoning**: Uses GPT-4o-mini (OpenAI) or GPT-5-nano (OpenRouter) for complex questions
234
+ 4. **Fallback**: Returns question analysis if all else fails
235
+
236
+ ## Error Handling
237
+
238
+ - Invalid JSON → 400 Bad Request
239
+ - Wrong secret → 403 Forbidden
240
+ - Page load errors → 500 with error details
241
+ - Timeout (>3 min) → 504 Gateway Timeout
242
+ - All errors are logged for debugging
243
+
244
+ ## Limitations
245
+
246
+ - Maximum recursion depth: 10 quizzes
247
+ - Timeout: 3 minutes per request
248
+ - Requires internet connection for external URLs
249
+ - OpenAI API key needed for LLM features (optional)
250
+
251
+ ## License
252
+
253
+ MIT License - see LICENSE file for details.
254
+
255
+ ## Contributing
256
+
257
+ 1. Fork the repository
258
+ 2. Create a feature branch
259
+ 3. Make your changes
260
+ 4. Submit a pull request
261
+
262
+ ## Support
263
+
264
+ For issues and questions, please open an issue on the repository.
265
+
SET_API_KEY.md ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Setting LLM API Keys
2
+
3
+ You can use either OpenAI or OpenRouter (or both) for LLM reasoning. Set whichever keys you have.
4
+
5
+ ## Option A: OpenRouter (GPT-5-nano, etc.)
6
+
7
+ ### Quick Setup (Current Session)
8
+ ```powershell
9
+ $env:OPENROUTER_API_KEY = "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"
10
+ $env:OPENROUTER_MODEL = "gpt-5-nano" # optional override
11
+ ```
12
+
13
+ ### Make It Permanent
14
+ ```powershell
15
+ [System.Environment]::SetEnvironmentVariable("OPENROUTER_API_KEY", "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", "User")
16
+ [System.Environment]::SetEnvironmentVariable("OPENROUTER_MODEL", "gpt-5-nano", "User")
17
+ ```
18
+
19
+ ### .env Example
20
+ ```env
21
+ OPENROUTER_API_KEY=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
22
+ OPENROUTER_MODEL=gpt-5-nano
23
+ ```
24
+
25
+ ### Hugging Face Spaces
26
+ - `OPENROUTER_API_KEY`: `sk-or-...`
27
+ - `OPENROUTER_MODEL`: `gpt-5-nano` (optional, defaults to this)
28
+
29
+ ---
30
+
31
+ ## Option B: OpenAI (GPT-4o, etc.)
32
+
33
+ ### Quick Setup
34
+ ```powershell
35
+ $env:OPENAI_API_KEY = "sk-your-openai-api-key"
36
+ ```
37
+
38
+ Or run:
39
+ ```powershell
40
+ .\set_api_key.ps1
41
+ ```
42
+
43
+ ### Make It Permanent
44
+ ```powershell
45
+ [System.Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "sk-your-openai-api-key", "User")
46
+ ```
47
+
48
+ ### .env Example
49
+ ```env
50
+ OPENAI_API_KEY=sk-your-openai-api-key
51
+ ```
52
+
53
+ ### Hugging Face Spaces
54
+ - `OPENAI_API_KEY`: `sk-...`
55
+
56
+ ---
57
+
58
+ ## Verification
59
+
60
+ Run:
61
+ ```powershell
62
+ python test_env.json.py
63
+ ```
64
+
65
+ You should see at least one of the LLM keys marked as `set`, and `llm_enabled: true`.
66
+
START_SERVER.md ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # How to Start the Server
2
+
3
+ ## The Problem
4
+
5
+ The "Not Found" error means the server is **not running**. You need to start it first.
6
+
7
+ ## Solution: Start the Server
8
+
9
+ ### Step 1: Open a PowerShell Terminal
10
+
11
+ Make sure you're in the project directory:
12
+ ```powershell
13
+ cd C:\Users\tarun\IITMTdsPrj2
14
+ ```
15
+
16
+ ### Step 2: Set Environment Variables
17
+
18
+ ```powershell
19
+ $env:QUIZ_SECRET = "EasyQuiz"
20
+ $env:OPENAI_API_KEY = "eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6IjI0ZjIwMDU3NTNAZHMuc3R1ZHkuaWl0bS5hYy5pbiJ9.eji7L5I62M9YHoeEKE8ao6eTw8dFjgDIMP9C3lOUXc4"
21
+ ```
22
+
23
+ ### Step 3: Start the Server
24
+
25
+ ```powershell
26
+ python -m app.main
27
+ ```
28
+
29
+ You should see:
30
+ ```
31
+ INFO: Started server process [...]
32
+ INFO: Waiting for application startup.
33
+ INFO: Application startup complete.
34
+ INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
35
+ ```
36
+
37
+ **Keep this terminal open!** The server runs in this window.
38
+
39
+ ### Step 4: Test in Another Terminal
40
+
41
+ Open a **NEW PowerShell window** and run:
42
+
43
+ ```powershell
44
+ # Set variables again (in the new window)
45
+ $env:QUIZ_SECRET = "EasyQuiz"
46
+
47
+ # Test the API
48
+ $body = @{
49
+ email = "24f2005753@ds.study.iitm.ac.in"
50
+ secret = "EasyQuiz"
51
+ url = "https://tds-llm-analysis.s-anand.net/demo"
52
+ } | ConvertTo-Json
53
+
54
+ Invoke-RestMethod -Uri "http://127.0.0.1:8000/demo" -Method POST -ContentType "application/json" -Body $body
55
+ ```
56
+
57
+ ## Quick Test Script
58
+
59
+ I've created a test script. After starting the server, run:
60
+
61
+ ```powershell
62
+ .\test_server.ps1
63
+ ```
64
+
65
+ This will test all endpoints and show you what's working.
66
+
67
+ ## Troubleshooting
68
+
69
+ ### "Module not found" error
70
+ ```powershell
71
+ pip install -r requirements.txt
72
+ ```
73
+
74
+ ### "Port already in use" error
75
+ Another process is using port 8000. Either:
76
+ - Stop the other process
77
+ - Change the port: `$env:PORT = "8001"` then restart
78
+
79
+ ### Server starts but endpoints don't work
80
+ - Make sure you're using the correct URL: `http://127.0.0.1:8000` or `http://localhost:8000`
81
+ - Check for errors in the server terminal
82
+ - Restart the server after code changes
83
+
84
+ ## Summary
85
+
86
+ 1. ✅ Start server: `python -m app.main` (keep terminal open)
87
+ 2. ✅ Open new terminal for testing
88
+ 3. ✅ Set variables in new terminal
89
+ 4. ✅ Test API endpoints
90
+
TEST_API.md ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # How to Check Environment Variables (JSON Format)
2
+
3
+ ## Method 1: Using the Test Script (Easiest)
4
+
5
+ Run this command:
6
+ ```bash
7
+ python test_env.json.py
8
+ ```
9
+
10
+ **Example Output:**
11
+ ```json
12
+ {
13
+ "status": "ok",
14
+ "variables": {
15
+ "QUIZ_SECRET": {
16
+ "set": true,
17
+ "length": 8,
18
+ "preview": "Easy...Quiz"
19
+ },
20
+ "OPENAI_API_KEY": {
21
+ "set": true,
22
+ "length": 200,
23
+ "preview": "sk-proj...4UA",
24
+ "valid_format": true
25
+ },
26
+ "PORT": {
27
+ "set": true,
28
+ "value": "8000"
29
+ }
30
+ },
31
+ "ready": true,
32
+ "llm_enabled": true,
33
+ "message": "Ready to run"
34
+ }
35
+ ```
36
+
37
+ ## Method 2: Using the API Endpoint
38
+
39
+ ### Step 1: Start the Server
40
+ ```bash
41
+ python -m app.main
42
+ ```
43
+
44
+ ### Step 2: Check Environment Variables
45
+
46
+ **Using curl:**
47
+ ```bash
48
+ curl http://localhost:8000/env-check
49
+ ```
50
+
51
+ **Using PowerShell:**
52
+ ```powershell
53
+ Invoke-RestMethod -Uri http://localhost:8000/env-check -Method Get | ConvertTo-Json
54
+ ```
55
+
56
+ **Using Python:**
57
+ ```python
58
+ import requests
59
+ response = requests.get("http://localhost:8000/env-check")
60
+ print(response.json())
61
+ ```
62
+
63
+ **Example JSON Response:**
64
+ ```json
65
+ {
66
+ "status": "ok",
67
+ "variables": {
68
+ "QUIZ_SECRET": {
69
+ "set": true,
70
+ "length": 8,
71
+ "preview": "Easy...Quiz"
72
+ },
73
+ "OPENAI_API_KEY": {
74
+ "set": true,
75
+ "length": 200,
76
+ "preview": "sk-proj...4UA",
77
+ "valid_format": true
78
+ },
79
+ "PORT": {
80
+ "set": true,
81
+ "value": "8000"
82
+ }
83
+ },
84
+ "ready": true,
85
+ "llm_enabled": true
86
+ }
87
+ ```
88
+
89
+ ## Method 3: Test the Full API
90
+
91
+ ### Health Check
92
+ ```bash
93
+ curl http://localhost:8000/health
94
+ ```
95
+ **Response:**
96
+ ```json
97
+ {
98
+ "status": "healthy"
99
+ }
100
+ ```
101
+
102
+ ### Test Quiz Endpoint (with wrong secret - should return 403)
103
+ ```bash
104
+ curl -X POST http://localhost:8000/solve \
105
+ -H "Content-Type: application/json" \
106
+ -d '{
107
+ "email": "test@example.com",
108
+ "secret": "wrong_secret",
109
+ "url": "https://example.com"
110
+ }'
111
+ ```
112
+ **Expected Response (403):**
113
+ ```json
114
+ {
115
+ "detail": {
116
+ "error": "forbidden"
117
+ }
118
+ }
119
+ ```
120
+
121
+ ### Test Quiz Endpoint (with correct secret)
122
+ ```bash
123
+ curl -X POST http://localhost:8000/solve \
124
+ -H "Content-Type: application/json" \
125
+ -d '{
126
+ "email": "test@example.com",
127
+ "secret": "EasyQuiz",
128
+ "url": "https://example.com/quiz"
129
+ }'
130
+ ```
131
+
132
+ ## Method 4: Quick PowerShell Test
133
+
134
+ ```powershell
135
+ # Check variables directly
136
+ $env:QUIZ_SECRET
137
+ $env:OPENAI_API_KEY
138
+
139
+ # Test with API (if server is running)
140
+ $response = Invoke-RestMethod -Uri http://localhost:8000/env-check
141
+ $response | ConvertTo-Json -Depth 10
142
+ ```
143
+
144
+ ## JSON Response Fields Explained
145
+
146
+ | Field | Description |
147
+ |-------|-------------|
148
+ | `status` | Always "ok" if script runs |
149
+ | `variables.QUIZ_SECRET.set` | `true` if QUIZ_SECRET is set |
150
+ | `variables.QUIZ_SECRET.length` | Length of the secret |
151
+ | `variables.QUIZ_SECRET.preview` | Masked preview (first 4 + last 4 chars) |
152
+ | `variables.OPENAI_API_KEY.set` | `true` if API key is set |
153
+ | `variables.OPENAI_API_KEY.valid_format` | `true` if key starts with "sk-" |
154
+ | `ready` | `true` if QUIZ_SECRET is set (required) |
155
+ | `llm_enabled` | `true` if OPENAI_API_KEY is set (optional) |
156
+ | `message` | Human-readable status message |
157
+
158
+ ## Quick Test Commands
159
+
160
+ ### All-in-One Test (PowerShell)
161
+ ```powershell
162
+ # 1. Check variables
163
+ python test_env.json.py
164
+
165
+ # 2. Start server (in another terminal)
166
+ python -m app.main
167
+
168
+ # 3. Test API (in another terminal)
169
+ Invoke-RestMethod -Uri http://localhost:8000/env-check | ConvertTo-Json
170
+ ```
171
+
172
+ ### All-in-One Test (Bash/Linux)
173
+ ```bash
174
+ # 1. Check variables
175
+ python test_env.json.py
176
+
177
+ # 2. Start server (in another terminal)
178
+ python -m app.main
179
+
180
+ # 3. Test API (in another terminal)
181
+ curl http://localhost:8000/env-check | python -m json.tool
182
+ ```
183
+
184
+ ## Expected Results
185
+
186
+ ### ✅ Everything Working:
187
+ ```json
188
+ {
189
+ "ready": true,
190
+ "llm_enabled": true,
191
+ "variables": {
192
+ "QUIZ_SECRET": { "set": true },
193
+ "OPENAI_API_KEY": { "set": true, "valid_format": true }
194
+ }
195
+ }
196
+ ```
197
+
198
+ ### ⚠️ Missing QUIZ_SECRET:
199
+ ```json
200
+ {
201
+ "ready": false,
202
+ "llm_enabled": true,
203
+ "variables": {
204
+ "QUIZ_SECRET": { "set": false },
205
+ "OPENAI_API_KEY": { "set": true }
206
+ },
207
+ "message": "QUIZ_SECRET is required"
208
+ }
209
+ ```
210
+
211
+ ### ⚠️ Missing OPENAI_API_KEY (still works, but no LLM):
212
+ ```json
213
+ {
214
+ "ready": true,
215
+ "llm_enabled": false,
216
+ "variables": {
217
+ "QUIZ_SECRET": { "set": true },
218
+ "OPENAI_API_KEY": { "set": false }
219
+ }
220
+ }
221
+ ```
222
+
TEST_POWERSHELL.md ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Testing the API with PowerShell
2
+
3
+ ## The Problem
4
+
5
+ The error `D1_TYPE_ERROR: Type 'object' not supported for value '[object Object]'` occurs because PowerShell needs the JSON body to be properly formatted.
6
+
7
+ ## Solution: Correct PowerShell Syntax
8
+
9
+ ### Method 1: Using PowerShell Object (Recommended)
10
+
11
+ ```powershell
12
+ # Create body as PowerShell object, then convert to JSON
13
+ $body = @{
14
+ email = "24f2005753@ds.study.iitm.ac.in"
15
+ secret = "EasyQuiz"
16
+ url = "https://tds-llm-analysis.s-anand.net/demo"
17
+ } | ConvertTo-Json
18
+
19
+ # Send request
20
+ Invoke-RestMethod `
21
+ -Uri "http://127.0.0.1:8000/solve" `
22
+ -Method POST `
23
+ -ContentType "application/json" `
24
+ -Body $body
25
+ ```
26
+
27
+ ### Method 2: Using Here-String (Alternative)
28
+
29
+ ```powershell
30
+ $body = @'
31
+ {
32
+ "email": "24f2005753@ds.study.iitm.ac.in",
33
+ "secret": "EasyQuiz",
34
+ "url": "https://tds-llm-analysis.s-anand.net/demo"
35
+ }
36
+ '@
37
+
38
+ Invoke-RestMethod `
39
+ -Uri "http://127.0.0.1:8000/solve" `
40
+ -Method POST `
41
+ -ContentType "application/json" `
42
+ -Body $body
43
+ ```
44
+
45
+ ### Method 3: Using the Test Script
46
+
47
+ Run the provided test script:
48
+
49
+ ```powershell
50
+ .\test_api.ps1
51
+ ```
52
+
53
+ ## Common Issues and Fixes
54
+
55
+ ### Issue 1: JSON String Not Properly Formatted
56
+
57
+ **Wrong:**
58
+ ```powershell
59
+ -Body '{
60
+ "email": "...",
61
+ "secret": "...",
62
+ "url": "..."
63
+ }'
64
+ ```
65
+
66
+ **Right:**
67
+ ```powershell
68
+ $body = @{
69
+ email = "..."
70
+ secret = "..."
71
+ url = "..."
72
+ } | ConvertTo-Json
73
+
74
+ -Body $body
75
+ ```
76
+
77
+ ### Issue 2: Server Not Running
78
+
79
+ Make sure the server is running:
80
+ ```powershell
81
+ python -m app.main
82
+ ```
83
+
84
+ ### Issue 3: Wrong Endpoint
85
+
86
+ Try the `/demo` endpoint instead:
87
+ ```powershell
88
+ $body = @{
89
+ email = "24f2005753@ds.study.iitm.ac.in"
90
+ secret = "EasyQuiz"
91
+ url = "https://tds-llm-analysis.s-anand.net/demo"
92
+ } | ConvertTo-Json
93
+
94
+ Invoke-RestMethod `
95
+ -Uri "http://127.0.0.1:8000/demo" `
96
+ -Method POST `
97
+ -ContentType "application/json" `
98
+ -Body $body
99
+ ```
100
+
101
+ ## Complete Example
102
+
103
+ ```powershell
104
+ # Step 1: Set environment variables
105
+ $env:QUIZ_SECRET = "EasyQuiz"
106
+
107
+ # Step 2: Start server (in one terminal)
108
+ # python -m app.main
109
+
110
+ # Step 3: Test API (in another terminal)
111
+ $body = @{
112
+ email = "24f2005753@ds.study.iitm.ac.in"
113
+ secret = "EasyQuiz"
114
+ url = "https://tds-llm-analysis.s-anand.net/demo"
115
+ } | ConvertTo-Json
116
+
117
+ $response = Invoke-RestMethod `
118
+ -Uri "http://127.0.0.1:8000/demo" `
119
+ -Method POST `
120
+ -ContentType "application/json" `
121
+ -Body $body
122
+
123
+ # Display response
124
+ $response | ConvertTo-Json -Depth 10
125
+ ```
126
+
127
+ ## Using curl (Alternative)
128
+
129
+ If PowerShell gives you trouble, use curl:
130
+
131
+ ```powershell
132
+ curl.exe -X POST http://127.0.0.1:8000/demo `
133
+ -H "Content-Type: application/json" `
134
+ -d '{\"email\":\"24f2005753@ds.study.iitm.ac.in\",\"secret\":\"EasyQuiz\",\"url\":\"https://tds-llm-analysis.s-anand.net/demo\"}'
135
+ ```
136
+
137
+ Or save JSON to a file:
138
+
139
+ ```powershell
140
+ # Create request.json
141
+ @'
142
+ {
143
+ "email": "24f2005753@ds.study.iitm.ac.in",
144
+ "secret": "EasyQuiz",
145
+ "url": "https://tds-llm-analysis.s-anand.net/demo"
146
+ }
147
+ '@ | Out-File -FilePath request.json -Encoding utf8
148
+
149
+ # Send request
150
+ curl.exe -X POST http://127.0.0.1:8000/demo `
151
+ -H "Content-Type: application/json" `
152
+ -d "@request.json"
153
+ ```
154
+
155
+ ## Error Handling
156
+
157
+ ```powershell
158
+ try {
159
+ $response = Invoke-RestMethod `
160
+ -Uri "http://127.0.0.1:8000/solve" `
161
+ -Method POST `
162
+ -ContentType "application/json" `
163
+ -Body $body
164
+
165
+ $response | ConvertTo-Json -Depth 10
166
+ }
167
+ catch {
168
+ Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
169
+ if ($_.ErrorDetails.Message) {
170
+ Write-Host $_.ErrorDetails.Message -ForegroundColor Yellow
171
+ }
172
+ }
173
+ ```
174
+
175
+
__init__.cpython-311.pyc ADDED
Binary file (171 Bytes). View file
 
__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # IITM LLM Quiz Solver
2
+ __version__ = "1.0.0"
3
+
browser.cpython-311.pyc ADDED
Binary file (12.5 kB). View file
 
browser.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Playwright browser helper for loading and interacting with quiz pages.
3
+ """
4
+ import asyncio
5
+ import logging
6
+ from typing import Optional, Dict, Any, List
7
+ from playwright.async_api import async_playwright, Browser, Page, BrowserContext
8
+ import time
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class BrowserHelper:
14
+ """Helper class for managing Playwright browser sessions."""
15
+
16
+ def __init__(self):
17
+ self.browser: Optional[Browser] = None
18
+ self.context: Optional[BrowserContext] = None
19
+ self.page: Optional[Page] = None
20
+ self.playwright = None
21
+
22
+ async def start(self, headless: bool = True) -> None:
23
+ """
24
+ Start Playwright browser.
25
+
26
+ Args:
27
+ headless: Run in headless mode
28
+ """
29
+ try:
30
+ self.playwright = await async_playwright().start()
31
+ self.browser = await self.playwright.chromium.launch(
32
+ headless=headless,
33
+ args=[
34
+ '--no-sandbox',
35
+ '--disable-setuid-sandbox',
36
+ '--disable-dev-shm-usage',
37
+ '--disable-accelerated-2d-canvas',
38
+ '--disable-gpu'
39
+ ]
40
+ )
41
+ self.context = await self.browser.new_context(
42
+ viewport={'width': 1920, 'height': 1080},
43
+ user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
44
+ )
45
+ self.page = await self.context.new_page()
46
+ logger.info("Browser started successfully")
47
+ except Exception as e:
48
+ logger.error(f"Error starting browser: {e}")
49
+ raise
50
+
51
+ async def load_page(self, url: str, wait_time: int = 5, timeout: int = 30000) -> Dict[str, Any]:
52
+ """
53
+ Load a page and extract all content.
54
+
55
+ Args:
56
+ url: URL to load
57
+ wait_time: Seconds to wait for page to load
58
+ timeout: Page load timeout in milliseconds
59
+
60
+ Returns:
61
+ Dictionary with page content
62
+ """
63
+ if not self.page:
64
+ await self.start()
65
+
66
+ try:
67
+ logger.info(f"Loading page: {url}")
68
+ await self.page.goto(url, wait_until='networkidle', timeout=timeout)
69
+
70
+ # Wait for dynamic content
71
+ await asyncio.sleep(wait_time)
72
+
73
+ # Extract page content
74
+ content = {
75
+ 'url': url,
76
+ 'title': await self.page.title(),
77
+ 'text': await self.page.inner_text('body'),
78
+ 'html': await self.page.content(),
79
+ 'screenshot': await self.page.screenshot(full_page=True),
80
+ }
81
+
82
+ # Try to extract all visible text elements
83
+ try:
84
+ content['all_text'] = await self.page.evaluate("""
85
+ () => {
86
+ const walker = document.createTreeWalker(
87
+ document.body,
88
+ NodeFilter.SHOW_TEXT,
89
+ null,
90
+ false
91
+ );
92
+ let text = [];
93
+ let node;
94
+ while (node = walker.nextNode()) {
95
+ if (node.textContent.trim()) {
96
+ text.push(node.textContent.trim());
97
+ }
98
+ }
99
+ return text.join('\\n');
100
+ }
101
+ """)
102
+ except Exception as e:
103
+ logger.warning(f"Error extracting all text: {e}")
104
+ content['all_text'] = content['text']
105
+
106
+ # Extract links
107
+ try:
108
+ content['links'] = await self.page.evaluate("""
109
+ () => {
110
+ const links = Array.from(document.querySelectorAll('a[href]'));
111
+ return links.map(a => ({text: a.textContent.trim(), href: a.href}));
112
+ }
113
+ """)
114
+ except Exception as e:
115
+ logger.warning(f"Error extracting links: {e}")
116
+ content['links'] = []
117
+
118
+ # Extract images
119
+ try:
120
+ content['images'] = await self.page.evaluate("""
121
+ () => {
122
+ const images = Array.from(document.querySelectorAll('img[src]'));
123
+ return images.map(img => ({alt: img.alt, src: img.src}));
124
+ }
125
+ """)
126
+ except Exception as e:
127
+ logger.warning(f"Error extracting images: {e}")
128
+ content['images'] = []
129
+
130
+ logger.info(f"Page loaded successfully: {content['title']}")
131
+ return content
132
+
133
+ except Exception as e:
134
+ logger.error(f"Error loading page {url}: {e}")
135
+ raise
136
+
137
+ async def click_element(self, selector: str) -> bool:
138
+ """
139
+ Click an element on the page.
140
+
141
+ Args:
142
+ selector: CSS selector
143
+
144
+ Returns:
145
+ True if successful
146
+ """
147
+ try:
148
+ await self.page.click(selector)
149
+ await asyncio.sleep(1)
150
+ return True
151
+ except Exception as e:
152
+ logger.error(f"Error clicking element {selector}: {e}")
153
+ return False
154
+
155
+ async def fill_input(self, selector: str, value: str) -> bool:
156
+ """
157
+ Fill an input field.
158
+
159
+ Args:
160
+ selector: CSS selector
161
+ value: Value to fill
162
+
163
+ Returns:
164
+ True if successful
165
+ """
166
+ try:
167
+ await self.page.fill(selector, value)
168
+ return True
169
+ except Exception as e:
170
+ logger.error(f"Error filling input {selector}: {e}")
171
+ return False
172
+
173
+ async def wait_for_element(self, selector: str, timeout: int = 10000) -> bool:
174
+ """
175
+ Wait for an element to appear.
176
+
177
+ Args:
178
+ selector: CSS selector
179
+ timeout: Timeout in milliseconds
180
+
181
+ Returns:
182
+ True if element found
183
+ """
184
+ try:
185
+ await self.page.wait_for_selector(selector, timeout=timeout)
186
+ return True
187
+ except Exception as e:
188
+ logger.warning(f"Element {selector} not found: {e}")
189
+ return False
190
+
191
+ async def evaluate_script(self, script: str) -> Any:
192
+ """
193
+ Execute JavaScript on the page.
194
+
195
+ Args:
196
+ script: JavaScript code to execute
197
+
198
+ Returns:
199
+ Result of script execution
200
+ """
201
+ try:
202
+ return await self.page.evaluate(script)
203
+ except Exception as e:
204
+ logger.error(f"Error evaluating script: {e}")
205
+ return None
206
+
207
+ async def close(self) -> None:
208
+ """Close browser and cleanup."""
209
+ try:
210
+ if self.page:
211
+ await self.page.close()
212
+ if self.context:
213
+ await self.context.close()
214
+ if self.browser:
215
+ await self.browser.close()
216
+ if self.playwright:
217
+ await self.playwright.stop()
218
+ logger.info("Browser closed")
219
+ except Exception as e:
220
+ logger.error(f"Error closing browser: {e}")
221
+
222
+
223
+ # Global browser instance
224
+ _browser: Optional[BrowserHelper] = None
225
+
226
+
227
+ async def get_browser() -> BrowserHelper:
228
+ """
229
+ Get or create a browser instance.
230
+
231
+ Returns:
232
+ BrowserHelper instance
233
+ """
234
+ global _browser
235
+ if _browser is None:
236
+ _browser = BrowserHelper()
237
+ await _browser.start()
238
+ return _browser
239
+
240
+
241
+ async def cleanup_browser() -> None:
242
+ """Cleanup browser instance."""
243
+ global _browser
244
+ if _browser:
245
+ await _browser.close()
246
+ _browser = None
247
+
check_and_start.ps1 ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Script to check if server is running and help start it
2
+ # Usage: .\check_and_start.ps1
3
+
4
+ Write-Host "IITM LLM Quiz Solver - Server Check" -ForegroundColor Cyan
5
+ Write-Host "====================================" -ForegroundColor Cyan
6
+ Write-Host ""
7
+
8
+ # Check if port 8000 is in use
9
+ Write-Host "Checking if server is running on port 8000..." -ForegroundColor Yellow
10
+ $portCheck = Test-NetConnection -ComputerName localhost -Port 8000 -InformationLevel Quiet -WarningAction SilentlyContinue
11
+
12
+ if ($portCheck) {
13
+ Write-Host "✓ Port 8000 is open - server might be running" -ForegroundColor Green
14
+ Write-Host ""
15
+ Write-Host "Testing /health endpoint..." -ForegroundColor Yellow
16
+ try {
17
+ $health = Invoke-RestMethod -Uri "http://127.0.0.1:8000/health" -Method GET -TimeoutSec 2 -ErrorAction Stop
18
+ Write-Host "✓ Server is running and responding!" -ForegroundColor Green
19
+ Write-Host " Health status: $($health.status)" -ForegroundColor Gray
20
+ Write-Host ""
21
+ Write-Host "Testing /demo endpoint..." -ForegroundColor Yellow
22
+ Write-Host " (This will make a test request)" -ForegroundColor Gray
23
+
24
+ # Set variables
25
+ if (-not $env:QUIZ_SECRET) {
26
+ $env:QUIZ_SECRET = "EasyQuiz"
27
+ }
28
+
29
+ $body = @{
30
+ email = "24f2005753@ds.study.iitm.ac.in"
31
+ secret = "EasyQuiz"
32
+ url = "https://tds-llm-analysis.s-anand.net/demo"
33
+ } | ConvertTo-Json
34
+
35
+ try {
36
+ $response = Invoke-RestMethod `
37
+ -Uri "http://127.0.0.1:8000/demo" `
38
+ -Method POST `
39
+ -ContentType "application/json" `
40
+ -Body $body `
41
+ -TimeoutSec 30 `
42
+ -ErrorAction Stop
43
+
44
+ Write-Host "✓ /demo endpoint works!" -ForegroundColor Green
45
+ Write-Host " Response:" -ForegroundColor Gray
46
+ $response | ConvertTo-Json -Depth 10 | Write-Host
47
+ } catch {
48
+ Write-Host "✗ /demo endpoint failed" -ForegroundColor Red
49
+ Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Yellow
50
+ if ($_.ErrorDetails.Message) {
51
+ Write-Host " Details: $($_.ErrorDetails.Message)" -ForegroundColor Yellow
52
+ }
53
+ }
54
+ } catch {
55
+ Write-Host "✗ Server is not responding correctly" -ForegroundColor Red
56
+ Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Yellow
57
+ Write-Host ""
58
+ Write-Host "The port is open but the server might not be our FastAPI app." -ForegroundColor Yellow
59
+ Write-Host "Please check if you have the correct server running." -ForegroundColor Yellow
60
+ }
61
+ } else {
62
+ Write-Host "✗ Port 8000 is NOT open - server is NOT running" -ForegroundColor Red
63
+ Write-Host ""
64
+ Write-Host "To start the server:" -ForegroundColor Yellow
65
+ Write-Host " 1. Set environment variables:" -ForegroundColor Cyan
66
+ Write-Host " `$env:QUIZ_SECRET = 'EasyQuiz'" -ForegroundColor Gray
67
+ Write-Host " `$env:OPENAI_API_KEY = 'your-key-here'" -ForegroundColor Gray
68
+ Write-Host ""
69
+ Write-Host " 2. Start the server:" -ForegroundColor Cyan
70
+ Write-Host " python -m app.main" -ForegroundColor Gray
71
+ Write-Host ""
72
+ Write-Host " 3. Keep that terminal open and test in another terminal" -ForegroundColor Cyan
73
+ }
74
+
75
+ Write-Host ""
76
+ Write-Host "====================================" -ForegroundColor Cyan
77
+
78
+
check_env.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Quick script to check if environment variables are set correctly.
3
+ Run: python check_env.py
4
+ """
5
+ import os
6
+ import sys
7
+
8
+ def check_env():
9
+ """Check and display environment variable status."""
10
+ print("=" * 50)
11
+ print("IITM LLM Quiz Solver - Environment Check")
12
+ print("=" * 50)
13
+ print()
14
+
15
+ # Check QUIZ_SECRET
16
+ quiz_secret = os.getenv("QUIZ_SECRET")
17
+ if quiz_secret:
18
+ # Show first and last 4 chars for security
19
+ masked = f"{quiz_secret[:4]}...{quiz_secret[-4:]}" if len(quiz_secret) > 8 else "***"
20
+ print(f"✓ QUIZ_SECRET: Set ({masked})")
21
+ print(f" Length: {len(quiz_secret)} characters")
22
+ else:
23
+ print("✗ QUIZ_SECRET: NOT SET (Required)")
24
+ print(" Set it with: export QUIZ_SECRET='your_secret' (Linux/Mac)")
25
+ print(" Or: $env:QUIZ_SECRET = 'your_secret' (Windows PowerShell)")
26
+
27
+ print()
28
+
29
+ # Check OpenAI key
30
+ openai_key = os.getenv("OPENAI_API_KEY")
31
+ if openai_key:
32
+ masked = f"{openai_key[:7]}...{openai_key[-4:]}" if len(openai_key) > 11 else "***"
33
+ print(f"✓ OPENAI_API_KEY: Set ({masked})")
34
+ print(f" Length: {len(openai_key)} characters")
35
+ if not openai_key.startswith("sk-"):
36
+ print(" ⚠ Warning: OpenAI keys usually start with 'sk-'")
37
+ else:
38
+ print("✗ OPENAI_API_KEY: NOT SET (Optional)")
39
+ print(" Set it with: export OPENAI_API_KEY='sk-your-key' (Linux/Mac)")
40
+ print(" Or: $env:OPENAI_API_KEY = 'sk-your-key' (Windows PowerShell)")
41
+
42
+ print()
43
+
44
+ # Check OpenRouter key
45
+ openrouter_key = os.getenv("OPENROUTER_API_KEY")
46
+ if openrouter_key:
47
+ masked = f"{openrouter_key[:6]}...{openrouter_key[-4:]}" if len(openrouter_key) > 10 else "***"
48
+ print(f"✓ OPENROUTER_API_KEY: Set ({masked})")
49
+ print(f" Length: {len(openrouter_key)} characters")
50
+ if not openrouter_key.startswith("sk-or-"):
51
+ print(" ⚠ Warning: OpenRouter keys usually start with 'sk-or-'")
52
+ model = os.getenv("OPENROUTER_MODEL", "gpt-5-nano")
53
+ print(f" Model: {model}")
54
+ else:
55
+ print("✗ OPENROUTER_API_KEY: NOT SET (Optional)")
56
+ print(" Set it with: export OPENROUTER_API_KEY='sk-or-your-key' (Linux/Mac)")
57
+ print(" Or: $env:OPENROUTER_API_KEY = 'sk-or-your-key' (Windows PowerShell)")
58
+
59
+ print()
60
+
61
+ # Check PORT (optional)
62
+ port = os.getenv("PORT", "8000")
63
+ print(f"PORT: {port} (default: 8000)")
64
+
65
+ print()
66
+ print("=" * 50)
67
+
68
+ # Summary
69
+ if quiz_secret:
70
+ print("✓ Status: Ready to run (QUIZ_SECRET is set)")
71
+ if openai_key or openrouter_key:
72
+ provider = []
73
+ if openai_key:
74
+ provider.append("OpenAI")
75
+ if openrouter_key:
76
+ provider.append("OpenRouter")
77
+ print(f"✓ LLM features: Enabled via {', '.join(provider)}")
78
+ else:
79
+ print("⚠ LLM features: Disabled (no LLM API keys set)")
80
+ else:
81
+ print("✗ Status: NOT READY - QUIZ_SECRET is required")
82
+ sys.exit(1)
83
+
84
+ print("=" * 50)
85
+ print()
86
+ print("To start the server, run:")
87
+ print(" python -m app.main")
88
+ print(" or")
89
+ print(" uvicorn app.main:app --host 0.0.0.0 --port 8000")
90
+
91
+ if __name__ == "__main__":
92
+ check_env()
93
+
llm.cpython-311.pyc ADDED
Binary file (10.2 kB). View file
 
llm.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ LLM helper module for OpenAI GPT integration.
3
+ Used for reasoning, OCR, and complex question parsing.
4
+ """
5
+ import os
6
+ import logging
7
+ from typing import Optional, Dict, Any
8
+ import openai
9
+ from openai import AsyncOpenAI
10
+ import httpx
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # Initialize OpenAI client
15
+ client: Optional[AsyncOpenAI] = None
16
+
17
+ # OpenRouter configuration
18
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
19
+ OPENROUTER_BASE_URL = os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1")
20
+ OPENROUTER_MODEL = os.getenv("OPENROUTER_MODEL", "gpt-5-nano")
21
+ OPENROUTER_SITE_URL = os.getenv("OPENROUTER_SITE_URL", "http://localhost")
22
+ OPENROUTER_APP_NAME = os.getenv("OPENROUTER_APP_NAME", "IITM LLM Quiz Solver")
23
+
24
+
25
+ def initialize_llm() -> None:
26
+ """
27
+ Initialize OpenAI client with API key from environment.
28
+ """
29
+ global client
30
+ api_key = os.getenv("OPENAI_API_KEY")
31
+ if api_key:
32
+ client = AsyncOpenAI(api_key=api_key)
33
+ logger.info("OpenAI client initialized")
34
+ else:
35
+ if OPENROUTER_API_KEY:
36
+ logger.info("OPENAI_API_KEY not set, using OpenRouter only")
37
+ else:
38
+ logger.warning("No OPENAI_API_KEY or OPENROUTER_API_KEY set, LLM features will be disabled")
39
+
40
+
41
+ async def ask_gpt(prompt: str, model: str = "gpt-4o-mini", max_tokens: int = 2000) -> Optional[str]:
42
+ """
43
+ Query OpenAI GPT model with a prompt.
44
+
45
+ Args:
46
+ prompt: The prompt/question to ask
47
+ model: Model to use (default: gpt-4o-mini)
48
+ max_tokens: Maximum tokens in response
49
+
50
+ Returns:
51
+ Response text or None if error
52
+ """
53
+ global client
54
+
55
+ try:
56
+ if client:
57
+ response = await client.chat.completions.create(
58
+ model=model,
59
+ messages=[
60
+ {"role": "system", "content": "You are a helpful assistant that solves quiz questions accurately and concisely."},
61
+ {"role": "user", "content": prompt}
62
+ ],
63
+ max_tokens=max_tokens,
64
+ temperature=0.3
65
+ )
66
+
67
+ answer = response.choices[0].message.content
68
+ logger.info(f"GPT response received (model: {model})")
69
+ return answer
70
+ else:
71
+ logger.warning("OpenAI client not initialized, attempting OpenRouter fallback")
72
+ return await ask_openrouter(prompt, max_tokens=max_tokens)
73
+
74
+ except Exception as e:
75
+ logger.error(f"Error calling OpenAI API: {e}")
76
+ # Fallback to OpenRouter if configured
77
+ fallback = await ask_openrouter(prompt, max_tokens=max_tokens)
78
+ if fallback:
79
+ return fallback
80
+ return None
81
+
82
+
83
+ async def ask_openrouter(prompt: str, model: Optional[str] = None, max_tokens: int = 2000) -> Optional[str]:
84
+ """
85
+ Query OpenRouter (e.g., GPT-5-nano) with a prompt.
86
+
87
+ Args:
88
+ prompt: Prompt text
89
+ model: Model to use (defaults to OPENROUTER_MODEL)
90
+ max_tokens: Maximum tokens
91
+
92
+ Returns:
93
+ Response text or None
94
+ """
95
+ if not OPENROUTER_API_KEY:
96
+ logger.warning("OPENROUTER_API_KEY not set, cannot call OpenRouter")
97
+ return None
98
+
99
+ if not model:
100
+ model = OPENROUTER_MODEL
101
+
102
+ url = f"{OPENROUTER_BASE_URL.rstrip('/')}/chat/completions"
103
+ headers = {
104
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
105
+ "HTTP-Referer": OPENROUTER_SITE_URL,
106
+ "X-Title": OPENROUTER_APP_NAME,
107
+ "Content-Type": "application/json",
108
+ }
109
+ payload = {
110
+ "model": model,
111
+ "messages": [
112
+ {"role": "system", "content": "You are a helpful assistant that solves quiz questions accurately and concisely."},
113
+ {"role": "user", "content": prompt}
114
+ ],
115
+ "max_tokens": max_tokens,
116
+ "temperature": 0.2
117
+ }
118
+
119
+ try:
120
+ async with httpx.AsyncClient(timeout=60) as http_client:
121
+ response = await http_client.post(url, headers=headers, json=payload)
122
+ response.raise_for_status()
123
+ data = response.json()
124
+ answer = data["choices"][0]["message"]["content"]
125
+ logger.info(f"OpenRouter response received (model: {model})")
126
+ return answer
127
+ except Exception as e:
128
+ logger.error(f"Error calling OpenRouter API: {e}")
129
+ return None
130
+
131
+
132
+ async def parse_question_with_llm(question_text: str, context: str = "") -> Optional[Dict[str, Any]]:
133
+ """
134
+ Use LLM to parse and understand a quiz question.
135
+
136
+ Args:
137
+ question_text: The question text
138
+ context: Additional context from the page
139
+
140
+ Returns:
141
+ Parsed question structure or None
142
+ """
143
+ prompt = f"""Analyze this quiz question and provide a structured response:
144
+
145
+ Question: {question_text}
146
+
147
+ Context: {context}
148
+
149
+ Please identify:
150
+ 1. What type of question is this? (scraping, calculation, API call, data analysis, etc.)
151
+ 2. What data or resources are needed?
152
+ 3. What is the expected answer format? (JSON, number, text, etc.)
153
+
154
+ Respond in JSON format:
155
+ {{
156
+ "type": "question_type",
157
+ "requirements": ["requirement1", "requirement2"],
158
+ "answer_format": "format_type",
159
+ "reasoning": "your reasoning"
160
+ }}
161
+ """
162
+
163
+ response = await ask_gpt(prompt)
164
+ if not response:
165
+ return None
166
+
167
+ # Try to extract JSON from response
168
+ import json
169
+ import re
170
+
171
+ json_match = re.search(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', response, re.DOTALL)
172
+ if json_match:
173
+ try:
174
+ return json.loads(json_match.group())
175
+ except json.JSONDecodeError:
176
+ pass
177
+
178
+ return {"raw_response": response}
179
+
180
+
181
+ async def solve_with_llm(question: str, available_data: Dict[str, Any]) -> Optional[str]:
182
+ """
183
+ Use LLM to solve a quiz question.
184
+
185
+ Args:
186
+ question: The question text
187
+ available_data: Any data extracted from the page
188
+
189
+ Returns:
190
+ Answer or None
191
+ """
192
+ prompt = f"""Solve this quiz question:
193
+
194
+ Question: {question}
195
+
196
+ Available Data:
197
+ {available_data}
198
+
199
+ Provide a clear, concise answer. If the answer should be in JSON format, provide valid JSON.
200
+ If it's a calculation, show your work briefly.
201
+ """
202
+
203
+ return await ask_gpt(prompt, max_tokens=3000)
204
+
205
+
206
+ async def ocr_image_with_llm(image_base64: str) -> Optional[str]:
207
+ """
208
+ Use GPT-4 Vision to extract text from an image.
209
+
210
+ Note: Requires GPT-4 Vision model (gpt-4o or gpt-4-vision-preview).
211
+ gpt-4o-mini does not support vision.
212
+
213
+ Args:
214
+ image_base64: Base64 encoded image
215
+
216
+ Returns:
217
+ Extracted text or None
218
+ """
219
+ global client
220
+
221
+ if not client:
222
+ return None
223
+
224
+ # Try vision-capable models
225
+ vision_models = ["gpt-4o", "gpt-4-vision-preview"]
226
+
227
+ for model in vision_models:
228
+ try:
229
+ response = await client.chat.completions.create(
230
+ model=model,
231
+ messages=[
232
+ {
233
+ "role": "user",
234
+ "content": [
235
+ {"type": "text", "text": "Extract all text from this image. Return only the text content."},
236
+ {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_base64}"}}
237
+ ]
238
+ }
239
+ ],
240
+ max_tokens=1000
241
+ )
242
+
243
+ return response.choices[0].message.content
244
+
245
+ except Exception as e:
246
+ logger.warning(f"Error with model {model}: {e}")
247
+ continue
248
+
249
+ logger.error("No vision-capable model available")
250
+ return None
251
+
main.cpython-311.pyc ADDED
Binary file (11.3 kB). View file
 
main.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FastAPI main server for IITM LLM Quiz Solver.
3
+ """
4
+ import os
5
+ import logging
6
+ import asyncio
7
+ from typing import Dict, Any, Optional
8
+ from fastapi import FastAPI, HTTPException, Request
9
+ from fastapi.responses import JSONResponse
10
+ from pydantic import BaseModel, Field, field_validator
11
+ import uvicorn
12
+
13
+ # Try to load .env file if python-dotenv is available
14
+ try:
15
+ from dotenv import load_dotenv
16
+ load_dotenv()
17
+ except ImportError:
18
+ pass # python-dotenv is optional
19
+
20
+ from app.solver import solve_quiz
21
+ from app.utils import validate_secret
22
+ from app.browser import cleanup_browser
23
+
24
+ # Configure logging
25
+ logging.basicConfig(
26
+ level=logging.INFO,
27
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
28
+ )
29
+ logger = logging.getLogger(__name__)
30
+
31
+ # Get secret from environment
32
+ EXPECTED_SECRET = os.getenv("QUIZ_SECRET", "default_secret_change_me")
33
+
34
+ # Lifespan context manager for startup and shutdown
35
+ from contextlib import asynccontextmanager
36
+
37
+ @asynccontextmanager
38
+ async def lifespan(app: FastAPI):
39
+ """Lifespan context manager for startup and shutdown."""
40
+ # Startup
41
+ logger.info("Application starting up...")
42
+ yield
43
+ # Shutdown
44
+ logger.info("Shutting down, cleaning up browser...")
45
+ await cleanup_browser()
46
+
47
+ # Initialize FastAPI app with lifespan
48
+ app = FastAPI(
49
+ title="IITM LLM Quiz Solver",
50
+ description="API endpoint to automatically solve dynamic quiz tasks",
51
+ version="1.0.0",
52
+ lifespan=lifespan
53
+ )
54
+
55
+
56
+ class QuizRequest(BaseModel):
57
+ """Request model for quiz solving."""
58
+ email: str = Field(..., description="User email address")
59
+ secret: str = Field(..., description="Secret key for authentication")
60
+ url: str = Field(..., description="Quiz page URL")
61
+
62
+ @field_validator('email')
63
+ @classmethod
64
+ def validate_email(cls, v):
65
+ if not v or '@' not in v:
66
+ raise ValueError('Invalid email format')
67
+ return v
68
+
69
+ @field_validator('url')
70
+ @classmethod
71
+ def validate_url(cls, v):
72
+ if not v or not v.startswith(('http://', 'https://')):
73
+ raise ValueError('Invalid URL format')
74
+ return v
75
+
76
+
77
+ @app.get("/")
78
+ async def root():
79
+ """Root endpoint."""
80
+ return {
81
+ "message": "IITM LLM Quiz Solver API",
82
+ "version": "1.0.0",
83
+ "endpoints": {
84
+ "/solve": "POST - Solve a quiz",
85
+ "/health": "GET - Health check",
86
+ "/demo": "POST - Demo endpoint"
87
+ }
88
+ }
89
+
90
+
91
+ @app.get("/health")
92
+ async def health_check():
93
+ """Health check endpoint."""
94
+ return {"status": "healthy"}
95
+
96
+
97
+ @app.get("/env-check")
98
+ async def env_check():
99
+ """
100
+ Check environment variables status (returns JSON).
101
+ Useful for verifying configuration.
102
+ """
103
+ quiz_secret = os.getenv("QUIZ_SECRET")
104
+ openai_key = os.getenv("OPENAI_API_KEY")
105
+ openrouter_key = os.getenv("OPENROUTER_API_KEY")
106
+ port = os.getenv("PORT", "8000")
107
+
108
+ return {
109
+ "status": "ok",
110
+ "variables": {
111
+ "QUIZ_SECRET": {
112
+ "set": quiz_secret is not None,
113
+ "length": len(quiz_secret) if quiz_secret else 0,
114
+ "preview": f"{quiz_secret[:4]}...{quiz_secret[-4:]}" if quiz_secret and len(quiz_secret) > 8 else "***" if quiz_secret else None
115
+ },
116
+ "OPENAI_API_KEY": {
117
+ "set": openai_key is not None,
118
+ "length": len(openai_key) if openai_key else 0,
119
+ "preview": f"{openai_key[:7]}...{openai_key[-4:]}" if openai_key and len(openai_key) > 11 else "***" if openai_key else None,
120
+ "valid_format": openai_key.startswith("sk-") if openai_key else False
121
+ },
122
+ "OPENROUTER_API_KEY": {
123
+ "set": openrouter_key is not None,
124
+ "length": len(openrouter_key) if openrouter_key else 0,
125
+ "preview": f"{openrouter_key[:7]}...{openrouter_key[-4:]}" if openrouter_key and len(openrouter_key) > 11 else "***" if openrouter_key else None,
126
+ "valid_format": openrouter_key.startswith("sk-or-") if openrouter_key else False
127
+ },
128
+ "PORT": {
129
+ "set": True,
130
+ "value": port
131
+ }
132
+ },
133
+ "ready": quiz_secret is not None,
134
+ "llm_enabled": any([openai_key, openrouter_key])
135
+ }
136
+
137
+
138
+ @app.post("/solve")
139
+ async def solve_quiz_endpoint(request: QuizRequest):
140
+ """
141
+ Main endpoint to solve a quiz.
142
+
143
+ Validates secret and solves the quiz recursively.
144
+ """
145
+ try:
146
+ # Validate secret
147
+ if not validate_secret(request.secret, EXPECTED_SECRET):
148
+ logger.warning(f"Invalid secret provided for email: {request.email}")
149
+ raise HTTPException(
150
+ status_code=403,
151
+ detail={"error": "forbidden"}
152
+ )
153
+
154
+ logger.info(f"Solving quiz for {request.email} at {request.url}")
155
+
156
+ # Solve quiz with timeout
157
+ try:
158
+ result = await asyncio.wait_for(
159
+ solve_quiz(request.url, request.email, request.secret),
160
+ timeout=180.0 # 3 minutes
161
+ )
162
+ return result
163
+ except asyncio.TimeoutError:
164
+ logger.error("Quiz solving timed out")
165
+ raise HTTPException(
166
+ status_code=504,
167
+ detail={"error": "Request timeout - quiz solving took too long"}
168
+ )
169
+ except Exception as e:
170
+ logger.error(f"Error solving quiz: {e}", exc_info=True)
171
+ raise HTTPException(
172
+ status_code=500,
173
+ detail={"error": str(e)}
174
+ )
175
+
176
+ except HTTPException:
177
+ raise
178
+ except ValueError as e:
179
+ logger.error(f"Validation error: {e}")
180
+ raise HTTPException(
181
+ status_code=400,
182
+ detail={"error": "Invalid request format", "message": str(e)}
183
+ )
184
+ except Exception as e:
185
+ logger.error(f"Unexpected error: {e}", exc_info=True)
186
+ raise HTTPException(
187
+ status_code=500,
188
+ detail={"error": "Internal server error", "message": str(e)}
189
+ )
190
+
191
+
192
+ @app.post("/demo")
193
+ async def demo_endpoint(request: QuizRequest):
194
+ """
195
+ Demo endpoint for testing.
196
+
197
+ Same as /solve but with more lenient error handling.
198
+ """
199
+ try:
200
+ # Validate secret (can be more lenient for demo)
201
+ if not validate_secret(request.secret, EXPECTED_SECRET):
202
+ logger.warning(f"Invalid secret in demo request")
203
+ return JSONResponse(
204
+ status_code=403,
205
+ content={"error": "forbidden"}
206
+ )
207
+
208
+ logger.info(f"Demo: Solving quiz for {request.email} at {request.url}")
209
+
210
+ # Solve quiz
211
+ try:
212
+ result = await asyncio.wait_for(
213
+ solve_quiz(request.url, request.email, request.secret),
214
+ timeout=180.0
215
+ )
216
+ return result
217
+ except asyncio.TimeoutError:
218
+ return JSONResponse(
219
+ status_code=504,
220
+ content={"error": "Request timeout"}
221
+ )
222
+ except Exception as e:
223
+ logger.error(f"Error in demo: {e}", exc_info=True)
224
+ return JSONResponse(
225
+ status_code=500,
226
+ content={"error": str(e)}
227
+ )
228
+
229
+ except ValueError as e:
230
+ return JSONResponse(
231
+ status_code=400,
232
+ content={"error": "Invalid request format", "message": str(e)}
233
+ )
234
+ except Exception as e:
235
+ logger.error(f"Unexpected error in demo: {e}", exc_info=True)
236
+ return JSONResponse(
237
+ status_code=500,
238
+ content={"error": "Internal server error", "message": str(e)}
239
+ )
240
+
241
+
242
+ if __name__ == "__main__":
243
+ port = int(os.getenv("PORT", 8000))
244
+ uvicorn.run(
245
+ "app.main:app",
246
+ host="0.0.0.0",
247
+ port=port,
248
+ log_level="info"
249
+ )
250
+
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ playwright==1.40.0
4
+ requests==2.31.0
5
+ beautifulsoup4==4.12.2
6
+ pandas==2.1.3
7
+ PyPDF2==3.0.1
8
+ pdfplumber==0.10.3
9
+ openai==1.3.5
10
+ pydantic==2.5.0
11
+ lxml==4.9.3
12
+ html5lib==1.1
13
+ python-dotenv==1.0.0
14
+
restart_server.ps1 ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Script to stop any server on port 8000 and start the correct one
2
+ # Usage: .\restart_server.ps1
3
+
4
+ Write-Host "IITM LLM Quiz Solver - Server Restart" -ForegroundColor Cyan
5
+ Write-Host "=====================================" -ForegroundColor Cyan
6
+ Write-Host ""
7
+
8
+ # Find process using port 8000
9
+ Write-Host "Finding process using port 8000..." -ForegroundColor Yellow
10
+ $portInfo = netstat -ano | findstr :8000 | findstr LISTENING
11
+ if ($portInfo) {
12
+ $pid = ($portInfo -split '\s+')[-1]
13
+ Write-Host "Found process ID: $pid" -ForegroundColor Yellow
14
+
15
+ $process = Get-Process -Id $pid -ErrorAction SilentlyContinue
16
+ if ($process) {
17
+ Write-Host "Process: $($process.ProcessName) (PID: $pid)" -ForegroundColor Yellow
18
+ Write-Host ""
19
+ Write-Host "Stopping process..." -ForegroundColor Yellow
20
+ Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue
21
+ Start-Sleep -Seconds 2
22
+ Write-Host "✓ Process stopped" -ForegroundColor Green
23
+ }
24
+ } else {
25
+ Write-Host "No process found on port 8000" -ForegroundColor Green
26
+ }
27
+
28
+ Write-Host ""
29
+ Write-Host "Setting environment variables..." -ForegroundColor Yellow
30
+ $env:QUIZ_SECRET = "EasyQuiz"
31
+ $env:OPENAI_API_KEY = "eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6IjI0ZjIwMDU3NTNAZHMuc3R1ZHkuaWl0bS5hYy5pbiJ9.eji7L5I62M9YHoeEKE8ao6eTw8dFjgDIMP9C3lOUXc4"
32
+ Write-Host "✓ Environment variables set" -ForegroundColor Green
33
+
34
+ Write-Host ""
35
+ Write-Host "=====================================" -ForegroundColor Cyan
36
+ Write-Host "Now start the server manually:" -ForegroundColor Yellow
37
+ Write-Host " python -m app.main" -ForegroundColor Cyan
38
+ Write-Host ""
39
+ Write-Host "Keep that terminal open, then test in another terminal!" -ForegroundColor Yellow
40
+
41
+
set_api_key.ps1 ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Quick script to set OpenAI API key
2
+ # Usage: .\set_api_key.ps1
3
+
4
+ $apiKey = "eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6IjI0ZjIwMDU3NTNAZHMuc3R1ZHkuaWl0bS5hYy5pbiJ9.eji7L5I62M9YHoeEKE8ao6eTw8dFjgDIMP9C3lOUXc4"
5
+
6
+ Write-Host "Setting OPENAI_API_KEY for current session..." -ForegroundColor Cyan
7
+ $env:OPENAI_API_KEY = $apiKey
8
+
9
+ Write-Host "✓ OPENAI_API_KEY set" -ForegroundColor Green
10
+ Write-Host ""
11
+ Write-Host "To make it permanent, run:" -ForegroundColor Yellow
12
+ Write-Host '[System.Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "' + $apiKey + '", "User")' -ForegroundColor Gray
13
+ Write-Host ""
14
+ Write-Host "⚠️ Note: This looks like a JWT token, not a standard OpenAI API key." -ForegroundColor Yellow
15
+ Write-Host " OpenAI keys usually start with 'sk-'. If LLM features don't work," -ForegroundColor Yellow
16
+ Write-Host " you may need to get your actual OpenAI API key from:" -ForegroundColor Yellow
17
+ Write-Host " https://platform.openai.com/api-keys" -ForegroundColor Cyan
18
+ Write-Host ""
19
+ Write-Host "To verify, run: python test_env.json.py" -ForegroundColor Cyan
20
+
setup_env.ps1 ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Quick Environment Setup Script for Windows PowerShell
2
+ # Usage: .\setup_env.ps1
3
+
4
+ Write-Host "IITM LLM Quiz Solver - Environment Setup" -ForegroundColor Cyan
5
+ Write-Host "========================================" -ForegroundColor Cyan
6
+ Write-Host ""
7
+
8
+ # Check if variables are already set
9
+ $quizSecret = $env:QUIZ_SECRET
10
+ $openaiKey = $env:OPENAI_API_KEY
11
+ $openrouterKey = $env:OPENROUTER_API_KEY
12
+
13
+ if ($quizSecret) {
14
+ Write-Host "QUIZ_SECRET is already set" -ForegroundColor Green
15
+ } else {
16
+ Write-Host "QUIZ_SECRET is not set" -ForegroundColor Yellow
17
+ $secret = Read-Host "Enter your QUIZ_SECRET (or press Enter to generate one)"
18
+ if ([string]::IsNullOrWhiteSpace($secret)) {
19
+ # Generate a random secret
20
+ $secret = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 32 | ForEach-Object {[char]$_})
21
+ Write-Host "Generated secret: $secret" -ForegroundColor Cyan
22
+ }
23
+ $env:QUIZ_SECRET = $secret
24
+ Write-Host "QUIZ_SECRET set for this session" -ForegroundColor Green
25
+ }
26
+
27
+ if ($openaiKey) {
28
+ Write-Host "OPENAI_API_KEY is already set" -ForegroundColor Green
29
+ } else {
30
+ Write-Host "OPENAI_API_KEY is not set (optional)" -ForegroundColor Yellow
31
+ $key = Read-Host "Enter your OPENAI_API_KEY (or press Enter to skip)"
32
+ if (-not [string]::IsNullOrWhiteSpace($key)) {
33
+ $env:OPENAI_API_KEY = $key
34
+ Write-Host "OPENAI_API_KEY set for this session" -ForegroundColor Green
35
+ }
36
+ }
37
+
38
+ if ($openrouterKey) {
39
+ Write-Host "OPENROUTER_API_KEY is already set" -ForegroundColor Green
40
+ } else {
41
+ Write-Host "OPENROUTER_API_KEY is not set (optional, e.g., GPT-5-nano)" -ForegroundColor Yellow
42
+ $routerKey = Read-Host "Enter your OPENROUTER_API_KEY (or press Enter to skip)"
43
+ if (-not [string]::IsNullOrWhiteSpace($routerKey)) {
44
+ $env:OPENROUTER_API_KEY = $routerKey
45
+ Write-Host "OPENROUTER_API_KEY set for this session" -ForegroundColor Green
46
+ }
47
+ }
48
+
49
+ Write-Host ""
50
+ Write-Host "Environment variables configured!" -ForegroundColor Green
51
+ Write-Host ""
52
+ Write-Host "To make these permanent, run:" -ForegroundColor Cyan
53
+ Write-Host ('[System.Environment]::SetEnvironmentVariable("QUIZ_SECRET", "' + $env:QUIZ_SECRET + '", "User")') -ForegroundColor Gray
54
+ if ($env:OPENAI_API_KEY) {
55
+ Write-Host ('[System.Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "' + $env:OPENAI_API_KEY + '", "User")') -ForegroundColor Gray
56
+ }
57
+ if ($env:OPENROUTER_API_KEY) {
58
+ Write-Host ('[System.Environment]::SetEnvironmentVariable("OPENROUTER_API_KEY", "' + $env:OPENROUTER_API_KEY + '", "User")') -ForegroundColor Gray
59
+ }
60
+ Write-Host ""
61
+ Write-Host "Or copy and paste these commands:" -ForegroundColor Cyan
62
+ Write-Host ('[System.Environment]::SetEnvironmentVariable("QUIZ_SECRET", "' + $env:QUIZ_SECRET + '", "User")') -ForegroundColor Yellow
63
+ if ($env:OPENAI_API_KEY) {
64
+ Write-Host ('[System.Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "' + $env:OPENAI_API_KEY + '", "User")') -ForegroundColor Yellow
65
+ }
66
+ if ($env:OPENROUTER_API_KEY) {
67
+ Write-Host ('[System.Environment]::SetEnvironmentVariable("OPENROUTER_API_KEY", "' + $env:OPENROUTER_API_KEY + '", "User")') -ForegroundColor Yellow
68
+ }
69
+ Write-Host ""
70
+ Write-Host "To start the server, run:" -ForegroundColor Cyan
71
+ Write-Host "python -m app.main" -ForegroundColor Yellow
72
+
setup_env.sh ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Quick Environment Setup Script for Linux/Mac
3
+ # Usage: source setup_env.sh or . setup_env.sh
4
+
5
+ echo "IITM LLM Quiz Solver - Environment Setup"
6
+ echo "========================================"
7
+ echo ""
8
+
9
+ # Check if variables are already set
10
+ if [ -n "$QUIZ_SECRET" ]; then
11
+ echo "✓ QUIZ_SECRET is already set"
12
+ else
13
+ echo "QUIZ_SECRET is not set"
14
+ read -p "Enter your QUIZ_SECRET (or press Enter to generate one): " secret
15
+ if [ -z "$secret" ]; then
16
+ # Generate a random secret
17
+ secret=$(python3 -c "import secrets; print(secrets.token_urlsafe(32))" 2>/dev/null || openssl rand -base64 24)
18
+ echo "Generated secret: $secret"
19
+ fi
20
+ export QUIZ_SECRET="$secret"
21
+ echo "✓ QUIZ_SECRET set for this session"
22
+ fi
23
+
24
+ if [ -n "$OPENAI_API_KEY" ]; then
25
+ echo "✓ OPENAI_API_KEY is already set"
26
+ else
27
+ echo "OPENAI_API_KEY is not set (optional)"
28
+ read -p "Enter your OPENAI_API_KEY (or press Enter to skip): " key
29
+ if [ -n "$key" ]; then
30
+ export OPENAI_API_KEY="$key"
31
+ echo "✓ OPENAI_API_KEY set for this session"
32
+ fi
33
+ fi
34
+
35
+ echo ""
36
+ echo "Environment variables configured!"
37
+ echo ""
38
+ echo "To make these permanent, add to ~/.bashrc or ~/.zshrc:"
39
+ echo "export QUIZ_SECRET=\"$QUIZ_SECRET\""
40
+ if [ -n "$OPENAI_API_KEY" ]; then
41
+ echo "export OPENAI_API_KEY=\"$OPENAI_API_KEY\""
42
+ fi
43
+ echo ""
44
+ echo "To start the server, run:"
45
+ echo "python -m app.main"
46
+
solver.cpython-311.pyc ADDED
Binary file (27.3 kB). View file
 
solver.py ADDED
@@ -0,0 +1,593 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Quiz solver module - main logic for solving quizzes.
3
+ """
4
+ import asyncio
5
+ import json
6
+ import logging
7
+ import re
8
+ from typing import Optional, Dict, Any, List
9
+ import requests
10
+ from bs4 import BeautifulSoup
11
+ import pandas as pd
12
+ import io
13
+ import base64
14
+
15
+ from app.browser import get_browser, cleanup_browser
16
+ from app.llm import ask_gpt, parse_question_with_llm, solve_with_llm, initialize_llm
17
+ from app.utils import extract_submit_url, clean_text, extract_json_from_text, is_valid_url
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Initialize LLM on module load
22
+ initialize_llm()
23
+
24
+
25
+ class QuizSolver:
26
+ """Main quiz solver class."""
27
+
28
+ def __init__(self):
29
+ self.browser = None
30
+ self.max_recursion = 10
31
+ self.current_recursion = 0
32
+
33
+ async def solve_quiz(self, url: str, email: str, secret: str) -> Dict[str, Any]:
34
+ """
35
+ Main entry point for solving a quiz.
36
+
37
+ Args:
38
+ url: Quiz page URL
39
+ email: User email
40
+ secret: Secret key
41
+
42
+ Returns:
43
+ Final response from quiz system
44
+ """
45
+ self.current_recursion = 0
46
+ self.browser = await get_browser()
47
+
48
+ try:
49
+ return await self._solve_recursive(url, email, secret)
50
+ finally:
51
+ # Don't close browser here as it might be reused
52
+ pass
53
+
54
+ async def _solve_recursive(self, url: str, email: str, secret: str) -> Dict[str, Any]:
55
+ """
56
+ Recursively solve quizzes.
57
+
58
+ Args:
59
+ url: Current quiz URL
60
+ email: User email
61
+ secret: Secret key
62
+
63
+ Returns:
64
+ Response from quiz system
65
+ """
66
+ if self.current_recursion >= self.max_recursion:
67
+ logger.error("Maximum recursion depth reached")
68
+ return {"error": "Maximum recursion depth reached"}
69
+
70
+ self.current_recursion += 1
71
+ logger.info(f"Solving quiz {self.current_recursion}: {url}")
72
+
73
+ try:
74
+ # Load the quiz page
75
+ page_content = await self.browser.load_page(url, wait_time=3)
76
+
77
+ # Extract submit URL
78
+ submit_url = extract_submit_url(page_content['text'], url)
79
+ if not submit_url:
80
+ # Try from HTML
81
+ soup = BeautifulSoup(page_content['html'], 'html.parser')
82
+ submit_url = extract_submit_url(soup.get_text(), url)
83
+
84
+ if not submit_url:
85
+ logger.error("Could not find submit URL")
86
+ return {"error": "Submit URL not found"}
87
+
88
+ # Extract question and solve
89
+ question_text = self._extract_question(page_content)
90
+ logger.info(f"Question extracted: {question_text[:200]}...")
91
+
92
+ # Solve the question
93
+ answer = await self._solve_question(question_text, page_content)
94
+
95
+ # Ensure answer is in the correct format (string or simple JSON-serializable)
96
+ answer = self._normalize_answer(answer)
97
+ logger.info(f"Answer computed: {str(answer)[:200]}...")
98
+
99
+ # Submit answer
100
+ response = await self._submit_answer(
101
+ submit_url, email, secret, url, answer
102
+ )
103
+
104
+ # Check if there's a next quiz
105
+ if isinstance(response, dict) and 'url' in response:
106
+ next_url = response['url']
107
+ if next_url and next_url != url and is_valid_url(next_url):
108
+ logger.info(f"Next quiz found: {next_url}")
109
+ # Recursively solve next quiz
110
+ next_response = await self._solve_recursive(next_url, email, secret)
111
+ return next_response
112
+
113
+ return response
114
+
115
+ except Exception as e:
116
+ logger.error(f"Error solving quiz: {e}", exc_info=True)
117
+ return {"error": str(e)}
118
+
119
+ def _extract_question(self, page_content: Dict[str, Any]) -> str:
120
+ """
121
+ Extract question text from page content.
122
+
123
+ Args:
124
+ page_content: Page content dictionary
125
+
126
+ Returns:
127
+ Question text
128
+ """
129
+ text = page_content.get('all_text', page_content.get('text', ''))
130
+
131
+ # Try to find question markers
132
+ question_patterns = [
133
+ r'[Qq]uestion[:\s]+(.*?)(?:\n\n|\n[A-Z]|$)',
134
+ r'[Pp]roblem[:\s]+(.*?)(?:\n\n|\n[A-Z]|$)',
135
+ r'[Tt]ask[:\s]+(.*?)(?:\n\n|\n[A-Z]|$)',
136
+ ]
137
+
138
+ for pattern in question_patterns:
139
+ match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
140
+ if match:
141
+ return clean_text(match.group(1))
142
+
143
+ # If no pattern matches, return first substantial paragraph
144
+ paragraphs = [p.strip() for p in text.split('\n\n') if len(p.strip()) > 50]
145
+ if paragraphs:
146
+ return paragraphs[0]
147
+
148
+ return clean_text(text[:1000]) # Return first 1000 chars
149
+
150
+ async def _solve_question(self, question: str, page_content: Dict[str, Any]) -> Any:
151
+ """
152
+ Solve a quiz question using various strategies.
153
+
154
+ Args:
155
+ question: Question text
156
+ page_content: Full page content
157
+
158
+ Returns:
159
+ Answer (can be dict, list, string, number, etc.)
160
+ """
161
+ logger.info("Analyzing question type...")
162
+
163
+ # Try to parse question with LLM first
164
+ parsed = await parse_question_with_llm(question, page_content.get('text', ''))
165
+
166
+ # Extract data from page
167
+ available_data = self._extract_data_from_page(page_content)
168
+
169
+ # Strategy 1: Check if answer is already in the page
170
+ answer_in_page = self._find_answer_in_page(page_content, question)
171
+ if answer_in_page:
172
+ logger.info("Answer found in page content")
173
+ return answer_in_page
174
+
175
+ # Strategy 2: Check for data files/links to download
176
+ data_files = self._find_data_files(page_content)
177
+ if data_files:
178
+ logger.info(f"Found data files: {data_files}")
179
+ processed_data = await self._process_data_files(data_files)
180
+ if processed_data:
181
+ answer = await self._solve_with_data(question, processed_data)
182
+ if answer:
183
+ return answer
184
+
185
+ # Strategy 3: Use LLM to solve
186
+ logger.info("Attempting to solve with LLM...")
187
+ llm_answer = await solve_with_llm(question, available_data)
188
+ if llm_answer:
189
+ # Try to parse as JSON if it looks like JSON
190
+ json_answer = extract_json_from_text(llm_answer)
191
+ if json_answer:
192
+ return json_answer
193
+ return llm_answer
194
+
195
+ # Strategy 4: Fallback - try to extract a simple answer from the question
196
+ # Many quiz pages have the answer in the question itself
197
+ simple_answer = self._extract_simple_answer(question, page_content)
198
+ if simple_answer:
199
+ logger.info("Extracted simple answer from question")
200
+ return simple_answer
201
+
202
+ # Strategy 5: Last resort - return a default answer
203
+ logger.warning("Could not solve question, using default answer")
204
+ return "answer"
205
+
206
+ def _extract_data_from_page(self, page_content: Dict[str, Any]) -> Dict[str, Any]:
207
+ """
208
+ Extract structured data from page.
209
+
210
+ Args:
211
+ page_content: Page content dictionary
212
+
213
+ Returns:
214
+ Dictionary of extracted data
215
+ """
216
+ data = {
217
+ 'text': page_content.get('text', ''),
218
+ 'html': page_content.get('html', ''),
219
+ 'links': page_content.get('links', []),
220
+ 'images': page_content.get('images', []),
221
+ }
222
+
223
+ # Try to extract tables
224
+ try:
225
+ soup = BeautifulSoup(page_content.get('html', ''), 'html.parser')
226
+ tables = soup.find_all('table')
227
+ if tables:
228
+ data['tables'] = []
229
+ for table in tables:
230
+ try:
231
+ df = pd.read_html(str(table))[0]
232
+ data['tables'].append(df.to_dict('records'))
233
+ except:
234
+ pass
235
+ except Exception as e:
236
+ logger.warning(f"Error extracting tables: {e}")
237
+
238
+ # Try to extract JSON from page
239
+ json_data = extract_json_from_text(page_content.get('text', ''))
240
+ if json_data:
241
+ data['json'] = json_data
242
+
243
+ return data
244
+
245
+ def _find_answer_in_page(self, page_content: Dict[str, Any], question: str) -> Optional[Any]:
246
+ """
247
+ Check if answer is already present in page content.
248
+
249
+ Args:
250
+ page_content: Page content
251
+ question: Question text
252
+
253
+ Returns:
254
+ Answer if found, None otherwise
255
+ """
256
+ text = page_content.get('all_text', page_content.get('text', ''))
257
+
258
+ # Look for answer patterns
259
+ answer_patterns = [
260
+ r'[Aa]nswer[:\s]+(.*?)(?:\n\n|$)',
261
+ r'[Ss]olution[:\s]+(.*?)(?:\n\n|$)',
262
+ r'[Rr]esult[:\s]+(.*?)(?:\n\n|$)',
263
+ ]
264
+
265
+ for pattern in answer_patterns:
266
+ match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
267
+ if match:
268
+ answer_text = clean_text(match.group(1))
269
+ # Try to parse as JSON
270
+ json_answer = extract_json_from_text(answer_text)
271
+ if json_answer:
272
+ return json_answer
273
+ return answer_text
274
+
275
+ return None
276
+
277
+ def _find_data_files(self, page_content: Dict[str, Any]) -> List[str]:
278
+ """
279
+ Find data files (CSV, JSON, PDF, etc.) linked in the page.
280
+
281
+ Args:
282
+ page_content: Page content
283
+
284
+ Returns:
285
+ List of file URLs
286
+ """
287
+ files = []
288
+
289
+ # Check links
290
+ for link in page_content.get('links', []):
291
+ href = link.get('href', '')
292
+ if any(href.lower().endswith(ext) for ext in ['.csv', '.json', '.pdf', '.xlsx', '.txt']):
293
+ files.append(href)
294
+
295
+ # Check text for file URLs
296
+ text = page_content.get('text', '')
297
+ file_pattern = r'https?://[^\s<>"\'\)]+\.(csv|json|pdf|xlsx|txt)'
298
+ matches = re.findall(file_pattern, text, re.IGNORECASE)
299
+ files.extend([m[0] for m in matches if m[0] not in files])
300
+
301
+ return files
302
+
303
+ async def _process_data_files(self, file_urls: List[str]) -> Dict[str, Any]:
304
+ """
305
+ Download and process data files.
306
+
307
+ Args:
308
+ file_urls: List of file URLs
309
+
310
+ Returns:
311
+ Dictionary of processed data
312
+ """
313
+ processed = {}
314
+
315
+ for url in file_urls:
316
+ try:
317
+ logger.info(f"Downloading file: {url}")
318
+ response = requests.get(url, timeout=30)
319
+ response.raise_for_status()
320
+
321
+ content_type = response.headers.get('content-type', '').lower()
322
+ filename = url.split('/')[-1]
323
+
324
+ if 'csv' in content_type or filename.endswith('.csv'):
325
+ df = pd.read_csv(io.StringIO(response.text))
326
+ processed[filename] = df.to_dict('records')
327
+
328
+ elif 'json' in content_type or filename.endswith('.json'):
329
+ processed[filename] = response.json()
330
+
331
+ elif 'pdf' in content_type or filename.endswith('.pdf'):
332
+ # PDF processing - try pdfplumber first, then PyPDF2
333
+ text = None
334
+
335
+ # Try pdfplumber
336
+ try:
337
+ import pdfplumber
338
+ with pdfplumber.open(io.BytesIO(response.content)) as pdf:
339
+ text = ""
340
+ for page in pdf.pages:
341
+ page_text = page.extract_text()
342
+ if page_text:
343
+ text += page_text + "\n"
344
+ if text:
345
+ processed[filename] = text.strip()
346
+ except ImportError:
347
+ logger.debug("pdfplumber not available")
348
+ except Exception as e:
349
+ logger.warning(f"Error reading PDF with pdfplumber {filename}: {e}")
350
+
351
+ # Fallback to PyPDF2
352
+ if not text or filename not in processed:
353
+ try:
354
+ import PyPDF2
355
+ pdf_file = io.BytesIO(response.content)
356
+ pdf_reader = PyPDF2.PdfReader(pdf_file)
357
+ text = ""
358
+ for page in pdf_reader.pages:
359
+ page_text = page.extract_text()
360
+ if page_text:
361
+ text += page_text + "\n"
362
+ if text:
363
+ processed[filename] = text.strip()
364
+ except ImportError:
365
+ logger.warning("Neither pdfplumber nor PyPDF2 available for PDF processing")
366
+ except Exception as e:
367
+ logger.warning(f"Error reading PDF with PyPDF2 {filename}: {e}")
368
+
369
+ elif filename.endswith('.txt'):
370
+ processed[filename] = response.text
371
+
372
+ except Exception as e:
373
+ logger.error(f"Error processing file {url}: {e}")
374
+ continue
375
+
376
+ return processed
377
+
378
+ def _normalize_answer(self, answer: Any) -> Any:
379
+ """
380
+ Normalize answer to ensure it's JSON-serializable and in correct format.
381
+
382
+ Args:
383
+ answer: Raw answer (can be dict, list, string, etc.)
384
+
385
+ Returns:
386
+ Normalized answer (preferably string or simple JSON)
387
+ """
388
+ if answer is None:
389
+ return "answer"
390
+
391
+ # If it's a dict with question/analysis, extract a simple answer
392
+ if isinstance(answer, dict):
393
+ # If it contains an 'answer' key, use that
394
+ if 'answer' in answer:
395
+ return self._normalize_answer(answer['answer'])
396
+ # If it's an analysis dict, try to extract something useful
397
+ if 'question' in answer and len(answer) > 1:
398
+ # Return a simple string instead of the whole dict
399
+ return "answer"
400
+ # If it's a simple dict, convert to JSON string
401
+ if len(answer) <= 3:
402
+ try:
403
+ return json.dumps(answer)
404
+ except:
405
+ return str(answer)
406
+ # Complex dict - return as JSON string
407
+ try:
408
+ return json.dumps(answer)
409
+ except:
410
+ return str(answer)
411
+
412
+ # If it's a list, convert to JSON string if small, otherwise string
413
+ if isinstance(answer, list):
414
+ if len(answer) <= 10:
415
+ try:
416
+ return json.dumps(answer)
417
+ except:
418
+ return str(answer)
419
+ return str(answer)
420
+
421
+ # For strings, return as-is (but clean up)
422
+ if isinstance(answer, str):
423
+ # Remove excessive whitespace
424
+ answer = ' '.join(answer.split())
425
+ # If it's very long, truncate
426
+ if len(answer) > 1000:
427
+ answer = answer[:1000] + "..."
428
+ return answer
429
+
430
+ # For other types, convert to string
431
+ return str(answer)
432
+
433
+ def _extract_simple_answer(self, question: str, page_content: Dict[str, Any]) -> Optional[str]:
434
+ """
435
+ Try to extract a simple answer from the question or page.
436
+
437
+ Args:
438
+ question: Question text
439
+ page_content: Page content
440
+
441
+ Returns:
442
+ Simple answer string or None
443
+ """
444
+ text = page_content.get('all_text', page_content.get('text', ''))
445
+ combined = question + "\n\n" + text
446
+
447
+ # Check if question says "anything" or similar - very common in demo quizzes
448
+ if re.search(r'"answer"\s*:\s*"anything\s+you\s+want"', combined, re.IGNORECASE):
449
+ return "answer"
450
+ if re.search(r'"answer"\s*:\s*"anything"', combined, re.IGNORECASE):
451
+ return "answer"
452
+ if re.search(r'anything\s+you\s+want|any\s+value|any\s+string|any\s+text|anything', question, re.IGNORECASE):
453
+ return "answer"
454
+
455
+ # Look for patterns like "answer: X" or "the answer is X"
456
+ patterns = [
457
+ r'"answer"\s*:\s*"([^"]+)"', # JSON format: "answer": "value"
458
+ r'[Aa]nswer[:\s]+["\']?([^"\'\n]+)["\']?',
459
+ r'[Tt]he\s+[Aa]nswer\s+[Ii]s[:\s]+["\']?([^"\'\n]+)["\']?',
460
+ r'[Yy]our\s+[Aa]nswer[:\s]+["\']?([^"\'\n]+)["\']?',
461
+ ]
462
+
463
+ for pattern in patterns:
464
+ match = re.search(pattern, combined, re.IGNORECASE)
465
+ if match:
466
+ answer = match.group(1).strip()
467
+ # Skip if it's a placeholder or instruction
468
+ if answer and len(answer) < 200 and answer.lower() not in ['your email', 'your secret', 'anything you want', 'anything']:
469
+ return answer
470
+
471
+ return None
472
+
473
+ async def _solve_with_data(self, question: str, data: Dict[str, Any]) -> Optional[Any]:
474
+ """
475
+ Solve question using processed data.
476
+
477
+ Args:
478
+ question: Question text
479
+ data: Processed data dictionary
480
+
481
+ Returns:
482
+ Answer or None
483
+ """
484
+ # Use LLM to solve with data
485
+ prompt = f"""Solve this question using the provided data:
486
+
487
+ Question: {question}
488
+
489
+ Data:
490
+ {json.dumps(data, indent=2, default=str)}
491
+
492
+ Provide the answer. If JSON format is required, return valid JSON.
493
+ """
494
+
495
+ answer = await ask_gpt(prompt, max_tokens=3000)
496
+ if answer:
497
+ json_answer = extract_json_from_text(answer)
498
+ if json_answer:
499
+ return json_answer
500
+ return answer
501
+
502
+ return None
503
+
504
+ async def _submit_answer(self, submit_url: str, email: str, secret: str,
505
+ quiz_url: str, answer: Any) -> Dict[str, Any]:
506
+ """
507
+ Submit answer to the quiz system.
508
+
509
+ Args:
510
+ submit_url: URL to submit answer to
511
+ email: User email
512
+ secret: Secret key
513
+ quiz_url: Original quiz URL
514
+ answer: Computed answer
515
+
516
+ Returns:
517
+ Response from submission endpoint
518
+ """
519
+ # Ensure answer is JSON-serializable
520
+ try:
521
+ # Try to serialize answer to check if it's valid JSON
522
+ json.dumps(answer)
523
+ except (TypeError, ValueError) as e:
524
+ logger.warning(f"Answer is not JSON-serializable, converting to string: {e}")
525
+ # Convert complex objects to string representation
526
+ if isinstance(answer, (dict, list)):
527
+ answer = json.dumps(answer)
528
+ else:
529
+ answer = str(answer)
530
+
531
+ payload = {
532
+ "email": email,
533
+ "secret": secret,
534
+ "url": quiz_url,
535
+ "answer": answer
536
+ }
537
+
538
+ try:
539
+ logger.info(f"Submitting answer to: {submit_url}")
540
+ logger.debug(f"Payload: {json.dumps(payload, indent=2, default=str)}")
541
+
542
+ response = requests.post(
543
+ submit_url,
544
+ json=payload,
545
+ headers={'Content-Type': 'application/json'},
546
+ timeout=60
547
+ )
548
+
549
+ # Log response details
550
+ logger.info(f"Response status: {response.status_code}")
551
+ logger.debug(f"Response headers: {dict(response.headers)}")
552
+
553
+ response.raise_for_status()
554
+
555
+ try:
556
+ result = response.json()
557
+ logger.info(f"Submission successful: {result}")
558
+ return result
559
+ except json.JSONDecodeError:
560
+ logger.warning(f"Response is not JSON, returning text: {response.text[:500]}")
561
+ return {"response": response.text, "status_code": response.status_code}
562
+
563
+ except requests.exceptions.HTTPError as e:
564
+ logger.error(f"HTTP error submitting answer: {e}")
565
+ if hasattr(e, 'response') and e.response is not None:
566
+ try:
567
+ error_response = e.response.json()
568
+ logger.error(f"Error response: {error_response}")
569
+ return error_response
570
+ except:
571
+ logger.error(f"Error response text: {e.response.text[:500]}")
572
+ return {"error": e.response.text, "status_code": e.response.status_code}
573
+ return {"error": str(e)}
574
+ except requests.exceptions.RequestException as e:
575
+ logger.error(f"Error submitting answer: {e}", exc_info=True)
576
+ return {"error": str(e)}
577
+
578
+
579
+ async def solve_quiz(url: str, email: str, secret: str) -> Dict[str, Any]:
580
+ """
581
+ Convenience function to solve a quiz.
582
+
583
+ Args:
584
+ url: Quiz page URL
585
+ email: User email
586
+ secret: Secret key
587
+
588
+ Returns:
589
+ Final response from quiz system
590
+ """
591
+ solver = QuizSolver()
592
+ return await solver.solve_quiz(url, email, secret)
593
+
test_api.ps1 ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PowerShell script to test the API correctly
2
+ # Usage: .\test_api.ps1
3
+
4
+ # Set variables if not already set
5
+ if (-not $env:QUIZ_SECRET) {
6
+ $env:QUIZ_SECRET = "EasyQuiz"
7
+ }
8
+
9
+ # Create the request body as a PowerShell object (not a string)
10
+ $body = @{
11
+ email = "24f2005753@ds.study.iitm.ac.in"
12
+ secret = "EasyQuiz"
13
+ url = "https://tds-llm-analysis.s-anand.net/demo"
14
+ } | ConvertTo-Json
15
+
16
+ Write-Host "Sending request to /solve endpoint..." -ForegroundColor Cyan
17
+ Write-Host "Body: $body" -ForegroundColor Gray
18
+ Write-Host ""
19
+
20
+ try {
21
+ $response = Invoke-RestMethod `
22
+ -Uri "http://127.0.0.1:8000/solve" `
23
+ -Method POST `
24
+ -ContentType "application/json" `
25
+ -Body $body
26
+
27
+ Write-Host "Response:" -ForegroundColor Green
28
+ $response | ConvertTo-Json -Depth 10
29
+ }
30
+ catch {
31
+ Write-Host "Error occurred:" -ForegroundColor Red
32
+ Write-Host $_.Exception.Message -ForegroundColor Red
33
+
34
+ if ($_.ErrorDetails.Message) {
35
+ Write-Host "Details:" -ForegroundColor Yellow
36
+ Write-Host $_.ErrorDetails.Message -ForegroundColor Yellow
37
+ }
38
+
39
+ if ($_.Response) {
40
+ $reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream())
41
+ $responseBody = $reader.ReadToEnd()
42
+ Write-Host "Response Body:" -ForegroundColor Yellow
43
+ Write-Host $responseBody -ForegroundColor Yellow
44
+ }
45
+ }
46
+
47
+
test_env.json.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test script that outputs environment variables as JSON.
3
+ Run: python test_env.json.py
4
+ """
5
+ import os
6
+ import json
7
+
8
+ def get_env_json():
9
+ """Get environment variables status as JSON."""
10
+ quiz_secret = os.getenv("QUIZ_SECRET")
11
+ openai_key = os.getenv("OPENAI_API_KEY")
12
+ port = os.getenv("PORT", "8000")
13
+ openrouter_key = os.getenv("OPENROUTER_API_KEY")
14
+
15
+ result = {
16
+ "status": "ok",
17
+ "variables": {
18
+ "QUIZ_SECRET": {
19
+ "set": quiz_secret is not None,
20
+ "length": len(quiz_secret) if quiz_secret else 0,
21
+ "preview": f"{quiz_secret[:4]}...{quiz_secret[-4:]}" if quiz_secret and len(quiz_secret) > 8 else "***" if quiz_secret else None
22
+ },
23
+ "OPENAI_API_KEY": {
24
+ "set": openai_key is not None,
25
+ "length": len(openai_key) if openai_key else 0,
26
+ "preview": f"{openai_key[:7]}...{openai_key[-4:]}" if openai_key and len(openai_key) > 11 else "***" if openai_key else None,
27
+ "valid_format": openai_key.startswith("sk-") if openai_key else False
28
+ },
29
+ "OPENROUTER_API_KEY": {
30
+ "set": openrouter_key is not None,
31
+ "length": len(openrouter_key) if openrouter_key else 0,
32
+ "preview": f"{openrouter_key[:7]}...{openrouter_key[-4:]}" if openrouter_key and len(openrouter_key) > 11 else "***" if openrouter_key else None,
33
+ "valid_format": openrouter_key.startswith("sk-or-") if openrouter_key else False
34
+ },
35
+ "PORT": {
36
+ "set": True,
37
+ "value": port
38
+ }
39
+ },
40
+ "ready": quiz_secret is not None,
41
+ "llm_enabled": any([openai_key, openrouter_key]),
42
+ "message": "Ready to run" if quiz_secret else "QUIZ_SECRET is required"
43
+ }
44
+
45
+ return result
46
+
47
+ if __name__ == "__main__":
48
+ result = get_env_json()
49
+ print(json.dumps(result, indent=2))
50
+
test_server.ps1 ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Test script to check if server is running and test endpoints
2
+ # Usage: .\test_server.ps1
3
+
4
+ Write-Host "Testing IITM LLM Quiz Solver API" -ForegroundColor Cyan
5
+ Write-Host "=================================" -ForegroundColor Cyan
6
+ Write-Host ""
7
+
8
+ # Test 1: Health check
9
+ Write-Host "1. Testing /health endpoint..." -ForegroundColor Yellow
10
+ try {
11
+ $health = Invoke-RestMethod -Uri "http://127.0.0.1:8000/health" -Method GET -ErrorAction Stop
12
+ Write-Host " ✓ Server is running!" -ForegroundColor Green
13
+ Write-Host " Response: $($health | ConvertTo-Json)" -ForegroundColor Gray
14
+ } catch {
15
+ Write-Host " ✗ Server is NOT running or not accessible" -ForegroundColor Red
16
+ Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Red
17
+ Write-Host ""
18
+ Write-Host " Please start the server first:" -ForegroundColor Yellow
19
+ Write-Host " python -m app.main" -ForegroundColor Cyan
20
+ exit 1
21
+ }
22
+
23
+ Write-Host ""
24
+
25
+ # Test 2: Root endpoint
26
+ Write-Host "2. Testing / endpoint..." -ForegroundColor Yellow
27
+ try {
28
+ $root = Invoke-RestMethod -Uri "http://127.0.0.1:8000/" -Method GET -ErrorAction Stop
29
+ Write-Host " ✓ Root endpoint works!" -ForegroundColor Green
30
+ Write-Host " Available endpoints:" -ForegroundColor Gray
31
+ $root.endpoints.PSObject.Properties | ForEach-Object {
32
+ Write-Host " $($_.Name): $($_.Value)" -ForegroundColor Gray
33
+ }
34
+ } catch {
35
+ Write-Host " ✗ Root endpoint failed: $($_.Exception.Message)" -ForegroundColor Red
36
+ }
37
+
38
+ Write-Host ""
39
+
40
+ # Test 3: Env check
41
+ Write-Host "3. Testing /env-check endpoint..." -ForegroundColor Yellow
42
+ try {
43
+ $envCheck = Invoke-RestMethod -Uri "http://127.0.0.1:8000/env-check" -Method GET -ErrorAction Stop
44
+ Write-Host " ✓ Environment check works!" -ForegroundColor Green
45
+ Write-Host " QUIZ_SECRET: $($envCheck.variables.QUIZ_SECRET.set)" -ForegroundColor Gray
46
+ Write-Host " OPENAI_API_KEY: $($envCheck.variables.OPENAI_API_KEY.set)" -ForegroundColor Gray
47
+ Write-Host " Ready: $($envCheck.ready)" -ForegroundColor Gray
48
+ } catch {
49
+ Write-Host " ✗ Env check failed: $($_.Exception.Message)" -ForegroundColor Red
50
+ }
51
+
52
+ Write-Host ""
53
+
54
+ # Test 4: Demo endpoint (if server is running)
55
+ Write-Host "4. Testing /demo endpoint..." -ForegroundColor Yellow
56
+ Write-Host " (This will make an actual request)" -ForegroundColor Gray
57
+
58
+ # Set variables if not set
59
+ if (-not $env:QUIZ_SECRET) {
60
+ $env:QUIZ_SECRET = "EasyQuiz"
61
+ }
62
+
63
+ $body = @{
64
+ email = "24f2005753@ds.study.iitm.ac.in"
65
+ secret = "EasyQuiz"
66
+ url = "https://tds-llm-analysis.s-anand.net/demo"
67
+ } | ConvertTo-Json
68
+
69
+ try {
70
+ Write-Host " Sending request..." -ForegroundColor Gray
71
+ $response = Invoke-RestMethod `
72
+ -Uri "http://127.0.0.1:8000/demo" `
73
+ -Method POST `
74
+ -ContentType "application/json" `
75
+ -Body $body `
76
+ -ErrorAction Stop
77
+
78
+ Write-Host " ✓ Demo endpoint works!" -ForegroundColor Green
79
+ Write-Host " Response:" -ForegroundColor Gray
80
+ $response | ConvertTo-Json -Depth 10 | Write-Host
81
+ } catch {
82
+ Write-Host " ✗ Demo endpoint failed" -ForegroundColor Red
83
+ Write-Host " Status: $($_.Exception.Response.StatusCode.value__)" -ForegroundColor Yellow
84
+ if ($_.ErrorDetails.Message) {
85
+ Write-Host " Error: $($_.ErrorDetails.Message)" -ForegroundColor Yellow
86
+ } else {
87
+ Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Yellow
88
+ }
89
+ }
90
+
91
+ Write-Host ""
92
+ Write-Host "=================================" -ForegroundColor Cyan
93
+ Write-Host "Test complete!" -ForegroundColor Cyan
94
+
utils.cpython-311.pyc ADDED
Binary file (6.5 kB). View file
 
utils.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Utility functions for the IITM LLM Quiz Solver.
3
+ """
4
+ import re
5
+ import json
6
+ import logging
7
+ from typing import Optional, Dict, Any
8
+ from urllib.parse import urlparse, urljoin
9
+
10
+ # Configure logging
11
+ logging.basicConfig(
12
+ level=logging.INFO,
13
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
14
+ )
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def extract_submit_url(text: str, base_url: str) -> Optional[str]:
19
+ """
20
+ Extract submit URL from page text.
21
+
22
+ Looks for patterns like:
23
+ - "Submit your answer to: https://example.com/submit"
24
+ - "Submit to: https://example.com/submit"
25
+ - "URL: https://example.com/submit"
26
+
27
+ Args:
28
+ text: The page text content
29
+ base_url: Base URL for relative URL resolution
30
+
31
+ Returns:
32
+ Extracted submit URL or None
33
+ """
34
+ # Common patterns for submit URLs
35
+ patterns = [
36
+ r'[Ss]ubmit\s+(?:your\s+)?(?:answer\s+)?(?:to|at|via):\s*(https?://[^\s<>"\'\)]+)',
37
+ r'[Ss]ubmit\s+[Tt]o:\s*(https?://[^\s<>"\'\)]+)',
38
+ r'[Uu][Rr][Ll]:\s*(https?://[^\s<>"\'\)]+)',
39
+ r'[Pp]ost\s+(?:to|at):\s*(https?://[^\s<>"\'\)]+)',
40
+ r'[Ss]end\s+(?:to|at):\s*(https?://[^\s<>"\'\)]+)',
41
+ r'(https?://[^\s<>"\'\)]*submit[^\s<>"\'\)]*)',
42
+ r'(https?://[^\s<>"\'\)]*answer[^\s<>"\'\)]*)',
43
+ ]
44
+
45
+ for pattern in patterns:
46
+ matches = re.findall(pattern, text, re.IGNORECASE)
47
+ if matches:
48
+ url = matches[0].strip().rstrip('.,;:!?)}]{["\'')
49
+ # Validate URL
50
+ try:
51
+ parsed = urlparse(url)
52
+ if parsed.scheme and parsed.netloc:
53
+ logger.info(f"Found submit URL: {url}")
54
+ return url
55
+ except Exception as e:
56
+ logger.warning(f"Invalid URL pattern found: {url}, error: {e}")
57
+ continue
58
+
59
+ # Try to find any URL that might be a submit endpoint
60
+ url_pattern = r'https?://[^\s<>"\'\)]+'
61
+ all_urls = re.findall(url_pattern, text)
62
+ for url in all_urls:
63
+ url_lower = url.lower()
64
+ if 'submit' in url_lower or 'answer' in url_lower:
65
+ try:
66
+ parsed = urlparse(url)
67
+ if parsed.scheme and parsed.netloc:
68
+ logger.info(f"Found potential submit URL: {url}")
69
+ return url
70
+ except:
71
+ continue
72
+
73
+ # Try to find relative submit links (e.g. href="/submit")
74
+ rel_patterns = [
75
+ r'href=["\\\'](/[^"\\\']*submit[^"\\\']*)["\\\']',
76
+ r'(/[^\\s"<>\']*submit[^\\s"<>\']*)',
77
+ ]
78
+ for pattern in rel_patterns:
79
+ matches = re.findall(pattern, text, re.IGNORECASE)
80
+ if matches:
81
+ candidate = matches[0].strip().rstrip('.,;:!?)}]{["\'')
82
+ joined = urljoin(base_url, candidate)
83
+ logger.info(f"Found relative submit URL: {joined}")
84
+ return joined
85
+
86
+ logger.warning("No submit URL found in page text")
87
+ return None
88
+
89
+
90
+ def validate_secret(secret: str, expected_secret: str) -> bool:
91
+ """
92
+ Validate the secret key.
93
+
94
+ Args:
95
+ secret: Provided secret
96
+ expected_secret: Expected secret from environment
97
+
98
+ Returns:
99
+ True if valid, False otherwise
100
+ """
101
+ return secret == expected_secret
102
+
103
+
104
+ def clean_text(text: str) -> str:
105
+ """
106
+ Clean and normalize text content.
107
+
108
+ Args:
109
+ text: Raw text content
110
+
111
+ Returns:
112
+ Cleaned text
113
+ """
114
+ if not text:
115
+ return ""
116
+
117
+ # Remove excessive whitespace
118
+ text = re.sub(r'\s+', ' ', text)
119
+ # Remove leading/trailing whitespace
120
+ text = text.strip()
121
+
122
+ return text
123
+
124
+
125
+ def extract_json_from_text(text: str) -> Optional[Dict[str, Any]]:
126
+ """
127
+ Try to extract JSON objects from text.
128
+
129
+ Args:
130
+ text: Text that may contain JSON
131
+
132
+ Returns:
133
+ Parsed JSON dict or None
134
+ """
135
+ # Try to find JSON blocks
136
+ json_pattern = r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}'
137
+ matches = re.findall(json_pattern, text, re.DOTALL)
138
+
139
+ for match in matches:
140
+ try:
141
+ return json.loads(match)
142
+ except json.JSONDecodeError:
143
+ continue
144
+
145
+ return None
146
+
147
+
148
+ def is_valid_url(url: str) -> bool:
149
+ """
150
+ Validate if a string is a valid URL.
151
+
152
+ Args:
153
+ url: URL string to validate
154
+
155
+ Returns:
156
+ True if valid URL, False otherwise
157
+ """
158
+ try:
159
+ result = urlparse(url)
160
+ return all([result.scheme, result.netloc])
161
+ except Exception:
162
+ return False
163
+
164
+
165
+ def sanitize_filename(filename: str) -> str:
166
+ """
167
+ Sanitize a filename by removing invalid characters.
168
+
169
+ Args:
170
+ filename: Original filename
171
+
172
+ Returns:
173
+ Sanitized filename
174
+ """
175
+ # Remove invalid characters
176
+ filename = re.sub(r'[<>:"/\\|?*]', '_', filename)
177
+ # Remove leading/trailing dots and spaces
178
+ filename = filename.strip('. ')
179
+ return filename
180
+