Project overview
I built a conversational language tutor that corrects grammar in real time, explains mistakes, and tracks vocabulary across turns. It runs on Oxlo.ai, where flat per-request pricing means a long, detailed correction costs the same as a single word, so extended practice sessions stay predictable. This tutorial walks through the exact code I shipped so you can adapt it for any target language.
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: Initialize the Oxlo.ai client
I start by importing the SDK and pointing it at Oxlo.ai's endpoint. Because Oxlo.ai is fully OpenAI SDK compatible, this is the only setup required.
from openai import OpenAI
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key="YOUR_OXLO_API_KEY"
)
Step 2: Define the tutor system prompt
The system prompt is the core product decision. I instruct the model to stay in the target language, wrap corrections in parseable tags, and append new vocabulary. I tuned this for Spanish learners at the A2 level, but you can swap the language and proficiency.
SYSTEM_PROMPT = """You are a patient Spanish tutor helping an English speaker at the A2 level.
Rules:
1. Always conduct the main conversation in Spanish.
2. If the student makes a grammar, spelling, or vocabulary error, wrap the corrected version in [correction]...[/correction] tags.
3. Immediately after the correction, add a short English explanation inside [explain]...[/explain] tags.
4. Identify any new or notable vocabulary words the student encountered in this turn. List them at the very end of your response using [vocab]Spanish word - English definition[/vocab] tags, one per line.
5. If the student writes in English, reply in Spanish but provide an English translation of your reply inside [translate]...[/translate] tags.
6. Keep your entire response under 120 words so it is digestible."""
MODEL = "qwen-3-32b"
Step 3: Parse structured tutor responses
To make the tutor useful, I extract the structured pieces so I can display corrections prominently and save vocabulary to a list. I use simple regex rather than a heavy parser.
import re
def parse_tutor_response(text):
correction = re.search(r'\[correction\](.*?)\[/correction\]', text, re.DOTALL)
explanation = re.search(r'\[explain\](.*?)\[/explain\]', text, re.DOTALL)
translation = re.search(r'\[translate\](.*?)\[/translate\]', text, re.DOTALL)
vocab = re.findall(r'\[vocab\](.*?)\[/vocab\]', text)
clean = re.sub(r'\[(correction|explain|translate|vocab)\].*?\[/\1\]', '', text, flags=re.DOTALL)
clean = re.sub(r'\n\s*\n', '\n', clean).strip()
return {
"clean_reply": clean,
"correction": correction.group(1).strip() if correction else None,
"explanation": explanation.group(1).strip() if explanation else None,
"translation": translation.group(1).strip() if translation else None,
"vocab": [v.strip() for v in vocab]
}
Step 4: Maintain conversation memory
The tutor needs context. I keep a messages list that grows with each turn and is sent to Oxlo.ai on every request. Because Oxlo.ai uses flat per-request pricing, growing this history does not inflate the cost, which makes long practice sessions predictable.
def get_tutor_reply(client, messages):
response = client.chat.completions.create(
model=MODEL,
messages=messages,
temperature=0.7,
max_tokens=512
)
return response.choices[0].message.content
Step 5: Build the interactive loop
I tie the pieces together in a small CLI loop. It prints the clean reply, surfaces corrections and explanations, and accumulates vocabulary in a session list.
def run_language_tutor():
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
session_vocab = set()
print("Spanish Tutor: Hola. ¿Cómo estás hoy? (type 'exit' to quit)")
while True:
user_input = input("You: ").strip()
if user_input.lower() in ("exit", "quit"):
break
messages.append({"role": "user", "content": user_input})
raw_reply = get_tutor_reply(client, messages)
parsed = parse_tutor_response(raw_reply)
print(f"Tutor: {parsed['clean_reply']}")
if parsed['correction']:
print(f" Correction: {parsed['correction']}")
print(f" Why: {parsed['explanation']}")
if parsed['translation']:
print(f" Translation: {parsed['translation']}")
if parsed['vocab']:
for item in parsed['vocab']:
if item not in session_vocab:
print(f" New vocab: {item}")
session_vocab.add(item)
messages.append({"role": "assistant", "content": raw_reply})
if __name__ == "__main__":
run_language_tutor()
Run it
Save the full script as tutor.py, export your key, and run it. Here is a sample session using Oxlo.ai's Qwen 3 32B.
$ export OXLO_API_KEY="sk-oxlo.ai-..."
$ python tutor.py
Spanish Tutor: Hola. ¿Cómo estás hoy? (type 'exit' to quit)
You: Yo tengo hambre, pero no sé donde está el restaurante
Tutor: Hola. Tienes hambre, qué pena. El restaurante está cerca del parque.
Correction: Yo tengo hambre, pero no sé dónde está el restaurante
Why: "donde" needs an accent when it means "where" as an interrogative or relative pronoun in a question.
New vocab: cerca del parque - near the park
You: Gracias. Voy a comer una manzana ahora.
Tutor: De nada. ¡Buen provecho! Disfruta tu manzana.
You: exit
Next steps
To productionize this, wire in Oxlo.ai's audio endpoints. You can pass the tutor's clean reply to the Kokoro text-to-speech model for pronunciation drills, or transcribe the student's spoken responses with Whisper Large v3 before sending them to the chat model. If you are building for a classroom, review the plans at https://oxlo.ai/pricing to find a daily request volume that fits your needs.
Top comments (0)