Introduction

AI Concepts: how the lessons are built

AI Concepts is bite-sized, interactive lessons — a few sentences, a worked example, then questions. Hearts, XP and a streak, in the Duolingo mould. This page is about how it's put together and the rules its content has to follow.

Chapter 1 is tokenisation, and it runs the real o200k_base vocabulary (gpt-tokenizer) in the browser. Every token count the app shows — in a lesson, in a question, in the playground — is the genuine one, not an approximation. That covers the surprising splits, the cost of writing in a language the tokeniser saw little of, the tokens-per-word ratio, and the byte-level fallback that means no text is ever "unknown".

Chapter 2 is embeddings, built on a small hand-made map of meaning, and chapter 3 is attention, built on hand-authored attention patterns. Those two are simplifications, and they say so on every screen — see Honesty about the map.

Chapter 4 is output logits, and it goes back to real maths: the starting scores are illustrative, but the softmax, temperature scaling, sampling, cross-entropy loss and perplexity are all the actual formulas computed live (web/lib/ai-concepts/logits.ts).

Chapter 5 is context windows, and its counts are real too — the budget and overflow screens run the same tokeniser as chapter 1 over the text on screen. The window sizes are deliberately tiny (512 and 40 tokens) so the effects fit on a screen; the chapter says so on every screen.

Where the code lives

The lessons are part of the web app — no separate install, no backend, no API keys. The tokeniser is a local vocabulary file, so everything runs client-side.

PathWhat's in it
web/app/ai-concepts/The routes
web/lib/ai-concepts/Tokeniser, embeddings, attention, logits, grading, progress
web/lib/ai-concepts/chapters/The content
web/components/ai-concepts/The lesson engine and screen components

Structure

Chapter → lesson → screen. A chapter is a topic; a lesson is one sitting of a handful of screens ending in a recap; a screen is a single beat. The engine in components/ai-concepts/Lesson.tsx runs one lesson: it renders screens, tracks hearts and XP, and decides what counts as correct.

All of it is data — adding a concept means authoring content, not writing UI.

Lessons must be self-contained

Every lesson has its own URL and is meant to be shared, so someone can arrive at lesson 3 having done nothing else. Each lesson therefore opens by establishing what it needs in a sentence or two, and no lesson leans on "as you saw earlier" to carry an idea. Cross-references are fine as flavour; they must not be load-bearing.

Each lesson is its own route, prerendered at build time with its own title and description:

/ai-concepts                                  the lesson map
/ai-concepts/tokenisation/case-digits-spaces  straight into one lesson
/ai-concepts/tokenisation                     redirects to the chapter's first lesson

A lesson id is a URL slug. Once shared, it shouldn't change — links to it will exist. An unknown chapter or lesson is a 404.

Every lesson row has a Copy link button, and the completion screen offers one too.

Screen kindWhat it doesScored
teachEyebrow, title, copy, optional token stripsNo
tapRevealTap a specimen to watch it split, then a takeawayNo
choiceAuthored multiple choice, for conceptual questionsYes
compare"Which costs more tokens?" between two stringsYes
count"How many tokens?" with generated near-miss optionsYes
playgroundFree typing against a target countYes
ratioA passage with words / tokens / ratio worked out liveNo
vectorRevealTap a word to see the numbers behind itNo
semanticMapThe meaning map, free to exploreNo
cosineViewTap two words, see the angle and cosine between themNo
attentionViewTap a word in a sentence, see what it leans onNo
softmaxViewLogits → probabilities, with an optional temperature dialNo
sampleRunDraw tokens at random and tally them against the oddsNo
budgetWhat's filling a context window, counted for realNo
overflowAdd chat turns until the earliest fall outNo
perplexityViewPer-token loss and perplexity over a sequenceNo
analogyDemoTeach screen that draws arrows between word pairsNo
resolve"Which word does X lean on?"Yes
pairCount"How many word-to-word links?" — n², computedYes
likeliest"Which token comes next?" — top logit, computedYes
nearest"Which sits closest to X?" — metric: 'distance' | 'cosine'Yes
odd"Which one doesn't belong?"Yes
analogy"a is to b as c is to ___"Yes
recapClosing summary and XPNo

Correct answers are worth 10 XP; a wrong one costs a heart, and three lost hearts ends the run.

Answers are computed, never authored

compare, count and playground don't store an answer — the tokeniser is the source of truth at runtime (lib/ai-concepts/grade.ts), so a question can't drift out of sync with the vocabulary. nearest, odd and analogy work the same way against the meaning map, so a question can never contradict the picture on screen.

choice is the only exception — it's for conceptual questions with no measurable answer, so its answer index is authored.

Set showCounts: true on a choice screen only when its options are specimens of text. On a conceptual question, annotating each option with its own token count is noise.

Adding a chapter or lesson

  1. Write lib/ai-concepts/chapters/<name>.ts exporting a Chapter with one or more lessons.
  2. Register it in lib/ai-concepts/chapters/index.ts and drop it from the upcoming list.
  3. If it needs a screen type that doesn't exist, add it to lib/ai-concepts/types.ts, grade.ts, components/ai-concepts/Screens.tsx and the Lesson switch.

New lessons are picked up automatically — the routes, the sitemap and the lesson map all derive from the chapters array.

Aim for roughly 5–7 screens per lesson, ending in a recap, with at least two scored screens so there's an XP payoff. If a lesson creeps past ~8 screens it wants splitting.

Chapter.source is required, so a chapter can't ship without saying where its numbers come from. It renders as a small line with a link on every screen of the lesson — not on a home screen the learner passes through once. Chapter 1 names the vocabulary and links to tiktoken; chapter 2 admits its map is hand-built.

Prose that quotes a specific split is a claim about the vocabulary. Verify it before shipping rather than trusting intuition — strawberry is three tokens (st + raw + berry), which almost nobody guesses:

node -e "import('gpt-tokenizer/encoding/o200k_base').then(({encode,decode})=>\
{const t=encode(process.argv[1]);console.log(t.length,t.map(i=>JSON.stringify(decode([i]))).join(' '))})" 'your text'

Any screen that states a count also has to say which vocabulary it came from. TokenStrip prints N tokens · o200k_base underneath whenever it shows IDs, and takes a source prop to force it on elsewhere. Without that, a learner checking a number against a different model — or against a chatbot, which can't see its own tokens and will simply guess — reasonably concludes the app is wrong. Chapter 1 makes the same point in a dedicated screen.

Honesty about the map

Chapter 1 can promise real data because a tokeniser is a small local file. Embeddings are not: a real one is learned from enormous amounts of text and has hundreds of dimensions, and fetching one would mean an API key and a backend — which these lessons deliberately don't have.

So lib/ai-concepts/embeddings.ts is a hand-placed 2-D map of 16 words. Two rules keep that from becoming a lie:

  1. The app never presents it as model output. The map screen says it's hand-built and 2-D, the home screen repeats it, and the vectorReveal screen shows the word's two real coordinates rather than inventing a realistic-looking 768-number vector.
  2. Everything measured is measured for real. Nearest words, odd-one-out and the analogy are all computed from the coordinates. Nothing is authored, so no question can disagree with the map the learner is looking at.

The royalty cluster is laid out as an exact parallelogram, which is why king - man + woman lands precisely on queen.

Distance vs cosine

The map's nearest-neighbour questions use Euclidean distance, because that's what a 2-D picture makes legible and it's how projected embedding plots are read in practice. But real embedding similarity is cosine — the angle between vectors — so the chapter teaches that explicitly rather than leaving distance as the takeaway.

cosine() measures the angle from CENTRE (the middle of the map), not from the top-left corner: direction is only meaningful relative to the middle of the space. Distance and cosine happen to agree on every graded question in the chapter, which was checked — so introducing cosine doesn't contradict anything taught earlier.

Upgrade path: to make this real, embed a word list once with a real model in a throwaway script, commit the resulting vectors as JSON, and swap WORDS for it — project the vectors to 2-D for drawing but run the maths on the full vectors. The screen types and grading wouldn't change.

The same applies to attention

lib/ai-concepts/attention.ts holds hand-authored patterns for two sentences, written as a sparse "which words does this one pull on" map and normalised into rows that sum to 1, the way a real attention row does after its softmax. Same two rules: the chapter never claims they came from a model, and every graded answer (resolve, pairCount) is computed from the numbers on screen.

The two sentences differ by one word — tired vs wide — which is what makes "it" resolve to animal in one and street in the other. That flip is the point of the chapter, so if you edit the links, check both still resolve the way the copy claims.

Design

Letterpress: tokens are sorts set in a composing stick, each stamped with its vocabulary ID. Leading spaces are drawn as a baseline underscore, because "the space travels with the word" is one of the things the chapter teaches — it has to be visible. Fraunces for display, Public Sans for body, IBM Plex Mono for anything that is literally text-as-data.

Typography is pending

The section's own typefaces haven't been wired up in web yet — font-display and font-mono currently fall back to the site's Geist. The letterpress palette (paper, ink, lead, rule, brass, press) is live in web/app/globals.css.

Notes

  • Progress (XP, streak, best score per lesson) is in localStorage under ai-concepts:progress:v2, keyed chapterId/lessonId. There is no account or backend. v1 stored chapter-level scores and is ignored, so anyone who used the earlier build starts fresh.
  • Because localStorage doesn't exist during prerendering, progress is read through lib/ai-concepts/use-progress.ts — the server and the hydration pass see zero, and real values arrive on the first commit in the browser.
  • The lesson route's client bundle is ~2 MB because it embeds the o200k vocabulary. That's the cost of real counts, and it's scoped to /ai-concepts — no other page on the site loads it. Lazy-load it if a chapter ever ships that doesn't need the tokeniser.
  • Keyboard: 14 pick an answer, Enter checks and continues.
Previous
AI Agent Harness