We are building a conversational CLI tutor that practices Spanish, logs your mistakes, and quizzes you on weak spots. It is aimed at developers who want a structured, repeatable way to build speaking confidence without scheduling human conversation partners. We will run it on Oxlo.ai because its flat per-request pricing keeps costs predictable even when we add agentic logging and review steps that would inflate token bills elsewhere.
What you'll need
- Python 3.10 or newer
- The OpenAI SDK:
pip install openai - An Oxlo.ai API key from https://portal.oxlo.ai
Step 1: Scaffold the client and tutor persona
First, initialize the OpenAI-compatible client pointing at Oxlo.ai. I use qwen-3-32b because it handles multilingual reasoning and agent workflows well. The system prompt below enforces Spanish output, gentle corrections, and follow-up questions.
SYSTEM_PROMPT = """You are a patient Spanish tutor for an English-speaking developer.
Rules:
1. Respond in Spanish first, then provide the English translation in parentheses.
2. If the user makes a grammar or vocabulary error, politely correct it and explain briefly in English.
3. Keep explanations under 25 words.
4. Ask a follow-up question to keep the conversation alive.
5. Do not switch to English outside of the parenthetical translation and the brief correction note."""
from openai import OpenAI
import json, datetime, os
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.getenv("OXLO_API_KEY")
)
history = [{"role": "system", "content": SYSTEM_PROMPT}]
Step 2: Build the conversation loop
We need a function that appends the user message, calls Oxlo.ai, and stores the assistant reply so context persists across turns.
def tutor_turn(user_input, history):
history.append({"role": "user", "content": user_input})
response = client.chat.completions.create(
model="qwen-3-32b",
messages=history,
temperature=0.7,
)
reply = response.choices[0].message.content
history.append({"role": "assistant", "content": reply})
return reply
Step 3: Log mistakes and vocabulary
To make progress tangible, we run a second lightweight call after each turn to extract mistakes and new vocabulary into a JSONL file. Because Oxlo.ai charges a flat rate per request rather than per token, adding this agentic extraction step stays economical even as the conversation grows long.
def extract_learning_data(user_input, tutor_reply, turn_index):
prompt = f"""Analyze this Spanish exchange. Return strict JSON with keys:
- mistakes: list of strings describing each error
- vocabulary: list of objects with "word" (Spanish) and "meaning" (English)
User: {user_input}
Tutor: {tutor_reply}"""
res = client.chat.completions.create(
model="llama-3.3-70b",
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"},
temperature=0.2,
)
data = json.loads(res.choices[0].message.content)
record = {
"turn": turn_index,
"timestamp": datetime.datetime.now().isoformat(),
**data
}
with open("session_log.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
return data
Step 4: Generate a personalized review quiz
After a few turns, the user can run a review. The function reads the session log and asks the model to build a short quiz targeting the logged mistakes and vocabulary.
def generate_review():
if not os.path.exists("session_log.jsonl"):
return {"note": "No session data found yet. Have a conversation first."}
entries = []
with open("session_log.jsonl", "r", encoding="utf-8") as f:
for line in f:
entries.append(json.loads(line))
recent = entries[-5:]
payload = json.dumps(recent, ensure_ascii=False)
quiz_prompt = f"""You are a quiz generator. Based on the following session log entries, create a 3-question multiple-choice quiz in Spanish to test the user.
For each question, provide the question, four options labeled A-D, and the correct letter.
Return strict JSON with key "quiz" containing a list of objects with "question", "options", and "answer".
Log entries: {payload}"""
res = client.chat.completions.create(
model="deepseek-v3.2",
messages=[{"role": "user", "content": quiz_prompt}],
response_format={"type": "json_object"},
temperature=0.3,
)
return json.loads(res.choices[0].message.content)
Step 5: Wire up the CLI
The main loop routes commands and orchestrates the tutor, the logger, and the review mode.
def main():
print("Spanish Tutor CLI")
print("Commands: /review, /quit")
print("-" * 40)
turn = 0
while True:
try:
user_input = input("You: ").strip()
except (EOFError, KeyboardInterrupt):
break
if not user_input:
continue
if user_input.lower() == "/quit":
print("Adios!")
break
if user_input.lower() == "/review":
quiz = generate_review()
print("\n--- Review ---")
print(json.dumps(quiz, indent=2, ensure_ascii=False))
print("--------------\n")
continue
turn += 1
reply = tutor_turn(user_input, history)
print(f"Tutor: {reply}\n")
try:
extract_learning_data(user_input, reply, turn)
except Exception as e:
print(f"[log error: {e}]")
if __name__ == "__main__":
main()
Run it
Export your key and start the script.
export OXLO_API_KEY="sk-oxlo.ai-..."
python tutor.py
Example session:
Spanish Tutor CLI
----------------------------------------
You: Quiero un cafe con leche, por favor.
Tutor: Quiero un café con leche, por favor. (I want a coffee with milk, please.)
Recuerda usar acentos: "café" lleva tilde. ¿Te gusta el café fuerte o suave?
(Remember to use accents: "café" has a tilde. Do you like strong or mild coffee?)
You: Me gusta fuerte. Donde esta la estacion de tren?
Tutor: Me gusta fuerte. ¿Dónde está la estación de tren? (I like it strong. Where is the train station?)
Nota: "Donde" y "estacion" necesitan tildes: "dónde", "estación". ¿Vas a viajar hoy?
(Note: "Donde" and "estacion" need accents: "dónde", "estación". Are you traveling today?)
You: /review
--- Review ---
{
"quiz": [
{
"question": "¿Cómo se escribe correctamente 'cafe' en español?",
"options": {
"A": "cafe",
"B": "café",
"C": "caffé",
"D": "kafe"
},
"answer": "B"
},
{
"question": "¿Cuál es la forma correcta de preguntar 'Where is the train station?'",
"options": {
"A": "Donde esta la estacion de tren",
"B": "Dónde está la estación de tren",
"C": "Donde es la estacion del tren",
"D": "Dónde es la estación de tren"
},
"answer": "B"
}
]
}
--------------
Wrap-up and next steps
This gives you a persistent, self-improving tutor in under 100 lines. Two concrete next steps: wire up Oxlo.ai's audio/transcriptions endpoint with Whisper to let users speak their answers instead of typing, or schedule a daily review by feeding session_log.jsonl into a spaced-repetition algorithm.
Top comments (0)