Several months ago I was doing French lessons the old school way — a 45-minute lesson with a private tutor once a week, for $200 per month. The tutor had a solid academic background, a sound methodology and was a nice person to interact with. But several months in, I started realizing that we were covering a lot of topics, but I wasn’t retaining them all that well — even given that we were repeating them from time to time.
Every session, we’d spend the first 20 minutes conversing, then a lot of time covering the topics we’d already covered, since many of the topics were complex and required a lot of repetition. As a result, the progress to new grammar topics was pretty slow, and I was getting impatient.
I’d read enough about the forgetting curve to understand where the challenge was coming from, and I’d been tinkering with LLM tools for a while, so I started wondering: could I build something that did this better — and cheaper?
The gap in my tutoring setup wasn’t the quality of the instruction. It was two structural things.
First, the review cadence. Human tutors schedule by calendar. The algorithm I needed schedules by forgetting curve — reviewing each topic right before I’d lose it, not at random times later. This is called spaced repetition, and there’s 40 years of memory research behind it. Apps like Anki have proven it works. The question was how to apply it to something more nuanced than flashcards, which I tried before and never liked.
Second, the error tracking. My tutor remembered, broadly, what I found hard and tested me on that periodically. But “Alex struggles with subjunctive” is not a very useful data point. What I actually needed was more along the lines of: “Alex consistently uses avoir as the auxiliary for aller in passé composé when the sentence is negative.” That’s what would have made it much more precise and help resolve the challenge faster.
And finally, did I really have to pay for just conversing for the first 20 minutes, and do that just once a week? What if I could practice my spoken French anytime I wanted and for free or at a minimal cost?
All of that felt like solvable software problems.
Two key components (grammar and speech practice), designed to work together.
The knowledge base tutor is a text-based workflow built on Claude. It manages a JSON file that tracks every grammar topic I’ve studied — when it was last reviewed, how confident I was, what specifically went wrong, when it’s due again. Before each session, it reads that file, identifies what’s due for review, and generates exercises in escalating difficulty: multiple choice first, then fill-in-the-blank, then translation, then free production. After the session, it writes everything back to the file.
I can also give it screenshots of any study book I’m working through and do exercises on that material, or ask any grammatical question I may encounter elsewhere. All this gets added to the knowledge base.
The algorithm underneath it is SM-2 — the same one Anki uses. After each topic, I rate my recall 1 to 4. That rating adjusts two things: the ease factor (how naturally this topic sticks for me specifically) and the interval before the next review. Nail something four times in a row and it comes back in three weeks. Blank on it and it comes back tomorrow. Over time, it helps the system to develop a pretty accurate model of my personal forgetting curve, topic by topic.
The weak-spot tracking is geniunely useful. Rather than logging “Alex has trouble with passé composé,” it logs “uses wrong auxiliary with motion verbs when sentence is negative — since 2026-04-12.” The next session that topic comes up, the exercise is built around exactly that construction. Not a general passé composé drill — the specific thing that broke.
I initially tracked every session separately but eventually decided to only keep the latest snapshot of my overall knowledge state, since the JSON was getting too long. It may still get too long even as a single snapshot eventually, and I may need to split it into several files in the future.
The speech tutor (called Causons). It’s a browser app where you can click the mic button, speak in a language of your choice and hear the response in the same language. It reads the same knowledge base at the start of each session and steers the conversation toward due topics and known weak spots. It also corrects genuine grammar errors, inline, in the flow of conversation. Not every error — only things that are objectively wrong, things a native speaker would never say. Transcription artifacts, punctuation, register choices, colloquial expressions stay invisible by design.
The two systems cover different ground. The KB tutor builds the knowledge. Causons activates it in conversational format. You can know the rule for reflexive pronouns in theory and completely freeze when you’re trying to say something in real time. The voice app help to break this barrier.
The voice pipeline is three API calls in sequence:
Before getting into the individual pieces: the constraint I was optimizing for, beyond just getting this to work, was spending as little money as possible while keeping conversation quality high. I already had a Claude subscription, and I wanted to use it for the KB tutor side without also burning API tokens on the voice app. So the goal was to find the cheapest good quality stack for STT, chat, and TTS that didn’t require Anthropic’s API at all — and to run it for a few cents a session, not a few dollars.
Not all three calls go to the same provider, and the reasoning behind that split is worth explaining because it took me multiple iterations to land on.
Speech-to-text goes to Groq, running Whisper-large-v3. I started on Microsoft Azure Cognitive Services initially, which advertised a generous free tier and seemed like the obvious low-cost option. Well, it kinda wasn’t. Within the first couple of weeks of light personal use, Azure pushed me to convert to a paid tier — not because I’d actually burned through a meaningful allocation, but because I simply started using my free tier actively. This user experience left a bad taste, and I didn’t want to continue dealing with any unexpected moves like that. I moved to Groq, which runs the same Whisper-large-v3 model but on their LPU hardware — 200–500ms per short utterance versus 800–1500ms on OpenAI’s hosting of the same model — and charges roughly $0.002 per minute.
Chat goes to gpt-4o-mini. This one took me a while to figure out. I started with Groq’s Llama 3.3 70B because it’s cheaper and it’s a capable model. It was fine for simple bookish French. Then it started flagging totally legit conversational expressions as grammar errors and producing confident-sounding explanations for why they were wrong. Which they weren’t — it was a completely standard colloquial French. I spent a while trying to fix this with prompt engineering before concluding that the problem wasn’t the instructions but most likely the model’s underlying knowledge of colloquial French grammar. Eventually I switched to gpt-4o-mini, which solved that problem, and the per-session cost difference turned out to be negligible anyway.
I also looked at GPT-4o (the full version) and Claude Sonnet. GPT-4o was around 10–15× pricier for quality gains I couldn’t actually measure in this task. Sonnet would have been the natural choice on quality — but using it via API would have meant paying Anthropic twice, once for my subscription and once per token. I was already using Claude for the KB tutor through my existing plan, and I wanted to keep the voice app on a completely separate cost surface.
Text-to-speech goes to OpenAI tts-1, which shares an API key with gpt-4o-mini — so the total setup is two keys, one Groq and one OpenAI. The Nova and Onyx voices handle any language automatically without per-language configuration, which was one of my goals in case I wanted to start practicing other languages. ElevenLabs has better voice quality, but would a third provider and a $5/month floor regardless of usage. Browser-native speechSynthesis is free but sounds rough outside English.
All-in cost: around $0.04 per 15-minute session across all APIs.
The piece I found most interesting to figure out was the system prompt architecture.
A language tutor prompt has two completely different kinds of content sitting in it, and they have different lifetimes. Teaching policy — how to decide when to correct, how to phrase a correction, what register to speak at — belongs to the specific user, changes slowly, and should survive unchanged across every session. Context data — which language is practiced today, which topics are due for review this session, which specific errors are currently on my radar — changes constantly and should be rebuilt from source every time it’s needed.
I baked all of it together in the first version. Within a week, the prompt had week-old topic lists. I also had a single combined “ROLE AND LANGUAGE” section that I updated with a regex when the practice language changed, which meant any customization I’d made to the role instructions got silently overwritten.
The fix was to treat these as two separate categories with different handling. The teaching policy sections live in IndexedDB and are used verbatim. The data-driven sections are stripped from whatever’s saved and rebuilt at chat time from their actual source of truth: the language dropdown, the KB file, today’s date. They’re never persisted because persisting them guarantees they go stale.
There’s also a composition order to this that matters. The stable sections go first in the assembled prompt because LLM providers can cache the prefix of a prompt across calls — if the beginning doesn’t change, you’re not paying to process it again. The date-volatile stuff goes last.
One principle that I had to explicitly define: a false correction is worse than a missed one. If a language tutor flags something correct as an error (and you know it), it’s genuinely irritating, and if you don’t know it’s a false correction, it’s detrimental to your learning. So the model only corrects things that are objectively wrong grammatical forms — not transcription artifacts, not punctuation, not register, not colloquialisms.
A speech practice app shouldn’t have a long delay between the moment you stop speaking and the moment when you hear a response. In other words, it should feel like a natural conversation as much as possible. The delay time I eventually settled on was under two seconds, which felt comfortable.
The first version of Causons was at ~2–6 seconds. The main reason was that I was waiting for the entire TTS audio file to finish downloading before playing any of it, so a 2–3 sentence reply might take 800ms–2.5 seconds to fully generate as MP3. You’d finish speaking, wait through transcription, wait through LLM streaming, wait for the whole audio file to arrive, then finally hear something.
The fix that helped was MediaSource Extensions — instead of waiting for the full response, you stream the MP3 bytes into a buffer as they arrive and start playback as soon as you have enough to play cleanly, typically 50–200ms of audio. The rest keeps downloading while you’re already listening.
Combined with flushing the first TTS request earlier — rather than waiting for a complete sentence, sending the first natural chunk of ≥25 characters — the end-to-end dropped to 0.8–2.5 seconds on a warm connection. It still gets a bit choppy in the very first response sometimes, but overall it performs much quicker and more steady vs. the first iterations, and it works well for me.
It took me a lot of back-and-forth with Claude Code to get there, going into plenty of architecture rabbit holes. The architecture doc in GitHub repo has the full version history if you want to see the details.
Saving $200/month is nice, but the best part is that my retention on grammar topics now is noticeably more durable than what I built in months of weekly tutoring. The difference is that I’m reviewing things on a schedule that matches my forgetting curve instead of a schedule that matches my calendar. Additionally, Claude Sonnet (the model I’m using for the grammar tutor) is actually very good at explaining grammar — probably, better than many tutors.
I now use Causons to practice speaking when I have time, and it surfaces my weaker points naturally. It also feels like it’s a bit less pressure to speak to an LLM, honestly, and you’re not constrained by your lesson’s timeframe.
Two things, in hindsight.
I stayed on the wrong LLM too long. Once Llama started hallucinating grammar corrections, I tried to fix it with more detailed system prompt instructions. That didn’t work, and it took me a while to realize that it wasn’t going to work for my use case. Switching providers was the right call from the moment I identified the pattern.
The security layer is fine for personal use but insufficient for anything more serious. API keys are stored in localStorage right now, which is fine for a personal tool on a device you control. For any more sensitive/exposed environment it would need to be stronger. I’d sketched a PIN-based key encryption flow early on and deprioritized it for the time being, as I’m the only person using my device.
Both pieces are on GitHub. The knowledge base tutor works as a Claude Project or Cowork workspace — add the files, fill in your name and target language, and start a session. Claude Sonnet 4.6 works well for me there. Causons runs locally with npm install && npm run dev and deploys to Cloudflare Pages in a single command (or any other hosting of your choice).
The KB tutor runs on your existing Claude subscription — no API key needed. For Causons, you’ll need a Groq key for STT (essentially free at personal use volumes) and an OpenAI key for chat and TTS (~$1–2/month for daily 15-minute sessions).
If you try it, extend it, or find something broken, I’d genuinely like to hear about it. Drop a comment or reach out directly.
Bon chance !


























