Upload 9 files
Browse files- app/main.py +5 -1
- app/solver.py +43 -60
app/main.py
CHANGED
|
@@ -67,7 +67,11 @@ class QuizRequest(BaseModel):
|
|
| 67 |
@field_validator('url')
|
| 68 |
@classmethod
|
| 69 |
def validate_url(cls, v):
|
| 70 |
-
if not v
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
raise ValueError('Invalid URL format')
|
| 72 |
return v
|
| 73 |
|
|
|
|
| 67 |
@field_validator('url')
|
| 68 |
@classmethod
|
| 69 |
def validate_url(cls, v):
|
| 70 |
+
if not v:
|
| 71 |
+
raise ValueError('Invalid URL format')
|
| 72 |
+
# Strip whitespace (handles newlines, spaces from JSON formatting)
|
| 73 |
+
v = v.strip()
|
| 74 |
+
if not v.startswith(('http://', 'https://')):
|
| 75 |
raise ValueError('Invalid URL format')
|
| 76 |
return v
|
| 77 |
|
app/solver.py
CHANGED
|
@@ -191,13 +191,13 @@ class BrowserHelper:
|
|
| 191 |
try:
|
| 192 |
logger.info(f"Loading page: {url}")
|
| 193 |
await self.page.goto(url, wait_until='load', timeout=timeout)
|
| 194 |
-
await asyncio.sleep(
|
| 195 |
content = {
|
| 196 |
'url': url,
|
| 197 |
'title': await self.page.title(),
|
| 198 |
'text': await self.page.inner_text('body'),
|
| 199 |
'html': await self.page.content(),
|
| 200 |
-
|
| 201 |
}
|
| 202 |
try:
|
| 203 |
content['all_text'] = await self.page.evaluate("""() => {
|
|
@@ -295,18 +295,21 @@ async def ask_openrouter(prompt: str, model: Optional[str] = None, max_tokens: i
|
|
| 295 |
"X-Title": OPENROUTER_APP_NAME,
|
| 296 |
"Content-Type": "application/json",
|
| 297 |
}
|
| 298 |
-
system_content = system_prompt if system_prompt else "You are a helpful assistant that solves quiz questions accurately and concisely."
|
|
|
|
|
|
|
| 299 |
payload = {
|
| 300 |
"model": model,
|
| 301 |
"messages": [
|
| 302 |
{"role": "system", "content": system_content},
|
| 303 |
{"role": "user", "content": prompt}
|
| 304 |
],
|
| 305 |
-
"max_tokens":
|
| 306 |
-
"temperature": 0.
|
| 307 |
}
|
| 308 |
try:
|
| 309 |
-
|
|
|
|
| 310 |
response = await http_client.post(url, headers=headers, json=payload)
|
| 311 |
response.raise_for_status()
|
| 312 |
data = response.json()
|
|
@@ -328,26 +331,12 @@ async def test_prompt_with_custom_messages(system_prompt: str, user_prompt: str,
|
|
| 328 |
|
| 329 |
async def parse_question_with_llm(question_text: str, context: str = "") -> Optional[Dict[str, Any]]:
|
| 330 |
"""Use LLM to parse and understand a quiz question."""
|
| 331 |
-
|
|
|
|
| 332 |
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
Please identify:
|
| 338 |
-
1. What type of question is this? (scraping, calculation, API call, data analysis, etc.)
|
| 339 |
-
2. What data or resources are needed?
|
| 340 |
-
3. What is the expected answer format? (JSON, number, text, etc.)
|
| 341 |
-
|
| 342 |
-
Respond in JSON format:
|
| 343 |
-
{{
|
| 344 |
-
"type": "question_type",
|
| 345 |
-
"requirements": ["requirement1", "requirement2"],
|
| 346 |
-
"answer_format": "format_type",
|
| 347 |
-
"reasoning": "your reasoning"
|
| 348 |
-
}}
|
| 349 |
-
"""
|
| 350 |
-
response = await ask_gpt(prompt)
|
| 351 |
if not response:
|
| 352 |
return None
|
| 353 |
json_match = re.search(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', response, re.DOTALL)
|
|
@@ -389,22 +378,14 @@ async def solve_with_llm(question: str, available_data: Dict[str, Any], question
|
|
| 389 |
# Format available_data more clearly
|
| 390 |
data_str = json.dumps(available_data, indent=2) if available_data else "No additional data"
|
| 391 |
|
| 392 |
-
prompt
|
|
|
|
| 393 |
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
Available Data:
|
| 397 |
-
{data_str}
|
| 398 |
-
{email_instruction}
|
| 399 |
-
{audio_data}
|
| 400 |
-
{format_instructions}
|
| 401 |
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
If it's an audio transcription, return the spoken phrase with any codes or numbers EXACTLY as transcribed above.
|
| 406 |
-
"""
|
| 407 |
-
return await ask_gpt(prompt, max_tokens=3000)
|
| 408 |
|
| 409 |
async def ocr_image_with_llm(image_base64: str) -> Optional[str]:
|
| 410 |
"""Use OpenRouter vision model to extract text from an image."""
|
|
@@ -432,7 +413,8 @@ async def ocr_image_with_llm(image_base64: str) -> Optional[str]:
|
|
| 432 |
}],
|
| 433 |
"max_tokens": 1000
|
| 434 |
}
|
| 435 |
-
|
|
|
|
| 436 |
response = await http_client.post(url, headers=headers, json=payload)
|
| 437 |
response.raise_for_status()
|
| 438 |
data = response.json()
|
|
@@ -677,7 +659,7 @@ class MediaProcessor:
|
|
| 677 |
"""Download and transcribe audio from URL."""
|
| 678 |
try:
|
| 679 |
logger.info(f"Processing audio from URL: {audio_url}")
|
| 680 |
-
response = requests.get(audio_url, timeout=
|
| 681 |
response.raise_for_status()
|
| 682 |
audio_data = response.content
|
| 683 |
audio_base64 = base64.b64encode(audio_data).decode('utf-8')
|
|
@@ -696,7 +678,7 @@ class MediaProcessor:
|
|
| 696 |
if openai_key and OPENAI_AVAILABLE:
|
| 697 |
try:
|
| 698 |
client = OpenAI(api_key=openai_key)
|
| 699 |
-
response = requests.get(audio_url, timeout=
|
| 700 |
response.raise_for_status()
|
| 701 |
with tempfile.NamedTemporaryFile(suffix='.opus', delete=False) as tmp_file:
|
| 702 |
tmp_file.write(response.content)
|
|
@@ -719,7 +701,7 @@ class MediaProcessor:
|
|
| 719 |
"""Process video from URL - extract frames, transcribe audio, OCR text."""
|
| 720 |
try:
|
| 721 |
logger.info(f"Processing video from URL: {video_url}")
|
| 722 |
-
response = requests.get(video_url, timeout=
|
| 723 |
response.raise_for_status()
|
| 724 |
video_info = {
|
| 725 |
'url': video_url,
|
|
@@ -747,7 +729,7 @@ Provide a comprehensive description."""
|
|
| 747 |
"""Process image from URL - extract text using OCR."""
|
| 748 |
try:
|
| 749 |
logger.info(f"Processing image from URL: {image_url}")
|
| 750 |
-
response = requests.get(image_url, timeout=
|
| 751 |
response.raise_for_status()
|
| 752 |
image_data = response.content
|
| 753 |
image_base64 = base64.b64encode(image_data).decode('utf-8')
|
|
@@ -838,7 +820,7 @@ async def extract_image_color(image_url: str, base_url: str = '') -> Optional[st
|
|
| 838 |
if image_url.startswith('/') and base_url:
|
| 839 |
image_url = urljoin(base_url, image_url)
|
| 840 |
logger.info(f"Processing image for color extraction: {image_url}")
|
| 841 |
-
response = requests.get(image_url, timeout=
|
| 842 |
response.raise_for_status()
|
| 843 |
img = Image.open(io.BytesIO(response.content))
|
| 844 |
if img.mode != 'RGB':
|
|
@@ -859,7 +841,7 @@ async def convert_csv_to_json(csv_url: str, base_url: str = '', normalize: bool
|
|
| 859 |
if csv_url.startswith('/') and base_url:
|
| 860 |
csv_url = urljoin(base_url, csv_url)
|
| 861 |
logger.info(f"Converting CSV to JSON: {csv_url}")
|
| 862 |
-
response = requests.get(csv_url, timeout=
|
| 863 |
response.raise_for_status()
|
| 864 |
df = pd.read_csv(io.StringIO(response.text))
|
| 865 |
if normalize:
|
|
@@ -907,7 +889,7 @@ async def call_github_api(endpoint: str, token: Optional[str] = None) -> Optiona
|
|
| 907 |
if token:
|
| 908 |
headers['Authorization'] = f'token {token}'
|
| 909 |
logger.info(f"Calling GitHub API: {url}")
|
| 910 |
-
async with httpx.AsyncClient(timeout=
|
| 911 |
response = await client.get(url, headers=headers)
|
| 912 |
response.raise_for_status()
|
| 913 |
return response.json()
|
|
@@ -1061,7 +1043,7 @@ def solve_project2_png(image_url: str, base_url: str) -> str:
|
|
| 1061 |
try:
|
| 1062 |
if image_url.startswith('/'):
|
| 1063 |
image_url = urljoin(base_url, image_url)
|
| 1064 |
-
response = requests.get(image_url, timeout=
|
| 1065 |
response.raise_for_status()
|
| 1066 |
img = Image.open(io.BytesIO(response.content))
|
| 1067 |
if img.mode != 'RGB':
|
|
@@ -1079,7 +1061,7 @@ def solve_project2_json(json_url: str, base_url: str) -> str:
|
|
| 1079 |
try:
|
| 1080 |
if json_url.startswith('/'):
|
| 1081 |
json_url = urljoin(base_url, json_url)
|
| 1082 |
-
response = requests.get(json_url, timeout=
|
| 1083 |
response.raise_for_status()
|
| 1084 |
data = response.json()
|
| 1085 |
if isinstance(data, list):
|
|
@@ -1196,7 +1178,7 @@ def solve_project2_sql(sql_query: str, csv_url: str, base_url: str) -> str:
|
|
| 1196 |
try:
|
| 1197 |
if csv_url.startswith('/'):
|
| 1198 |
csv_url = urljoin(base_url, csv_url)
|
| 1199 |
-
response = requests.get(csv_url, timeout=
|
| 1200 |
response.raise_for_status()
|
| 1201 |
df = pd.read_csv(io.StringIO(response.text))
|
| 1202 |
conn = duckdb.connect(':memory:')
|
|
@@ -1304,10 +1286,10 @@ class QuizSolver:
|
|
| 1304 |
return {"error": "Timeout imminent - insufficient time remaining"}
|
| 1305 |
|
| 1306 |
try:
|
| 1307 |
-
#
|
| 1308 |
-
wait_time =
|
| 1309 |
# Load the quiz page with optimized timeout - use less time for page load
|
| 1310 |
-
page_timeout = min(
|
| 1311 |
page_content = await self.browser.load_page(url, wait_time=wait_time, timeout=page_timeout)
|
| 1312 |
|
| 1313 |
# Extract submit URL
|
|
@@ -1502,8 +1484,9 @@ class QuizSolver:
|
|
| 1502 |
logger.info("Analyzing question type...")
|
| 1503 |
|
| 1504 |
# Try to parse question with LLM first (only if we have enough time)
|
|
|
|
| 1505 |
remaining = self._check_time_remaining()
|
| 1506 |
-
if remaining >=
|
| 1507 |
parsed = await parse_question_with_llm(question, page_content.get('text', ''))
|
| 1508 |
else:
|
| 1509 |
parsed = None
|
|
@@ -1745,7 +1728,7 @@ class QuizSolver:
|
|
| 1745 |
from openai import OpenAI
|
| 1746 |
import tempfile
|
| 1747 |
client = OpenAI(api_key=openai_key)
|
| 1748 |
-
response = requests.get(audio_url, timeout=
|
| 1749 |
response.raise_for_status()
|
| 1750 |
with tempfile.NamedTemporaryFile(suffix='.opus', delete=False) as tmp_file:
|
| 1751 |
tmp_file.write(response.content)
|
|
@@ -1926,10 +1909,10 @@ class QuizSolver:
|
|
| 1926 |
# Use LLM more aggressively - lower thresholds to prioritize LLM solving
|
| 1927 |
is_audio_question = 'transcribe' in question.lower() or 'passphrase' in question.lower() or 'spoken phrase' in question.lower()
|
| 1928 |
# Very low thresholds - use LLM as primary solver whenever possible
|
| 1929 |
-
min_time_needed =
|
| 1930 |
|
| 1931 |
# Use LLM if we have enough time AND haven't found answer yet
|
| 1932 |
-
#
|
| 1933 |
if remaining >= min_time_needed:
|
| 1934 |
logger.info("Attempting to solve with LLM...")
|
| 1935 |
try:
|
|
@@ -1954,7 +1937,7 @@ class QuizSolver:
|
|
| 1954 |
# Try to extract any useful information from the error
|
| 1955 |
pass
|
| 1956 |
else:
|
| 1957 |
-
logger.
|
| 1958 |
|
| 1959 |
# Strategy 8: Fallback - try to extract a simple answer from the question
|
| 1960 |
# Many quiz pages have the answer in the question itself
|
|
@@ -2497,8 +2480,8 @@ class QuizSolver:
|
|
| 2497 |
break
|
| 2498 |
|
| 2499 |
logger.info(f"Downloading file: {url}")
|
| 2500 |
-
# Use adaptive timeout based on remaining time (max
|
| 2501 |
-
file_timeout = min(
|
| 2502 |
response = requests.get(url, timeout=file_timeout)
|
| 2503 |
response.raise_for_status()
|
| 2504 |
|
|
|
|
| 191 |
try:
|
| 192 |
logger.info(f"Loading page: {url}")
|
| 193 |
await self.page.goto(url, wait_until='load', timeout=timeout)
|
| 194 |
+
await asyncio.sleep(0.1) # Minimal wait - just enough for JS to execute
|
| 195 |
content = {
|
| 196 |
'url': url,
|
| 197 |
'title': await self.page.title(),
|
| 198 |
'text': await self.page.inner_text('body'),
|
| 199 |
'html': await self.page.content(),
|
| 200 |
+
# Skip screenshot to save time - not needed for solving
|
| 201 |
}
|
| 202 |
try:
|
| 203 |
content['all_text'] = await self.page.evaluate("""() => {
|
|
|
|
| 295 |
"X-Title": OPENROUTER_APP_NAME,
|
| 296 |
"Content-Type": "application/json",
|
| 297 |
}
|
| 298 |
+
system_content = system_prompt if system_prompt else "You are a helpful assistant that solves quiz questions accurately and concisely. Be direct and brief."
|
| 299 |
+
# Optimize max_tokens - reduce for faster responses (default 1000 instead of 2000)
|
| 300 |
+
optimized_max_tokens = min(max_tokens, 1000) if max_tokens > 1000 else max_tokens
|
| 301 |
payload = {
|
| 302 |
"model": model,
|
| 303 |
"messages": [
|
| 304 |
{"role": "system", "content": system_content},
|
| 305 |
{"role": "user", "content": prompt}
|
| 306 |
],
|
| 307 |
+
"max_tokens": optimized_max_tokens,
|
| 308 |
+
"temperature": 0.1 # Lower temperature for more deterministic, faster responses
|
| 309 |
}
|
| 310 |
try:
|
| 311 |
+
# Reduced timeout for faster responses - 15s is enough for most LLM calls
|
| 312 |
+
async with httpx.AsyncClient(timeout=15) as http_client:
|
| 313 |
response = await http_client.post(url, headers=headers, json=payload)
|
| 314 |
response.raise_for_status()
|
| 315 |
data = response.json()
|
|
|
|
| 331 |
|
| 332 |
async def parse_question_with_llm(question_text: str, context: str = "") -> Optional[Dict[str, Any]]:
|
| 333 |
"""Use LLM to parse and understand a quiz question."""
|
| 334 |
+
# Optimized prompt - more concise for faster processing
|
| 335 |
+
prompt = f"""Analyze: {question_text[:500]}
|
| 336 |
|
| 337 |
+
Type? Data needed? Format? JSON: {{"type":"...","requirements":[],"answer_format":"..."}}"""
|
| 338 |
+
# Reduced max_tokens for faster response
|
| 339 |
+
response = await ask_gpt(prompt, max_tokens=500)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
if not response:
|
| 341 |
return None
|
| 342 |
json_match = re.search(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', response, re.DOTALL)
|
|
|
|
| 378 |
# Format available_data more clearly
|
| 379 |
data_str = json.dumps(available_data, indent=2) if available_data else "No additional data"
|
| 380 |
|
| 381 |
+
# Optimized prompt - more concise for faster LLM processing
|
| 382 |
+
prompt = f"""Solve: {question}
|
| 383 |
|
| 384 |
+
Data: {data_str[:1000]}{email_instruction}{audio_data}{format_instructions}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
|
| 386 |
+
Answer directly. JSON if needed. Command/path: return ONLY that. Audio: use transcription exactly."""
|
| 387 |
+
# Reduced max_tokens for faster response
|
| 388 |
+
return await ask_gpt(prompt, max_tokens=1500)
|
|
|
|
|
|
|
|
|
|
| 389 |
|
| 390 |
async def ocr_image_with_llm(image_base64: str) -> Optional[str]:
|
| 391 |
"""Use OpenRouter vision model to extract text from an image."""
|
|
|
|
| 413 |
}],
|
| 414 |
"max_tokens": 1000
|
| 415 |
}
|
| 416 |
+
# Reduced timeout for vision calls - 30s should be enough
|
| 417 |
+
async with httpx.AsyncClient(timeout=30) as http_client:
|
| 418 |
response = await http_client.post(url, headers=headers, json=payload)
|
| 419 |
response.raise_for_status()
|
| 420 |
data = response.json()
|
|
|
|
| 659 |
"""Download and transcribe audio from URL."""
|
| 660 |
try:
|
| 661 |
logger.info(f"Processing audio from URL: {audio_url}")
|
| 662 |
+
response = requests.get(audio_url, timeout=15)
|
| 663 |
response.raise_for_status()
|
| 664 |
audio_data = response.content
|
| 665 |
audio_base64 = base64.b64encode(audio_data).decode('utf-8')
|
|
|
|
| 678 |
if openai_key and OPENAI_AVAILABLE:
|
| 679 |
try:
|
| 680 |
client = OpenAI(api_key=openai_key)
|
| 681 |
+
response = requests.get(audio_url, timeout=15)
|
| 682 |
response.raise_for_status()
|
| 683 |
with tempfile.NamedTemporaryFile(suffix='.opus', delete=False) as tmp_file:
|
| 684 |
tmp_file.write(response.content)
|
|
|
|
| 701 |
"""Process video from URL - extract frames, transcribe audio, OCR text."""
|
| 702 |
try:
|
| 703 |
logger.info(f"Processing video from URL: {video_url}")
|
| 704 |
+
response = requests.get(video_url, timeout=15, stream=True)
|
| 705 |
response.raise_for_status()
|
| 706 |
video_info = {
|
| 707 |
'url': video_url,
|
|
|
|
| 729 |
"""Process image from URL - extract text using OCR."""
|
| 730 |
try:
|
| 731 |
logger.info(f"Processing image from URL: {image_url}")
|
| 732 |
+
response = requests.get(image_url, timeout=15)
|
| 733 |
response.raise_for_status()
|
| 734 |
image_data = response.content
|
| 735 |
image_base64 = base64.b64encode(image_data).decode('utf-8')
|
|
|
|
| 820 |
if image_url.startswith('/') and base_url:
|
| 821 |
image_url = urljoin(base_url, image_url)
|
| 822 |
logger.info(f"Processing image for color extraction: {image_url}")
|
| 823 |
+
response = requests.get(image_url, timeout=15)
|
| 824 |
response.raise_for_status()
|
| 825 |
img = Image.open(io.BytesIO(response.content))
|
| 826 |
if img.mode != 'RGB':
|
|
|
|
| 841 |
if csv_url.startswith('/') and base_url:
|
| 842 |
csv_url = urljoin(base_url, csv_url)
|
| 843 |
logger.info(f"Converting CSV to JSON: {csv_url}")
|
| 844 |
+
response = requests.get(csv_url, timeout=15)
|
| 845 |
response.raise_for_status()
|
| 846 |
df = pd.read_csv(io.StringIO(response.text))
|
| 847 |
if normalize:
|
|
|
|
| 889 |
if token:
|
| 890 |
headers['Authorization'] = f'token {token}'
|
| 891 |
logger.info(f"Calling GitHub API: {url}")
|
| 892 |
+
async with httpx.AsyncClient(timeout=15) as client:
|
| 893 |
response = await client.get(url, headers=headers)
|
| 894 |
response.raise_for_status()
|
| 895 |
return response.json()
|
|
|
|
| 1043 |
try:
|
| 1044 |
if image_url.startswith('/'):
|
| 1045 |
image_url = urljoin(base_url, image_url)
|
| 1046 |
+
response = requests.get(image_url, timeout=15)
|
| 1047 |
response.raise_for_status()
|
| 1048 |
img = Image.open(io.BytesIO(response.content))
|
| 1049 |
if img.mode != 'RGB':
|
|
|
|
| 1061 |
try:
|
| 1062 |
if json_url.startswith('/'):
|
| 1063 |
json_url = urljoin(base_url, json_url)
|
| 1064 |
+
response = requests.get(json_url, timeout=15)
|
| 1065 |
response.raise_for_status()
|
| 1066 |
data = response.json()
|
| 1067 |
if isinstance(data, list):
|
|
|
|
| 1178 |
try:
|
| 1179 |
if csv_url.startswith('/'):
|
| 1180 |
csv_url = urljoin(base_url, csv_url)
|
| 1181 |
+
response = requests.get(csv_url, timeout=15)
|
| 1182 |
response.raise_for_status()
|
| 1183 |
df = pd.read_csv(io.StringIO(response.text))
|
| 1184 |
conn = duckdb.connect(':memory:')
|
|
|
|
| 1286 |
return {"error": "Timeout imminent - insufficient time remaining"}
|
| 1287 |
|
| 1288 |
try:
|
| 1289 |
+
# Minimal wait time - just enough for page to load
|
| 1290 |
+
wait_time = 0.1 # Fixed minimal wait - no dynamic calculation needed
|
| 1291 |
# Load the quiz page with optimized timeout - use less time for page load
|
| 1292 |
+
page_timeout = min(8000, int(remaining * 1000 * 0.4)) # 40% of remaining time, max 8s (reduced from 12s)
|
| 1293 |
page_content = await self.browser.load_page(url, wait_time=wait_time, timeout=page_timeout)
|
| 1294 |
|
| 1295 |
# Extract submit URL
|
|
|
|
| 1484 |
logger.info("Analyzing question type...")
|
| 1485 |
|
| 1486 |
# Try to parse question with LLM first (only if we have enough time)
|
| 1487 |
+
# Reduced threshold - parse even with less time for better adaptability
|
| 1488 |
remaining = self._check_time_remaining()
|
| 1489 |
+
if remaining >= 10.0: # Reduced from 30s to 10s - parse faster
|
| 1490 |
parsed = await parse_question_with_llm(question, page_content.get('text', ''))
|
| 1491 |
else:
|
| 1492 |
parsed = None
|
|
|
|
| 1728 |
from openai import OpenAI
|
| 1729 |
import tempfile
|
| 1730 |
client = OpenAI(api_key=openai_key)
|
| 1731 |
+
response = requests.get(audio_url, timeout=15)
|
| 1732 |
response.raise_for_status()
|
| 1733 |
with tempfile.NamedTemporaryFile(suffix='.opus', delete=False) as tmp_file:
|
| 1734 |
tmp_file.write(response.content)
|
|
|
|
| 1909 |
# Use LLM more aggressively - lower thresholds to prioritize LLM solving
|
| 1910 |
is_audio_question = 'transcribe' in question.lower() or 'passphrase' in question.lower() or 'spoken phrase' in question.lower()
|
| 1911 |
# Very low thresholds - use LLM as primary solver whenever possible
|
| 1912 |
+
min_time_needed = 3.0 if is_audio_question else 5.0 # Reduced further - use LLM more aggressively
|
| 1913 |
|
| 1914 |
# Use LLM if we have enough time AND haven't found answer yet
|
| 1915 |
+
# Reduced threshold - use LLM more aggressively for adaptability
|
| 1916 |
if remaining >= min_time_needed:
|
| 1917 |
logger.info("Attempting to solve with LLM...")
|
| 1918 |
try:
|
|
|
|
| 1937 |
# Try to extract any useful information from the error
|
| 1938 |
pass
|
| 1939 |
else:
|
| 1940 |
+
logger.debug(f"Skipping LLM call - insufficient time remaining ({remaining:.1f}s, need {min_time_needed}s)")
|
| 1941 |
|
| 1942 |
# Strategy 8: Fallback - try to extract a simple answer from the question
|
| 1943 |
# Many quiz pages have the answer in the question itself
|
|
|
|
| 2480 |
break
|
| 2481 |
|
| 2482 |
logger.info(f"Downloading file: {url}")
|
| 2483 |
+
# Use adaptive timeout based on remaining time (max 8s, min 2s) - faster
|
| 2484 |
+
file_timeout = min(8, max(2, int(remaining * 0.3))) # Use less time for downloads
|
| 2485 |
response = requests.get(url, timeout=file_timeout)
|
| 2486 |
response.raise_for_status()
|
| 2487 |
|