VOOZH about

URL: https://dev.to/shashank_ms_6a35baa4be138/building-conversational-ai-systems-for-language-learning-with-llm-4j90

⇱ Building Conversational AI Systems for Language Learning with LLM - DEV Community


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

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.