We are building an adaptive conversational language tutor that maintains session memory, corrects grammar gently, and tracks recurring mistakes. It is useful for developers integrating interactive speaking practice into ed-tech products or internal training tools. Because the agent runs long, multi-turn sessions, Oxlo.ai's flat per-request pricing keeps costs predictable regardless of how much context you accumulate.
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: Verify the Oxlo.ai endpoint
Before adding any tutoring logic, confirm that your environment can reach Oxlo.ai and that your key is active. This sanity check avoids debugging connection issues later.
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": "user", "content": "Reply with exactly: Oxlo.ai connection OK"}
],
)
print(response.choices[0].message.content)
Step 2: Write the system prompt
The system prompt is the core of the tutor. It constrains the model to stay in the target language, correct errors inline, and append structured metadata so we can extract mistakes programmatically.
SYSTEM_PROMPT = """You are a patient conversational language tutor helping an adult learner practice Spanish.
Rules:
1. Conduct the entire session in Spanish, except when explaining grammar.
2. If the student makes a mistake, gently correct it and briefly explain why.
3. Keep responses to 2 or 3 sentences so the student does most of the talking.
4. Ask a follow-up question to keep the conversation alive.
5. Adapt vocabulary to an intermediate level unless the student asks otherwise.
6. After your reply, output a line exactly `---METADATA---` followed by a JSON object with keys: "correction" (the corrected phrase, or null) and "error_type" (e.g., "verb_conjugation", "gender", or null).
Example:
Student: Yo tener dos gatos.
Tutor: Casi. Di "Yo tengo dos gatos". "Tener" cambia a "tengo" para "yo". ¿Tienes otros animales en casa?
---METADATA---
{"correction": "Yo tengo dos gatos", "error_type": "verb_conjugation"}
"""
Step 3: Build the tutor class with memory
We need a class that persists conversation history across turns. Each exchange appends to a message list that gets fed back into the context window on the next request.
import json
from openai import OpenAI
class LanguageTutor:
def __init__(self, api_key, target_language="Spanish"):
self.client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key=api_key)
self.model = "llama-3.3-70b"
self.history = []
self.target_language = target_language
def chat(self, user_message):
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
] + self.history + [
{"role": "user", "content": user_message},
]
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=0.7,
)
raw = response.choices[0].message.content
self.history.append({"role": "user", "content": user_message})
self.history.append({"role": "assistant", "content": raw})
return raw
Step 4: Add adaptive error tracking
To make the tutor adaptive, we parse the metadata block after each reply, log the error type, and inject a short instructor note into the system context when patterns repeat. This nudges the model to pay closer attention to persistent weak spots without adding extra API calls.
class AdaptiveLanguageTutor(LanguageTutor):
def __init__(self, api_key, target_language="Spanish"):
super().__init__(api_key, target_language)
self.error_log = []
def chat(self, user_message):
context_note = ""
if self.error_log:
recent_types = {
e["error_type"] for e in self.error_log[-3:] if e["error_type"]
}
if recent_types:
context_note = (
"\n[Instructor note: the student recently struggled with "
+ ", ".join(recent_types)
+ ". Please monitor these patterns.]\n"
)
messages = [
{"role": "system", "content": SYSTEM_PROMPT + context_note},
] + self.history + [
{"role": "user", "content": user_message},
]
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=0.7,
)
raw = response.choices[0].message.content
# Strip metadata before storing in history so the model does not echo it
reply = raw
if "---METADATA---" in raw:
parts = raw.split("---METADATA---", 1)
reply = parts[0].strip()
try:
data = json.loads(parts[1].strip())
if data.get("correction"):
self.error_log.append({
"correction": data["correction"],
"error_type": data.get("error_type", "unknown"),
})
except json.JSONDecodeError:
pass
self.history.append({"role": "user", "content": user_message})
self.history.append({"role": "assistant", "content": reply})
return reply
Run it
Here is a minimal CLI loop that drives the tutor. I usually run this in a terminal to sanity-check the tone and correction behavior before wiring it into a WebSocket or voice pipeline.
if __name__ == "__main__":
import os
tutor = AdaptiveLanguageTutor(api_key=os.getenv("OXLO_API_KEY"))
print("Tutor: Hola. ¿Cómo estás hoy?")
while True:
try:
user_input = input("You: ")
if user_input.lower() in ("/quit", "/exit"):
break
reply = tutor.chat(user_input)
print(f"Tutor: {reply}")
except KeyboardInterrupt:
break
Example session:
Tutor: Hola. ¿Cómo estás hoy?
You: Yo tener hambre
Tutor: Casi. Di "Yo tengo hambre". "Tener" se conjuga como "tengo" para la primera persona. ¿Qué te gustaría comer?
You: Quiero una manzana
Tutor: Muy bien. "Quiero" es la conjugación correcta. ¿Prefieres la manzana roja o verde?
Next steps
Ship this behind a FastAPI endpoint and stream responses with Oxlo.ai's streaming support to reduce perceived latency for the learner. If you expand to other languages, swap the model to qwen-3-32b or kimi-k2.6 on Oxlo.ai, both of which handle multilingual reasoning and long context windows well under the same flat per-request pricing.
For further actions, you may consider blocking this person and/or reporting abuse
