We are building an adaptive quiz generator that turns any study topic into an interactive tutoring session. It tracks what you get wrong, adjusts difficulty, and explains answers in plain language. I built this for a certification prep group that needed something cheaper and more flexible than static flashcards.
What you'll need
- Python 3.10 or newer
- An Oxlo.ai API key from https://portal.oxlo.ai
- The OpenAI SDK:
pip install openai
Step 1: Initialize the Oxlo.ai client
Create a client pointing to Oxlo.ai. I use llama-3.3-70b as the workhorse because it handles general reasoning and knowledge well, but you can swap in kimi-k2.6 or deepseek-v3.2 without changing any other code.
from openai import OpenAI
import re
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
def chat(messages, model="llama-3.3-70b"):
response = client.chat.completions.create(
model=model,
messages=messages,
)
return response.choices[0].message.content
Step 2: Define the system prompt
The system prompt enforces a rigid output format so we can parse questions and difficulty levels without guesswork. This reduces the need for retries and keeps the session moving.
SYSTEM_PROMPT = """You are an adaptive exam tutor.
Rules:
1. Generate one multiple-choice question at a time.
2. Provide exactly four options labeled A, B, C, D.
3. State the correct answer clearly.
4. Explain why the correct answer is right and why the distractors are wrong.
5. If the user was previously incorrect, lower the difficulty or reinforce the concept. If correct, raise it or test an adjacent topic.
6. Use this exact format:
Question:
A.
B.
C.
D.
Correct:
Explanation:
Next Difficulty:
"""
Step 3: Parse structured output
I use a small line parser so we can extract the question text, options, and difficulty without extra dependencies. It overwrites fields as it scans, so if the model returns an evaluation followed by a new question, the last values win.
def parse_question(text):
data = {
"question": "",
"options": {},
"correct": "",
"explanation": "",
"next_difficulty": "Medium"
}
for line in text.splitlines():
line = line.strip()
if line.startswith("Question:"):
data["question"] = line.split(":", 1)[1].strip()
elif re.match(r"^[A-D]\.", line):
key = line[0]
data["options"][key] = line[2:].strip()
elif line.startswith("Correct:"):
data["correct"] = line.split(":", 1)[1].strip()
elif line.startswith("Explanation:"):
data["explanation"] = line.split(":", 1)[1].strip()
elif line.startswith("Next Difficulty:"):
data["next_difficulty"] = line.split(":", 1)[1].strip()
return data
Step 4: Generate the first question
Seed the conversation with a topic and a starting difficulty. Because Oxlo.ai charges per request rather than per token, growing the conversation history does not inflate cost, which matters once we start appending turns. You can see plan details at https://oxlo.ai/pricing.
def start_session(topic):
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Topic: {topic}\nDifficulty: Medium\nGenerate the first question."}
]
return messages
Step 5: Evaluate answers and adapt
Wire everything into a small CLI loop that runs for a fixed number of questions. I keep the full history in a single messages list so the tutor remembers what the user struggled with. Each iteration prints the model output, reads the user answer, and appends both sides to the context.
def run_session(topic, rounds=3):
messages = start_session(topic)
print(f"Starting study session on: {topic}\n")
for i in range(rounds):
resp = chat(messages)
print(resp)
print("-" * 40)
parsed = parse_question(resp)
if not parsed["question"]:
print("Could not parse a question. Stopping.")
break
user_ans = input("Your answer (A/B/C/D): ").strip().upper()
messages.append({"role": "assistant", "content": resp})
messages.append({
"role": "user",
"content": f"My answer is {user_ans}. Evaluate it, explain briefly, and then generate the next question at the appropriate difficulty."
})
print("Study session complete.")
Run it
Save the script as tutor.py and run it. Here is a sample session on distributed systems.
if __name__ == "__main__":
run_session("Distributed Systems", rounds=2)
Example output:
$ python tutor.py
Starting study session on: Distributed Systems
Question: According to the CAP theorem, which statement is true for distributed systems during a network partition?
A. All three properties can be guaranteed simultaneously.
B. Partition tolerance can be sacrificed to maintain consistency and availability.
C. A choice must be made between consistency and availability.
D. The theorem only applies to NoSQL databases.
Correct: C
Explanation: The CAP theorem states that a distributed system cannot simultaneously provide consistency, availability, and partition tolerance. Because network partitions are unavoidable, the system must choose between consistency and availability.
Next Difficulty: Medium
----------------------------------------
Your answer (A/B/C/D): C
That is correct. During a partition, trading off between consistency and availability is the core implication of the theorem.
Question: What does a vector clock primarily help determine in a distributed system?
A. Encryption strength between nodes.
B. The partial ordering of events without synchronized physical clocks.
C. Automatic data replication frequency.
D. Network latency bounds.
Correct: B
Explanation: Vector clocks capture the happens-before relationship across events in different nodes, establishing partial ordering without requiring synchronized clocks.
Next Difficulty: Hard
----------------------------------------
Your answer (A/B/C/D): B
Correct again. Vector clocks are fundamental to understanding causality in distributed environments.
Study session complete.
Next steps
Store performance per topic in a local SQLite database so the tutor remembers weak areas across sessions. You could also integrate spaced repetition by scheduling follow-up topics based on the difficulty progression and error rate.
Top comments (0)