Explore LLM Fundamentals with Gemini
Build Python scripts that explore tokens, prompts, and context with Gemini.
Introduction
30 Second Summary
Every time you ask an AI chatbot a question, something fascinating happens behind the scenes. Your words get broken into tiny pieces, fed through a massive neural network, and reassembled into a response. Understanding how that process works is the first step to building AI-powered tools yourself.
In this project, you will build four Python scripts that explore the fundamental building blocks of large language models using the Google Gemini API. You will count tokens, experiment with prompts and temperature, generate structured JSON, and manage multi-turn conversations.
What You'll Build
A collection of four Python scripts that each demonstrate a core LLM concept through hands-on experimentation with the Gemini API.
By the end of this project, you'll have:
- A working token_explorer.py script that counts tokens and inspects usage metadata from model responses.
- A prompt_lab.py script demonstrating how system instructions and temperature change LLM behavior.
- A structured_output.py script that generates type-safe JSON matching a schema you define.
- A chat_context.py script that manages multi-turn conversations and tracks how the context window fills up.
- Secret Mission: Build a context window visualizer that displays a progress bar showing how much of the model's context is consumed per turn.
Are there any prerequisites?
You need basic Python knowledge and Python installed on your machine. You also need a code editor (VS Code, Cursor, or Antigravity IDE). No credit card is required for this project.
Set Up Your Environment
Before you can explore how LLMs work under the hood, you need a way to talk to one. Google provides free access to their Gemini model through an API key that requires nothing more than a Google account.
In this step, you'll grab your free API key, install Google's Python SDK, and run a test script that proves everything is connected.
In this step, get ready to:
- Get a free Gemini API key from Google AI Studio.
- Install the google-genai package and verify the connection.
- Confirm your Python version is 3.9 or higher.
Get your Gemini API key
Google AI Studio is a free web interface where you can generate API keys for the Gemini model. No credit card is required.
- Open your browser and go to aistudio.google.com.
- Sign in with your Google account.
- Click Get API key in the left panel.
- Click Create API key to generate a new key.
- Copy the key and keep it ready. You will paste it into your terminal in a moment.
Why copy it now?
Google AI Studio only shows the full key once at creation time. After you dismiss the dialog, the key is masked. Copy it immediately so you can paste it into your terminal next.
Now open a terminal where you will set the key as an environment variable and run all your scripts.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
Set the GEMINI_API_KEY environment variable so the SDK can detect it automatically.
macOS
- Set your API key by running this command (replace the placeholder with your actual key):
export GEMINI_API_KEY=your-api-key-here
Windows
- Set your API key by running this command (replace the placeholder with your actual key):
set GEMINI_API_KEY=your-api-key-here
How does the SDK find my key?
The google-genai SDK automatically looks for the GEMINI_API_KEY environment variable when you create a client. You never need to paste the key into your code.
This variable only lasts for your current terminal session. If you close the terminal, you will need to set it again.
Install the Google GenAI SDK and verify connection
With your key set, you can now create a project folder, install the SDK, and confirm everything connects to Gemini.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir llm-fundamentals
cd llm-fundamentals
- Confirm the folder exists by running:
ls
You should see an empty output since the folder is brand new. The important thing is that no error appeared, which means you are inside llm-fundamentals.
- Install the Google GenAI SDK by running:
pip install google-genai
You should see output ending with a success message indicating google-genai was installed.
Seeing a permission error?
On some systems you may need to use pip3 instead of pip. Try running the install with pip3 if the command fails.
pip3 install google-genai
Help me debug my pip install error
Now open your code editor so you can create the test script.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type the name of your code editor (such as VS Code, Cursor, or Antigravity) and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the llm-fundamentals folder, and click Open.
- Create a new file by clicking File then New File.
- Save the file immediately as setup_test.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Copy the following code into setup_test.py:
from google import genai
client = genai.Client()
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="Say hello and confirm you are working!"
)
print(response.text)
What does this code do?
- from google import genai imports the Google GenAI SDK.
- client = genai.Client() creates a client that automatically reads your GEMINI_API_KEY environment variable.
- client.models.generate_content() sends a prompt to the gemini-3.5-flash model and returns a response.
- response.text contains the model's text reply.
- Save setup_test.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Switch back to the terminal you opened earlier.
- Run the test script:
macOS
python3 setup_test.py
Windows
python setup_test.py
You should see a friendly text response from Gemini printed in your terminal, something like a greeting confirming the model is working.
Not seeing a response?
- If you see an error about the API key, make sure you ran the export or set command in the same terminal session where you run the script. Opening a new terminal resets the variable.
- If you see a ModuleNotFoundError, the SDK is not installed in this terminal session. Run the pip install command again.
- If you see a 403 error, confirm you copied the full API key without extra spaces.
Help me debug my setup_test.py error
✔️ Awesome, I've got everything!
Great. Double check you have saved setup_test.py and that you saw a response from Gemini in your terminal.
ⓧ I'd like to double check the full code
Here is the full setup_test.py file for comparison:
from google import genai
client = genai.Client()
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="Say hello and confirm you are working!"
)
print(response.text)
Verify Python version
The google-genai SDK requires Python 3.9 or higher. Confirm your version now so you do not hit unexpected errors later.
- In the same terminal, check your Python version by running:
macOS
python3 --version
Windows
python --version
✔️ I see version 3.9 or higher
You are all set. Your Python version is compatible with the SDK.
ⓧ I see a version lower than 3.9
You need Python 3.9 or higher. Visit python.org/downloads and install the latest version.
On macOS, the system python3 may point to an older version. After installing a newer Python, use the full path or a version manager like pyenv to activate the correct version.
ⓧ Command not found
Python is not installed or not on your PATH. Visit python.org/downloads and download Python 3.9 or higher for your operating system.
After installation, close and reopen your terminal, then run the version check again.
Your environment is ready. You have a working API key, the SDK installed, and a confirmed Python version. Next up, you will explore how tokens work and count exactly how many go in and out of the model.
Explore Tokens and Counting
Your connection to the Gemini API is working. Now it's time to explore the most fundamental unit of how LLMs process language: tokens.
Every time you send a prompt to an LLM, it doesn't read words the way you do. It breaks your text into tokens, which are small chunks of characters. Understanding tokens is critical because they determine how much a request costs, how long a response can be, and how much conversation history fits in a model's memory.
In this step, get ready to:
- Count tokens for prompts of different lengths.
- Inspect usage metadata to see how many tokens go in and come out of a generation.
- Check the model's maximum token limit (its context window).
Count tokens for different prompts
Tokenization splits text into small pieces that the model can process. A short prompt like "Hi" might be a single token, while a detailed paragraph could be dozens. Counting tokens before sending a request helps you predict cost and stay within limits.
- Create a new file called token_explorer.py in your project folder.
- Add the following code to count tokens for three prompts of increasing complexity:
from google import genai
client = genai.Client()
# 1. Count tokens for different prompts
prompts = [
"Hi",
"Explain quantum computing in one sentence.",
"Explain quantum computing in detail, covering qubits, superposition, entanglement, and quantum gates. Include examples of real-world applications.",
]
print("--- Token Counting ---\n")
for prompt in prompts:
token_count = client.models.count_tokens(
model="gemini-3.5-flash", contents=prompt
)
print(f"Prompt: \"{prompt[:60]}{'...' if len(prompt) > 60 else ''}\"")
print(f" Tokens: {token_count.total_tokens}\n")
What does this code do?
This script defines three prompts that increase in length and detail. For each prompt, it calls client.models.count_tokens() to see how many tokens the model would consume without actually generating a response.
Token counting is a separate API call that does not use your generation quota. You can count as many times as you want without affecting your free-tier limits.
- Save token_explorer.py.
- Run the script by executing this command:
python token_explorer.py
- You should see three prompts printed, each with a different token count. The short "Hi" prompt has the fewest tokens, while the detailed paragraph has the most.
Seeing an error when running the script?
- Make sure your GEMINI_API_KEY environment variable is still set in the same terminal session. If you closed the terminal since Step 1, set it again.
- Check for any typos in the file, especially around quotation marks and parentheses.
- Help me debug my token counting script.
Generate content and inspect usage metadata
Counting tokens before a request is useful, but you can also see exact token usage after a generation completes. Every response from Gemini includes usage metadata that tells you precisely how many tokens the prompt consumed and how many the model generated.
- In token_explorer.py, add the following code below the existing token counting section:
# 2. Generate content and inspect usage metadata
print("--- Usage Metadata ---\n")
prompt = "Explain how a neural network learns in 3 sentences."
response = client.models.generate_content(
model="gemini-3.5-flash", contents=prompt
)
print(f"Prompt: \"{prompt}\"")
print(f"Response: {response.text}\n")
print(f"Usage metadata:")
print(f" Input tokens (prompt_token_count): {response.usage_metadata.prompt_token_count}")
print(f" Output tokens (candidates_token_count): {response.usage_metadata.candidates_token_count}")
print(f" Total tokens (total_token_count): {response.usage_metadata.total_token_count}")
What does this code do?
After generating a response, you access response.usage_metadata to see three key numbers:
- prompt_token_count tells you how many tokens your input consumed.
- candidates_token_count tells you how many tokens the model generated in its response.
- total_token_count is the sum of input plus output tokens for the entire request.
- Save token_explorer.py and run it again:
python token_explorer.py
- You should see the token counts from before, followed by a new section showing the generated response and its usage metadata. The input tokens, output tokens, and total tokens are all printed.
Not seeing the usage metadata?
- Make sure you are accessing response.usage_metadata with the correct spelling. A typo in the attribute name will cause an AttributeError.
- Confirm the new code is placed after the token counting loop, not inside it.
- Help me fix my usage metadata code.
Check the model's token limit
Every model has a maximum number of input tokens it can handle in a single request. This ceiling is called the context window. Knowing this limit tells you how much information you can pack into a single prompt or conversation before the model cuts you off.
- Add the following code at the bottom of token_explorer.py:
# 3. Check model's token limit
print("\n--- Model Token Limit ---\n")
model_info = client.models.get(model="gemini-3.5-flash")
print(f"Model: gemini-3.5-flash")
print(f"Maximum input token limit: {model_info.input_token_limit}")
What does this code do?
The client.models.get() method retrieves metadata about a model, including its input_token_limit. This number is the maximum size of the context window for that model.
In later steps, you will use this limit to track how much of the context window your conversation consumes.
- Save token_explorer.py and run it one more time:
python token_explorer.py
- At the bottom of the output, you should see the model's maximum input token limit printed. This is a large number representing the total context window size for gemini-3.5-flash.
Getting an error on client.models.get()?
- Make sure the model name is exactly gemini-3.5-flash with no extra spaces or characters.
- Confirm your API key is still valid by checking that the earlier sections of the script output successfully.
- Help me fix the model info retrieval.
✔️ Awesome, I've got everything!
Great. Double check you've saved token_explorer.py and that all three sections print output when you run it.
ⓧ I'd like to double check the full code
Here is the complete token_explorer.py file. Compare it against yours to spot any differences.
from google import genai
client = genai.Client()
# 1. Count tokens for different prompts
prompts = [
"Hi",
"Explain quantum computing in one sentence.",
"Explain quantum computing in detail, covering qubits, superposition, entanglement, and quantum gates. Include examples of real-world applications.",
]
print("--- Token Counting ---\n")
for prompt in prompts:
token_count = client.models.count_tokens(
model="gemini-3.5-flash", contents=prompt
)
print(f"Prompt: \"{prompt[:60]}{'...' if len(prompt) > 60 else ''}\"")
print(f" Tokens: {token_count.total_tokens}\n")
# 2. Generate content and inspect usage metadata
print("--- Usage Metadata ---\n")
prompt = "Explain how a neural network learns in 3 sentences."
response = client.models.generate_content(
model="gemini-3.5-flash", contents=prompt
)
print(f"Prompt: \"{prompt}\"")
print(f"Response: {response.text}\n")
print(f"Usage metadata:")
print(f" Input tokens (prompt_token_count): {response.usage_metadata.prompt_token_count}")
print(f" Output tokens (candidates_token_count): {response.usage_metadata.candidates_token_count}")
print(f" Total tokens (total_token_count): {response.usage_metadata.total_token_count}")
# 3. Check model's token limit
print("\n--- Model Token Limit ---\n")
model_info = client.models.get(model="gemini-3.5-flash")
print(f"Model: gemini-3.5-flash")
print(f"Maximum input token limit: {model_info.input_token_limit}")
You now know how to measure token usage before and after a request, and you've seen the ceiling that limits how much text you can fit into a single call. Next up, you'll use system instructions and temperature to control how the model responds.
Engineer Prompts and Tune Temperature
You now know how tokens work and how to measure them. The next question is: how do you actually control what an LLM says?
Two of the most powerful levers are system instructions (which set the model's persona and constraints) and temperature (which controls how creative or deterministic the output is). In this step, you will experiment with both to see how drastically they change model behavior.
In this step, get ready to:
- Compare how system instructions change LLM responses.
- Experiment with temperature to control creativity vs. determinism.
Compare system instructions
A system instruction tells the model who it is and how it should behave. The same question can produce wildly different answers depending on the persona you assign.
- Create a new file called prompt_lab.py in your project folder.
- Add the system instruction comparison code by pasting the following:
from google import genai
from google.genai import types
client = genai.Client()
# 1. Compare system instructions
print("--- System Instruction Comparison ---\n")
system_prompts = [
"You are a helpful assistant.",
"You are a pirate who explains everything using nautical metaphors.",
"You are a five-year-old child explaining things simply.",
]
user_prompt = "What is machine learning?"
for system_prompt in system_prompts:
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=user_prompt,
config=types.GenerateContentConfig(
system_instruction=system_prompt,
max_output_tokens=100,
),
)
print(f"System: \"{system_prompt}\"")
print(f"Response: {response.text}\n")
print("-" * 50 + "\n")
What does this code do?
You send the exact same user prompt ("What is machine learning?") to Gemini three times. Each time, a different system_instruction changes the model's persona.
The GenerateContentConfig object from google.genai.types is how you pass generation settings like system instructions, temperature, and output length limits to the API.
Setting max_output_tokens=100 keeps responses short so you can compare them side by side without scrolling through walls of text.
- Save prompt_lab.py.
- Run the script by entering this command:
python prompt_lab.py
- You should see three clearly different responses to "What is machine learning?" printed in your terminal. One will be straightforward, one nautical, and one child-like.
Seeing an import error for types?
Make sure line 2 reads from google.genai import types exactly. The types module lives inside google.genai, not google directly.
help me fix an import error in my prompt_lab.py script
Why do system instructions matter?
In production AI applications, the system instruction is where you define guardrails, tone, and domain expertise. A customer support bot, a code reviewer, and a creative writing assistant all use the same underlying model. The system instruction is what makes each one behave differently.
Experiment with temperature and sampling
Temperature controls how "random" the model's word choices are. A temperature of 0.0 makes the model pick the most likely token every time. Higher values let it explore less probable options.
- Add the temperature experiment to the bottom of prompt_lab.py by pasting this code below the existing system instruction section:
# 2. Experiment with temperature
print("--- Temperature Comparison ---\n")
prompt = "Write a one-sentence tagline for an AI startup."
for temp in [0.0, 0.5, 1.0, 2.0]:
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
temperature=temp,
max_output_tokens=50,
),
)
print(f"Temperature {temp}: {response.text}")
What does this code do?
You ask for the same creative tagline four times, each with a different temperature value: 0.0, 0.5, 1.0, and 2.0.
At 0.0, the output is nearly identical every run because the model always picks the highest-probability token. At 2.0, the model explores far-flung token choices, producing wilder and sometimes nonsensical text. Values like 0.5 and 1.0 sit between these extremes.
The max_output_tokens=50 cap keeps each tagline to one sentence so the temperature effect is easy to spot.
- Save prompt_lab.py.
- Run the script again:
python prompt_lab.py
- Scroll down past the system instruction results. You should see four taglines. The 0.0 and 0.5 outputs will be conservative and similar. The 1.0 and 2.0 outputs will get progressively more creative (or strange).
All four taglines look the same?
Check that the temperature parameter is inside the GenerateContentConfig() call, not as a separate argument to generate_content(). Also verify you have four distinct values in the list: [0.0, 0.5, 1.0, 2.0].
help me debug my temperature experiment
When to use which temperature?
For factual tasks (data extraction, classification, code generation), use temperature 0.0 to 0.3. For creative tasks (brainstorming, writing, marketing copy), use 0.7 to 1.0. Values above 1.5 are rarely useful in production but help you see how the sampling mechanism works.
✔️ Awesome, I've got everything!
Great. Double check you have saved prompt_lab.py with both sections (system instructions and temperature).
ⓧ I'd like to double check the full code
from google import genai
from google.genai import types
client = genai.Client()
# 1. Compare system instructions
print("--- System Instruction Comparison ---\n")
system_prompts = [
"You are a helpful assistant.",
"You are a pirate who explains everything using nautical metaphors.",
"You are a five-year-old child explaining things simply.",
]
user_prompt = "What is machine learning?"
for system_prompt in system_prompts:
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=user_prompt,
config=types.GenerateContentConfig(
system_instruction=system_prompt,
max_output_tokens=100,
),
)
print(f"System: \"{system_prompt}\"")
print(f"Response: {response.text}\n")
print("-" * 50 + "\n")
# 2. Experiment with temperature
print("--- Temperature Comparison ---\n")
prompt = "Write a one-sentence tagline for an AI startup."
for temp in [0.0, 0.5, 1.0, 2.0]:
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
temperature=temp,
max_output_tokens=50,
),
)
print(f"Temperature {temp}: {response.text}")
You now have direct control over how an LLM responds. Next, you will take this a step further by making the model return data in a strict, predictable format using structured outputs.
Generate Structured Outputs
Your prompt experiments in the last step showed how system instructions and temperature control the style of LLM output. But every response came back as free-form text.
Real applications need data in a predictable, machine-readable format. In this step, you will tell Gemini to respond exclusively in JSON that matches a schema you define. This turns an LLM from a text generator into a reliable data source your code can parse without guessing.
In this step, get ready to:
- Define a TypedDict schema for quiz question data.
- Generate type-safe JSON from the Gemini API.
- Parse and display the structured data.
Define a structured output schema
When you ask an LLM a question, it returns prose. Your code then has to guess where the answer starts, what format it is in, and how to extract individual fields. A schema solves this by telling the model exactly what shape of data to return.
- Create a new file called structured_output.py in your project folder and add the following code:
import json
from google import genai
from google.genai import types
from typing_extensions import TypedDict
client = genai.Client()
# 1. Define structured output schema
class QuizQuestion(TypedDict):
question: str
options: list[str]
correct_answer: str
explanation: str
What does this code do?
- TypedDict lets you define the exact shape of data you expect back from the model.
- QuizQuestion specifies four fields: a question string, a list of option strings, a correct answer string, and an explanation string.
- The Gemini API will use this class as a constraint when generating its response.
- Save structured_output.py.
- You won't see output yet. The next chunk uses this schema to generate structured data.
Why TypedDict instead of Pydantic?
The Gemini SDK supports TypedDict schemas directly through the response_schema parameter. This keeps your code simple with no extra dependencies beyond what google-genai already includes.
TypedDict is a lightweight Python typing construct that defines dictionary shapes at the type level. It gives you schema enforcement without needing a full validation library.
Generate type-safe JSON from the LLM
With the schema defined, you can now pass it to the Gemini API alongside a special configuration that forces the model to respond only with valid JSON matching your structure.
- Add the following code below your schema definition in structured_output.py:
# 2. Generate structured quiz questions
print("--- Structured Output: Quiz Questions ---\n")
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="Generate 3 multiple-choice quiz questions about Python programming fundamentals. Each question should have 4 options.",
config=types.GenerateContentConfig(
response_mime_type="application/json",
response_schema=list[QuizQuestion],
),
)
What does this code do?
- response_mime_type="application/json" tells the model to respond only with valid JSON. No preamble, no explanation, just data.
- response_schema=list[QuizQuestion] constrains the JSON to match your TypedDict structure exactly. The model must return a list of objects with the four fields you defined.
- The prompt asks for 3 quiz questions, and the schema ensures each one includes a question, options, correct answer, and explanation.
- Save structured_output.py.
- You won't see the quiz questions yet. The next chunk parses and displays them.
Display the structured output
The response is now guaranteed to be valid JSON matching your schema. You can parse it with Python's built-in json module and iterate over the structured data like any list of dictionaries.
- Add the following code at the bottom of structured_output.py:
# 3. Parse and display structured output
questions = json.loads(response.text)
print(f"Generated {len(questions)} questions:\n")
for i, q in enumerate(questions, 1):
print(f"Q{i}: {q['question']}")
for opt in q["options"]:
print(f" - {opt}")
print(f" Correct Answer: {q['correct_answer']}")
print(f" Explanation: {q['explanation']}\n")
print(f"\nUsage: {response.usage_metadata.total_token_count} total tokens")
What does this code do?
- json.loads(response.text) parses the raw JSON string into a Python list of dictionaries.
- The loop formats each question with its options, correct answer, and explanation in a readable layout.
- The final line prints the total token count so you can see how many tokens the structured generation consumed.
- Save structured_output.py.
- Run the script by running this command:
python structured_output.py
- You should see 3 numbered quiz questions about Python, each with 4 options, a correct answer, and an explanation. The total token count prints at the end.
Not seeing quiz questions?
- Make sure you saved structured_output.py before running the command.
- Check that your GEMINI_API_KEY environment variable is still set in this terminal session. If you opened a new terminal, you need to set it again.
- If you see a JSON parsing error, verify that response_mime_type is set to exactly "application/json" in your code.
Help me debug my structured_output.py script
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete structured_output.py for comparison:
import json
from google import genai
from google.genai import types
from typing_extensions import TypedDict
client = genai.Client()
# 1. Define structured output schema
class QuizQuestion(TypedDict):
question: str
options: list[str]
correct_answer: str
explanation: str
# 2. Generate structured quiz questions
print("--- Structured Output: Quiz Questions ---\n")
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="Generate 3 multiple-choice quiz questions about Python programming fundamentals. Each question should have 4 options.",
config=types.GenerateContentConfig(
response_mime_type="application/json",
response_schema=list[QuizQuestion],
),
)
# 3. Parse and display structured output
questions = json.loads(response.text)
print(f"Generated {len(questions)} questions:\n")
for i, q in enumerate(questions, 1):
print(f"Q{i}: {q['question']}")
for opt in q["options"]:
print(f" - {opt}")
print(f" Correct Answer: {q['correct_answer']}")
print(f" Explanation: {q['explanation']}\n")
print(f"\nUsage: {response.usage_metadata.total_token_count} total tokens")
You now have an LLM that returns predictable, type-safe data your code can process without fragile string parsing. Next up, you will build a multi-turn conversation and watch the context window fill up in real time.
Build Multi-Turn Conversations
In the previous step, you used structured output schemas to get predictable, typed JSON from Gemini. Now you'll explore how LLMs handle ongoing conversations.
When you chat with an AI assistant, it seems to "remember" what you said earlier. In reality, the entire conversation history gets sent back to the model on every turn, consuming more and more of the context window. In this step, you'll build a multi-turn chat and measure how that context grows with each message.
In this step, get ready to:
- Create a multi-turn chat and send multiple messages that build on each other.
- Track context token growth after each turn to see how conversations fill the context window.
Create a multi-turn chat
With generate_content(), each call is stateless. The model has no memory of previous requests. The google-genai SDK provides a chat interface that automatically manages conversation history for you.
- Create a new file called chat_context.py in your project folder.
- Add the following setup code to create a chat session and define a list of questions:
from google import genai
from google.genai import types
client = genai.Client()
# 1. Create a multi-turn chat
chat = client.chats.create(model="gemini-3.5-flash")
# 2. Multi-turn conversation with token tracking
print("--- Multi-turn Chat with Context Tracking ---\n")
questions = [
"What is a token in the context of LLMs?",
"How does the context window relate to tokens?",
"What happens when the context window is full?",
"Summarize everything we discussed in one sentence.",
]
What does this code do?
client.chats.create(model="gemini-3.5-flash") creates a chat session that tracks conversation history automatically. Every time you send a message, the SDK appends it to the history and sends the full context to the model.
The questions list is designed so each question builds on the previous answer. The final question asks the model to summarize the entire conversation, which only works if it can "see" everything that came before.
- Save chat_context.py.
- You won't see a change yet. The next chunk adds the loop that sends messages and tracks context growth.
Track context growth across turns
Each turn of conversation adds tokens to the context. You can measure this by counting the tokens in the chat history before each new message is sent.
- Add the following loop below your questions list in chat_context.py:
for question in questions:
# Count tokens in current history
history = chat.get_history()
if history:
history_tokens = client.models.count_tokens(
model="gemini-3.5-flash",
contents=history,
)
context_tokens = history_tokens.total_tokens
else:
context_tokens = 0
response = chat.send_message(message=question)
print(f"You: {question}")
print(f"AI: {response.text}")
print(f" [Context before this turn: {context_tokens} tokens]")
print(f" [This response total: {response.usage_metadata.total_token_count} tokens]")
print()
What does this code do?
- chat.get_history() retrieves all messages sent and received so far as a list of Content objects.
- client.models.count_tokens() measures how many tokens the history consumes without using any generation quota.
- chat.send_message() sends a new message and automatically appends both the question and the response to the chat history.
- The printed context_tokens value shows how much of the context window is consumed BEFORE the model generates a new response.
- Save chat_context.py.
- Run the script by executing this command in your terminal:
python chat_context.py
You should see four question-and-answer pairs printed, each with a growing Context before this turn value. The first turn starts at 0 tokens, and each subsequent turn shows a larger number as the history accumulates.
Why does the context keep growing?
LLMs don't have persistent memory. To "remember" earlier messages, the entire conversation must be re-sent on every turn. This is why the token count grows with each exchange.
Eventually, long conversations hit the context window limit. At that point, older messages must be dropped or summarized. Understanding this constraint is essential for building AI agents and RAG systems.
Not seeing growing token counts?
- Make sure the for loop is placed after the questions list, not inside it.
- Check that your GEMINI_API_KEY environment variable is still set in the same terminal session where you run the script.
- If you see a rate limit error, wait a moment and try again. The free tier allows 1,500 requests per day.
Help me debug my chat_context.py script that isn't showing growing token counts.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete chat_context.py file for comparison:
from google import genai
from google.genai import types
client = genai.Client()
# 1. Create a multi-turn chat
chat = client.chats.create(model="gemini-3.5-flash")
# 2. Multi-turn conversation with token tracking
print("--- Multi-turn Chat with Context Tracking ---\n")
questions = [
"What is a token in the context of LLMs?",
"How does the context window relate to tokens?",
"What happens when the context window is full?",
"Summarize everything we discussed in one sentence.",
]
for question in questions:
# Count tokens in current history
history = chat.get_history()
if history:
history_tokens = client.models.count_tokens(
model="gemini-3.5-flash",
contents=history,
)
context_tokens = history_tokens.total_tokens
else:
context_tokens = 0
response = chat.send_message(message=question)
print(f"You: {question}")
print(f"AI: {response.text}")
print(f" [Context before this turn: {context_tokens} tokens]")
print(f" [This response total: {response.usage_metadata.total_token_count} tokens]")
print()
You now have a complete picture of how LLMs handle multi-turn conversations. The growing token count makes it clear why context windows matter and why long conversations eventually need strategies like summarization or truncation. Next up, you'll tackle a Secret Mission that visualizes this context growth as a progress bar.
Secret mission
Build a Context Window Visualizer
Your chat script prints raw token numbers, but numbers alone don't convey urgency. What if you could see a progress bar filling up in real time, warning you before you hit the wall?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally with a free API tier, so there are no ongoing costs. Decide whether to keep your resources for future experimentation, or clean them up.
Resources you used:
- Project folder with Python scripts (setup_test.py, token_explorer.py, prompt_lab.py, structured_output.py, chat_context.py).
- google-genai Python package.
- Google AI Studio API key.
Keep everything running
No action needed. Choose this if you plan to keep experimenting with the Gemini API or building on these scripts.
- Your project folder stays on your Desktop.
- The google-genai package stays installed in your Python environment.
- Your API key remains active in Google AI Studio. The free tier has no charges.
- The GEMINI_API_KEY environment variable is session-scoped. It disappears when you close your terminal window.
Pause - I'll come back to this later
Free up terminal resources while keeping your files and API key intact for later.
- Close any terminal windows running your scripts.
- Your project files remain on disk. No action needed.
- Your API key stays active in Google AI Studio at no cost. When you return, set GEMINI_API_KEY again in a new terminal session.
Delete - I don't want to use this again
Remove all project resources and start fresh.
- Delete your project folder by running this in your terminal (macOS/Linux):
cd ~/Desktop
rm -rf llm-fundamentals
On Windows, run:
cd ~\Desktop
rmdir /s /q llm-fundamentals
- Uninstall the google-genai package by running:
pip uninstall google-genai
- Delete your API key from Google AI Studio. Go to aistudio.google.com, open the API keys page, click the three-dot menu next to your key, and select the option to delete it.
Nice Work!
Nice Work!
Nice work! You just built four Python scripts that explore the fundamental building blocks of large language models using the Google Gemini API.
You've learned how to:
- Count and inspect tokens to understand how LLMs measure input and output, including retrieving a model's maximum context window size.
- Use system instructions and temperature to control LLM behavior, from deterministic responses to creative outputs.
- Generate type-safe structured JSON from an LLM using schemas, and manage multi-turn conversations while tracking how context grows with each turn.
- Secret Mission: Built a context window visualizer that displays a progress bar showing token usage and warns when context is nearly full.
Ready to quiz yourself?