Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
AI Tutor
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Salma Mohammed Hamed
AI Tutor
Commits
56712e0e
Commit
56712e0e
authored
Nov 04, 2025
by
SalmaMohammedHamedMustafa
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
edit mcq schema
parent
8bbfd066
Changes
10
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
315 additions
and
355 deletions
+315
-355
main.py
self_hosted_env/voice_agent/main.py
+23
-12
__init__.py
self_hosted_env/voice_agent/schemas/__init__.py
+2
-1
mcq.py
self_hosted_env/voice_agent/schemas/mcq.py
+31
-0
agent_service.py
self_hosted_env/voice_agent/services/agent_service.py
+95
-80
pgvector_service.py
self_hosted_env/voice_agent/services/pgvector_service.py
+6
-6
setup_mcq_table.py
self_hosted_env/voice_agent/setup_mcq_table.py
+13
-13
dynamic_quiz_interface.html
...hosted_env/voice_agent/static/dynamic_quiz_interface.html
+26
-8
va.tar
self_hosted_env/voice_agent/va.tar
+0
-0
test_mcq.py
test_cases/test_mcq.py
+119
-0
test_msq.py
test_cases/test_msq.py
+0
-235
No files found.
self_hosted_env/voice_agent/main.py
View file @
56712e0e
...
@@ -25,6 +25,8 @@ from services import (
...
@@ -25,6 +25,8 @@ from services import (
DataIngestionService
DataIngestionService
)
)
from
schemas.mcq
import
QuestionResponse
,
QuizResponse
,
MCQListResponse
class
DIContainer
:
class
DIContainer
:
...
@@ -339,7 +341,7 @@ def create_app() -> FastAPI:
...
@@ -339,7 +341,7 @@ def create_app() -> FastAPI:
async
def
generate_mcqs_handler
(
async
def
generate_mcqs_handler
(
request
:
Request
,
request
:
Request
,
curriculum
:
str
=
Form
(
...
),
curriculum
:
str
=
Form
(
...
),
grade
:
str
=
Form
(
...
),
# Changed to str
grade
:
str
=
Form
(
...
),
subject
:
str
=
Form
(
...
),
subject
:
str
=
Form
(
...
),
unit
:
str
=
Form
(
...
),
unit
:
str
=
Form
(
...
),
concept
:
str
=
Form
(
...
),
concept
:
str
=
Form
(
...
),
...
@@ -347,7 +349,10 @@ def create_app() -> FastAPI:
...
@@ -347,7 +349,10 @@ def create_app() -> FastAPI:
is_arabic
:
bool
=
Form
(
False
),
is_arabic
:
bool
=
Form
(
False
),
):
):
"""
"""
Generates and stores a new set of MCQs for a specific topic, using the new schema.
Generates and stores a new set of MCQs.
NOTE: This endpoint intentionally returns the FULL question object,
including curriculum, grade, etc., as it might be useful for the client
that just initiated the generation. The GET endpoints will be filtered.
"""
"""
container
=
request
.
app
.
state
.
container
container
=
request
.
app
.
state
.
container
try
:
try
:
...
@@ -371,7 +376,8 @@ def create_app() -> FastAPI:
...
@@ -371,7 +376,8 @@ def create_app() -> FastAPI:
logger
.
error
(
f
"Error in generate_mcqs_handler: {e}"
)
logger
.
error
(
f
"Error in generate_mcqs_handler: {e}"
)
raise
HTTPException
(
status_code
=
500
,
detail
=
str
(
e
))
raise
HTTPException
(
status_code
=
500
,
detail
=
str
(
e
))
@
app
.
get
(
"/mcq"
)
# --- STEP 2: UPDATE THE /mcq ENDPOINT SIGNATURE ---
@
app
.
get
(
"/mcq"
,
response_model
=
MCQListResponse
)
async
def
get_mcqs_handler
(
async
def
get_mcqs_handler
(
request
:
Request
,
request
:
Request
,
curriculum
:
str
,
curriculum
:
str
,
...
@@ -383,11 +389,12 @@ def create_app() -> FastAPI:
...
@@ -383,11 +389,12 @@ def create_app() -> FastAPI:
limit
:
Optional
[
int
]
=
None
limit
:
Optional
[
int
]
=
None
):
):
"""
"""
Retrieves existing MCQs
for a specific topic, now filtering by curriculum
.
Retrieves existing MCQs
, filtered to the 11-field response model
.
"""
"""
container
=
request
.
app
.
state
.
container
container
=
request
.
app
.
state
.
container
try
:
try
:
questions
=
container
.
agent_service
.
pgvector
.
get_mcqs
(
# The service layer still returns the full objects from the DB
questions_from_db
=
container
.
agent_service
.
pgvector
.
get_mcqs
(
curriculum
=
curriculum
,
curriculum
=
curriculum
,
grade
=
grade
,
grade
=
grade
,
subject
=
subject
,
subject
=
subject
,
...
@@ -396,16 +403,18 @@ def create_app() -> FastAPI:
...
@@ -396,16 +403,18 @@ def create_app() -> FastAPI:
is_arabic
=
is_arabic
,
is_arabic
=
is_arabic
,
limit
=
limit
limit
=
limit
)
)
# FastAPI will automatically filter `questions_from_db` to match the model
return
{
return
{
"status"
:
"success"
,
"status"
:
"success"
,
"count"
:
len
(
questions
),
"count"
:
len
(
questions
_from_db
),
"questions"
:
questions
"questions"
:
questions
_from_db
}
}
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"Error in get_mcqs_handler: {e}"
)
logger
.
error
(
f
"Error in get_mcqs_handler: {e}"
)
raise
HTTPException
(
status_code
=
500
,
detail
=
str
(
e
))
raise
HTTPException
(
status_code
=
500
,
detail
=
str
(
e
))
@
app
.
post
(
"/quiz/dynamic"
)
# --- STEP 3: UPDATE THE /quiz/dynamic ENDPOINT SIGNATURE ---
@
app
.
post
(
"/quiz/dynamic"
,
response_model
=
QuizResponse
)
async
def
get_dynamic_quiz_handler
(
async
def
get_dynamic_quiz_handler
(
request
:
Request
,
request
:
Request
,
curriculum
:
str
=
Form
(
...
),
curriculum
:
str
=
Form
(
...
),
...
@@ -417,11 +426,12 @@ def create_app() -> FastAPI:
...
@@ -417,11 +426,12 @@ def create_app() -> FastAPI:
count
:
int
=
Form
(
5
)
count
:
int
=
Form
(
5
)
):
):
"""
"""
Generates a dynamic quiz,
now using curriculum as a key identifier
.
Generates a dynamic quiz,
filtered to the 11-field response model
.
"""
"""
container
=
request
.
app
.
state
.
container
container
=
request
.
app
.
state
.
container
try
:
try
:
quiz_questions
=
container
.
agent_service
.
get_dynamic_quiz
(
# The service layer still returns the full objects
quiz_questions_full
=
container
.
agent_service
.
get_dynamic_quiz
(
curriculum
=
curriculum
,
curriculum
=
curriculum
,
grade
=
grade
,
grade
=
grade
,
subject
=
subject
,
subject
=
subject
,
...
@@ -430,10 +440,11 @@ def create_app() -> FastAPI:
...
@@ -430,10 +440,11 @@ def create_app() -> FastAPI:
is_arabic
=
is_arabic
,
is_arabic
=
is_arabic
,
count
=
count
count
=
count
)
)
# FastAPI will automatically filter `quiz_questions_full` to match the model
return
{
return
{
"status"
:
"success"
,
"status"
:
"success"
,
"message"
:
f
"Successfully generated a dynamic quiz with {len(quiz_questions)} questions."
,
"message"
:
f
"Successfully generated a dynamic quiz with {len(quiz_questions
_full
)} questions."
,
"quiz"
:
quiz_questions
"quiz"
:
quiz_questions
_full
}
}
except
HTTPException
as
e
:
except
HTTPException
as
e
:
raise
e
raise
e
...
...
self_hosted_env/voice_agent/schemas/__init__.py
View file @
56712e0e
from
.response
import
WebhookResponse
from
.response
import
WebhookResponse
from
.message
import
TextMessage
from
.message
import
TextMessage
from
.mcq
import
QuestionResponse
,
QuizResponse
,
MCQListResponse
\ No newline at end of file
self_hosted_env/voice_agent/schemas/mcq.py
0 → 100644
View file @
56712e0e
from
pydantic
import
BaseModel
from
typing
import
List
,
Optional
class
QuestionResponse
(
BaseModel
):
"""Defines the exact 11 fields to be returned for each question."""
question_text
:
str
question_type
:
Optional
[
str
]
=
None
correct_answer
:
str
wrong_answer_1
:
Optional
[
str
]
=
None
wrong_answer_2
:
Optional
[
str
]
=
None
wrong_answer_3
:
Optional
[
str
]
=
None
wrong_answer_4
:
Optional
[
str
]
=
None
difficulty_level
:
Optional
[
int
]
=
None
blooms_level
:
Optional
[
str
]
=
None
is_arabic
:
bool
hint
:
Optional
[
str
]
=
None
class
Config
:
orm_mode
=
True
# This helps Pydantic work with dictionary-like objects
class
QuizResponse
(
BaseModel
):
"""Defines the structure for the quiz endpoints."""
status
:
str
message
:
str
quiz
:
List
[
QuestionResponse
]
class
MCQListResponse
(
BaseModel
):
"""Defines the structure for the GET /mcq endpoint."""
status
:
str
count
:
int
questions
:
List
[
QuestionResponse
]
\ No newline at end of file
self_hosted_env/voice_agent/services/agent_service.py
View file @
56712e0e
This diff is collapsed.
Click to expand it.
self_hosted_env/voice_agent/services/pgvector_service.py
View file @
56712e0e
...
@@ -527,32 +527,32 @@ class PGVectorService:
...
@@ -527,32 +527,32 @@ class PGVectorService:
def
insert_mcqs
(
self
,
mcq_list
:
List
[
Dict
]):
def
insert_mcqs
(
self
,
mcq_list
:
List
[
Dict
]):
"""
"""
Inserts a batch of MCQs, now including
ALL new fields from the updated schema
.
Inserts a batch of MCQs, now including
the blooms_level field
.
"""
"""
if
not
mcq_list
:
if
not
mcq_list
:
return
return
with
self
.
pool_handler
.
get_connection
()
as
conn
:
with
self
.
pool_handler
.
get_connection
()
as
conn
:
with
conn
.
cursor
()
as
cur
:
with
conn
.
cursor
()
as
cur
:
# --- UPDATED INSERT QUERY
WITH ALL NEW COLUMNS
---
# --- UPDATED INSERT QUERY ---
insert_query
=
"""
insert_query
=
"""
INSERT INTO mcq_questions (
INSERT INTO mcq_questions (
curriculum, grade, subject, unit, concept, question_text,
curriculum, grade, subject, unit, concept, question_text,
question_type, difficulty_level, is_arabic, correct_answer,
question_type, difficulty_level,
blooms_level,
is_arabic, correct_answer,
wrong_answer_1, wrong_answer_2, wrong_answer_3, wrong_answer_4,
wrong_answer_1, wrong_answer_2, wrong_answer_3, wrong_answer_4,
question_image_url, correct_image_url, wrong_image_url_1,
question_image_url, correct_image_url, wrong_image_url_1,
wrong_image_url_2, wrong_image_url_3, wrong_image_url_4, hint
wrong_image_url_2, wrong_image_url_3, wrong_image_url_4, hint
) VALUES (
) VALUES (
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s
%
s,
%
s,
%
s,
%
s,
%
s,
%
s,
%
s
);
);
"""
"""
# --- UPDATED DATA PREPARATION TO MATCH THE NEW SCHEMA ---
# --- UPDATED DATA PREPARATION ---
# Using .get() provides safety against missing keys from the LLM response
data_to_insert
=
[
data_to_insert
=
[
(
(
q
.
get
(
'curriculum'
),
q
.
get
(
'grade'
),
q
.
get
(
'subject'
),
q
.
get
(
'unit'
),
q
.
get
(
'concept'
),
q
.
get
(
'curriculum'
),
q
.
get
(
'grade'
),
q
.
get
(
'subject'
),
q
.
get
(
'unit'
),
q
.
get
(
'concept'
),
q
.
get
(
'question_text'
),
q
.
get
(
'question_type'
),
q
.
get
(
'difficulty_level'
),
q
.
get
(
'question_text'
),
q
.
get
(
'question_type'
),
q
.
get
(
'difficulty_level'
),
q
.
get
(
'blooms_level'
),
# <-- ADDED THIS
q
.
get
(
'is_arabic'
),
q
.
get
(
'correct_answer'
),
q
.
get
(
'wrong_answer_1'
),
q
.
get
(
'is_arabic'
),
q
.
get
(
'correct_answer'
),
q
.
get
(
'wrong_answer_1'
),
q
.
get
(
'wrong_answer_2'
),
q
.
get
(
'wrong_answer_3'
),
q
.
get
(
'wrong_answer_4'
),
q
.
get
(
'wrong_answer_2'
),
q
.
get
(
'wrong_answer_3'
),
q
.
get
(
'wrong_answer_4'
),
q
.
get
(
'question_image_url'
),
q
.
get
(
'correct_image_url'
),
q
.
get
(
'wrong_image_url_1'
),
q
.
get
(
'question_image_url'
),
q
.
get
(
'correct_image_url'
),
q
.
get
(
'wrong_image_url_1'
),
...
...
self_hosted_env/voice_agent/setup_mcq_table.py
View file @
56712e0e
# setup_mcq_table.py
import
psycopg2
import
psycopg2
import
os
import
os
from
dotenv
import
load_dotenv
from
dotenv
import
load_dotenv
...
@@ -6,7 +8,7 @@ load_dotenv()
...
@@ -6,7 +8,7 @@ load_dotenv()
def
setup_mcq_table
(
drop_existing_table
:
bool
=
False
):
def
setup_mcq_table
(
drop_existing_table
:
bool
=
False
):
"""
"""
Sets up the mcq_questions table with the final
, comprehensive schema
.
Sets up the mcq_questions table with the final
schema, now including blooms_level
.
"""
"""
try
:
try
:
conn
=
psycopg2
.
connect
(
conn
=
psycopg2
.
connect
(
...
@@ -24,8 +26,8 @@ def setup_mcq_table(drop_existing_table: bool = False):
...
@@ -24,8 +26,8 @@ def setup_mcq_table(drop_existing_table: bool = False):
cur
.
execute
(
"DROP TABLE IF EXISTS mcq_questions CASCADE;"
)
cur
.
execute
(
"DROP TABLE IF EXISTS mcq_questions CASCADE;"
)
print
(
"Table dropped."
)
print
(
"Table dropped."
)
print
(
"Creating mcq_questions table with
the NEW COMPREHENSIVE schema
..."
)
print
(
"Creating mcq_questions table with
blooms_level column
..."
)
# ---
THIS IS THE FULLY UPDATED TABLE
SCHEMA ---
# ---
UPDATED
SCHEMA ---
cur
.
execute
(
"""
cur
.
execute
(
"""
CREATE TABLE IF NOT EXISTS mcq_questions (
CREATE TABLE IF NOT EXISTS mcq_questions (
id SERIAL PRIMARY KEY,
id SERIAL PRIMARY KEY,
...
@@ -37,25 +39,25 @@ def setup_mcq_table(drop_existing_table: bool = False):
...
@@ -37,25 +39,25 @@ def setup_mcq_table(drop_existing_table: bool = False):
question_text TEXT NOT NULL,
question_text TEXT NOT NULL,
question_type TEXT,
question_type TEXT,
difficulty_level INTEGER,
difficulty_level INTEGER,
blooms_level TEXT,
is_arabic BOOLEAN NOT NULL,
is_arabic BOOLEAN NOT NULL,
correct_answer TEXT NOT NULL,
correct_answer TEXT NOT NULL,
wrong_answer_1 TEXT,
wrong_answer_1 TEXT,
wrong_answer_2 TEXT,
wrong_answer_2 TEXT,
wrong_answer_3 TEXT,
wrong_answer_3 TEXT,
wrong_answer_4 TEXT,
wrong_answer_4 TEXT,
question_image_url TEXT,
-- Placeholder for MinIO URL
question_image_url TEXT,
correct_image_url TEXT,
-- Placeholder for MinIO URL
correct_image_url TEXT,
wrong_image_url_1 TEXT,
-- Placeholder for MinIO URL
wrong_image_url_1 TEXT,
wrong_image_url_2 TEXT,
-- Placeholder for MinIO URL
wrong_image_url_2 TEXT,
wrong_image_url_3 TEXT,
-- Placeholder for MinIO URL
wrong_image_url_3 TEXT,
wrong_image_url_4 TEXT,
-- Placeholder for MinIO URL
wrong_image_url_4 TEXT,
hint TEXT,
hint TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
);
"""
)
"""
)
print
(
"Creating indexes on mcq_questions table..."
)
print
(
"Creating indexes on mcq_questions table..."
)
# --- UPDATED INDEX TO INCLUDE CURRICULUM ---
cur
.
execute
(
"""
cur
.
execute
(
"""
CREATE INDEX IF NOT EXISTS idx_mcq_topic
CREATE INDEX IF NOT EXISTS idx_mcq_topic
ON mcq_questions(curriculum, grade, is_arabic, subject, unit, concept);
ON mcq_questions(curriculum, grade, is_arabic, subject, unit, concept);
...
@@ -71,7 +73,5 @@ def setup_mcq_table(drop_existing_table: bool = False):
...
@@ -71,7 +73,5 @@ def setup_mcq_table(drop_existing_table: bool = False):
print
(
"Database connection closed."
)
print
(
"Database connection closed."
)
if
__name__
==
"__main__"
:
if
__name__
==
"__main__"
:
# To apply the new schema, run this script.
print
(
"Setting up the MCQ table structure..."
)
# Set drop_existing_table=True to ensure a clean recreation.
print
(
"Setting up the new MCQ table structure..."
)
setup_mcq_table
(
drop_existing_table
=
True
)
setup_mcq_table
(
drop_existing_table
=
True
)
\ No newline at end of file
self_hosted_env/voice_agent/static/dynamic_quiz_interface.html
View file @
56712e0e
...
@@ -248,8 +248,15 @@
...
@@ -248,8 +248,15 @@
document
.
body
.
removeChild
(
link
);
document
.
body
.
removeChild
(
link
);
});
});
// --- THIS IS THE CORRECTED CSV EXPORT FUNCTION ---
function
convertQuizToCSV
(
quiz
)
{
function
convertQuizToCSV
(
quiz
)
{
const
headers
=
[
'Question'
,
'Correct Answer'
,
'Wrong Answer 1'
,
'Wrong Answer 2'
,
'Wrong Answer 3'
];
// Define the exact, case-sensitive headers you requested
const
headers
=
[
'question_text'
,
'question_type'
,
'correct_answer'
,
'wrong_answer_1'
,
'wrong_answer_2'
,
'wrong_answer_3'
,
'wrong_answer_4'
,
'difficulty_level'
,
'blooms_level'
,
'is_arabic'
,
'hint'
];
const
escapeCSV
=
(
str
)
=>
{
const
escapeCSV
=
(
str
)
=>
{
if
(
str
===
null
||
str
===
undefined
)
return
''
;
if
(
str
===
null
||
str
===
undefined
)
return
''
;
let
result
=
str
.
toString
();
let
result
=
str
.
toString
();
...
@@ -258,13 +265,24 @@
...
@@ -258,13 +265,24 @@
}
}
return
result
;
return
result
;
};
};
const
rows
=
quiz
.
map
(
q
=>
[
const
rows
=
quiz
.
map
(
q
=>
{
// Map the data to the specific headers in the correct order
return
[
escapeCSV
(
q
.
question_text
),
escapeCSV
(
q
.
question_text
),
escapeCSV
(
q
.
question_type
),
escapeCSV
(
q
.
correct_answer
),
escapeCSV
(
q
.
correct_answer
),
escapeCSV
(
q
.
wrong_answer_1
),
escapeCSV
(
q
.
wrong_answer_1
),
escapeCSV
(
q
.
wrong_answer_2
),
escapeCSV
(
q
.
wrong_answer_2
),
escapeCSV
(
q
.
wrong_answer_3
)
escapeCSV
(
q
.
wrong_answer_3
),
].
join
(
','
));
escapeCSV
(
q
.
wrong_answer_4
),
escapeCSV
(
q
.
difficulty_level
),
escapeCSV
(
q
.
blooms_level
),
q
.
is_arabic
?
'1'
:
'0'
,
escapeCSV
(
q
.
hint
)
].
join
(
','
);
});
return
[
headers
.
join
(
','
),
...
rows
].
join
(
'
\
n'
);
return
[
headers
.
join
(
','
),
...
rows
].
join
(
'
\
n'
);
}
}
...
...
self_hosted_env/voice_agent/va.tar
0 → 100644
View file @
56712e0e
File added
test_cases/test_mcq.py
0 → 100644
View file @
56712e0e
import
requests
import
json
import
time
# --- Configuration ---
# The base URL of your running FastAPI application.
# Change this if your app is running on a different host or port.
BASE_URL
=
"https://voice-agent-v2.caprover.al-arcade.com"
# Define the topic we will use for all tests
TEST_DATA
=
{
"curriculum"
:
"EGYPTIAN National"
,
"grade"
:
"4th Grade"
,
"subject"
:
"Science"
,
"unit"
:
"الوحدة الأولى: ما النظام؟"
,
"concept"
:
"المفهوم 3.1: الطاقة كنظام"
,
}
# --- Helper Function to Print Formatted JSON ---
def
print_json
(
data
,
title
=
""
):
"""
This function is responsible for printing the entire JSON response.
`json.dumps` with `indent=2` makes it readable.
`ensure_ascii=False` correctly displays Arabic characters.
"""
if
title
:
print
(
f
"--- {title} ---"
)
# This line prints the FULL response data.
print
(
json
.
dumps
(
data
,
indent
=
2
,
ensure_ascii
=
False
))
print
(
"
\n
"
+
"="
*
50
+
"
\n
"
)
# --- Test Functions ---
def
test_generate_mcqs
():
"""Tests the POST /mcq/generate endpoint."""
print
(
" Starting test for: POST /mcq/generate"
)
form_data
=
{
**
TEST_DATA
,
"count"
:
2
,
"is_arabic"
:
True
}
try
:
start_time
=
time
.
time
()
response
=
requests
.
post
(
f
"{BASE_URL}/mcq/generate"
,
data
=
form_data
)
duration
=
time
.
time
()
-
start_time
print
(
f
"Status Code: {response.status_code} (took {duration:.2f} seconds)"
)
response
.
raise_for_status
()
response_data
=
response
.
json
()
# >>> THE RESPONSE IS PRINTED RIGHT HERE <<<
print_json
(
response_data
,
"Full Response from /mcq/generate"
)
assert
response_data
[
"status"
]
==
"success"
print
(
"✅ Test for /mcq/generate PASSED"
)
except
requests
.
exceptions
.
RequestException
as
e
:
print
(
f
"❌ Test for /mcq/generate FAILED: An HTTP error occurred: {e}"
)
print
(
"--- Raw Response Text ---"
)
print
(
response
.
text
)
except
Exception
as
e
:
print
(
f
"❌ Test for /mcq/generate FAILED: An error occurred: {e}"
)
print
(
"--- Raw Response Text ---"
)
print
(
response
.
text
)
def
test_dynamic_quiz
():
"""Tests the POST /quiz/dynamic endpoint."""
print
(
" Starting test for: POST /quiz/dynamic"
)
form_data
=
{
**
TEST_DATA
,
"count"
:
3
,
"is_arabic"
:
True
}
try
:
start_time
=
time
.
time
()
response
=
requests
.
post
(
f
"{BASE_URL}/quiz/dynamic"
,
data
=
form_data
)
duration
=
time
.
time
()
-
start_time
print
(
f
"Status Code: {response.status_code} (took {duration:.2f} seconds)"
)
response
.
raise_for_status
()
response_data
=
response
.
json
()
# >>> THE RESPONSE IS PRINTED RIGHT HERE <<<
print_json
(
response_data
,
"Full Response from /quiz/dynamic"
)
assert
response_data
[
"status"
]
==
"success"
# Verify that the response is indeed filtered
if
response_data
.
get
(
"quiz"
):
first_question
=
response_data
[
"quiz"
][
0
]
if
"curriculum"
in
first_question
:
# This is not a failure of the test, but a failure of the API logic.
print
(
"⚠️ WARNING: /quiz/dynamic response was NOT filtered. It still contains the 'curriculum' field."
)
else
:
print
(
"✔️ Verification successful: /quiz/dynamic response is correctly filtered."
)
print
(
"✅ Test for /quiz/dynamic PASSED"
)
except
requests
.
exceptions
.
RequestException
as
e
:
print
(
f
"❌ Test for /quiz/dynamic FAILED: An HTTP error occurred: {e}"
)
print
(
"--- Raw Response Text ---"
)
print
(
response
.
text
)
except
Exception
as
e
:
print
(
f
"❌ Test for /quiz/dynamic FAILED: An error occurred: {e}"
)
print
(
"--- Raw Response Text ---"
)
print
(
response
.
text
)
# --- Main Execution Block ---
if
__name__
==
"__main__"
:
print
(
"Starting MCQ Endpoint Tests...
\n
"
)
# Run the first test
test_generate_mcqs
()
# Run the second test
test_dynamic_quiz
()
print
(
"All tests completed."
)
\ No newline at end of file
test_cases/test_msq.py
deleted
100644 → 0
View file @
8bbfd066
"""
======================================================================
MCQ API Cookbook & Test Script
======================================================================
Purpose:
This script serves as both a live integration test and a practical guide ("cookbook")
for using the Multiple-Choice Question (MCQ) generation and retrieval API endpoints.
It demonstrates how to:
1. Generate and store new MCQs for a specific curriculum topic.
2. Retrieve existing MCQs from the database for that same topic.
----------------------------------------------------------------------
API Endpoints Guide
----------------------------------------------------------------------
There are two main endpoints for the MCQ feature:
1. Generate Questions (POST /mcq/generate)
------------------------------------------
This is the "creator" endpoint. It uses an AI model to generate a new set of questions
based on the curriculum content stored in the vector database. It then saves these
new questions to the `mcq_questions` table for future use.
- Method: POST
- URL: [BASE_URL]/mcq/generate
- Data Format: Must be sent as `application/x-www-form-urlencoded` (form data).
Parameters (Form Data):
- grade (int, required): The grade level of the curriculum (e.g., 4).
- subject (str, required): The subject of the curriculum (e.g., "Science").
- unit (str, required): The exact name of the unit.
- concept (str, required): The exact name of the concept.
- is_arabic (bool, required): Set to `true` for Arabic curriculum, `false` for English.
- count (int, optional, default=5): The number of new questions to generate.
Example Usage (using cURL):
curl -X POST [BASE_URL]/mcq/generate
\
-F "grade=4"
\
-F "subject=Science"
\
-F "unit=الوحدة الأولى: الأنظمة الحية"
\
-F "concept=المفهوم الأول: التكيف والبقاء"
\
-F "is_arabic=true"
\
-F "count=3"
2. Retrieve Questions (GET /mcq)
---------------------------------
This is the "reader" endpoint. It quickly and cheaply retrieves questions that have
already been generated and stored in the database. It does NOT call the AI model.
- Method: GET
- URL: [BASE_URL]/mcq
Parameters (URL Query Parameters):
- grade (int, required): The grade level.
- subject (str, required): The subject.
- unit (str, required): The unit name.
- concept (str, required): The concept name.
- is_arabic (bool, required): `true` for Arabic, `false` for English.
- limit (int, optional, default=None): The maximum number of questions to retrieve.
If omitted, it will retrieve ALL questions for that topic.
Example Usage (using cURL):
# Get the 5 most recent questions for a topic
curl "[BASE_URL]/mcq?grade=4&subject=Science&unit=...&concept=...&is_arabic=true&limit=5"
# Get ALL questions for a topic
curl "[BASE_URL]/mcq?grade=4&subject=Science&unit=...&concept=...&is_arabic=true"
----------------------------------------------------------------------
How to Run This Script
----------------------------------------------------------------------
1. Ensure your FastAPI server is running.
2. Make sure the BASE_URL variable below is set to your server's address.
3. Run the script from your terminal: python3 msq_test.py
"""
import
requests
import
json
import
time
from
typing
import
Optional
# The base URL of your API server.
BASE_URL
=
"https://voice-agent.caprover.al-arcade.com"
def
test_mcq_generation
(
grade
:
int
,
subject
:
str
,
unit
:
str
,
concept
:
str
,
is_arabic
:
bool
,
count
:
int
):
"""
Tests the POST /mcq/generate endpoint.
"""
endpoint
=
f
"{BASE_URL}/mcq/generate"
payload
=
{
"grade"
:
grade
,
"subject"
:
subject
,
"unit"
:
unit
,
"concept"
:
concept
,
"is_arabic"
:
is_arabic
,
"count"
:
count
,
}
print
(
f
">> Attempting to GENERATE {count} new questions for:"
)
print
(
f
" Topic: Grade {grade} {subject} -> {unit} -> {concept}"
)
print
(
f
" Language: {'Arabic' if is_arabic else 'English'}"
)
try
:
response
=
requests
.
post
(
endpoint
,
data
=
payload
,
timeout
=
120
)
if
response
.
status_code
==
200
:
print
(
f
"SUCCESS: API returned status code {response.status_code}"
)
data
=
response
.
json
()
print
(
f
" Message: {data.get('message')}"
)
if
'questions'
in
data
and
data
[
'questions'
]:
print
(
"
\n
--- Details of Generated Questions ---"
)
for
i
,
q
in
enumerate
(
data
[
'questions'
],
1
):
print
(
f
" {i}. Question: {q['question_text']}"
)
print
(
f
" Correct: {q['correct_answer']}"
)
print
(
f
" Wrong 1: {q['wrong_answer_1']}"
)
print
(
f
" Wrong 2: {q['wrong_answer_2']}"
)
print
(
f
" Wrong 3: {q['wrong_answer_3']}
\n
"
)
return
True
else
:
print
(
f
"FAILED: API returned status code {response.status_code}"
)
try
:
error_data
=
response
.
json
()
print
(
f
" Error Detail: {error_data.get('detail', 'No detail provided.')}"
)
except
json
.
JSONDecodeError
:
print
(
f
" Response was not valid JSON: {response.text}"
)
return
False
except
requests
.
exceptions
.
RequestException
as
e
:
print
(
f
"FAILED: An error occurred while making the request: {e}"
)
return
False
def
test_mcq_retrieval
(
grade
:
int
,
subject
:
str
,
unit
:
str
,
concept
:
str
,
is_arabic
:
bool
,
limit
:
Optional
[
int
]):
"""
Tests the GET /mcq endpoint with detailed output.
"""
endpoint
=
f
"{BASE_URL}/mcq"
params
=
{
"grade"
:
grade
,
"subject"
:
subject
,
"unit"
:
unit
,
"concept"
:
concept
,
"is_arabic"
:
is_arabic
,
}
if
limit
is
not
None
:
params
[
"limit"
]
=
limit
limit_str
=
f
"up to {limit}"
if
limit
is
not
None
else
"ALL"
print
(
f
">> Attempting to RETRIEVE {limit_str} stored questions for the same topic..."
)
try
:
response
=
requests
.
get
(
endpoint
,
params
=
params
,
timeout
=
30
)
if
response
.
status_code
==
200
:
print
(
f
"SUCCESS: API returned status code {response.status_code}"
)
data
=
response
.
json
()
print
(
f
" Found {data.get('count')} stored questions in the database."
)
if
'questions'
in
data
and
data
[
'questions'
]:
print
(
"
\n
--- Details of Retrieved Questions ---"
)
for
i
,
q
in
enumerate
(
data
[
'questions'
],
1
):
print
(
f
" {i}. Question: {q['question_text']}"
)
print
(
f
" Correct: {q['correct_answer']}"
)
print
(
f
" Wrong 1: {q['wrong_answer_1']}"
)
print
(
f
" Wrong 2: {q['wrong_answer_2']}"
)
print
(
f
" Wrong 3: {q['wrong_answer_3']}
\n
"
)
elif
data
.
get
(
'count'
)
==
0
:
print
(
" (This is expected if this is the first time generating questions for this topic)"
)
return
True
else
:
print
(
f
"FAILED: API returned status code {response.status_code}"
)
try
:
error_data
=
response
.
json
()
print
(
f
" Error Detail: {error_data.get('detail', 'No detail provided.')}"
)
except
json
.
JSONDecodeError
:
print
(
f
" Response was not valid JSON: {response.text}"
)
return
False
except
requests
.
exceptions
.
RequestException
as
e
:
print
(
f
"FAILED: An error occurred while making the request: {e}"
)
return
False
if
__name__
==
"__main__"
:
print
(
"
\n
"
+
"="
*
50
)
print
(
"STARTING TEST 1: ARABIC MCQ GENERATION & RETRIEVAL"
)
print
(
"="
*
50
)
# IMPORTANT: Use actual Unit/Concept names from your database for the best results.
arabic_test_data
=
{
"grade"
:
4
,
"subject"
:
"Science"
,
"unit"
:
"الوحدة الأولى: الأنظمة الحية"
,
"concept"
:
"المفهوم الأول: التكيف والبقاء"
,
"is_arabic"
:
True
,
"count"
:
3
}
generation_successful
=
test_mcq_generation
(
**
arabic_test_data
)
if
generation_successful
:
print
(
"-"
*
25
)
time
.
sleep
(
2
)
test_mcq_retrieval
(
limit
=
None
,
**
{
k
:
v
for
k
,
v
in
arabic_test_data
.
items
()
if
k
!=
'count'
})
print
(
"
\n
"
+
"="
*
50
)
print
(
"STARTING TEST 2: ENGLISH MCQ GENERATION & RETRIEVAL"
)
print
(
"="
*
50
)
english_test_data
=
{
"grade"
:
5
,
"subject"
:
"Science"
,
"unit"
:
"Unit 1: Matter and Energy in Ecosystems"
,
"concept"
:
"Concept 1.1: Properties of Matter"
,
"is_arabic"
:
False
,
"count"
:
2
}
generation_successful
=
test_mcq_generation
(
**
english_test_data
)
if
generation_successful
:
print
(
"-"
*
25
)
time
.
sleep
(
2
)
test_mcq_retrieval
(
limit
=
None
,
**
{
k
:
v
for
k
,
v
in
english_test_data
.
items
()
if
k
!=
'count'
})
print
(
"
\n
"
+
"="
*
50
)
print
(
"All tests complete."
)
print
(
"="
*
50
)
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment