How a chatbot is built
University of Colorado Anschutz School of Medicine
October 15, 2026
How often do you use a chatbot (ChatGPT, Copilot, Gemini, Claude, or similar)?
How confident are you using a chatbot?
Scale: Not at all · A little · Somewhat · Fairly · Very
When you use a chatbot, what do you use it for most?
The first three are the top uses in OpenAI’s study of 1.5 million ChatGPT conversations: together, nearly 80% of them (Chatterji et al., 2025).
Today: the same loop, with words. Then we open ChatGPT Edu.
The patient was started on empiric _____
One word. Whatever comes to mind first.
You each drew on every sentence like this one you have read or heard on the wards. Together, your answers made a spread.
A chatbot has learned from millions of sentences like this one, and similar ones, and how each was finished. From them it gives every possible next word a probability, then picks one.
It doesn’t look those sentences up while it answers. Training turned them into its dials, the way yesterday’s 8,254 patients became the few numbers in the eGFR.
the patient was started on empiric vancomycin
| Predictors: the words so far | Target: the next word |
|---|---|
| the | patient |
| the patient | was |
| the patient was | started |
| the patient was started | on |
| the patient was started on | empiric |
| the patient was started on empiric | vancomycin |
Nobody labels anything. Every sentence ever written is already a table like this.
Yesterday’s two questions: the answer is in the data, and the output is a category, one token out of a vocabulary of about 200,000. A language model is a classifier, run again and again.
A token is a word or a piece of a word.
tokenFallback = ["Started", " on", " empir", "ic", " van", "com", "y", "cin", " and", " p", "iper", "ac", "illin", "-t", "az", "ob", "act", "am", "."]
tokenPieces = tokenizer ? tokenizer.encode(phrase).map(id => tokenizer.decode([id])) : tokenFallback
tokenSpan = (p, i) => Object.assign(document.createElement("span"), { className: `tok t${i % 6}`, textContent: p.replace(/ /g, "·") })
tokenBoxState = { viewof phrase.querySelector("input").disabled = !tokenizer; return !tokenizer; }
html`<div class="tokens" role="status">${tokenPieces.map(tokenSpan)}</div>
<p class="defn">${tokenizer
? `${tokenPieces.length} tokens for ${phrase.trim().split(/\s+/).filter(Boolean).length} words. A dot (·) marks a space that belongs to the token.`
: "The tokenizer could not load on this network, so this shows the split for the example sentence only."}</p>`The real tokenizer used by OpenAI’s GPT-4o models (o200k_base), from the open-source gpt-tokenizer library. It runs in your browser: nothing you type leaves your computer. Other models split text differently.
Each token becomes a list of numbers called an embedding. Tokens used in similar sentences get similar numbers.
Fever and hypotension. She has a central line and grew MRSA on her last admission. The patient was started on empiric ___
A healthy 25-year-old with a cough and a lobar infiltrate, sent home. The patient was started on empiric ___
The same last six words, the sentence from the word cloud, and a different next word. At each step the model weighs every earlier word and decides which matter for the next one. This is attention, the core of the transformer, the “T” in GPT (Vaswani et al., 2017). Probabilities made up for this slide.
nextTable = ({
"empiric": [["vancomycin", .30], ["antibiotics", .24], ["piperacillin-tazobactam", .14], ["coverage", .12], ["ceftriaxone", .09], ["therapy", .07], ["meropenem", .04]],
"vancomycin": [["and", .52], ["for", .22], [".", .14], ["plus", .08], ["after", .04]],
"antibiotics": [["for", .46], [".", .24], ["after", .16], ["and", .14]],
"piperacillin-tazobactam": [["for", .44], ["and", .30], [".", .18], ["after", .08]],
"coverage": [["for", .58], ["with", .28], [".", .14]],
"ceftriaxone": [["for", .40], ["and", .36], [".", .24]],
"therapy": [["for", .40], ["with", .38], [".", .22]],
"meropenem": [["for", .50], ["and", .30], [".", .20]],
"cefepime": [["for", .48], [".", .32], ["after", .20]],
"fluids": [["for", .45], [".", .35], ["after", .20]],
"and": [["piperacillin-tazobactam", .38], ["cefepime", .26], ["fluids", .20], ["blood", .16]],
"plus": [["piperacillin-tazobactam", .55], ["cefepime", .45]],
"for": [["presumed", .40], ["possible", .26], ["suspected", .22], ["sepsis", .12]],
"presumed": [["sepsis", .55], ["pneumonia", .30], ["cellulitis", .15]],
"possible": [["sepsis", .45], ["pneumonia", .35], ["meningitis", .20]],
"suspected": [["sepsis", .50], ["pneumonia", .35], ["cellulitis", .15]],
"sepsis": [[".", .85], ["after", .15]],
"pneumonia": [[".", .80], ["after", .20]],
"cellulitis": [[".", 1]],
"meningitis": [[".", 1]],
"after": [["blood", .62], ["cultures", .38]],
"blood": [["cultures", 1]],
"cultures": [[".", .70], ["were", .30]],
"were": [["drawn", .80], ["sent", .20]],
"drawn": [[".", 1]],
"sent": [[".", 1]],
"with": [["vancomycin", .50], ["broad-spectrum", .30], ["gram-negative", .20]],
"broad-spectrum": [["coverage", 1]],
"gram-negative": [["coverage", 1]]
})
mutable story = ["empiric"]
viewof temperature = rangeInput({ label: "Temperature", min: 0.2, max: 2, step: 0.1, value: 1 })storyDone = story.at(-1) === "." ? "finished" : story.length >= 10 ? "capped" : null
nextDist = {
if (storyDone) return null;
const w = nextTable[story.at(-1)].map(([t, p]) => [t, Math.pow(p, 1 / temperature)]);
const z = w.reduce((a, [, v]) => a + v, 0);
return w.map(([t, v]) => ({ token: t, p: v / z }));
}
{
const pick = () => { let r = Math.random(); for (const d of nextDist) { r -= d.p; if (r <= 0) return d.token; } return nextDist.at(-1).token; };
const go = html`<button class="w-btn" type="button">Pick the next word</button>`;
go.disabled = !nextDist;
go.onclick = () => { mutable story = [...story, pick()]; };
const reset = html`<button class="w-btn ghost" type="button">Start over</button>`;
reset.onclick = () => { mutable story = ["empiric"]; };
const line = html`<div class="story" role="status">The patient was started on</div>`;
story.forEach((w, i) => {
if (w !== ".") line.append(" ");
line.append(Object.assign(document.createElement(i === 0 ? "b" : "span"), { className: i === 0 ? "" : "picked", textContent: w }));
});
return html`<div class="sampler"><div>${line}
<div class="illus-label">Made-up probabilities for this slide, not from a real model or from prescribing data.</div>
<div>${go}${reset}</div></div>
<div class="bars with-num">${nextDist
? nextDist.map(d => html`<div class="bar-row"><span class="bar-label">${d.token}</span><div class="bar-track"><div class="bar-fill warm" style="width:${(d.p * 100).toFixed(0)}%"></div></div><span class="bar-num">${(d.p * 100).toFixed(0)}%</span></div>`)
: storyDone === "finished"
? html`<p>The sentence is finished. Press <b>Start over</b> and you will probably get a different one.</p>`
: html`<p>This small made-up table stops here. A real model never runs out: it has a probability for every token it knows, every time.</p>`}</div></div>`;
}Temperature: how willing the model is to pick less likely words. ChatGPT doesn’t let you change it, but the pick is still random. Whole words are used here instead of tokens, to keep it readable.
It predicts the next word of this whole document, not of your question. Your message is only the last few words. Illustrative: the real hidden text is longer and isn’t published in full.
The model is the same at every turn: your chat doesn’t turn its dials. The app sends the whole transcript again each time. A long chat can outgrow the context window, and older parts get dropped. Whether your chats train a future model depends on the terms: personal ChatGPT may, unless you opt out; CU’s agreement rules it out for ChatGPT Edu.
1 · Pretraining
Reads: trillions of words of web pages, books, articles, and code.
Learns: to continue any text.
Gives: a document completer.
Almost all of the cost.
2 · Instruction tuning
Reads: thousands of example conversations, written by people: a request, then a good answer.
Learns: what a helpful reply looks like.
Gives: something that answers.
3 · Preference tuning
Reads: people’s choices between pairs of answers.
Learns: to give the kind of answer people prefer.
Gives: the assistant you use.
All three are the fitting loop. Only the table changes.
The loss is yesterday’s idea, for words: the less probability the model gave the word that actually came next, the bigger the loss. Made-up probabilities. Scale from Llama Team, AI @ Meta (2024).
| Dials (parameters) | Training data | |
|---|---|---|
| eGFR, CKD-EPI 2021 | fewer than 10 | 8,254 people |
| GPT-3, 2020 (Brown et al., 2020) | 175 billion | 300 billion tokens |
| Llama 3, 2024 (Llama Team, AI @ Meta, 2024) | 405 billion | about 15 trillion tokens, on up to 16,000 chips |
Reading 15 trillion tokens at 250 words a minute, 8 hours a day, would take a person about a quarter of a million years.
Our arithmetic: a token is about ¾ of a word. The companies behind ChatGPT, Claude, and Gemini don’t publish these numbers for their current models.
Prompt: What is the first-line treatment for community-acquired pneumonia in a healthy adult outpatient?
Pretraining only: it continues the text
What is the first-line treatment for community-acquired pneumonia in an adult with COPD?
What is the first-line treatment for hospital-acquired pneumonia?
What is the first-line treatment for aspiration pneumonia?
Chapter 12 review questions: answers on page 418.
After stages 2 and 3: it answers
For a healthy adult outpatient with no comorbidities, US guidelines recommend amoxicillin. Doxycycline is an alternative, or a macrolide where pneumococcal resistance to macrolides is low (Metlay et al., 2019).
Neither is broken. Online, a list of questions is often followed by more questions, so the pretrained model continues the document it thinks it is in. Stages 2 and 3 teach it that a question is followed by an answer.
2 · Instruction tuning
3 · Preference tuning
In one study, people preferred the answers of a model 100 times smaller that had these two stages over the full-size model without them (Ouyang et al., 2022).
It is trained toward answers people rate well.
That is not the same as answers that are true.
Telling people what they want to hear has a name: sycophancy.
| The eGFR | The model behind a chatbot | |
|---|---|---|
| Predictors | creatinine, age, sex | all the tokens so far |
| Target | measured GFR | the next token |
| Output | a number: regression | one token of ~200,000: classification |
| Training set | 8,254 adults in 10 studies | trillions of tokens of text, then examples and ratings from people |
| Dials | fewer than 10 | hundreds of billions |
| Loss | how far the estimate misses | how surprised it was by the true next token |
| Test set | 4,050 people in 12 other studies | benchmark questions, if they were not in the training text |
Useful, often right, and never checked.
Checking is your job.
The training population, a random pick, and no truth check:
all three, in answers about a number you used yesterday.
The race-free CKD-EPI 2021 equation was the national recommendation from 2021. A lot of older text online still describes the race term. Today’s models may answer differently; the way to know is to test them.
Open ChatGPT Edu.
As we work, watch for what the recipe predicts:
Most of the words so far are yours. Write them on purpose.
Instructions
Turn the chatbot into a journal coach: it knows the five journal questions and your goals, asks you questions, and drafts your entry in your words.
Files
The handouts from the talks: slides plus speaker notes. Add each week’s as you go.
One chat a week
Week 1, Week 2, … The chat is your log: short updates through the week, by voice if you like. Then “draft my entry; ask me what’s missing first”, and paste it into Canvas.
Why not one chat for all four weeks? Remember “Every turn, the whole thing goes back”: a month-long chat can outgrow the context window. Keep each chat to a week.
A bonus: with the handouts in the Project, you can ask about the lectures too. “I’m reviewing Wednesday’s talk. Using that handout, quiz me one question at a time on the six words, and point me to the slide when I’m wrong.”
Go to chatgpt.com and log in with your CU email.
Slides for copying text: talks.seandavis.net
How-to, from the source
Read the terms
Workspace data isn’t used to train models. Check for yourself: CU’s FAQ and OpenAI’s data-use page.
Open ⋯ → Project settings → Instructions. Copy this in (the copy button is at the top right of the box), fill in the brackets and your goals, and close the dialog.
About me: [program and year]; interested in [___];
AI experience: [none / some / a lot].
You are my learning-journal coach for a 4-week course
on AI in medicine. Each Thursday I submit an entry
answering these five questions:
1. What I chose to learn, and why (tie it to my goals)
2. How AI helped, and where it did not; one time I
checked the AI: why, how, and what I found
3. What surprised me
4. What changed for me
5. What I used: about 3 resources with links, at least
one I found myself, and whether each was worth it
When I give an update: reply briefly, then ask ONE
question, often "Did you check anything the AI told
you? How, and what did you find?"
When I'm unsure how to do something: ask what I'm
trying to do, suggest 2-3 approaches, and say which
you'd pick and why. Look things up first if that helps.
When I say "draft my entry": first ask me about
anything missing. Then draft in my words; don't invent
experiences or judgments. End with one line on how AI
helped with the entry.
Remind me to keep real patients out of this project.
My goals:
1.
2.
3.
The handout has the slides and the speaker notes, so the chatbot gets the explanations. Add today’s handout when it’s posted, after class.
In the Project’s New chat box, give it context, then ask. Two ways to start:
No goals yet?
“I’m a first-year [program] student, interested in [___]. I’ve used ChatGPT [never / for ___]. This course asks me to set three learning goals about AI in medicine. Before suggesting any, ask me a few questions, then offer options and say which you’d pick.”
Then paste your goals into Instructions.
Your first update
Tell it what you tried, why, and what you weren’t sure about. Haven’t tried anything yet? “I’m studying [___] and have 20 minutes. Suggest two small things I could try with AI, and how I’d check what you tell me.”
Tonight (Week 1 is due today), in the same chat: “Draft my Week 1 entry. First ask me what’s missing.” Read it, fix it, paste it into Canvas. Rename the chat Week 1 from its ⋯ menu.
Watch: inside the model (8 min)
Large Language Models explained briefly, 3Blue1Brown, 2024. Today’s story, beautifully animated. Captions on YouTube; transcript at 3blue1brown.com/lessons/mini-llm.
Read: LLMs in medicine
Large language models in medicine, Nature Medicine, 2023. How these models are built, and what that means for clinical use (Thirunavukarasu et al., 2023).
Use: ChatGPT Edu at CU
function rangeInput({ label, min, max, step, value }) {
const form = html`<form class="w-range" onsubmit="return false"><label><span>${label}</span><input type="range" min="${min}" max="${max}" step="${step}" value="${value}" aria-label="${label}"><output>${value}</output></label></form>`;
const input = form.querySelector("input"), out = form.querySelector("output");
form.value = +input.value;
input.addEventListener("input", () => { form.value = +input.value; out.textContent = input.value; });
return form;
}
function textInput({ label, value }) {
const form = html`<form class="w-text" onsubmit="return false"><label><span>${label}</span><input type="text" aria-label="${label}"></label></form>`;
const input = form.querySelector("input");
input.value = value;
form.value = value;
input.addEventListener("input", () => { form.value = input.value; });
return form;
}One Word at a Time · IDPT 8079 · October 15, 2026