Education › AI Engineering › Stage 2: Building with LLMs

How LLMs work

Tokens, context windows, sampling, embeddings — and what that means for your app.

Intermediate ~30 min read Module 5 of 16

A large language model can write code, summarise a contract and explain a stack trace, and it can also state something false with complete confidence. Both behaviours come from the same mechanism, and once you understand that mechanism, LLMs stop being magic and become a component you can engineer with. This module explains what a model actually does when you call it, and what that means for cost, latency, limits and reliability in the applications you build.

After this module you can
  • Explain next-token prediction, and how pretraining and instruction tuning produce an assistant
  • Reason about tokens as the unit of cost, speed and limits
  • Treat the context window as the model's only working memory, and the API as stateless
  • Control output with temperature, top-p, maximum tokens and stop sequences
  • Explain why models hallucinate, and use embeddings to find relevant text

One thing, done repeatedly: predict the next token

An LLM is the decoder-only transformer from the previous module, at very large scale. Given a sequence of tokens, it outputs a probability for every token in its vocabulary as the one that comes next. To generate text, the application picks one token from that distribution, appends it to the sequence, and runs the model again. It repeats until the model emits a special end token, or a limit is reached. This loop is called autoregressive generation.

text
input:   "The capital of France is"

model -> " Paris"   0.92
         " the"     0.03
         " located" 0.01
         ...every other token in the vocabulary...

pick " Paris" -> input becomes "The capital of France is Paris" -> run the model again -> ...

Everything else follows from this. The model has no database of facts and no separate reasoning engine. It has billions of numerical weights that encode statistical patterns of language, and those patterns happen to capture a remarkable amount of knowledge and problem-solving ability. It also means that output is produced one token at a time, strictly in order, which is why generation is slow compared with reading, why responses can be streamed as they are produced, and why the model cannot revise an earlier sentence once it has been written.

A model becomes an assistant in stages.

StageDataResult
PretrainingA very large corpus of text and code; the task is simply to predict the next tokenA base model: fluent and knowledgeable, but it only continues text. Ask it a question and it may reply with more questions.
Instruction tuningCurated examples of instructions paired with good responsesA model that follows instructions and holds a conversation
Preference tuningHuman or AI judgements about which of two responses is better (RLHF and related methods)Responses that are more helpful, more honest and safer

Two practical facts follow. A model's knowledge stops at its training cutoff: it knows nothing about later events, or about your private data, unless you put that information into the prompt. And the model you call through an API is a fixed snapshot. It does not learn from your conversations.

Tokens: the unit of everything

Models read and write tokens, not characters or words. A tokeniser splits text into pieces from a fixed vocabulary, using an algorithm such as byte-pair encoding that gives short codes to common sequences. Frequent English words are usually one token. Rare words, names, code, numbers and text in many other languages break into several.

text
"Hello world"            -> ["Hello", " world"]                        2 tokens
"Kubernetes"             -> ["Kub", "ernetes"]                         2 tokens (illustrative)
"antidisestablishment"   -> ["ant", "idis", "establish", "ment"]       4 tokens (illustrative)
"2026-09-17T14:02:11Z"   -> many small pieces                          10+ tokens

Rule of thumb for English prose:  1 token is about 4 characters, or 0.75 words.
                                  1,000 words is about 1,300 tokens.
The exact split differs between model families. Count with the provider's tokeniser.
  • Cost is charged per token, separately for input and output. Output tokens usually cost several times more than input tokens.
  • Latency depends mostly on the number of output tokens, because they are generated one after another. A long prompt adds a little; a long answer adds a lot.
  • Limits are in tokens: the context window, the maximum output, and your rate limits per minute.
  • Non-English text and structured data such as JSON, logs and UUIDs can use far more tokens than their length suggests, which affects cost and how much fits.
  • Character-level tasks are hard. The model sees ["Kub", "ernetes"], not letters, so counting the letters in a word, reversing a string, or exact arithmetic on long numbers is unreliable. Have it write code that does such work, or use a tool.
python
def estimate_tokens(text: str) -> int:
    """Rough estimate for English prose. Use the provider's tokeniser for real budgeting."""
    return max(1, round(len(text) / 4))


def fits(prompt: str, context_window: int, reserve_for_output: int) -> bool:
    return estimate_tokens(prompt) + reserve_for_output <= context_window

The context window, and statelessness

The context window is the maximum number of tokens the model can consider at once, counting everything: system instructions, conversation history, documents you pasted in, tool definitions and results, and the response being generated. It is the model's only working memory. Whatever is not in the window does not exist for the model on that call.

The most important architectural fact for application builders is that the API is stateless. The model remembers nothing between calls. A chat application creates the appearance of memory by sending the whole conversation again with every request.

YOUR APPLICATION: THE ONLY MEMORYPROVIDER: STATELESSmessages[]tokens in: paidtokens out: slow, paidappend as assistantMessage historysystem + all turnsTruncatewhen the window fillsRequestentire history, againModelnext-token predictionReplyone token at a time
The API is stateless: your application keeps the conversation history and sends all of it with every request, the model generates the reply one token at a time, and the reply is appended to the history for the next turn.
python
# call_llm is a stand-in: wire it to whichever provider you use.
# It takes a list of messages and returns the assistant's reply as a string.

messages = [
    {"role": "system", "content": "You are a concise assistant for on-call engineers."},
    {"role": "user", "content": "What does exit code 137 mean in Kubernetes?"},
]
reply = call_llm(messages)
messages.append({"role": "assistant", "content": reply})

# The follow-up only makes sense because the WHOLE history is sent again
messages.append({"role": "user", "content": "How do I find out which container it was?"})
reply = call_llm(messages)
  • Each turn costs more than the last, because the history grows. A fifty-turn conversation pays for the first message fifty times.
  • When the history would overflow the window, you must do something: truncate the oldest turns, summarise them, or retrieve only the relevant parts. That decision is yours, not the model's.
  • "Memory" features in products are built the same way: something stores notes outside the model, and places the relevant ones into the prompt.
  • A larger window is not a substitute for selecting well. Models attend less reliably to material buried in the middle of a very long context, more tokens mean more cost and latency, and irrelevant text dilutes the relevant. Put in what is needed, clearly organised.
  • Roles matter. The system message sets behaviour for the whole conversation. User and assistant messages alternate. Providers differ in the details, and the pattern is universal.

Sampling: controlling the output

The model outputs a probability distribution, and the sampling settings decide how a token is chosen from it. These are the main controls you have at request time.

ParameterEffectGuidance
TemperatureDivides the logits before the softmax. Low values sharpen the distribution; high values flatten it.Around 0 for extraction, classification and code. Higher for brainstorming and creative writing.
Top-p (nucleus)Samples only from the smallest set of tokens whose probabilities add up to pLeave at the default, or adjust temperature or top-p, not both
Top-kSamples only from the k most likely tokensOffered by some providers; rarely needs changing
Max output tokensA hard cap on the length of the responseAlways set it. It bounds cost and latency, and stops runaway output.
Stop sequencesStrings at which generation haltsUseful for structured formats and for ending at a delimiter

Temperature 0 makes the model pick the most likely token every time, which is called greedy decoding. It makes output far more repeatable, and still not perfectly deterministic, because of floating-point behaviour on parallel hardware and because providers update models. Do not build anything that requires byte-identical output from one call to the next. If a response ends abruptly, check the stop reason that the API returns: a value indicating that the token limit was reached means the output was cut off, not that the model had finished.

python
# Deterministic-leaning task: extract fields from a log line
extraction = call_llm(messages, temperature=0, max_tokens=300)

# Open-ended task: propose names for an internal tool
ideas = call_llm(messages, temperature=1.0, max_tokens=500)
Note

Some recent models that reason at length before answering fix or restrict certain sampling parameters. Check the documentation for the model you are using instead of assuming that every setting is available.

Why models hallucinate

A hallucination is output that is fluent, confident and false: a function that does not exist, an invented citation, a plausible configuration flag. It is not a malfunction. It is the normal mechanism operating where it lacks grounding. The model is trained to produce a likely continuation. It has no internal marker that separates "I know this" from "this is the kind of thing that usually comes next", and a text that says "I am not sure" is, in most training data, less common than a text that answers.

  • Hallucination is most likely for specifics: exact names, numbers, quotations, URLs, API signatures, version details, anything recent or obscure, and anything about your private systems.
  • It is least likely where the answer is in the context. A model asked to answer from a document you have supplied is far more reliable than one asked from memory. This is the basis of retrieval-augmented generation, two modules ahead.
  • Give it a way out. Instruct the model to say when the provided material does not contain the answer, and hallucination falls noticeably.
  • Ask for evidence. Have it quote the passage that supports each claim, then check that the quote really appears in the source.
  • Give it tools for what it is bad at: a calculator, a code interpreter, a search, a database query. A later module covers tool use.
  • Verify mechanically whatever can be verified: compile the code, run the tests, validate the JSON against a schema, check that the URL exists.
  • Keep a human in the loop in proportion to the stakes.
Watch out

Fluency is not evidence of correctness. Models write with the same assured tone whether they are right or wrong, and people are strongly inclined to trust well-written text. Design your application on the assumption that any unverified specific may be wrong.

Embeddings

An embedding model is a related kind of model that, instead of generating text, turns a piece of text into a single vector which represents its meaning. Texts with similar meanings produce vectors that point in similar directions, so cosine similarity, from the maths module, measures how closely two texts are related, even when they share no words at all.

python
import numpy as np

# embed is a stand-in: it takes a list of strings and returns an array (n, dimension)
docs = [
    "Pod is in CrashLoopBackOff after the deploy",
    "How to request annual leave",
    "Container keeps restarting with exit code 137",
]
doc_vecs = embed(docs)
query_vec = embed(["my application restarts over and over"])[0]


def normalise(v: np.ndarray) -> np.ndarray:
    return v / np.linalg.norm(v, axis=-1, keepdims=True)


scores = normalise(doc_vecs) @ normalise(query_vec)     # cosine similarities, shape (3,)
for i in np.argsort(-scores):
    print(f"{scores[i]:.3f}  {docs[i]}")
# the two restart-related documents rank first, despite sharing few words with the query
  • Uses: semantic search, retrieval for RAG, clustering similar tickets or incidents, removing near-duplicates, recommendations, and classification with very few examples.
  • Embedding calls are far cheaper and faster than generation, and the vectors can be calculated once and stored.
  • Vectors from different embedding models cannot be compared with each other. If you change the model, you must embed everything again.
  • Embedding models have their own input limits, so long documents are split into chunks first. How to chunk is a design decision that the RAG module covers.
  • Similar does not mean correct, or relevant to the question. "How do I enable feature X?" and "How do I disable feature X?" are close together in embedding space.
Hands-on practice

Probe a model's behaviour

  1. Get API access to any LLM provider, or run a small open model locally. Write a call_llm(messages, **params) function that wraps it, and print the token usage that the API reports with each response.
  2. Use the provider's tokeniser or token-counting endpoint on an English paragraph, the same paragraph in another language, a block of JSON, and a block of code. Compare tokens per character across them.
  3. Prove that the API is stateless. Tell the model your name in one call, and ask for it in a separate call with no history. Then send both turns together.
  4. Run the same creative prompt five times at temperature 0 and five times at temperature 1. Compare the variation. Then run an extraction task both ways, and note which setting you would ship.
  5. Set a very low maximum output, ask for a long answer, and find the field in the response that tells you the output was cut off.
  6. Provoke a hallucination: ask for the exact signature of an obscure function in a niche library, or for a citation. Then supply the real documentation in the prompt, with an instruction to answer only from it, and compare.
  7. Ask the model to count the letter "r" in a long word, and then ask it to write and reason through Python code that does so. Explain the difference in terms of tokens.
  8. Embed ten short sentences on two or three topics. Compute the matrix of cosine similarities, and check whether sentences on the same topic score highest with each other.
Cheat sheet

How LLMs work — at a glance

Main things to focus on

  • An LLM predicts the next token, repeatedly. There is no fact database and no separate reasoning engine.
  • Pretraining gives knowledge; instruction and preference tuning make it an assistant. Knowledge stops at the training cutoff.
  • Tokens are the unit of cost, latency and limits. Output tokens are the slow and expensive ones.
  • The context window is the model's only memory, and the API is stateless: you send the history again every time.
  • Temperature near 0 for extraction and code, higher for creative work. Always set a maximum output, and check the stop reason.
  • Low temperature is repeatable, not deterministic.
  • Hallucination is likely output without grounding. Supply the source, allow "I don't know", ask for quotes, and verify.
  • Embeddings turn text into vectors, and cosine similarity finds related text. Never mix vectors from different models.

Token rules of thumb (English)

1 token ~ 4 characters ~ 0.75 wordsRough average for prose
1,000 words ~ 1,300 tokensFor estimating documents
code, JSON, logs, IDsMore tokens per character than prose
non-English textOften noticeably more tokens for the same meaning
cost = in_tokens x in_price + out_tokens x out_priceOutput is usually priced higher
latency ~ time to first token + out_tokens / tokens per secondOutput length dominates

Request parameters

temperature = 0Most likely token each time; repeatable; for extraction, classification, code
temperature ~ 0.7 to 1.0More varied; for ideas and prose
top_pSample from the smallest set of tokens totalling probability p
max_tokensHard cap on output length; always set it
stop sequencesHalt when any of these strings is generated
stop reasonWhy generation ended: natural end, length limit, stop sequence, tool call

Message structure

{"role": "system", "content": ...}Standing instructions and persona
{"role": "user", "content": ...}The person's turn, or your application's request
{"role": "assistant", "content": ...}The model's earlier replies, sent back as history
messages.append(...)You maintain the history; the API remembers nothing
truncate / summarise / retrieveThree ways to keep history inside the window

Reducing hallucination

ground itPut the source material in the prompt
allow a way out"If the answer is not in the text, say so"
ask for quotesThen check that they appear in the source
use toolsCalculator, code execution, search, database
verify mechanicallyCompile, test, validate against a schema, resolve the link
human reviewIn proportion to the stakes

Embeddings

embed(texts) -> array (n, d)One vector per text
v / np.linalg.norm(v)Normalise, so that a dot product is cosine similarity
docs_n @ query_nSimilarity of one query to every document
np.argsort(-scores)[:k]Indices of the k most similar
same model for queries and documentsVectors from different models are not comparable

Common pitfalls

  • Assuming that the model remembers earlier calls, when it sees only what you send this time.
  • Letting conversation history grow without limit, until cost climbs and the window overflows.
  • Leaving the maximum output unset, and paying for, and waiting on, runaway responses.
  • Relying on temperature 0 for byte-identical output.
  • Trusting a fluent answer about a specific API, number or citation without checking it.
  • Asking the model for character-level or exact arithmetic work that a line of code would do reliably.
Quiz

Check your understanding

5 questions · 4 to pass · answers are explained as you go. Your best score is saved on this device only.

Progress and quiz scores are saved in this browser only. Back up or restore on the hub.

Was this lesson useful? Tell me what to improve →