
I built a terminal-based Spanish tutor that corrects grammar in real time, adapts to my level, and collects vocabulary for end-of-session review. It runs on Oxlo.ai so I am not charged extra when the conversation history grows across a long practice session. That matters because language learning is inherently iterative. You make mistakes, get corrections, ask follow-ups, and reference earlier topics. On a token-based provider, every turn makes the next one more expensive because the entire message history gets resent as input tokens. On Oxlo.ai, the flat per-request price keeps the cost predictable even when the messages list spans dozens of turns. You can adapt the prompt for any language. I chose Spanish because the model needs to handle accents, gender agreement, and verb conjugations accurately, which stresses the reasoning capabilities of the underlying LLM.
What you'll need
Before starting, gather the following. The entire project is less than a hundred lines of Python, and the only external dependency is the official OpenAI SDK. You will not need LangChain, LlamaIndex, or any other orchestration layer because a well-written system prompt and a simple loop are enough for this use case.
- Python 3.10 or newer installed locally.
- An Oxlo.ai API key. Create one at https://portal.oxlo.ai. Oxlo.ai offers a free tier with 60 requests per day, which is enough to test and iterate.
- The OpenAI SDK installed with
pip install openai. Oxlo.ai is fully OpenAI-compatible, so this is the only client library we need. - A terminal or IDE where you can set environment variables and run interactive Python scripts.
Step 1: Initialize the Oxlo.ai client
I always verify the endpoint before adding application logic. Oxlo.ai exposes a drop-in replacement for the OpenAI chat completions endpoint at https://api.oxlo.ai/v1, which means I can use the standard SDK and migrate existing code by changing two lines. I use Llama 3.3 70B here because it handles multilingual instructions reliably and stays responsive during long sessions. If you are practicing Arabic, Mandarin, or Japanese, Qwen 3 32B is another strong option on Oxlo.ai for multilingual reasoning and agent workflows. The test call below simply confirms that authentication and routing work.
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": "Confirm you are ready to tutor Spanish."},
],
)
print(response.choices[0].message.content)
Step 2: Write the system prompt
The system prompt is the entire product. It defines the tutor's tone, the target language, the learner's assumed proficiency, and a strict output format. I force the model to append structured sections so I can parse corrections and vocabulary automatically without calling the model a second time. This single-call approach keeps latency low and keeps my daily request budget under control. Keeping the prompt in a dedicated constant makes it easy to tweak the persona or switch to French, German, or Japanese later without hunting through loop logic.
SYSTEM_PROMPT = """You are a patient Spanish tutor for an intermediate learner.
Rules:
1. Reply in Spanish first, then give an English translation in parentheses.
2. Gently correct grammar or vocabulary mistakes.
3. End every response with exactly these delimited sections:
[CORRECTIONS]
List errors and fixes, or write "None."
[VOCABULARY]
List up to 3 new words or phrases with English definitions.
Keep the conversation natural and adaptive."""
Step 3: Parse structured output
I want the terminal to show the dialogue, corrections, and new words in separate blocks. Because I control the prompt, I can split on the literal section headers without importing any extra libraries like Pydantic or a JSON parser. This keeps the script lightweight and avoids schema validation failures when the model gets creative with formatting. The function is pure string manipulation, so it runs instantly and never breaks the interactive flow.
def parse_tutor_response(text):
parts = text.split("[CORRECTIONS]")
dialogue = parts[0].strip()
remainder = parts[1] if len(parts) > 1 else ""
vocab_parts = remainder.split("[VOCABULARY]")
corrections = vocab_parts[0].strip()
vocabulary = vocab_parts[1].strip() if len(vocab_parts) > 1 else ""
return dialogue, corrections, vocabulary
Step 4: Build the interactive loop
Language learning needs memory. I accumulate every exchange in a messages list so the model remembers that I struggled with subjunctive verbs ten minutes ago and can reference that in future explanations. This is where Oxlo.ai's pricing model becomes a practical advantage. On token-based providers, every appended message increases the input token count, so a long tutoring session gets progressively more expensive. Oxlo.ai charges per request, not per token, so I can maintain a rich conversation history without watching the meter run. The loop below also collects every vocabulary block into a running list for review at the end.
def run_tutor():
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": "Hola, quiero practicar español hoy."},
]
vocab_log = []
print("Tutor:", messages[1]["content"])
print("-" * 40)
while True:
user_input = input("You: ").strip()
if user_input.lower() in ["exit", "quit"]:
break
messages.append({"role": "user", "content": user_input})
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=messages,
)
raw = response.choices[0].message.content
dialogue, corrections, vocabulary = parse_tutor_response(raw)
messages.append({"role": "assistant", "content": raw})
print("\nTutor:", dialogue)
if corrections and corrections.lower() != "none.":
print("Corrections:", corrections)
if vocabulary:
print("New vocab:", vocabulary)
vocab_log.append(vocabulary)
print("-" * 40)
print("\nSession complete. Review these words:")
for entry in vocab_log:
print(entry)
if __name__ == "__main__":
run_tutor()
Step 5: Export the session log
Reviewing words after the session is useful, but I also want a file I can import into Anki or Quizlet. I will add a small helper that writes the accumulated vocabulary to a timestamped text file. This turns the tutor from a toy into a study tool that persists across days. The helper runs only when the user types exit, so it does not interrupt the conversational flow.
import datetime
def save_vocab(vocab_log):
if not vocab_log:
return
filename = f"vocab_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
with open(filename, "w", encoding="utf-8") as f:
f.write("Vocabulary Session\n")
f.write("-" * 40 + "\n")
for entry in vocab_log:
f.write(entry + "\n")
print(f"Saved review sheet to {filename}")
# Add this call inside run_tutor() just before the final print block:
# save_vocab(vocab_log)
Run it
Save the full script as tutor.py, export your key, and start a session. Here is a sample exchange after running python tutor.py. Notice how the tutor keeps the conversation going while quietly logging corrections and new phrases. The output format is readable in the terminal and also easy to pipe to other tools.
$ export OXLO_API_KEY="sk-..."
$ python tutor.py
Tutor: Hola, quiero practicar español hoy.
----------------------------------------
You: Me gusta mucho la comida mexicana, es muy sabroso.
Tutor: ¡Qué bueno! La comida mexicana es deliciosa. ¿Cuál es tu platillo favorito?
(That's great! Mexican food is delicious. What is your favorite dish?)
Corrections: None.
New vocab: platillo - dish, deliciosa - delicious
----------------------------------------
You: Mi favorito es el mole. Es muy dificil cocinarlo.
Tutor: El mole es una excelente elección. Es muy difícil de cocinar, ¿verdad?
(Mole is an excellent choice. It is very difficult to cook, right?)
Corrections: "dificil" needs an accent: "difícil". Use "difícil de cocinar" instead of "difícil cocinarlo".
New vocab: elección - choice, cocinar - to cook
----------------------------------------
You: exit
Session complete. Review these words:
platillo - dish, deliciosa - delicious
elección - choice, cocinar - to cook
Wrap-up and next steps
This tutor works because the system prompt forces structure, and Oxlo.ai's request-based pricing removes the penalty for long practice threads. The whole application is stateful, interactive, and useful without a single database or framework. If you want to extend it, wire in Oxlo.ai's Whisper Large v3 endpoints so the user can speak their answers and hear pronunciation feedback. Another path is to add vision support with Kimi K2.6 or Gemma 3 27B, letting the tutor analyze photos of textbook pages or handwritten homework through the image input API. You can explore model options and pricing details at https://oxlo.ai/pricing.


