---
title: "Three eras of analytics"
subtitle: "From rules and ML to generative systems"
jupyter: gaba-core
---
Consider a Monday morning on the analytics desk. The support manager forwards a folder of 10,000 customer support tickets from the last quarter and asks for three things: a breakdown of the categories the tickets fall into, a list of the top recurring complaints, and a rough sense of which tickets contain churn-risk language. She wants the answers by Friday, and she has asked some version of this question every quarter for the past five years.
The analytics techniques suited to this request were different five years ago, different again two years ago, and are different once more today. Each produces its own kind of useful outputs, demands its own kind of input-processing effort, and has its own unique failure modes. This chapter explains which technique fits which kind of problem, and the rest of the book teaches how to build and measure systems with the newest of these techniques.
::: {.callout-note title="Setup for this chapter"}
The one code cell in this chapter runs locally in the `gaba-core` environment. If
you have not set up this environment yet, [Appendix A](../../appendices/a-setup.qmd)
has complete setup instructions, from a clean machine to a verified installation.
:::
## Three eras of text analysis
What an analyst could realistically build in plain Python falls into three eras,
each defined less by the tools at hand than by where the analyst's effort
concentrated.
**Era one: rules.** We begin by reading a few hundred tickets to learn the vocabulary of the queue, then encode what we find as regular expressions. Tickets that mention *refund* sort into one category, those that mention *broken* or *doesn't work* into another, and a deliberate exception captures the ones that cite both a refund and a shipping delay, since those tend to be the angriest. By Thursday the system classifies perhaps seventy percent of the tickets correctly and misclassifies the rest. Its value is evident, because it is fast and auditable enough that the support manager can read the rules herself and confirm they make sense. Its weakness is equally genuine: the rules are brittle, and the first time a new product launches and the vocabulary shifts, they fall out of date within a week.
That era is computationally cheap enough to revisit right now. Here is a working era-one system for a thirty-ticket sample of the queue: keyword rules checked in order, first match wins, scored against hand-assigned gold categories. The data loaders come from the book's shared `gaba` package, introduced properly in the next two chapters.
::: {.callout-tip title="With an AI coding tool"}
Drafting the first pass of a `RULES` list like the one below is a reasonable ask for an assistant: paste a dozen sample tickets and have it propose keyword-category pairs. The assistant cannot decide which misses are acceptable, so read its proposals against tickets you have actually read. A pair that maps "feature" to feature requests looks self-evident, yet it misroutes the customer whose export feature stopped working. Misclassifications like this are invisible in the aggregate accuracy reported below.
:::
```{python}
# An era-one triage system in miniature. Each rule is a (keyword, category)
# pair; the first keyword found in the ticket decides, and no match means "other".
from gaba.data import load_tickets, load_ticket_labels
RULES = [
("refund", "refund_request"), ("charg", "billing_problem"), ("invoice", "billing_problem"),
("cracked", "damaged_item"), ("defect", "damaged_item"), ("instead", "wrong_item"),
("someone else", "wrong_item"), ("deliver", "shipping_delay"), ("where is my", "shipping_delay"),
("password", "account_access"), ("locked out", "account_access"), ("cancel", "cancellation"),
("switch", "subscription_change"), ("upgrade", "subscription_change"),
("used my card", "fraud_report"), ("error", "technical_issue"),
("would be great", "feature_request"), ("feature", "feature_request"),
("worst", "complaint"), ("fantastic", "praise"), ("thank", "praise"),
]
def classify(text: str) -> str:
for keyword, category in RULES:
if keyword in text.lower():
return category
return "other"
tickets, labels = load_tickets(), load_ticket_labels()
gold = dict(zip(labels["ticket_id"], labels["category"]))
predictions = [classify(t) for t in tickets["text"]]
correct = sum(p == gold[t] for p, t in zip(predictions, tickets["ticket_id"]))
print(f"keyword rules: {correct}/{len(tickets)} tickets correct ({correct / len(tickets):.0%})")
```
A simple rule-based system reaches seventy-three percent classification accuracy with no model training and no meaningful compute cost. The misclassifications show part of why such a system is brittle: the customer who writes "take me off my plan immediately and stop charging my card" is categorized as a billing problem, because a rule matched the word *charging* and the cancellation intent could not be captured by simple word-matching. This seventy-three percent serves as the era-one baseline: Chapter 3 evaluates a generative system on the same thirty tickets against the same labels.
**Era two: traditional machine learning.** We hand-label a thousand tickets across the categories that are important in our domain, represent each ticket as a TF-IDF vector (a weighted count of which words appear, with common words discounted) and train a classifier on those numbers, a logistic regression or a small gradient-boosted model, reaching perhaps ninety percent accuracy on a held-out test set. The result is far more robust to variation in phrasing, because a thousand labeled examples expose the classifier to many wordings of the same intent. The model, however, is opaque in a new way: the support manager can no longer read the reasoning behind any single classification, though she can read a confusion matrix (a table of predicted against actual categories) and trust the headline number. The labor has shifted from writing rules to labeling examples, and the risk has shifted with it, from missed edge cases to distribution shift. When next quarter's tickets arrive from a subtly different distribution, a shift not immediately evident to the analyst or the manager, the model can degrade quietly, and no one notices until customers complain about misrouted tickets.
**Era three: generative systems.** We write a prompt that instructs a model to read each ticket and return a JSON object (a structured text format of keys and values that code can read directly) with three fields: `category`, chosen from a list of fifteen, `top_complaint`, and `churn_risk`. The prompt goes to a **foundation model** (a large neural network pretrained on broad text, adaptable to many tasks), whose training text includes the very phrases our customers use, and we run it across all ten thousand tickets without training anything ourselves. It fails and succeeds in unfamiliar ways: it makes mistakes the era-two model never would, such as occasionally returning the opposite of what we asked, yet it recognizes intent that no wordlist could reach, such as the customer who requests a refund without once using the word. The system is more flexible, more expensive per ticket, and opaque in yet a third way, because neither the prompt nor the model's weights explain any single answer. The analyst's effort therefore moves once more, into writing the prompt, choosing the model, and evaluating the output against examples we already trust.
Each era demands roughly the same amount of work from the analyst; what changes is the task that work goes into: writing rules in the first era, labeling examples in the second, and framing the problem and measuring the result in the third. The eras can also be read as successive steps from words toward intent: rules match exact words, a trained classifier matches patterns of wording, and a generative model responds to what the customer meant, irrespective of the words used by the customer.
| | Era one: rules | Era two: traditional ML | Era three: generative |
|---|---|---|---|
| What we build | keyword rules encoding the queue's vocabulary | a classifier trained on labeled examples | a prompt for a foundation model |
| Where effort concentrates | writing and maintaining the rules | labeling a thousand tickets well | framing the problem, measuring the result |
| Failure mode to guard against | rules go stale when the vocabulary shifts | quiet degradation under distribution drift | confident errors, if left unmeasured |
: Three eras of text analysis, compared on what we build, where the analyst's effort concentrates, and the failure mode to guard against. {#tbl-three-eras}
## What changed and what did not
Because the shift is easy both to overstate and to understate, it is worth being
precise about what is new, what is not, and what sits in between.
### What is genuinely new
Three things are new in a way that should change the systems we build.
**Zero-shot capability that often works.** A modern foundation model can do a task
it was never explicitly trained on, in any of dozens of languages, on the strength
of a paragraph of instructions. While this capability is neither magic nor reliable
across the board, the tasks a business analyst is asked to do (classify this,
extract that, summarize the other, route based on a rule) are precisely the ones
where it works often enough to be worth our time.
**Instruction following in plain English.** Where the earlier eras required
engineered features or hand-written rules, the instruction to a modern model is
written in plain English, which the model maps to behavior. That sounds trivial, but
it changes who can build what: a senior analyst with a few days of Python can now
build systems that, in 2020, required a machine-learning engineer.
**Tool use and structured outputs.** A modern model can return JSON that conforms to
a schema, call tools, plan multi-step actions, and feed the results back into its
own next step. Structured output is the bridge from "the model said something" to
"I can put it in a dataframe," and tool use extends the same bridge to "the model
did something." We develop this capability in Chapter 3 (structured outputs) and put
it into action in Chapters 11 (workflows) and 12 (agents).
### What is not new
The big mistakes in this field come from forgetting what did not change.
**We still have to evaluate.** A model that says something plausibly correct may nonetheless be wrong. The single most common failure in generative AI projects is putting a system into use that nobody knows how to measure, discovering three months later that it has been quietly wrong, and not knowing when the errors began. This failure is not hypothetical: in May 2026, EY Canada withdrew a published cybersecurity report after an outside review found fabricated and misattributed citations behind most of its footnotes, along with internally inconsistent figures, in a document the firm had been putting in front of clients for months.^[Financial Times, "EY retracts study after researchers discover AI hallucinations," May 2026. The underlying review, by the AI-detection firm GPTZero, found 16 of the report's 27 cited sources fabricated, misattributed, or unreachable (<https://gptzero.me/investigations/ey>). Verified July 2026.] Measuring what you build is part of building it; a common mistake is to treat measurement as a step bolted on at the end.
**Bad data still poisons everything.** If we ask the model to extract a field that
is only sometimes present in the documents, the model will hallucinate that field
for the documents where it is absent. The remedy predates generative models: a
held-out test set, a baseline, and quantifiable numbers.
**Probabilistic outputs are still probabilistic.** The same prompt against the same model can produce different outputs depending on temperature (a setting that controls randomness), sampling, and the model provider's internal state. A system designed as if these outputs were deterministic will fail when they vary, and a system designed as if they were reliably correct will pass the model's errors through unchecked, with potential to propogate through automation pipelines with expensive consequences.
**Cost matters and compounds.** Unlike the first two eras, which were free at inference time, inference in the third era is metered: each classified ticket, summarized page, and agent step carries a per-call charge, and those charges scale with volume. Building a system that performs the task well is therefore only half the job, because the system must also pay for itself. The cost callout in every applied chapter works out the economics of the system built there.
### What is maybe new
One thing has shifted enough that it deserves its own category: **an analyst can now build systems that act, going beyond systems that report.** A dashboard tells us what is happening, and a traditional ML model tells us what is likely to happen, while a generative system can, if we let it, read an email, look something up in our CRM (the customer-records system), draft a response, and, if we really let it, send the response. Because the risk profile changes once a system can take actions, safety and governance get their own chapters in Part IX. The capability still belongs among the options an analyst weighs, because some problems, such as routing a ticket onward once it has been categorized, are only fully served by a system that acts. Systems of this kind, called **agentic systems**, have become a central pattern in applied generative AI, and Part IV builds them.
## The three eras side by side
@tbl-three-eras summarized the narrative of the three eras. Choosing between them in
practice involves several further criteria, from cost at inference to auditability,
some of which are shown in the following table.
| Criterion | Rules | Traditional ML | Generative systems |
|---|---|---|---|
| Cost at inference | Free | Almost free | Pay per call |
| Effort to start | Days | Weeks (labeling) | Hours |
| Effort to improve | Linear (more rules) | Linear (more labels) | Sub-linear (better prompts, retrieval, eval) |
| Handles new vocabulary | Poorly | Moderately | Well |
| Handles novel tasks | No | No (retrain) | Often yes (zero-shot) |
| Auditability | High (readable rules) | Medium (confusion matrix) | New techniques (Ch 9, Ch 24) |
| Determinism | High | Medium | Low (without care) |
| Risk of confident error | Low | Medium | High |
| Can take actions | No | No | Yes (Ch 11, 12) |
Every cell of this table is the topic of at least one later chapter. None of these
criteria is settled in the field, but the working rule when in doubt is to build the
simplest thing that meets the bar and then measure it.
Expressed in business terms, the same comparison takes a different form: the table below prices the 10,000-ticket quarterly job for each era. Although the numbers are illustrative, the trade-off structure is real, because each era exchanges fixed effort, marginal cost, and the cost of change in its own proportions.
| The 10,000-ticket job, per era (illustrative) | Rules | Traditional ML | Generative systems |
|---|---|---|---|
| Build effort | ~4 analyst-days | ~15 analyst-days, mostly labeling | ~2 analyst-days, mostly evaluation |
| Marginal cost per quarter | ~$0 | ~$0 | well under $1 |
| Adapting to a new product line | days of rewriting rules | weeks of relabeling and retraining | hours of editing the prompt, plus re-measuring |
## The model landscape
"Generative AI" covers several distinct families of model, organized most clearly by what each family produces and the inputs it accepts. The newest text models accept several of those inputs at once, text together with an image or audio in the same call, so a model is described by the set of inputs it accepts, which may contain more than one type. The table below lists every family this book builds with, tagged with its chapters, so it doubles as a map of what is ahead in the book.
::: {.content-visible when-format="html"}
```{ojs}
//| echo: false
//| label: fig-modality-matrix
//| fig-cap: "The generative model landscape organized by what each family produces and the inputs it accepts. The newest text models take several inputs in a single call; the + marks them. Click a row for what the family does, representative open and hosted models with links to their model cards, and a business use; the last column shows where this book builds with each family."
viewof modality_landscape = {
const accent = "#0969da", muted = "var(--bs-secondary-color,#57606a)";
// One row per family in real production use. Grouped by what it produces; the
// accepts column carries multimodality (a + means more than one input at once).
const FAMILIES = [
{produces: "text", accepts: "text", extra: " + image, audio", multi: true, family: "Language / multimodal",
what: "Classifies, extracts, summarizes, drafts, and reasons; the workhorse of this book. The frontier models are natively multimodal, taking text together with an image or audio in the same call.",
open: ["Llama 3.1 8B Instruct", "https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct"],
hosted: ["GPT-4.1 / Claude families", "https://platform.openai.com/docs/models"],
business: "Ticket triage, document summarization, drafting at scale.",
book: "Chapters 2-3, 7, 11-12"},
{produces: "text", accepts: "image + text", multi: true, family: "Vision-language",
what: "Reads charts, documents, and screens into text and structured data.",
open: ["Qwen2.5-VL 7B", "https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct"],
hosted: ["Gemini multimodal", "https://ai.google.dev/gemini-api/docs"],
business: "Invoice extraction; shelf-compliance audits; chart reading.",
book: "Chapter 19"},
{produces: "text", accepts: "video + text", multi: true, family: "Video understanding",
what: "Reasons over video frames and their order in a single call.",
open: ["Qwen-VL family (video input)", "https://huggingface.co/Qwen"],
hosted: ["Gemini video understanding", "https://ai.google.dev/gemini-api/docs/video-understanding"],
business: "Footage triage; queue and safety monitoring.",
book: "Chapter 19"},
{produces: "text", accepts: "audio", multi: false, family: "Speech-to-text",
what: "Transcribes speech; paired with a diarization model, it also records who spoke when.",
open: ["Whisper large-v3", "https://huggingface.co/openai/whisper-large-v3"],
hosted: ["Hosted transcription services", "https://platform.openai.com/docs/guides/speech-to-text"],
business: "Call-center analytics; meeting and earnings-call mining.",
book: "Chapter 18"},
{produces: "embedding", accepts: "text", multi: false, family: "Embedding model",
what: "Turns text into vectors for search, clustering, and classification features.",
open: ["BGE-M3", "https://huggingface.co/BAAI/bge-m3"],
hosted: ["OpenAI text-embedding-3", "https://platform.openai.com/docs/guides/embeddings"],
business: "Semantic search over contracts; deduplicating support tickets.",
book: "Chapters 6, 13"},
{produces: "image", accepts: "text", multi: false, family: "Image generation",
what: "Renders an image from a text description.",
open: ["FLUX.1 [dev]", "https://huggingface.co/black-forest-labs/FLUX.1-dev"],
hosted: ["Imagen / DALL-E pages", "https://deepmind.google/models/imagen/"],
business: "Ad-creative variants; product mockups.",
book: "used to build Ch 19's assets"},
{produces: "audio", accepts: "text", multi: false, family: "Text-to-speech",
what: "Produces natural voices from text; the other half of a voice agent.",
open: ["Kokoro 82M", "https://huggingface.co/hexgrad/Kokoro-82M"],
hosted: ["Provider TTS endpoints", "https://platform.openai.com/docs/guides/text-to-speech"],
business: "IVR and voice-bot output; audio versions of written content.",
book: "used to build Ch 18's call"},
{produces: "video", accepts: "text", multi: false, family: "Video generation",
what: "Renders short video clips from a text description; the family is young, expensive, and improving quickly.",
open: ["Open video models (survey)", "https://huggingface.co/models?pipeline_tag=text-to-video"],
hosted: ["Veo / Sora pages", "https://deepmind.google/models/veo/"],
business: "Advertising and training-video drafts.",
book: "used to build Ch 19's assets"},
{produces: "time series", accepts: "time series", multi: false, family: "Forecasting",
what: "Produces zero-shot forecasts for series the model has never seen.",
open: ["Chronos-Bolt", "https://huggingface.co/amazon/chronos-bolt-small"],
hosted: ["TimesFM and peers", "https://huggingface.co/google/timesfm-2.0-500m-pytorch"],
business: "Demand and occupancy forecasting across thousands of series.",
book: "Chapter 20"}
];
const container = document.createElement("div");
container.style.cssText = "font-family:inherit;color:var(--bs-body-color);border:1px solid var(--bs-border-color,#d0d7de);border-radius:6px;padding:12px;max-width:680px;";
const table = document.createElement("table");
table.style.cssText = "border-collapse:collapse;width:100%;font-size:12.5px;";
const head = table.insertRow();
["Produces", "Accepts", "Family", "In this book"].forEach((h, i) => {
const c = head.insertCell(); c.textContent = h;
c.style.cssText = `padding:5px 8px;font-weight:600;text-align:${i === 0 ? "left" : "center"};border-bottom:2px solid var(--bs-border-color,#d0d7de);`;
});
const groupCount = {};
for (const f of FAMILIES) groupCount[f.produces] = (groupCount[f.produces] || 0) + 1;
const seen = {}, rows = [];
let selected = 0;
FAMILIES.forEach((f, idx) => {
const row = table.insertRow();
row.style.cursor = "pointer";
if (!seen[f.produces]) {
seen[f.produces] = true;
const pc = row.insertCell();
pc.textContent = f.produces;
pc.rowSpan = groupCount[f.produces];
pc.style.cssText = "padding:6px 8px;font-weight:600;text-align:left;vertical-align:top;border-right:1px solid var(--bs-border-color,#d0d7de);white-space:nowrap;";
}
const ac = row.insertCell();
ac.style.cssText = "padding:6px 8px;font-family:monospace;text-align:center;";
ac.innerHTML = f.accepts + (f.extra ? `<span style="color:${accent};">${f.extra}</span>` : "");
const fc = row.insertCell();
fc.style.cssText = "padding:6px 8px;text-align:center;";
fc.innerHTML = f.family + (f.multi ? ` <span style="font-size:10px;color:${accent};border:1px solid ${accent};border-radius:3px;padding:0 4px;white-space:nowrap;">multimodal</span>` : "");
const bc = row.insertCell();
bc.textContent = f.book;
bc.style.cssText = `padding:6px 8px;color:${muted};text-align:center;white-space:nowrap;`;
row.onmouseenter = () => { if (idx !== selected) row.style.background = "rgba(9,105,218,0.07)"; };
row.onmouseleave = () => { if (idx !== selected) row.style.background = "transparent"; };
row.onclick = () => { selected = idx; render(); };
rows.push(row);
});
container.appendChild(table);
const panel = document.createElement("div");
panel.style.cssText = "margin-top:10px;padding:10px;border:1px solid var(--bs-border-color,#d0d7de);border-radius:6px;font-size:13px;min-height:120px;";
container.appendChild(panel);
const legend = document.createElement("div");
legend.style.cssText = `margin-top:6px;font-size:11px;color:${muted};`;
legend.innerHTML = `The <span style="color:${accent};font-family:monospace;">+</span> marks a multimodal family that takes more than one kind of input in a single call. Click any row for details.`;
container.appendChild(legend);
function render() {
rows.forEach((r, i) => { r.style.background = i === selected ? "rgba(9,105,218,0.16)" : "transparent"; });
const f = FAMILIES[selected];
panel.innerHTML =
`<div style="font-weight:600;margin-bottom:4px;">${f.accepts}${f.extra || ""} → ${f.produces} <span style="color:${muted};font-weight:400;">(${f.family})</span></div>` +
`<div>${f.what}</div>` +
`<div style="margin-top:6px;">Open: <a href="${f.open[1]}" target="_blank">${f.open[0]}</a> | Hosted: <a href="${f.hosted[1]}" target="_blank">${f.hosted[0]}</a></div>` +
`<div style="margin-top:4px;">Business use: ${f.business}</div>`;
}
render();
return container;
}
```
:::
::: {.content-visible when-format="pdf,epub"}
| Produces | Accepts | Family | Example models | In this book |
|---|---|---|---|---|
| text | text (+ image, audio) | language / multimodal | Llama 3.1, GPT-4.1, Claude | Chs 2-3, 7, 11-12 |
| text | image + text | vision-language | Qwen-VL, Gemini | Ch 19 |
| text | video + text | video understanding | Qwen-VL, Gemini | Ch 19 |
| text | audio | speech-to-text | Whisper large-v3 | Ch 18 |
| embedding | text | embedding models | BGE-M3 | Chs 6, 13 |
| image | text | image generators | FLUX.1 | builds Ch 19 assets |
| audio | text | text-to-speech | Kokoro | builds Ch 18's call |
| video | text | video generators | Veo-class | builds Ch 19 assets |
| time series | time series | forecasting FMs | Chronos-Bolt | Ch 20 |
: The generative model landscape by what each family produces and the inputs it accepts; the newest text models accept several inputs in a single call. The online edition makes this clickable, with links to each family's model cards. {#tbl-modality-matrix}
:::
The model names in the table carry a distinction worth understanding now. Pretraining produces a **base model**, a machine that continues whatever text it is given, fluently but unhelpfully, like a new hire who has read every book in the library but has never sat in a meeting. Ask a base model to write a poem and it may list poetry exercises, because that is what often follows such requests in its training data. Instruction tuning and preference training, the on-the-job training that turns continuation into answering, produce an **instruct model**; every large language model (LLM) this book uses is of this kind, and the suffix in names like `Llama-3.1-8B-Instruct` signifies exactly this step. Chapter 2 returns to the distinction with an interactive demo, right before we call a model for the first time.
```{mermaid}
%%| echo: false
%%| label: fig-base-to-instruct
%%| fig-cap: "How a base model becomes the assistant you actually call: pretraining produces a text continuer; supervised fine-tuning on instruction-response pairs teaches the answering format; preference tuning teaches which answers people prefer. The same pipeline, run on your own examples, is Chapter 16's fine-tuning in miniature."
flowchart LR
pre["pretraining<br/>(predict the next token<br/>over the internet)"] --> base["base model:<br/>a text continuer"]
base --> sft["supervised fine-tuning<br/>(instruction-response pairs)"]
sft --> pref["preference tuning<br/>(which answer do people prefer?)"]
pref --> inst["instruct model:<br/>the assistant you call"]
```
One more distinction matters when you read a model card: most large models today are **mixture-of-experts** designs that advertise two parameter counts, total and active. Chapter 4 explains why both numbers matter in operational terms.
## Where this book takes us
The rest of the book assembles, in order, the layers of one working system. From here forward we are on the analytics desk at a mid-sized company, and by the last chapter the system we build there does three jobs: it triages the incoming ticket queue, it answers questions over the company's filings and reports, and it runs an agent when a question takes more than one step. Most chapters add a layer to this system, and a few add a capability the desk reaches for when the work calls for it, so the pieces accumulate the way they do in practice.
```{mermaid}
%%| echo: false
%%| label: fig-lifecycle
%%| fig-cap: "The journey of a business question, from deciding whether it is even an LLM job to a deployed, auditable answer. Each stage is a part of the book, and evaluation, cost, and compliance run under all of them. Later chapters point back to where they sit on this map."
flowchart TB
s1["Frame the question:<br/>is this even an LLM job? (Chapter 1)"]
s2["Reach a model:<br/>calling it, structured output, where it runs (Part I)"]
s3["Prepare the data:<br/>extract text from documents and embed it (Part II)"]
s4["Retrieve and ground:<br/>find the few passages that answer it (Parts II and III)"]
s5["Orchestrate:<br/>one call, a workflow, or an agent (Part IV)"]
s6["Specialize as needed:<br/>analytics, a custom model, other modalities, knowledge graphs (Parts V to VIII)"]
s7["Deploy and operate:<br/>measure it, watch it, and guard it (Part IX)"]
s1 --> s2 --> s3 --> s4 --> s5 --> s6 --> s7
```
We start with the substrate (Part I): how to call a model, get structured output,
and pick where the model runs. Then we build the document layer (Parts II and III):
how to extract text from PDFs, search over it with embeddings, build a basic
retrieval-augmented system, and improve it with better retrieval techniques. In Part
III we reach the evaluation toolkit (Chapter 9), after which everything is easier to
judge, because we finally have a way to tell whether each thing we build is better
than the last.
From there we move into workflows and agents (Part IV), embedding analytics and
natural-language Q&A over structured data (Part V), customization of the underlying
model (Part VI), modalities beyond text (Part VII), and structured knowledge (Part
VIII). The last three chapters (Part IX) are about turning the working system we
have built into something a stakeholder can actually rely on: safety, observability,
and a small chat interface that sits on top of everything we have built. Part X
closes with capstone projects, each of which demands several of the book's
techniques in combination, as real assignments do. For example, one of the projects
is a responsible resume screener whose bias audit is part of the build.
If we only had time for three chapters, they would be Chapter 2 (calling models),
Chapter 7 (the basic RAG system), and Chapter 9 (evaluation). Those three give us
the smallest working system and the discipline to know if it is any good.
## Matching a problem to a technique
Each era added a tool to the analyst's repertoire without retiring the ones before it, so the practical question is which technique the problem's requirements select, a question that the newness of any technique does not answer by itself. Where the logic is explicit and every decision must be auditable, rules remain the right choice, because anyone can read them and confirm what the system will do. Where labeled examples are plentiful, the vocabulary is stable, and the volume demands near-zero marginal cost, traditional ML offers high accuracy at almost no cost per item. Where the task is specified in written instructions, where phrasing varies freely, or where no training data exists, a generative system is the tool that applies; and where the problem requires taking actions beyond producing a report, an agentic system, introduced in Part IV, extends the generative approach with the ability to act. These considerations compress into four questions, asked in order:
```{mermaid}
%%| echo: false
%%| label: fig-which-era
%%| fig-cap: "Four questions that match a problem to a technique. Logic you can write down stays in rules; plentiful labels with a stable vocabulary point to traditional ML, as does a hard requirement for near-zero marginal cost; otherwise the problem is generative, and if the system must go beyond reporting and take actions, it is agentic."
flowchart TB
d1{"Can the logic be written as<br/>explicit rules a colleague<br/>could follow?"} -- yes --> rulesleaf["rules"]
d1 -- no --> d2{"Plentiful labeled examples<br/>and a stable vocabulary?"}
d2 -- yes --> ml["traditional ML"]
d2 -- no --> d3{"Near-zero marginal cost<br/>per item required?"}
d3 -- "yes: invest in labels" --> ml
d3 -- no --> d4{"Must the system take<br/>actions beyond reporting?"}
d4 -- no --> gen["generative system"]
d4 -- yes --> agentic["agentic system<br/>(Part IV)"]
```
A fair test before moving on is to take a problem your own team is facing and walk it through the four questions. If the walk stalls, the stall itself is informative, because each question names a fact about your situation, such as how stable the vocabulary is, how many labeled examples exist, and whether the volume demands near-zero marginal cost, and the fact you cannot supply is the one to go and establish. The next twenty-four chapters assume this placement judgment and build on it.
::: {.callout-warning title="Don't outsource this"}
It is tempting to paste a problem description into an assistant and ask which
technique fits, but the four-question map depends on facts that only you can supply:
how stable your vocabulary actually is, how many labeled examples exist, and what
near-zero marginal cost is worth to your business. The recommendation that comes
back will sound confident either way, because assistants are trained toward answers
that satisfy the person asking. Whatever your description omits, the model fills
with assumptions you never stated and therefore cannot check.
:::
The ticket-triage task from the opening scene will return throughout the book: Chapter 2 makes the first programmatic triage pass, Chapter 3 gives that triage a typed and validated output, Chapter 11 makes it the target of a routing workflow, and Chapter 25 deploys it as a service. Other chapters draw on other running examples, among them a corpus of annual reports for retrieval and a sentiment dataset for fine-tuning, but following one problem across the book's capabilities is the clearest way to see how they fit together.
::: {.callout-tip title="Cost: what this chapter's example actually costs"}
Era three is the first era in which inference itself carries a marginal cost. For
the 10,000-ticket triage problem above, classifying every ticket with a lightweight
model at typical 2026 rates costs well under one dollar, and because the batch is a
full quarter's volume, that figure is also the quarterly cost. Chapter 2 measures
this cost precisely and introduces the two mechanisms that control it: prompt
caching and model routing.
:::
::: {.callout-important title="Compliance: where this chapter's example is regulated"}
The same triage system, run over customer messages, processes personal information, and the rules that apply depend on where the customers are. Customers in the European Union place the data under the General Data Protection Regulation (GDPR), while the EU AI Act (the European Union's risk-tiered rules for AI systems) applies separately and is triggered by the risk class of the system, irrespective of the data it processes. Customers in Colorado fall under a separate state framework. Appendix D provides the decision flowchart, and every later chapter that touches regulated data refers back to that appendix.
:::
## Exercises
The build labs and evaluate labs start in Chapter 2. Chapter 1 is conceptual, so its
exercises are too.
### Conceptual questions
1. A team flags incoming insurance claims as "high risk" from free-text descriptions. The vocabulary in claims changes slowly, and they have 100,000 labeled historical claims. Which technique is the best starting point?
A. Rules, since a slow-moving vocabulary can be encoded by hand once
B. Traditional ML, since plentiful labels and a stable vocabulary suit it
C. A generative system, since zero-shot capability makes the labels unnecessary
D. A generative agent, since claims must be acted on beyond simply being flagged
2. A generative system summarizes earnings transcripts. After three months in production, complaints arrive that the summaries miss material disclosures. Which failure best explains three months of undetected misses?
A. The transcripts grew past the model's context window during the quarter
B. The provider deprecated the foundation model behind the system
C. The team launched without a way to measure summary quality over time
D. The prompt was tuned on press releases, which differ in form from transcripts
3. A senior leader asks: "Now that we have generative models, do we still need data labeling?" The most accurate framing is:
A. Yes. Labeled examples are still how we evaluate generative systems
B. No. A generative model can label its own data without human review
C. No. Labels were only ever needed when training supervised models
D. Yes, but only for classification tasks with a fixed category list
4. Which statement about the three eras is the *least* accurate?
A. Generative systems can often handle tasks they were never trained for
B. Traditional ML is usually cheaper per inference than a generative call
C. Rule-based systems are usually easier to audit than generative ones
D. Generative systems need no test set, since the model comes pre-evaluated
5. You want to convince a sponsor that a generative ticket-triage system is worth its cost. Which framing is most persuasive?
A. The model's price per million tokens beats every major competitor
B. The model places near the top of a widely cited public benchmark
C. Triage costs X dollars per ticket against Y dollars of manual handling
D. Response latency stays under two seconds at the 95th percentile under load
6. Moving from rules to traditional ML to generative systems, which of these rises?
A. The risk of a confident answer that is simply wrong
B. The effort required to get a first working version running
C. The brittleness of the system when vocabulary shifts
D. The cost of labeling enough examples before starting
7. In the generative era, where does the analyst's effort concentrate?
A. In writing and maintaining the regular expressions
B. In hand-labeling thousands of training examples
C. In tuning hyperparameters across training runs
D. In framing the problem and evaluating the result
8. The chapter places one capability in the "maybe new" category. Which one?
A. Models can classify text in dozens of languages at once
B. An analyst can build systems that act, going beyond systems that report
C. Models can return JSON that conforms to a declared schema
D. A company can fine-tune a foundation model on its own data
### Build lab and evaluate lab
These begin in Chapter 2.
::: {.callout-note title="Where we go next"}
In Chapter 2 we make our first programmatic call to a model, route between a cheap
model and a reasoning model based on the task, and use prompt caching to make the
same prompt cost a fraction of itself the second time. The ticket-triage
problem from the top of this chapter becomes a working five-line script by the end
of the next one.
:::