Skip to content

feat: adaptive and explainable quiz generation (Phase 1)#397

Open
maheshxsharmask wants to merge 1 commit intoAOSSIE-Org:mainfrom
maheshxsharmask:feature/adaptive-explainable-quiz-phase-1
Open

feat: adaptive and explainable quiz generation (Phase 1)#397
maheshxsharmask wants to merge 1 commit intoAOSSIE-Org:mainfrom
maheshxsharmask:feature/adaptive-explainable-quiz-phase-1

Conversation

@maheshxsharmask
Copy link

@maheshxsharmask maheshxsharmask commented Jan 31, 2026

Summary

This PR implements Phase 1 of Issue #392 by adding difficulty control, source-grounded explanations, and basic question quality improvements across the quiz generation pipeline.

The focus is on backend robustness and generation quality while keeping frontend changes minimal.

Changes

Backend

Difficulty-Controlled Generation

  • Added Easy vs Hard difficulty handling for Short, MCQ, and Boolean question types.
  • Implemented dedicated hard endpoints:
    • /get_shortq_hard
    • /get_mcq_hard
    • /get_boolq_hard

Source-Grounded Explanations

  • Each generated question now includes an explanation derived from the input/context.
  • Improves answer transparency and learning value.

Question Quality Control

  • Added basic deduplication to reduce repeated or near-duplicate questions.

Stability Improvements

  • Google Docs service initialization is now optional.
  • Backend no longer crashes when service_account_key.json is missing.
  • get_content returns a safe 503 response when Docs integration is unavailable.

Google Forms Integration

  • Standardized response shape:
    • { "form_link": "" }
  • Fixes frontend compatibility.

Frontend

  • Hard difficulty selection now routes correctly to *_hard endpoints.
  • Difficulty value is included in backend request payload.

Validation

Backend

Short Hard

  • POST /get_shortq_hard returns hardened question with answer and explanation

Boolean Hard

  • POST /get_boolq_hard returns hardened True/False style prompts

MCQ Hard

  • Generates valid output on texts containing named entities

Frontend

  • npm run build completed successfully
  • Output page and Google Form generation use stable API response shapes

Limitations

  • MCQ hard generation depends on named entity detection; short or generic inputs may produce no MCQs.
  • Google Docs integration requires valid credentials but no longer blocks backend startup.

Follow-Up (Phase 2)

  • Adaptive quiz flow using performance-based difficulty tuning
  • Embedding-based duplicate and ambiguity detection
  • Medium difficulty tier and finer difficulty calibration

Closes #392

Summary by CodeRabbit

  • New Features

    • Extended difficulty level support to boolean questions.
    • Implemented question deduplication to eliminate duplicate questions across all question types.
  • Bug Fixes

    • Improved explanation consistency and alignment across MCQ, short-form, and boolean questions.
    • Enhanced resilience of content retrieval with better error handling.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 31, 2026

📝 Walkthrough

Walkthrough

These changes implement difficulty-controlled question generation with automatic deduplication and source-grounded explanations. The backend now supports difficulty levels (Easy/Medium/Hard) across all question types, with dedicated hard-variant endpoints, while the frontend routes requests to appropriate endpoints based on selected difficulty.

Changes

Cohort / File(s) Summary
Google Docs Integration & Helpers
backend/server.py
Introduces resilient service initialization with fallback to None; adds _dedup_strings and _dedup_questions helpers for normalizing and deduplicating content; adds 503 error handling when docs_service unavailable.
Difficulty-Controlled Question Generation
backend/server.py
Extends get_mcq, get_shortq, and get_problems to accept difficulty field; applies question hardening via make_question_harder for non-Easy difficulties; ensures explanations are populated from context or input_text; deduplicates questions by statement/content.
Hard Variant Endpoints
backend/server.py
Reworks get_shortq_hard, get_mcq_hard, and get_boolq_hard endpoints to use specialized generators (Question Generator with evaluator, BoolQGen) and format responses with hardened questions and explanations.
Frontend Difficulty Routing
eduaid_web/src/pages/Text_Input.jsx
Routes get_shortq to get_shortq_hard and get_mcq to get_mcq_hard when difficulty is not Easy; adds mapping for get_boolq to get_boolq_hard; includes difficulty field in backend request payload.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Frontend as Frontend<br/>(Text_Input.jsx)
    participant Backend as Backend<br/>(server.py)
    participant Generator as Question<br/>Generators
    participant DocsService as Google Docs<br/>Service

    User->>Frontend: Select content, difficulty level
    Frontend->>Backend: POST with input_text + difficulty
    
    alt Difficulty != "Easy"
        Backend->>Backend: Route to *_hard endpoint
    else Difficulty = "Easy"
        Backend->>Backend: Route to standard endpoint
    end
    
    Backend->>DocsService: Fetch content context
    DocsService-->>Backend: Return content
    
    Backend->>Generator: Generate questions<br/>(with hardening if needed)
    Generator-->>Backend: Raw questions
    
    Backend->>Backend: Deduplicate by<br/>question_statement
    Backend->>Backend: Populate explanations<br/>from context/input_text
    
    Backend-->>Frontend: Formatted questions with<br/>explanations
    Frontend-->>User: Display quiz
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Hops of joy through questions hard,
Deduped with care, each marked and charred,
Explanations bloom from source so true,
Difficulty tamed—now quizzes flew!
No more duplicates in our hutch,

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: implementing adaptive and explainable quiz generation (Phase 1) with difficulty control and source-grounded explanations.
Linked Issues check ✅ Passed The PR successfully implements key Phase 1 objectives: difficulty-controlled question generation (Easy/Hard), source-grounded explanations, basic deduplication, optional Google Docs integration, and hard endpoints for MCQ/ShortQ/BoolQ.
Out of Scope Changes check ✅ Passed All changes are within scope of Issue #392 Phase 1: difficulty handling, explanations, deduplication, Google Docs resilience, and hard endpoints are all explicitly covered by PR objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@backend/server.py`:
- Around line 432-436: Check for a missing responderUri before returning and
calling webbrowser.open_new_tab: read result.get("responderUri") into edit_url,
and if it's falsy, build a safe fallback using result["formId"] (e.g.,
"https://docs.google.com/forms/d/<formId>/edit") or return a clear error
response (jsonify with an error and 400) instead of returning None. Update the
webbrowser.open_new_tab call to use the same resolved link and ensure the
jsonify({"form_link": edit_url}) returns the resolved (fallback or error) value;
reference symbols: result, responderUri, formId, edit_url,
webbrowser.open_new_tab, jsonify.
🧹 Nitpick comments (4)
backend/server.py (4)

41-47: Consider logging the exception for debugging purposes.

The bare except Exception silently swallows all errors during service initialization. While falling back to None is appropriate for optional functionality, logging the exception would aid debugging when the service unexpectedly fails to initialize.

♻️ Suggested improvement
+import logging
+
 try:
     if os.path.exists(SERVICE_ACCOUNT_FILE):
         docs_service = main.GoogleDocsService(SERVICE_ACCOUNT_FILE, SCOPES)
     else:
         docs_service = None
-except Exception:
+except Exception as e:
+    logging.warning("Google Docs service initialization failed: %s", e)
     docs_service = None

444-456: Missing deduplication for consistency with /get_shortq.

The regular /get_shortq endpoint applies _dedup_questions before processing, but this hard endpoint does not. This inconsistency could result in duplicate questions in hard mode output.

♻️ Suggested fix
     qa_list = qg.generate(article=input_text, use_evaluator=True, num_questions=max_questions, answer_style="sentences")
+    qa_list = _dedup_questions(qa_list, lambda x: x.get("question"))
     out = []
     for qa in qa_list:

464-489: Missing deduplication and potential None correct answer.

Two concerns:

  1. No deduplication: Unlike /get_mcq, this hard endpoint doesn't deduplicate questions.

  2. correct can be None: If no option has "correct": True, correct will be None, which may cause issues for consumers expecting a valid answer.

♻️ Suggested fix
     qa_list = qg.generate(article=input_text, use_evaluator=True, num_questions=max_questions, answer_style="multiple_choice")
+    qa_list = _dedup_questions(qa_list, lambda x: x.get("question"))
     out = []
     for qa in qa_list:
         q_text = make_question_harder(qa.get("question"))
         ans = qa.get("answer")
         if isinstance(ans, list):
             opts = [o.get("answer") for o in ans if o.get("answer")]
             correct = next((o.get("answer") for o in ans if o.get("correct")), None)
+            if not correct and opts:
+                correct = opts[0]  # Fallback to first option if no correct flag
             out.append({

496-502: Missing deduplication and inconsistent output format.

  1. No deduplication: /get_problems applies _dedup_strings to boolean questions, but this endpoint does not.

  2. Output format: Returns a flat list of strings, while other hard endpoints return dicts with question_type and explanation. Consider aligning the format for consistency.

♻️ Suggested fix for deduplication
     questions = final.get("Boolean_Questions", [])
+    questions = _dedup_strings(questions)
     harder = [make_question_harder(q) for q in questions]
     return jsonify({"output": harder})

Comment on lines +432 to +436
edit_url = result.get("responderUri")
webbrowser.open_new_tab(
"https://docs.google.com/forms/d/" + result["formId"] + "/edit"
)
return edit_url
return jsonify({"form_link": edit_url})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle missing responderUri gracefully.

If the Forms API response doesn't include responderUri, form_link will be None, which may cause frontend issues. Consider providing a fallback or returning an error.

🛡️ Suggested fix
     edit_url = result.get("responderUri")
+    if not edit_url:
+        # Fallback to constructing the URL from formId
+        edit_url = f"https://docs.google.com/forms/d/{result['formId']}/viewform"
     webbrowser.open_new_tab(
         "https://docs.google.com/forms/d/" + result["formId"] + "/edit"
     )
     return jsonify({"form_link": edit_url})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
edit_url = result.get("responderUri")
webbrowser.open_new_tab(
"https://docs.google.com/forms/d/" + result["formId"] + "/edit"
)
return edit_url
return jsonify({"form_link": edit_url})
edit_url = result.get("responderUri")
if not edit_url:
# Fallback to constructing the URL from formId
edit_url = f"https://docs.google.com/forms/d/{result['formId']}/viewform"
webbrowser.open_new_tab(
"https://docs.google.com/forms/d/" + result["formId"] + "/edit"
)
return jsonify({"form_link": edit_url})
🤖 Prompt for AI Agents
In `@backend/server.py` around lines 432 - 436, Check for a missing responderUri
before returning and calling webbrowser.open_new_tab: read
result.get("responderUri") into edit_url, and if it's falsy, build a safe
fallback using result["formId"] (e.g.,
"https://docs.google.com/forms/d/<formId>/edit") or return a clear error
response (jsonify with an error and 400) instead of returning None. Update the
webbrowser.open_new_tab call to use the same resolved link and ensure the
jsonify({"form_link": edit_url}) returns the resolved (fallback or error) value;
reference symbols: result, responderUri, formId, edit_url,
webbrowser.open_new_tab, jsonify.

@maheshxsharmask
Copy link
Author

Hi @Aditya062003 @yatikakain
I’ve submitted a PR implementing Phase 1 of difficulty control, explanations, and generation quality improvements.
Could you please review it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adaptive and Explainable Quiz Generation

1 participant