Lesson 1: Your first model call, and what it cannot know
Module 0 · Lesson 1 · About 20 minutes · Needs: a Google account; no coding experience
The situation
Priya, BrightCart's Head of Support, forwards a message from a customer, Aisha Rahman: "My BrightCart robot vacuum will not charge. Can I talk to a human agent on Sunday, and is it covered by the warranty?"
BrightCart's policies hold the answer: human agents work Monday to Saturday, and electronics have a 1-year warranty for faults. Can a language model answer this? You will find out with real calls, and see where it goes wrong.
A real case, first
In April 2025, users of Cursor, an AI code editor, were being logged out when they used it on more than one machine. Some wrote to support and were told this was expected behaviour. The reply came from an AI support bot, and the policy it described did not exist (Thomas Claburn, The Register, 18 April 2025).
Co-founder Michael Truell answered on Reddit: "We have no such policy," calling it "an incorrect response from a front-line AI support bot." The logouts were a bug, since fixed, and the developer who raised it was refunded.
The bot met a question its company's rules did not answer, and answered anyway, fluently. You are about to watch a model do the same to Aisha.
What you will be able to do
- Store a free key safely in Colab Secrets.
- Run the setup cell and make a model call.
- Give the model BrightCart's policies, in the prompt and as a system message.
- Read the reply's text and token counts.
- Switch provider with one word, and spot an invented answer.
1. Get a key and keep it secret (Essential)
Every request to a model provider carries a key, a secret string tied to your account. Anyone holding it can spend your quota, so where you keep it matters from minute one.
Get a free key from Groq (console.groq.com, then API Keys) or Google Gemini (aistudio.google.com, then Get API key). In Colab, click the key icon in the left sidebar: that is Colab Secrets. Add a secret named exactly GROQ_API_KEY or GOOGLE_API_KEY, paste the key as its value, and switch on Notebook access.
Never type the key into a code cell. Notebooks get shared and copied, and the key goes with them.
2. The setup cell (Essential)
Every lesson that calls a model starts with these two cells. Learn them once.
%pip install -q -U langchain langgraph langchain-groq langchain-google-genai langchain-ollamaimport os
from langchain.chat_models import init_chat_model
PROVIDER = "groq" # "groq" or "gemini" in Colab; "ollama" on your own computer
MODELS = {"groq": "groq:openai/gpt-oss-20b", "gemini": "google_genai:gemini-3.5-flash-lite", "ollama": "ollama:llama3.2:3b"}
KEY = {"groq": "GROQ_API_KEY", "gemini": "GOOGLE_API_KEY", "ollama": None}[PROVIDER]
if KEY and KEY not in os.environ:
from google.colab import userdata # keys live in Colab's Secrets panel
os.environ[KEY] = userdata.get(KEY)
llm = init_chat_model(MODELS[PROVIDER], temperature=0, max_retries=6)Code walkthrough
%pip installadds LangChain, a library that gives every provider the same interface, plus one connector per provider.PROVIDERis the one word you change to switch provider.MODELSnames one model per provider (checked October 2026; on "model not found", put a current name here).- The
ifblock copies your key from Secrets into an environment variable, where LangChain looks for it. Ollama runs locally and needs no key. init_chat_model(...)buildsllm, the object you talk to.temperature=0asks for consistent answers.max_retries=6waits and tries again when the provider says "too many requests" (a 429). It does not retry a reply the provider rejected: see course-guide/02.
3. The first call, with and without the policy (Essential)
This is the moment the course is built around: Aisha's question, bare, then with BrightCart's rules attached.
question = ("My BrightCart robot vacuum will not charge. Can I talk to a human agent on Sunday, "
"and is it covered by the warranty? Answer in two or three sentences.")
reply = llm.invoke(question)
print(reply.text)Code walkthrough
questionholds Aisha's message and asks for a short answer.llm.invoke(question)sends it to the provider;reply.textis the answer as text.
Your wording will differ. In our Groq run (9 October 2026) it said a live agent could be reached "on Sunday" through a toll-free number or the app's chat, "which is available 24/7".
None of that is BrightCart's. The model has never seen BrightCart's rules, so it filled the gap with what shops often do. The warranty part matched by luck; the Sunday part was wrong, in the same confident tone.
The fix is to hand it the facts. These are BrightCart's six policies, used all through the course.
POLICIES = {
"returns": "Returns: 30 days from delivery. Item must be unused and in its original box.",
"refunds": "Refunds: paid to the original payment method within 5 to 7 working days of the return arriving.",
"shipping": "Shipping: standard delivery takes 3 to 5 working days. Free over $50.",
"warranty": "Warranty: 1 year on all electronics. Covers faults, not accidental damage.",
"order changes": "Order changes: an order can be changed or cancelled until it is marked shipped.",
"support hours": "Support hours: human agents are available 8am to 8pm, Monday to Saturday.",
}
POLICY_TEXT = "\n".join(POLICIES.values())
prompt = "BrightCart policies:\n" + POLICY_TEXT + "\n\nCustomer question: " + question
reply = llm.invoke(prompt)
print(reply.text)Code walkthrough
POLICIESis a dictionary: a topic on the left, the rule on the right."\n".join(...)glues the rules into one block, one per line.promptputs the policies first, then the question.
This time: human agents "are only available Monday through Saturday from 8 am to 8 pm, so you won't be able to speak to one on Sunday", and the fault is covered by the 1-year warranty. Same model, same question; only the text you sent changed.
4. A system message (Essential)
Pasting rules into every question gets messy. Chat models accept a standing briefing above the conversation.
system = ("You are BrightCart's customer support assistant. Be friendly and brief. "
"Use these policies:\n" + POLICY_TEXT)
messages = [("system", system), ("human", question)]
reply = llm.invoke(messages)
print(reply.text)Code walkthrough
systemis the system message: the assistant's role, tone and policies.messagesis a list of (role, text) pairs;"human"marks the customer's words.
Our run kept both facts right: no human agent on Sunday, and the fault covered by the 1-year warranty. But it also offered Sunday "chat or email", which no policy mentions. Small inventions creep in even with the rules present. The policies narrow the gap the model has to fill, but they do not close it: where the rules are silent, it still guesses, in the same confident tone. You will meet this again with order 5521.
5. Reading the reply (Essential)
The reply carries a receipt, and the receipt is how you will later measure cost.
print(type(reply).__name__)
print(reply.usage_metadata)
print(reply.response_metadata.get("model_name"))
print(len(reply.text), "characters")Code walkthrough
type(reply).__name__printsAIMessage, a message written by the model.usage_metadatacounts tokens, the word pieces a model reads and writes. Providers charge by them.response_metadata.get("model_name")names the model that answered.
Our run printed 235 input and 239 output tokens (yours will differ a little). Of the output, 145 were reasoning tokens: this model thinks before answering, and you pay for the thinking although the visible reply was 322 characters.
6. Switching provider (Recommended)
Providers change prices, limits and models often, so keep the provider a single setting.
In your own notebook, set PROVIDER = "gemini" and rerun the setup cell. The cell below builds the other provider alongside, to compare. Skip it if you have one key.
OTHER = "gemini" if PROVIDER == "groq" else "groq" # the provider you did not start with
OTHER_KEY = {"groq": "GROQ_API_KEY", "gemini": "GOOGLE_API_KEY"}[OTHER]
if OTHER_KEY not in os.environ:
from google.colab import userdata
os.environ[OTHER_KEY] = userdata.get(OTHER_KEY)
other_llm = init_chat_model(MODELS[OTHER], temperature=0, max_retries=6)
other_reply = other_llm.invoke(messages)
print(OTHER, "says:", other_reply.text)
print(other_reply.usage_metadata)Code walkthrough
OTHERpicks the provider you did not start with, and theifblock fetches its key.other_llm.invoke(messages)sends the same messages, so only the model differs.max_retries=6is the setup cell's.
Expect different wording and token counts, but the same two facts, because both models got the same policies.
Break it: ask about an order it cannot see
Aisha asks about her order. BrightCart's order system says order 5521, her RoboSweep X2, was delivered on 2 April 2026. The model has never seen that system.
order_question = "Hi, it's Aisha. What date was my order 5521 delivered, and can I still return it?"
reply = llm.invoke([("system", system), ("human", order_question)])
print(reply.text)Code walkthrough
order_questionasks for a delivery date, a fact in no policy.- The same
systemmessage goes with it: rules, but no order data.
One Groq run replied "Let me pull up the details for order 5521", gave a delivery date of March 12, 2026, and said "yes, you can still return it". It pulled up nothing. The real date, 2 April, is far outside the 30-day window. A second run promised to "check the delivery date" and "get back to you in just a moment": a lookup it cannot do. Either way, this is the Cursor failure in miniature.
In production
Cursor's first response was procedural: Truell said AI email replies "are now clearly labeled as such" and serve as a first filter (The Register, 2025). The lasting fix is engineering, and it is what this course teaches.
Module 7 teaches the assistant to say "I don't know". Module 8 gives it BrightCart's documents to answer from. Module 9 lets it look up order 5521 for real.
What you added to BrightCart
- A notebook with a safely stored key and a one-word provider switch.
- The assistant's first correct answer to Aisha, from BrightCart's real policies.
- A recorded failure, the invented order date, that Modules 7 to 9 fix.
Check yourself
- Why does the key go in Colab Secrets, not in a code cell?
Cells travel with every shared copy of the notebook. Secrets stay with your account.
- Without the policies, the warranty answer was right. Why is that still a problem?
It was a guess, stated as confidently as the wrong Sunday answer. Reading it, you cannot tell luck from fact.
- Why did the model give a date for order 5521?
It had no order data and nothing told it to admit that, so it produced a likely-sounding reply.
Exercise
Ask a returns question with the system message and check the reply quotes BrightCart's real window. If it fails, add "Answer only from these policies." to system and rerun.
my_question = "I received an unused kettle 3 weeks ago. Can I still return it?"
answer = llm.invoke([("system", system), ("human", my_question)]).text
print(answer)
assert "30" in answer, "The answer should give BrightCart's real return window"
print("Check passed")Code walkthrough
.texton the end keeps only the reply's text.assertstops with the message if "30" is missing.
Key terms
| Term | Meaning |
|---|---|
| Key | A secret string that identifies you to a provider |
| Colab Secrets | Colab's panel for keys, outside your code |
| LangChain | A library giving every provider one interface |
| System message | A standing briefing above the conversation |
| Token | A word piece; models are billed in tokens |
| Reasoning tokens | Hidden thinking before the answer, billed as output |
Sources
- Thomas Claburn, "Cursor AI's own support bot hallucinated its usage policy", The Register, 18 April 2025. https://www.theregister.com/2025/04/18/cursor_ai_support_bot_lies/
- Groq, "Quickstart", GroqDocs, checked October 2026. https://console.groq.com/docs/quickstart
- Google, "Using Gemini API keys", Google AI for Developers, checked October 2026. https://ai.google.dev/gemini-api/docs/api-key