We are going to build a conversational language tutor that corrects mistakes in real time, adapts its complexity to your proficiency, and maintains context across a long practice session. It is meant for developers who want to drop a speaking partner into an edtech product, or for learners who prefer to own their data and their prompt. Oxlo.ai works well here because its request-based pricing stays flat even when you send long conversation histories or full grammar guides in the system prompt.
What you'll need
You need Python 3.10 or newer, the official OpenAI SDK, and an API key from Oxlo.ai. Head to https://portal.oxlo.ai to grab a key. Install the SDK with pip.
pip install openai
Step 1: Initialize the Oxlo.ai client
I always start with a smoke test to confirm the key and base URL are correct. The snippet below calls llama-3.3-70b, which is my default for English and Romance language tutoring because it follows long instructions without drifting.
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Say hello in Spanish and ask how I am doing."},
],
)
print(response.choices[0].message.content)
Step 2: Write the tutor system prompt
This prompt is the core product decision. It tells the model to stay in the target language, correct errors inline, and adapt to a CEFR level. I keep it in a constant so I can version it in git.
SYSTEM_PROMPT = """You are a conversational language tutor.
Target language: {target_language}
Learner CEFR level: {level}
Rules:
1. Conduct the session entirely in the target language unless the user writes "ENGLISH".
2. When the learner makes a mistake, repeat the corrected phrase immediately, then continue.
3. Use vocabulary and grammar suited to the CEFR level.
4. Keep replies to two or three sentences so the learner is not overwhelmed.
5. End every reply with one short question to keep the conversation alive."""
target_language = "Spanish"
level = "A2"
formatted_prompt = SYSTEM_PROMPT.format(target_language=target_language, level=level)
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": formatted_prompt},
{"role": "user", "content": "Hola, me llamo David. Soy de Boston."},
],
)
print(response.choices[0].message.content)
Step 3: Manage conversation memory and session state
A real tutor remembers what you got wrong three turns ago. I store the message list in a simple class and append each exchange. Because Oxlo.ai charges per request, not per token, I do not stress about the growing context length. I still cap history at twenty turns to keep latency reasonable.
from openai import OpenAI
class TutorSession:
def __init__(self, api_key, target_language="Spanish", level="A2"):
self.client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key=api_key)
self.target_language = target_language
self.level = level
self.messages = []
self._inject_system_prompt()
def _inject_system_prompt(self):
prompt = f"""You are a conversational language tutor.
Target language: {self.target_language}
Learner CEFR level: {self.level}
Rules:
1. Conduct the session entirely in the target language unless the user writes "ENGLISH".
2. When the learner makes a mistake, repeat the corrected phrase immediately, then continue.
3. Use vocabulary and grammar suited to the CEFR level.
4. Keep replies to two or three sentences.
5. End every reply with one short question."""
self.messages.append({"role": "system", "content": prompt})
def say(self, user_text):
self.messages.append({"role": "user", "content": user_text})
response = self.client.chat.completions.create(
model="llama-3.3-70b",
messages=self.messages,
)
reply = response.choices[0].message.content
self.messages.append({"role": "assistant", "content": reply})
# Keep last 20 turns plus system
if len(self.messages) > 41:
self.messages = [self.messages[0]] + self.messages[-40:]
return reply
# Test
tutor = TutorSession(api_key="YOUR_OXLO_API_KEY")
print(tutor.say("Hola, me llamo David. Soy de Boston."))
Step 4: Adapt difficulty based on learner level
I want to change the CEFR level mid-session without losing the conversation history. The cleanest way is to rewrite the system message in place and keep the rest of the list intact.
from openai import OpenAI
class AdaptiveTutorSession:
def __init__(self, api_key, target_language="Spanish", level="A2"):
self.client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key=api_key)
self.target_language = target_language
self.level = level
self.messages = []
self._inject_system_prompt()
def _inject_system_prompt(self):
prompt = f"""You are a conversational language tutor.
Target language: {self.target_language}
Learner CEFR level: {self.level}
Rules:
1. Stay in the target language.
2. Correct mistakes inline.
3. Match vocabulary and grammar to the stated CEFR level.
4. Keep replies to two or three sentences.
5. Ask a follow-up question."""
if self.messages and self.messages[0]["role"] == "system":
self.messages[0]["content"] = prompt
else:
self.messages.insert(0, {"role": "system", "content": prompt})
def set_level(self, new_level):
self.level = new_level
self._inject_system_prompt()
def say(self, user_text):
self.messages.append({"role": "user", "content": user_text})
response = self.client.chat.completions.create(
model="llama-3.3-70b",
messages=self.messages,
)
reply = response.choices[0].message.content
self.messages.append({"role": "assistant", "content": reply})
if len(self.messages) > 41:
self.messages = [self.messages[0]] + self.messages[-40:]
return reply
tutor = AdaptiveTutorSession(api_key="YOUR_OXLO_API_KEY")
print(tutor.say("Hola, me llamo David."))
tutor.set_level("B1")
print(tutor.say("Me gusta mucho la musica y quiero viajar a Madrid."))
Step 5: Route multilingual sessions to the right model
Llama 3.3 70B is my workhorse for Spanish and English, but when a friend wanted to practice Mandarin I switched to qwen-3-32b. It is built for multilingual reasoning and handles tonal languages with fewer romanization crutches. I wrap the model choice in a property so the rest of the tutor stays the same.
from openai import OpenAI
class MultilingualTutor:
MODEL_MAP = {
"Spanish": "llama-3.3-70b",
"English": "llama-3.3-70b",
"Mandarin": "qwen-3-32b",
"French": "llama-3.3-70b",
"German": "qwen-3-32b",
}
def __init__(self, api_key, target_language="Spanish", level="A2"):
self.client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key=api_key)
self.target_language = target_language
self.level = level
self.messages = []
self._inject_system_prompt()
@property
def model(self):
return self.MODEL_MAP.get(self.target_language, "llama-3.3-70b")
def _inject_system_prompt(self):
prompt = f"""You are a conversational language tutor.
Target language: {self.target_language}
Learner CEFR level: {self.level}
Rules:
1. Stay in the target language.
2. Correct mistakes gently and immediately.
3. Match complexity to the CEFR level.
4. Keep replies short.
5. Ask one follow-up question."""
if self.messages and self.messages[0]["role"] == "system":
self.messages[0]["content"] = prompt
else:
self.messages.insert(0, {"role": "system", "content": prompt})
def say(self, user_text):
self.messages.append({"role": "user", "content": user_text})
response = self.client.chat.completions.create(
model=self.model,
messages=self.messages,
)
reply = response.choices[0].message.content
self.messages.append({"role": "assistant", "content": reply})
if len(self.messages) > 41:
self.messages = [self.messages[0]] + self.messages[-40:]
return reply
tutor = MultilingualTutor(api_key="YOUR_OXLO_API_KEY", target_language="Mandarin", level="A1")
print(tutor.say("你好,我叫大卫。"))
Run it
Here is a complete script that runs a three-turn Spanish drill at A2 level. I print the tutor's replies so you can see the inline corrections and the follow-up questions.
from openai import OpenAI
class LanguageTutor:
def __init__(self, api_key, target_language="Spanish", level="A2"):
self.client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key=api_key)
self.target_language = target_language
self.level = level
self.messages = []
self._inject_system_prompt()
@property
def model(self):
mapping = {
"Spanish": "llama-3.3-70b",
"Mandarin": "qwen-3-32b",
"German": "qwen-3-32b",
}
return mapping.get(self.target_language, "llama-3.3-70b")
def _inject_system_prompt(self):
prompt = f"""You are a conversational language tutor.
Target language: {self.target_language}
Learner CEFR level: {self.level}
Rules:
1. Conduct the session entirely in the target language.
2. Correct mistakes by stating the correction, then continue naturally.
3. Use grammar and vocabulary suited to the CEFR level.
4. Limit replies to two or three sentences.
5. End with one short question."""
if self.messages and self.messages[0]["role"] == "system":
self.messages[0]["content"] = prompt
else:
self.messages.insert(0, {"role": "system", "content": prompt})
def say(self, user_text):
self.messages.append({"role": "user", "content": user_text})
response = self.client.chat.completions.create(
model=self.model,
messages=self.messages,
)
reply = response.choices[0].message.content
self.messages.append({"role": "assistant", "content": reply})
if len(self.messages) > 41:
self.messages = [self.messages[0]] + self.messages[-40:]
return reply
if __name__ == "__main__":
tutor = LanguageTutor(api_key="YOUR_OXLO_API_KEY", target_language="Spanish", level="A2")
turns = [
"Hola, me llamo David. Soy de Boston.",
"Me gusta mucho la musica y quiero viajar a Espana.",
"Prefiero el flamenco y la comida tradicional.",
]
for user_input in turns:
print(f"User: {user_input}")
reply = tutor.say(user_input)
print(f"Tutor: {reply}\n")
Example output:
User: Hola, me llamo David. Soy de Boston.
Tutor: ¡Hola, David! Encantado de conocerte. (Soy de Boston is correct.) ¿Desde cuándo vives allí?
User: Me gusta mucho la musica y quiero viajar a Espana.
Tutor: ¡Qué bien! (musica -> música; Espana -> España). ¿Qué ciudad te gustaría visitar primero?
User: Prefiero el flamenco y la comida tradicional.
Tutor: El flamenco es muy apasionado. ¿Has visto algún espectáculo en vivo?
The actual output will vary slightly, but you should see corrections in parentheses and a follow-up question each time.
Wrap-up and next steps
You now have a stateful language tutor that runs on Oxlo.ai with flat per-request pricing, so you can stuff the system prompt with long grammar guides without inflating your bill. See https://oxlo.ai/pricing for plan details. If you want the tutor to explain complex grammar with explicit chain-of-thought reasoning, swap the model to deepseek-v3.2. If you add image-based vocabulary flashcards, switch to kimi-k2.6 for its vision and long-context capabilities.
Top comments (0)