Building a chatbot is the easy part: a few lines and a key. The hard part — and what decides whether it helps you or lands you in trouble — is what it talks about, what it does not, and who answers for what it says. This lesson is mostly about that.
- Diagnose. Not even sideways: "that sounds like…" is a written diagnosis.
- Recommend a prescription medicine, or compare one with another for the public.
- Replace a referral. If the right answer is "see your doctor", it has to say so and stop there.
So what is it for?
For what people ask twenty times a day that is not clinical. Which is far more than it sounds:
| Yes | No |
|---|---|
| Opening hours, on-call rota, where to park | "What should I take for this?" |
| What you need for an electronic prescription | "Can I swap ibuprofen for…?" |
| Whether you stock something and if it has to be ordered | "Is this serious?" |
| How to book a service | Interpreting a blood test |
| Taking the person to the tool or to the counter | Stating a dose |
And three things have to be said, always visible
- That it is a bot. Not as a formality: somebody who thinks they are talking to the pharmacist trusts it differently, and nobody gave you that trust.
- That it does not replace pharmacist advice, on the first screen and not in a footer.
- That the conversation travels to a third party. What gets typed there leaves your pharmacy and reaches the model provider. That is data processing and it has to be disclosed — and it is why the bot must never ask for anyone's name, ID number or medication.
The architecture, in one line
browser → your server → the model
On Vercel that piece is a server function: a file in api/ that takes the
question, adds your instructions, calls the model with the key that lives in the
environment variables, and returns the answer. It is about forty lines.
The five pieces of one that works
-
The system prompt: what it is and what it does NOT answer
This is the fixed text that goes ahead of every question. What actually makes it safe is not telling it what it knows: it is telling it what to do when asked something that is not its business.
You are the assistant for [Pharmacy X]. You answer ONLY about: opening hours and on-call rota, services, whether we stock something, and how to book. If you are asked anything clinical — symptoms, what to take, doses, whether a medicine is right for something, interpreting a blood test — do NOT answer. Say exactly: "That is one for the counter, or call us on [phone]." Never state a dose. Never recommend a medicine. Never give an opinion on what somebody might have. If you do not know something about the pharmacy, say so and give the phone number.That exit sentence — the one saying what to answer when it is not its business — is the most important line in the file. Without it the model improvises something friendly… and clinical.
-
A spending cap, from day one
Not because it will be expensive: because you do not control it. A link shared in a group chat, or somebody poking at it, and the bill jumps in an afternoon.
Three brakes, all of them simple:
- Per-person limit (by IP or by session): so many questions an hour.
- Global daily limit. Once hit, the bot says come back tomorrow. Better than a bill.
- Short answers. The cost is in what it writes, not in what it reads.
-
Cache what repeats
"What time do you open?" is always the same question. Storing the answer and serving it without calling the model removes most of a pharmacy bot's cost at a stroke, because almost everything it gets asked repeats.
And cache the failure too, with a short window. Writing to the cache only on success is what turns a bad day into one call per visit: if the model fails and you store nothing, every visitor retries and you burn the whole quota. It happened here — 166 calls and zero rows in the cache. -
Store the question AND the answer
Almost everyone who builds a bot stores the question and the cost. That tells you how much you spend and what people ask, but not whether it answers well — which is the only thing that decides whether you keep it.
Here the text had been stored from the start and was displayed nowhere. Storing it and never looking is nearly the same as not storing it.
-
Read it all, the first month
Fifty real conversations teach you more about your pharmacy than any analysis: you will see questions you did not expect — which probably ought to be answered on the site — and you will see where your prompt falls short.
It is also where you will find out whether somebody has tried to get clinical advice out of it, and what the bot did. Nobody else can run that check.
A warning about the empty answer
Here that left one model showing 91 calls and zero failures without having produced a single sentence. A failure logged as a success never gets fixed, because nobody goes looking for it. Check that there is text, not that there was no error.
Where a message actually goes, and why the order matters
The system prompt, line by line
The system prompt is the fixed text that goes in front of every conversation. It is where you decide what the bot talks about, what it does not, and what it does when it does not know. In a pharmacy, half the work is in the prohibitions, not the capabilities.
You are the assistant for [NAME] Pharmacy, in [TOWN].
# What you DO
- Opening hours, address, phone and how to get here.
- Whether we stock a health and beauty product, and its price.
- Explaining services: dosette boxes, blood pressure checks, booking a
consultation with a pharmacist.
- How prescriptions work in general terms.
# What you NEVER do
- No personalised health advice, no assessing symptoms, no suggesting
what to take. If asked, refer: "that is something we look at at the
counter with a pharmacist, or call [PHONE]".
- No discussing prescription medicines, doses, or whether something can
be taken with something else.
- No booking, no taking payment, no confirming medicine stock.
- Do not request or accept health data. If somebody types it, do not
repeat it and tell them not to write it here.
# How you answer
- Short: two or three sentences.
- In the language you are written to in.
- If you do not know, say so and give the phone number. Do NOT make it up.
- After anything health related: "this does not replace advice from your
pharmacist".The caps: three layers, and all three are needed
A public chatbot is an API open to the internet that costs money per call. With no caps, one person with a script burns a month's quota in an afternoon — and it does not take malice: a badly written loop will do.
| Layer | What it limits | What it protects you from |
|---|---|---|
| Per message | Length of the input and of the answer | Somebody pasting an entire book, which is billed by tokens |
| Per session | Messages per conversation and per minute | The looping script, which is the realistic case |
| Per day | A global counter for your whole site | The strange day. It is the only cap that guarantees the bill cannot run away |
const MAX_INPUT = 800; // characters
const MAX_PER_SESSION = 20; // messages
const MAX_PER_DAY = 400; // model calls, across the whole site
if (message.length > MAX_INPUT) {
return reply('Could you ask that more briefly, please.');
}
if (await messagesInSession(sessionId) >= MAX_PER_SESSION) {
return reply('That is plenty for today. Give us a ring on [PHONE].');
}
if (await callsToday() >= MAX_PER_DAY) {
// ⚠️ NOT an error: a useful answer. And it tells you.
await notifyAdmin('Chatbot daily cap reached');
return reply('I cannot help right now. Please call us on [PHONE].');
}Caching: the difference between pennies and a bill
The questions a pharmacy bot gets repeat enormously: opening hours, bank holidays, whether you stock something, where to park. Each of those, uncached, is a paid model call. Cached, the second and every one after it is free and instant.
function key(text) {
return text
.toLowerCase()
.normalize('NFD').replace(/[\u0300-\u036f]/g, '') // strip accents
.replace(/[^a-z0-9 ]/g, ' ') // and punctuation
.replace(/\s+/g, ' ').trim();
}
// "What time do you open?" and "what time do you open" are the SAME key.
const k = key(message);
const stored = await cache.get(k);
if (stored) return reply(stored, { from_cache: true });
const text = await callTheModel(message);
await cache.set(k, text, { hours: 24 });
return reply(text);What gets stored from each conversation
| Stored | What for |
|---|---|
| The question and the answer | It is the only thing that says whether the bot answers well. Without the answer stored, the dashboard only counts calls |
| Whether it came from cache | To know whether the cache is doing anything |
| Model, tokens and milliseconds | Cost and speed, which is what you check weekly |
| A session identifier | So you can read whole conversations, not loose sentences |
| The IP or the email | No. None of the above needs it |
Quick questions: the detail that decides whether it gets used
A chat that opens empty with a blinking cursor does not get used: nobody knows what to ask it. Three or four buttons with the real questions change usage completely — and they protect you as well, because they steer towards what the bot can actually answer.
| Good | Bad |
|---|---|
| "What are today's opening hours?" | "Ask me a question" |
| "Do you do dosette boxes?" | "How can I help you?" |
| "Where can I park?" | "Ask me anything" |
The four numbers that say whether it works
| Number | What it tells you |
|---|---|
| Conversations going past one message | Whether the first answer was worth anything. A one-message conversation is somebody who left |
| % served from cache | How much it is saving you. If it is low, everybody asks differently — interesting in itself |
| Times it said "I don't know" | It is the list of what to add to the prompt, handed to you |
| Times it referred to the counter | The bot doing this is a SUCCESS, not a failure. It is its job |
Before you publish it
- The key lives on the server and does not appear in the page source.
- The prompt says what to answer when asked something clinical, with the sentence written out.
- There is a per-person cap and a daily cap.
- It says it is a bot, that it does not replace advice, and that the conversation goes to a third party.
- I store question and answer, and I have committed to reading them the first month.
- I have tried myself to get clinical advice out of it, and seen what it says.
← Previous: Analytics and Search Console Next: an app with AI Studio →