final quiz generation

parent 7212b9bd
# Network settings
bind = "0.0.0.0:8000"
# Worker settings
worker_class = "uvicorn.workers.UvicornWorker"
workers = 4
timeout = 300
# Logging settings
accesslog = "-"
errorlog = "-"
loglevel = "info"
\ No newline at end of file
......@@ -162,9 +162,7 @@ def create_app() -> FastAPI:
shutil.move(temp_csv_path, csv_dest_path)
print(f"--- Background task: Saved new embeddings to '{csv_dest_path}' ---", flush=True)
# ==========================================================
# === CORRECTED LOGIC: APPEND NEW CURRICULUM TO EXISTING ===
# ==========================================================
# --- 3. Read both JSON files ---
print("--- Background task: Reading generated JSON structure... ---", flush=True)
with open(temp_json_path, 'r', encoding='utf-8') as f:
......@@ -324,8 +322,7 @@ def create_app() -> FastAPI:
"""
pdf_bytes = await file.read()
# --- THIS IS THE FIX ---
# We now pass BOTH required arguments to the add_task method.
background_tasks.add_task(
process_pdf_curriculum_in_background,
pdf_bytes,
......@@ -443,6 +440,25 @@ def create_app() -> FastAPI:
except Exception as e:
logger.error(f"Error in get_dynamic_quiz_handler: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/quiz-interface")
async def serve_quiz_interface():
"""Serve the dynamic quiz generator HTML file"""
try:
# Check for the file in a 'static' folder first
static_file = Path("static/dynamic_quiz_interface.html")
if static_file.exists():
return FileResponse(static_file)
# Fallback to the root directory
current_file = Path("dynamic_quiz_interface.html")
if current_file.exists():
return FileResponse(current_file)
raise HTTPException(status_code=404, detail="Dynamic quiz interface not found")
except Exception as e:
print(f"Error serving quiz interface: {e}")
raise HTTPException(status_code=500, detail=f"Error serving interface: {str(e)}")
@app.options("/get-audio-response")
async def audio_response_options():
......
......@@ -361,18 +361,21 @@ class AgentService:
def get_dynamic_quiz(
self, grade: int, subject: str, unit: str, concept: str, is_arabic: bool, count: int
) -> List[Dict]:
) -> List[Dict]:
"""
Generates a dynamic quiz of 'count' questions using a hybrid approach:
Generates a dynamic quiz of 'count' questions using a hybrid approach with BATCHED generation:
1. Always generates a "freshness batch" of new questions.
2. Retrieves all questions and checks if the total meets the 'count'.
3. If not, generates the remaining number of questions needed.
3. If not, generates the remaining number of questions needed IN BATCHES.
"""
if not self.pgvector:
raise HTTPException(status_code=503, detail="Vector service is not available for this feature.")
# --- PART 1: Follow the original logic to ensure freshness ---
# Define maximum questions per batch to avoid token limits
MAX_QUESTIONS_PER_BATCH = 10
# --- PART 1: Generate freshness questions ---
# 1. Calculate how many new questions to generate for freshness.
num_fresh_questions = min(max(1, math.floor(count / 3)), 5)
logger.info(f"Request for {count} questions. Step 1: Generating {num_fresh_questions} new 'freshness' questions.")
......@@ -386,7 +389,7 @@ class AgentService:
except Exception as e:
logger.warning(f"Could not generate 'freshness' questions for the quiz due to an error: {e}")
# --- PART 2: Check for a shortfall and generate more if needed ---
# --- PART 2: Check for shortfall and generate more if needed (WITH BATCHING) ---
# 3. Retrieve ALL available questions for the topic from the database.
all_mcqs_after_freshness = self.pgvector.get_mcqs(
......@@ -397,20 +400,37 @@ class AgentService:
# 4. Calculate if there is still a shortfall.
questions_still_needed = count - len(all_mcqs_after_freshness)
# 5. If we still need more questions, generate the exact number missing.
# 5. If we still need more questions, generate them IN BATCHES.
if questions_still_needed > 0:
logger.info(f"After freshness batch, have {len(all_mcqs_after_freshness)} questions. Generating {questions_still_needed} more to meet count of {count}.")
logger.info(f"After freshness batch, have {len(all_mcqs_after_freshness)} questions. Need to generate {questions_still_needed} more to meet count of {count}.")
# Cap this second generation step as a safeguard.
num_to_generate_gap = min(questions_still_needed, 10)
total_generated = 0
remaining = questions_still_needed
try:
self.generate_and_store_mcqs(
grade=grade, subject=subject, unit=unit, concept=concept,
is_arabic=is_arabic, num_questions=num_to_generate_gap
)
except Exception as e:
logger.warning(f"Could not generate the remaining {num_to_generate_gap} questions due to an error: {e}")
while remaining > 0:
# Determine batch size (cap at MAX_QUESTIONS_PER_BATCH and also cap total at 20 per session)
batch_size = min(remaining, MAX_QUESTIONS_PER_BATCH)
try:
logger.info(f"Generating batch {total_generated // MAX_QUESTIONS_PER_BATCH + 1} of {batch_size} questions...")
self.generate_and_store_mcqs(
grade=grade, subject=subject, unit=unit, concept=concept,
is_arabic=is_arabic, num_questions=batch_size
)
total_generated += batch_size
remaining -= batch_size
logger.info(f"Successfully generated batch. Total gap-filling questions generated: {total_generated}")
except Exception as e:
logger.error(f"Failed to generate batch of {batch_size} questions: {e}")
# If we've generated at least some questions, continue
if total_generated > 0:
logger.warning(f"Continuing with {total_generated} gap-filling questions generated so far.")
break
else:
logger.warning(f"Could not generate gap-filling questions: {e}")
break
# --- PART 3: Final Assembly and Return ---
......@@ -422,10 +442,14 @@ class AgentService:
if not final_pool:
raise HTTPException(status_code=404, detail="No questions could be found or generated for this topic.")
# Check if we have enough questions
if len(final_pool) < count:
logger.warning(f"Could only gather {len(final_pool)} questions out of {count} requested. Returning all available questions.")
# 7. Randomly select the desired number of questions from the final pool.
random.shuffle(final_pool)
final_quiz = final_pool[:count]
final_quiz = final_pool[:min(count, len(final_pool))]
logger.info(f"Returning a dynamic quiz of {len(final_quiz)} questions for '{concept}'.")
return final_quiz
\ No newline at end of file
......@@ -15,4 +15,4 @@ echo "MCQ table setup complete."
sleep 5
# Start the web server and keep it as the main process
exec gunicorn -c gunicorn.conf.py main:app
\ No newline at end of file
exec gunicorn -c gunicorn.conf.py main:app
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment