Foundations · Day 1

Generative AI

Generative AI learns patterns from existing data and uses those patterns to create new text, code, images, audio, or video. This page is the map you need before any framework.

Beginner No framework yet

Machine learning, deep learning, generative AI

AnalogyThink of a kitchen. Machine learning is a cook who tastes a dish and says “too salty.” Deep learning is a cook trained on millions of dishes. Generative AI is the cook who then plates a new dish you have never ordered.
Data flow
Past examples→Learn a pattern→Predict or create something new

Most modern generators are deep-learning models. Text and code use transformers. Pictures often use diffusion: start from noise and remove it until a picture appears. A product can use both layers. A fraud model decides. A language model explains the decision in a sentence a customer can read.

Starter

task = "write a refund email"  # create  -> generative
task = "is this email spam?"     # decide -> classic ML
print("generative" if task.startswith("write") else "predictive")

Example

Spam detection is prediction. “Write a polite reply to this spam complaint” is generation. The second one needs a generator even if the first one is a small classifier.

These three names sit on top of each other. Mixing them up makes every later topic feel harder than it is.

LayerWhat it learnsEveryday example
Machine learningPatterns used to predict or decideSpam vs not spam from past emails
Deep learningThe same idea, with many layers of a neural networkA photo model that finds a face
Generative AIThose patterns, used to produce new contentA paragraph, a Java method, a picture

Modern generative systems are mostly deep learning. Text and code models are usually transformers. Image models are often diffusion models. A large language model (LLM) is the text-and-code case: GPT, Claude, Gemini, Llama, Qwen.

Use case. A bank’s fraud model is classic ML: it scores a transaction. A support bot that writes a reply is generative AI. Many products use both: the fraud model decides, the LLM explains the decision in plain language.

An LLM does not look the answer up

AnalogyAn LLM is autocomplete with a huge vocabulary. It does not open a book. It bets on the next piece of text, called a token, then bets again.
Data flow
Your prompt→Tokens→Neural net→Next token→Repeat until stop

A token is often a word or part of a word. “Unbelievable” may be several tokens. You pay, and you hit length limits, in tokens. The loop is the whole trick: score every possible next token, pick one, append it, continue. Streaming is that loop shown live.

Starter

text = "Binary search keeps"
# A tokenizer would split this into word pieces. This starter just shows the loop idea.
pieces = text.split()
print("so far:", pieces)
print("model would append one more piece, then repeat")

Example

Ask “explain binary search in Java.” The model continues the pattern of a good explanation. It can be excellent and still invent a method that does not exist, because nothing in the loop checks a compiler.

Search engines retrieve a page. An LLM predicts the next piece of text, called a token, over and over until the reply is done.

Prompt
  → Tokenization (split text into tokens)
  → Neural network
  → Predict next token
  → Predict next token
  → … until a stop
  → Final response

A token is a chunk of text, often a word or part of a word. “Unbelievable” might be several tokens. You pay, and you hit length limits, in tokens, not in characters.

Real use case: a coding tutor

You ask “explain binary search in Java.” The model has seen millions of explanations. It does not open a textbook. It continues the pattern of a good explanation, one token at a time. That is why the answer can be excellent and still invent a method name that does not exist.

A chat request is a list of messages

AnalogyA chat API is a script handed to an actor. The system line is the role. User lines are the other person. Assistant lines are what the actor already said. If you leave a line out, the actor never heard it.
Data flow
system: rules→user: question→assistant: earlier reply→user: follow-up→new assistant reply

Three roles matter. system sets behavior. user is the request. assistant is earlier output you want the model to continue from. Put system first. A vague “help me” produces a vague reply. A role, a format, and one example produce text a program can read.

Starter

messages = [
    {"role": "system", "content": "You teach Java. One example, then the code."},
    {"role": "user", "content": "What is a for-loop?"},
]
print(messages[0]["role"], "sets the behavior")
print("send this whole list every call; the API does not remember the last one")

Example

A tutor bot that “forgets” you are learning Java forgot because you stopped sending that system message, not because the model has a bad memory inside one request.

The model does not remember your last API call. You send the conversation you want it to see. Each message has a role and content.

RoleJob
systemHow the assistant should behave. “You are a Java tutor. Answer with a short explanation, then code.”
userThe person’s request.
assistantEarlier replies, so the next turn can continue them.
{
  "model": "some-model",
  "messages": [
    {"role": "system", "content": "You are a Java programming tutor."},
    {"role": "user", "content": "Explain binary search."},
    {"role": "assistant", "content": "Binary search repeatedly divides a sorted array in half."},
    {"role": "user", "content": "Give me Java code."}
  ]
}

Put the system message first. The quality of this list changes the answer more than most people expect. A vague “help me” produces a vague reply. A role, a format, and one example produce something you can parse.

Temperature: predictable or varied

AnalogyTemperature is how wild the autocomplete is allowed to be. Zero is “always pick the most likely word.” High is “sometimes pick a surprising word.”
Data flow
Model scores→Temperature→Pick a token→Stable or varied text

Low temperature (0 to 0.3) is for labels, JSON, and money. Medium is for normal explanations. High is for titles and brainstorms, and it also raises the chance of drifting. Many teams pin temperature at 0 whenever code will parse the answer.

Starter

def style(task):
    if task in {"invoice", "json", "sql"}:
        return 0.0
    if task == "brainstorm titles":
        return 0.9
    return 0.4

print("ticket label temperature:", style("invoice"))

Example

A payment-status sentence should not be creative. A slogan workshop can be. Use 0 for “classify this ticket,” and something higher only when variety is the point.

Temperature controls how willing the model is to pick a less likely next token.

  • Low (0 to 0.3): stable, repetitive, good for JSON, classification, banking text, ticket labels.
  • Medium (0.4 to 0.7): normal chat and explanations.
  • High (0.8 to 1.2): more variety, more risk of drifting, useful for brainstorming titles or marketing lines.
Banking app? Use a low temperature. A payment-status sentence should not be creative. A slogan workshop can be high. Many teams set temperature to 0 for anything a program will read.

The context window is the model’s desk

AnalogyThe context window is the desk. Everything the model can see this turn — rules, history, documents, the new question, and room for the answer — has to fit on that desk.
Data flow
System prompt→History→Retrieved docs→New question→Room for the answer

If an early “I am coding in Java” message falls off the desk, the model does not know it. A bigger window is not a reason to paste a whole wiki: cost, delay, and noise all get worse, and private text you did not need is now in the request.

Starter

limit = 8000  # tokens, toy number
used = {"system": 400, "history": 2200, "docs": 3000, "question": 80}
left = limit - sum(used.values())
print("tokens left for the answer:", left)

Example

A support chat that suddenly answers in the wrong language often lost the system line or the user’s preference because the history was trimmed to save tokens.

The context window is the maximum number of tokens the model can read in one request: system prompt, history, retrieved documents, and the new question, plus room for the answer.

Imagine a chat that starts with “I am coding in Java.” Twenty turns later you ask “what language am I using?” The model only knows if that earlier message is still inside the window you sent. If you dropped it to save tokens, the model honestly does not know.

You cannot paste a million internal documents into the window. Cost, latency, and noise all get worse, and private text you did not need to send is now in the request.

Hallucination

AnalogyA hallucination is a student who would rather finish the sentence than say “I don’t know.” The sentence sounds confident because confidence is how the model was trained to write.
Data flow
Prompt→Plausible next tokens→Confident answer→Check against a source

Three reducers work in practice. Ask the model to abstain. Put the real document in the prompt (RAG). Validate the output: types, tests, or a second check. Generated code is untrusted input. Read it, then run it in a sandbox with no extra network and a time limit.

Starter

answer = "The return window is 90 days."  # sounds fluent
policy = "Returns are allowed for 30 days."
print("trust it?", answer.split()[-2] in policy)  # starter: do not trust without a check

Example

A bot that invents a 90-day return policy is worse than a bot that says the policy page does not say. Treat invented Java the same way: do not exec() it on your laptop.

A hallucination is a confident answer that is wrong, unsupported, or invented. It happens because the model is trained to continue plausible text, not to check every fact against a source.

Three practical reducers:

  1. Better prompts. Ask it to say “I don’t know” and to quote the source you provided.
  2. RAG. Put the real document in the prompt and tell it to use only that.
  3. Validation. Check types, run tests, or have a second model score the answer.

Real use case: generated Java

Do not execute model-written code on your machine as if it were trusted. Treat it like input from the internet. Read it, run static checks, then execute inside a sandbox: no extra network, limited CPU, memory, and time, and a filesystem it cannot use to touch your home directory. A container is the usual boundary. Infinite loops, file reads, and surprise network calls are normal failure modes, not rare ones.

When the model has never seen your documents

AnalogyRAG is an open-book exam. You are not allowed to bring the whole library into the room. You bring the three pages that match the question, then you write.
Data flow
Documents→Chunks→Vectors→Search→Prompt + chunks→Answer

Do not send every company document. They will not fit, the bill explodes, and irrelevant pages confuse the model. Embed the question and the chunks, keep the closest chunks, and tell the model to use only those. If 80 of 100 questions retrieve the right page and 70 of those 80 answers are right, retrieval is the bigger leak.

Starter

docs = ["Returns: 30 days, unused.", "Shipping: free over $50."]
q = "return window"
hits = [d for d in docs if "return" in q or "30" in d]
print("put only this in the prompt:", hits[0])

Example

An employee handbook bot for “how many parental-leave weeks?” must quote the policy chunk. It must not answer from general knowledge about other companies.

An internal Amazon-style employee bot cannot answer from training data about this quarter’s runbooks. The design is retrieval-augmented generation (RAG):

Internal documents
  → extract text
  → chunk into passages
  → embedding model turns each chunk into a vector
  → store vectors in a vector database

User question
  → embed the question
  → find the closest chunks
  → put those chunks in the prompt
  → LLM writes the answer

Why not send every document? A company wiki will not fit the window, the bill would be huge, irrelevant pages confuse the model, and access control gets harder. RAG says: find what this question needs, then generate.

A small eval that tells you where it broke

You run 100 questions. 80 retrieve the right document. Of those 80, 70 answers are correct. Retrieval accuracy is 80%. Generation accuracy, given the right context, is 87.5%. The bigger leak is retrieval. Fix chunking, the embedding model, top-k, or add a reranker before you blame the chat model.

Chunk size is a tradeoff. Too small and you lose the sentence that explains the rule. Too large and you pull in noise and spend tokens. Prefer paragraph or section boundaries, overlap a little so a rule split across a boundary still appears, then measure which size retrieves better. Do not send all 10 retrieved chunks blindly: drop weak scores and leave room in the window for the prompt and the answer.

Embeddings and cosine similarity

AnalogyAn embedding is a map pin for meaning. Two sentences about the same idea land near each other even when they share no words. Cosine similarity measures the angle between those pins.
Data flow
Text→Embedding model→Vector→Compare angles→Nearest chunk

Keyword search misses “customer-first leadership” when the page says “start with the customer and work backwards.” Vectors catch that. You store the vectors in a vector database and query with the same embedding model you used to store them. Mixing two embedding models in one index makes the geometry meaningless.

Starter

# Tiny stand-in: count overlapping words. A real embedding model replaces this.
def silly_sim(a, b):
    return len(set(a.lower().split()) & set(b.lower().split()))

q = "customer first leadership"
page = "start with the customer and work backwards"
print("overlap", silly_sim(q, page), "(real vectors would still score this as close)")

Example

Duplicate tickets: “package never came” and “where is my parcel?” should land near each other. Resume search: “built data pipelines” should match “ETL on Spark.”

An embedding is a list of numbers that stands for meaning. Similar sentences point in a similar direction. Cosine similarity measures that angle. A smaller angle means a closer meaning. Keyword search misses “customer-first leadership” when the page says “start with the customer and work backwards.” Embeddings catch that.

from sentence_transformers import SentenceTransformer
from sentence_transformers.util import cos_sim

documents = [
    "Amazon leaders start with the customer and work backwards.",
    "Amazon employees should take ownership of their projects.",
    "Binary search works efficiently on sorted arrays.",
    "Java uses classes and objects for object-oriented programming.",
]
query = "Tell me about customer-first leadership"
model = SentenceTransformer("all-MiniLM-L6-v2")

doc_vecs = model.encode(documents)
query_vec = model.encode(query)
scores = cos_sim(query_vec, doc_vecs)[0]

for document, score in zip(documents, scores):
    print(f"{score:.4f} -> {document}")

print(documents[int(scores.argmax())])

argmax() returns the index of the largest score, not the score itself. For the top two, sort the indexes and take the first two. With several queries, cos_sim returns one row per query.

More use cases for the same idea

  • Duplicate detection on support tickets: “package never came” and “where is my parcel?” land near each other.
  • Resume search: “built data pipelines” matches “ETL on Spark” without sharing those words.
  • Recommendation: embed a product description and suggest neighbours.

Practice

  • Write a system, user, and assistant message for a tutor that only answers Java questions.
  • Pick temperature for three apps: invoice classification, a story, a SQL generator. Say why.
  • Explain why a 500-page PDF should be chunked before it is embedded.
  • Given 100 questions, 60 good retrievals, and 55 good answers among those 60, say whether you fix search or generation first.