Google AI Studio is where you try a prompt out before turning it into anything, and where you get the key to call it from your own site. The screen is not the important part: what matters is that there is one afternoon between trying an idea and having it running, and that along the way there are three or four things that cost you a week if you do not know them.
| For | Where |
|---|---|
| Trying the prompt out and tuning it | AI Studio, in the browser |
| Fixing how it must always answer | AI Studio's system instructions |
| Getting the key for your site | AI Studio → "Get API key" |
| Letting people use it | Your server, with the key hidden there |
What "free lite model" actually means
The "lite" models are the small, fast versions of the family, and they come with a no-cost tier. But there are three things almost nobody explains, and they change how you design a whole application:
- The free tier is a daily quota, not a bottomless "free". It runs out, and when it does the API answers with an error — not with a bill, but not with an answer either.
- Each model has its OWN separate quota. This is the good one, and most of this lesson comes out of it: if the small one runs dry, the next one still has all of its own.
- Writing costs considerably more than reading. The price difference between models is mostly in what they produce, not in what you send them.
I am not putting quota figures or prices here: they change every few months and a lesson with stale numbers is worse than one with none. What does not change is the mechanics, which is the part worth learning.
From AI Studio to an app, in four steps
-
Tune the prompt on screen
With the system instructions written out — what it does, in what format, and what to do when it does not know — and tried with five different inputs, not one. One input is enough to believe it works; five show you where it breaks.
And if you need the answer in a fixed format for your code to read it, say so there and check it there.
-
Get the key and store it in Vercel
In the project's environment variables, never in the code or in the repository (lesson 4). And your server makes the call: if the key reaches the browser, anyone can spend it.
-
Set up a model cascade, cheapest to dearest
Here is the trick that turns the free tier into something you can rely on. Because each model has a separate quota, you define a list and, if the first one says it is out, you try the next.
// cheapest to dearest const MODELS = ['...-flash-lite', '...-flash', '...-pro']; for (const model of MODELS) { const r = await call(model, prompt); if (r.ok) return r; // got it: done if (isModelFault(r)) continue; // that model will not do: next // any other failure: carry on too (see below) }The order decides which quota you burn first, not how much you have. Putting the expensive one last does not cost you headroom: it means you only reach it once the cheap ones are exhausted.
-
Cache, or you will pay for the same answer a hundred times
If two people ask the same thing, the second one need not cost anything. In a pharmacy tool the repetition is enormous: here, across the interaction checker's queries, one in three combinations had already been asked before.
Cache the failure too, with a short window — twenty minutes — or a bad patch turns into one call per visitor until the day is gone.
Four things learned by breaking them
| What sounds reasonable | Why it is wrong |
|---|---|
| "If it returns a request error, I stop the cascade: the prompt is bad" | No. That error almost always comes from ONE model rejecting a field in the body, not from your text. Stopping there leaves the tool dead when trying the next one would have worked — and that failure consumes no quota. Only report "prompt rejected" when they all fail. |
| "If the call did not error, I have an answer" | No. It can come back successful and empty. Here one model showed 91 calls and zero logged failures without producing a single sentence. Check that there is text. |
| "I will lower the length cap to spend less" | Careful. On models that reason, that cap is shared with the reasoning: you can leave it no room to write anything and get an empty answer. That happened here with a cap that looked generous. |
| "I ask for text and force a data format, just in case" | No. If the prompt asks for a sentence, forcing a data format makes the answer unreadable and burns the whole cascade. |
What to build first
Whatever you already do by hand every week and that carries nobody's data. Three that work well and take an afternoon:
- Classifying your own sales into your own categories, from the CSV your management software exports.
- Drafting the product page for your site from its data — with the claims checker from the website lesson behind it.
- Summarising what customers ask you on WhatsApp, to decide what to answer on the site once and for all.
From a test to something published: the four steps
AI Studio is a place to try prompts against a model and see what comes out. It is excellent at that and it is not a host: whatever you build there is not your app. The confusion is common and costs days, so it is worth having the whole route clear from the start.
-
Tune the prompt in AI Studio, with real examples
This is where the actual work happens. Paste five or six of the hard cases — not the easy ones — and rewrite the prompt until all five come out right. A prompt tuned against easy cases falls over on day one.
-
Take the key and store it where it belongs
Once. It goes into a server environment variable, never into a file in the repository and never into the browser.
-
Call from YOUR function, not from the browser
It is the same piece as in the chatbot lesson and for the same two reasons: the key and the caps. Without it you do not have an app: you have a published key.
-
Log what happens
Model, tokens, whether there was an error and how long it took. Without this you cannot know whether it works, what it costs, or why it stopped working one day.
What "free lite model" means, exactly
| What people hear | What it is |
|---|---|
| "It's free" | There is a free tier with per-minute and per-day limits. Past that you either pay or wait for tomorrow |
| "Lite is the bad model" | It is the fast, cheap one. For classifying, extracting and summarising it is usually indistinguishable from the big one — and three times faster |
| "If I run out of quota, that's it" | Every model has its OWN SEPARATE quota. That is the basis of the cascade |
| "The question is the expensive part" | The answer is. Writing costs several times more than reading |
The cascade, with code and the three rules that hold it up
// ⚠️ ONE list, in ONE file. Copying it into every endpoint is
// how you end up with six versions and one of them pointing
// at a model that no longer exists.
const MODELS = ['cheap-lite', 'mid', 'new-lite']; // cheap -> expensive
const down = new Set(); // forgotten on restart, on purpose
async function ask(body) {
let last = null;
for (const model of MODELS) {
if (down.has(model)) continue;
const r = await call(model, body);
if (r.ok && hasText(r)) return { ...r, model };
// The model does not exist or rejects a field: set it aside, carry on.
if (isModelFault(r)) { down.add(model); last = r; continue; }
// ⚠️ Any other failure ALSO carries on. See the rule below.
last = r;
}
throw new Error('No model answered: ' + (last?.reason || 'no detail'));
}-
A request error NEVER cuts the cascade
The tempting reasoning is "if the prompt is bad, repeating it on another model just burns quota". Both halves are false: a rejection like that fails instantly and without consuming generation quota, and in practice it comes from ONE model not accepting a field in the body, not from a bad prompt.
Compare the costs of being wrong: carry on with a bad prompt and you lose two instant failures. Cut the cascade on a bad model and your site loses the tool. It is not close.
-
A successful response WITH NO TEXT is a failure
It is the most treacherous of the lot: the server reports success and there is nothing inside. If you only check the status code, that gets logged as success — and then you have a model showing a hundred 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. That is why
hasText(r)is in the condition and not justr.ok. -
The circuit breaker forgets on restart
A model set aside is not set aside forever: the
Setlives in memory and disappears on the next start. If the failure was transient, it comes back on its own. If it was real, it gets set aside again on the first call. Zero maintenance.
Token budgets, and the one that eats the answer
You can limit how much the model writes, and you should: that is the expensive part. But there is a trap that leaves the tool mute without giving any error.
Structured answers: the decision that simplifies the most code
If what you need is not prose but data — a classification, three fields, a list — you can ask the model to answer in a fixed shape. It completely changes what you have to write afterwards.
// ❌ Prose: you have to "guess" by reading the text
"It looks like the product is an SPF50 sunscreen, suitable for
sensitive skin and fragrance-free, though I cannot be certain."
// ✅ Structured: used directly
{ "category": "suncare", "spf": 50, "fragrance_free": true, "confidence": "medium" }confidence — a field where the model states how sure it is, which is vastly more
useful than a "it looks like" buried in a sentence, because your code can act on it.
Second: with a fixed shape, an odd answer detects itself; with prose, somebody has to read it.
What deserves a model call, and what does not
| Yes | No — and what to do instead |
|---|---|
| Classifying free text into your categories | Adding, subtracting or applying a percentage. That is a formula: exact, free and always the same |
| Summarising a long sheet into three lines | Searching a list you already have. That is a filter |
| Extracting fields from messy text | Formatting a date or an amount |
| Drafting something a person will review | Deciding something that must be identical every time |
A whole app, end to end
A complete, small example, which is how to start: you paste an order list exactly as it arrives and it comes back sorted into categories, flagging whatever it could not classify.
| Piece | What it does | How long it takes to write |
|---|---|---|
| A page with a text box and a button | Paste and send | Twenty minutes |
| Your function | Caps, cache, cascade and the call | An hour |
| The prompt | Your categories and a fixed output shape | Two hours, and they are the ones that matter |
| The log table | Model, tokens, error, time | Ten minutes |
Classify each line into EXACTLY one of these categories:
analgesia · digestive · respiratory · skincare · infant · hygiene · other
Return a list. For each line:
{ "text": "the line as it came", "category": "...", "sure": true|false }
RULES:
- If torn between two, pick the likelier one and set sure: false.
- If it fits none, category "other" and sure: false.
- Do NOT invent new categories.
- Do NOT correct the original line: return it exactly as received.sure field is what turns a toy into a tool. Without it, the
doubtful lines mix in with the good ones and you have to check everything — which saves nothing.
With it, the doubtful ones are painted amber at the top and you check ten out of eighty.
The value is not in classifying: it is in knowing what was NOT classified well.
Before you call this learned
- I tune the prompt in AI Studio before writing code.
- The key lives in the environment variables and my server makes the call.
- I have a model cascade, cheapest to dearest, and I know why that order.
- I check that the answer HAS TEXT, not just that it did not error.
- I cache what repeats, and the failure too with a short window.
- The model list lives in one single file.
← Previous: a chatbot for your pharmacy Back to the school →