CourseAI Engineering: The Complete Course · Module 0: Quick Win · part 7 of 105
Part 7 · Module 0: Quick Win

Lesson 1: Your first model call, and what it cannot know

9 min read·13 Oct 2026
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.

CURSOR, APRIL 2025 User email why am I logged out? AI support bot no rule to go on "Expected" a rule it invented Company: "We have no such policy"
Cursor's support bot (amber: the model) answered with a rule nobody had written (red: failure); the company (green: the truth) said it had no such policy.

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.

WHERE THE KEY GOES Provider site Groq or Gemini Create key copy it once Colab Secrets key icon, left Notebook access switch on os.environ in the notebook secret name: GROQ_API_KEY or GOOGLE_API_KEY
The key travels from the provider's site into Colab Secrets (blue: your system), and from there into the notebook, never into a code cell.

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.

TYPED IN A CELL STORED IN SECRETS api_key = "gsk_..." shared notebook key goes with it userdata.get(KEY) shared notebook only the name goes
A key typed into a cell travels with every copy of the notebook (red: danger); a key in Secrets stays with your account (green: safe).

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.

PROVIDER "groq" MODELS model name KEY from Secrets llm ready to call you change this you use this init_chat_model builds the connector for that provider
The setup cell turns one word, PROVIDER, into a ready model object called llm (amber: the model).
python
%pip install -q -U langchain langgraph langchain-groq langchain-google-genai langchain-ollama
python
import 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 install adds LangChain, a library that gives every provider the same interface, plus one connector per provider.
  • PROVIDER is the one word you change to switch provider.
  • MODELS names one model per provider (checked October 2026; on "model not found", put a current name here).
  • The if block copies your key from Secrets into an environment variable, where LangChain looks for it. Ollama runs locally and needs no key.
  • init_chat_model(...) builds llm, the object you talk to. temperature=0 asks for consistent answers.
  • max_retries=6 waits 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.

your question a string llm.invoke(...) Groq or Gemini runs the model reply object print(reply.text)
One call: your text goes out to the provider with llm.invoke, and a reply object comes back with the answer inside.
python
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

  • question holds Aisha's message and asks for a short answer.
  • llm.invoke(question) sends it to the provider; reply.text is 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".

GROQ, NO POLICY GIVEN (9 OCT 2026) "one-year limited warranty" matches, by luck "live support agent on Sunday" "app chat available 24/7" BrightCart: Monday to Saturday only
Without BrightCart's policy, the model guessed: the warranty part matched by luck (green), the Sunday support was invented (red).

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 prompt Returns: 30 days... Refunds: 5 to 7 days... Shipping: 3 to 5 days... Warranty: 1 year... Order changes... Support: Mon to Sat... Customer question: ... model now has the facts
Pasting the six policy lines above the question gives the model the facts it was missing; the prompt is everything it knows.

The fix is to hand it the facts. These are BrightCart's six policies, used all through the course.

python
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

  • POLICIES is a dictionary: a topic on the left, the rule on the right.
  • "\n".join(...) glues the rules into one block, one per line.
  • prompt puts the policies first, then the question.
WITHOUT POLICY WITH POLICY Sunday: yes toll-free number, 24/7 chat Sunday: no Monday to Saturday, 8am to 8pm Warranty: 1 year a guess Warranty: 1 year faults covered
Same model, same question: only the answer with the policy can be checked against BrightCart's rules.

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 who you are, how to sound, the six policies human Aisha's question model reply
A system message is the standing briefing (blue); the human message is the customer's question; both go to the model in one call.
python
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

  • system is the system message: the assistant's role, tone and policies.
  • messages is a list of (role, text) pairs; "human" marks the customer's words.
GROQ, WITH SYSTEM MESSAGE (9 OCT 2026) No human agent on Sunday Fault covered by the 1-year warranty "chat or email" on Sunday no policy mentions it
With the system message both facts are right (green), but the reply still adds one detail no policy gives (red): the rules narrow guessing, they do not stop it.

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.

AIMessage .text the answer, 322 characters .usage_metadata tokens in and out .response_metadata model name, more
The reply is an object, not a string: the text is one field, the token counts and model name are others.
python
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__ prints AIMessage, a message written by the model.
  • usage_metadata counts tokens, the word pieces a model reads and writes. Providers charge by them.
  • response_metadata.get("model_name") names the model that answered.
ONE CALL ON GROQ, IN TOKENS input 235 reasoning 145 answer 94 output 239 = 145 + 94, total 474
Most tokens in this call were the briefing you sent; of the 239 output tokens, 145 were hidden reasoning (grey) and you pay for them too.

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.

PROVIDER one word groq gpt-oss-20b gemini 3.5 Flash-Lite ollama your computer same llm.invoke() for all three
One word picks the connector (grey: the options not in use); the rest of your code does not change.

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.

python
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

  • OTHER picks the provider you did not start with, and the if block fetches its key.
  • other_llm.invoke(messages) sends the same messages, so only the model differs. max_retries=6 is 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.

python
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_question asks for a delivery date, a fact in no policy.
  • The same system message goes with it: rules, but no order data.
"order 5521?" Aisha model policies only March 12, 2026 "you can still return" order system not connected 2 April 2026 the real date
Asked about order 5521, the model had policies but no order data, so it invented a date (red) instead of the real one (green).

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.

THE GAPS YOU SAW, AND WHO CLOSES THEM Module 7 says "I don't know" checkable replies Module 8 answers from BrightCart's documents Module 9 looks up 5521 in the order system
Each gap you saw here is closed later: Module 7 makes it honest, Module 8 gives it documents, Module 9 connects the order system.

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

  1. 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.

  1. 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.

  1. 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.

python
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

  • .text on the end keeps only the reply's text.
  • assert stops with the message if "30" is missing.

Key terms

TermMeaning
KeyA secret string that identifies you to a provider
Colab SecretsColab's panel for keys, outside your code
LangChainA library giving every provider one interface
System messageA standing briefing above the conversation
TokenA word piece; models are billed in tokens
Reasoning tokensHidden 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