Terminal AI Chat Assistant in Python
Build a streaming Gemini-powered chatbot with memory and Rich CLI output.
Introduction
30 Second Summary
You probably already chat with AI through a web browser. But what if you could talk to an AI directly from your terminal, right where you write and run code?
In this project, you will build a terminal-based chat assistant powered by Google's Gemini API. It streams responses in real-time and remembers your full conversation history.
What You'll Build
A Python command-line application that connects to Google Gemini's free API for streaming, multi-turn conversations with polished terminal output.
By the end of this project, you'll have:
- A live Gemini API connection that streams AI responses to your terminal in real-time.
- Multi-turn conversation memory so your AI assistant remembers everything you discussed.
- A polished CLI experience with Rich markdown rendering, syntax highlighting, and a custom system prompt to shape the AI's personality.
- Secret Mission: Add slash commands like /save to export your chat history and /system to change the AI's personality on the fly.
Are there any prerequisites?
This project assumes you are comfortable with Python basics like functions, loops, dictionaries, and f-strings. No experience with AI APIs or terminal UI libraries is needed.
Set Up Your Project and Get a Gemini API Key
Before you can chat with an AI from your terminal, you need a few things in place. A project folder, the right Python packages, and an API key that connects your code to Google's Gemini model.
This step covers all of that setup. You'll create a virtual environment, grab a free key from Google AI Studio, and run a quick test to make sure everything works.
In this step, get ready to:
- Create a project folder and Python virtual environment.
- Install the google-genai and rich packages.
- Get a free Gemini API key and verify it works.
Create your project folder and virtual environment
A virtual environment is an isolated Python workspace. It keeps the packages you install for this project separate from your system-wide Python installation.
- 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 up your project folder and virtual environment by running these commands:
macOS
cd ~/Desktop
mkdir gemini-chat
cd gemini-chat
python3 -m venv venv
source venv/bin/activate
Windows
cd %USERPROFILE%\Desktop
mkdir gemini-chat
cd gemini-chat
python -m venv venv
venv\Scripts\activate
- Check that (venv) appears at the start of your terminal prompt. This confirms the virtual environment is active.
Don't see (venv) in your prompt?
Make sure you ran the activate command, not just the python3 -m venv venv command. Creating the virtual environment and activating it are two separate steps.
On Windows, if you see a permissions error, try running Command Prompt as Administrator.
Help me activate my Python virtual environment.
- Install the two Python packages your project needs by running:
pip install google-genai==1.74.0 rich==15.0.0
What are these packages?
google-genai is Google's official Python SDK for the Gemini API. It handles authentication, request formatting, and streaming responses for you.
rich is a library for beautiful terminal output. You'll use it later to render Markdown, syntax-highlighted code, and colored text right in your terminal.
- Wait for the installation to finish. You should see Successfully installed followed by the package names in your terminal output.
Seeing a pip error?
If you see command not found: pip, make sure your virtual environment is active. You should see (venv) in your prompt before running the install command.
If you see a network error, check your internet connection and try running the command again.
Help me fix my pip install error.
Get your free Gemini API key
An API key is a unique string that identifies your application to a service. Gemini uses it to track your usage against the free tier limits.
- Open your browser and go to aistudio.google.com.
- Sign in with your Google account.
- Click Get API key in the left sidebar.
- Click Create API key.
- Copy your new API key and keep it somewhere safe. You will need it in the next substep.
What if I already have an API key?
If you have already created a Gemini API key before, you can reuse it. Just make sure it is still active by checking the key list on the Get API key page.
Why Gemini over other LLM APIs?
Gemini has the most generous free tier for a learning project. The gemini-2.5-flash model gives you up to 250 requests per day and 250,000 tokens per minute at no cost. No credit card required.
OpenAI requires payment from the start. Claude gives limited one-time starter credits that expire. Gemini lets you build and experiment freely.
Set your API key and test the connection
The google-genai SDK automatically reads your API key from an environment variable called GEMINI_API_KEY. You need to set this in your terminal session so the SDK can find it.
- In the same terminal where your virtual environment is active, set your API key by running this command:
macOS
export GEMINI_API_KEY="your-api-key-here"
Windows
set GEMINI_API_KEY=your-api-key-here
- Replace your-api-key-here with the actual API key you copied from Google AI Studio.
This key only lasts for your current session
If you close your terminal, you will need to set the environment variable again. To make it permanent on macOS or Linux, add the export line to your ~/.bashrc or ~/.zshrc file.
Now let's confirm the API key works by making a quick test call to Gemini.
- Create a new file called test_api.py in your gemini-chat folder. Open it in your text editor and add the following code:
from google import genai
# Create a client that reads GEMINI_API_KEY automatically
client = genai.Client()
# Send a simple prompt to Gemini and print the response
response = client.models.generate_content(
model="gemini-2.5-flash",
contents="Say hello!"
)
print(response.text)
What does this code do?
- genai.Client() creates an API client. It automatically picks up the GEMINI_API_KEY environment variable you just set.
- client.models.generate_content() sends your prompt to the gemini-2.5-flash model and waits for the full response.
- response.text contains the text that Gemini generated. Printing it shows the AI's reply in your terminal.
- Save test_api.py.
- Run the test script by running:
python test_api.py
- You should see a friendly greeting from Gemini printed in your terminal. The exact wording will vary each time since the AI generates a fresh response.
Seeing an error instead of a greeting?
- If you see ModuleNotFoundError: No module named 'google', make sure your virtual environment is active. You should see (venv) in your prompt.
- If you see an authentication or API key error, double-check that you set the GEMINI_API_KEY environment variable in this same terminal session. Run your export or set command again if needed.
- If you see a network error, check your internet connection and try again.
Help me fix my Gemini API test script error.
✔️ 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 test_api.py file. Make sure yours matches exactly.
from google import genai
client = genai.Client()
response = client.models.generate_content(
model="gemini-2.5-flash",
contents="Say hello!"
)
print(response.text)
Your environment is ready and your API key is working. Next up, you'll build your first real API call and watch Gemini stream a response in real time.
Make Your First API Call and Print a Response
Your environment is ready with Python, the SDK, and a working API key. Now it's time to put them to use.
In this step, you'll create the main script for your chat assistant. You'll start with a basic API call, then layer on a system prompt to control the AI's tone, and finish by switching to streaming so responses appear in real time.
In this step, get ready to:
- Send a prompt to Gemini and print the response.
- Add a system instruction to shape the AI's personality.
- Switch to streaming for real-time, token-by-token output.
Create chat.py with a basic API call
Instead of modifying your test file, you'll build a dedicated script that will grow into the full chat assistant.
- Create a new file called chat.py in your project folder (the same folder where test_api.py lives).
- Add the following code to chat.py:
from google import genai
# Create an API client (reads GEMINI_API_KEY automatically)
client = genai.Client()
# Send a prompt to Gemini and get the full response
response = client.models.generate_content(
model="gemini-2.5-flash",
contents="What is a Python virtual environment?"
)
# Display the AI's response
print(response.text)
What does this code do?
- genai.Client() creates an API client that automatically reads the GEMINI_API_KEY environment variable you set in Step 1.
- client.models.generate_content() sends your prompt to the gemini-2.5-flash model and waits for the complete response before returning.
- response.text contains the model's full reply as a single string.
- Save chat.py.
- Run the script by running this command:
python chat.py
You should see a multi-paragraph explanation of Python virtual environments printed in your terminal.
Seeing an error instead of a response?
- If you see ModuleNotFoundError: No module named 'google', make sure your virtual environment is activated. You should see the environment name in parentheses at the start of your terminal prompt.
- If you see an authentication error, confirm your GEMINI_API_KEY environment variable is still set by running echo $GEMINI_API_KEY in your terminal.
- If the output is empty or the command hangs, check that you have an internet connection. The API call requires network access.
Still stuck? Ask for help here.
Add a system instruction
Right now Gemini responds with its default personality. A system instruction is a special prompt that tells the model how to behave for every message in the conversation.
What is a system instruction?
A system instruction sets the AI's persona, rules, and output format before the user says anything. Think of it as a backstage briefing for the model.
For example, you could tell it to always respond in bullet points, act as a pirate, or keep answers under 100 words. The model follows these instructions for the entire session.
To pass a system instruction, you need to import the types module from the SDK and wrap your instruction in a config object.
- In chat.py, find the import line at the very top of the file:
from google import genai
- Add a new import directly below it, so the top of your file looks like this:
from google import genai
from google.genai import types
- Now find the generate_content call:
response = client.models.generate_content(
model="gemini-2.5-flash",
contents="What is a Python virtual environment?"
)
- Replace it with the following version that includes a config parameter:
# Send a prompt with a system instruction that shapes the AI's behavior
response = client.models.generate_content(
model="gemini-2.5-flash",
contents="What is a Python virtual environment?",
config=types.GenerateContentConfig(
system_instruction="You are a helpful terminal assistant. Keep responses concise and use markdown formatting for code."
)
)
What does the config do?
- types.GenerateContentConfig() bundles all the settings for your API call into a single object.
- system_instruction tells the model to act as a concise terminal assistant and format code using markdown. Every response will follow these rules.
- Save chat.py and run it again:
python chat.py
Compare this response to your first run. You should notice the answer is shorter, more structured, and uses markdown-style formatting for code snippets.
Response looks the same as before?
- Make sure you saved chat.py after editing. If you didn't save, Python runs the old version.
- Check that from google.genai import types appears on its own line directly below the first import.
- If you see NameError: name 'types' is not defined, the types import is missing or has a typo.
Need a hand? Ask for help here.
Switch to streaming responses
Right now, your script waits for the entire response to finish generating before printing anything. With streaming, you get text token-by-token as the model produces it. This makes the experience feel much more responsive.
- In chat.py, find this line inside the generate_content call:
response = client.models.generate_content(
- Change generate_content to generate_content_stream, so the line looks like this:
response = client.models.generate_content_stream(
With streaming enabled, response is no longer a single object with a .text property. It's an iterator of chunks. You need to loop through them.
- Find the last line of your file:
print(response.text)
- Replace it with the following loop that prints each chunk as it arrives:
# Print each chunk as it arrives from the model
for chunk in response:
print(chunk.text, end="")
print()
What does this loop do?
- for chunk in response: iterates over each piece of text as the model generates it.
- end="" prevents Python from adding a newline after each chunk, so the text flows together naturally.
- The final print() adds one newline at the very end so your terminal prompt doesn't land on the same line as the response.
- Save chat.py and run it one more time:
python chat.py
This time, watch your terminal closely. You should see text appear gradually, word by word, instead of all at once.
Text still appears all at once?
- Double-check that you changed generate_content to generate_content_stream. If you still have the old method name, the response returns all at once.
- If you see AttributeError: 'NoneType' object has no attribute 'text', some chunks may be empty. This is normal for streaming. You can safely ignore it for now.
- Make sure the old print(response.text) line is completely removed, not just commented out.
Still stuck? Ask for help here.
✔️ Awesome, I've got everything!
Great. Double check that you've saved chat.py before moving on.
ⓧ I'd like to double check the full code
Here is the complete chat.py file at the end of this step.
from google import genai
from google.genai import types
client = genai.Client()
response = client.models.generate_content_stream(
model="gemini-2.5-flash",
contents="What is a Python virtual environment?",
config=types.GenerateContentConfig(
system_instruction="You are a helpful terminal assistant. Keep responses concise and use markdown formatting for code."
)
)
for chunk in response:
print(chunk.text, end="")
print()
Your script can now talk to Gemini, follow a system prompt, and stream responses in real time. Next up, you'll turn this one-shot script into a full conversation loop that remembers what you've said.
Build the Chat Loop with Conversation Memory
In the last step, you connected to Gemini and streamed a response token-by-token. That's great for a single question, but a real assistant needs to hold a conversation. Right now, every time you run the script, the AI has zero memory of anything you said before.
In this step, you will replace the one-shot call with a persistent chat session that remembers every message. You will build an interactive loop where you type messages, the AI streams back responses, and the full conversation stays in memory.
In this step, get ready to:
- Create a persistent chat session that tracks conversation history automatically.
- Build an interactive chat loop with streaming responses and graceful exit handling.
- Verify that the AI remembers context across multiple turns of conversation.
Replace the one-shot call with a chat session
The google-genai SDK has a built-in chat feature that automatically tracks every message you send and every response the AI returns. Instead of calling generate_content_stream() each time, you create a single chat session and talk to it over and over.
- In chat.py, delete everything below the two import lines at the top of the file.
- Add the following constants and chat session setup below the imports:
# System prompt that shapes the AI's personality
SYSTEM_PROMPT = (
"You are a helpful terminal assistant. "
"Keep responses concise and use markdown formatting for code."
)
# Model name stored as a constant for easy updates
MODEL = "gemini-2.5-flash"
client = genai.Client()
# Create a persistent chat session that remembers conversation history
chat = client.chats.create(
model=MODEL,
config=types.GenerateContentConfig(system_instruction=SYSTEM_PROMPT)
)
What does this code do?
SYSTEM_PROMPT and MODEL are now constants at the top of the file. This makes them easy to find and update later without digging through function calls.
client.chats.create() creates a persistent chat session. Every message you send through this chat object is automatically stored alongside the AI's response. The SDK sends the full conversation history with each new request so the AI has context.
You won't see a change yet. The next chunk adds the chat loop that uses this setup.
- Add the following welcome banner and chat loop below the code you just added:
# Welcome banner
print("Terminal AI Assistant (powered by Gemini)")
print("Type your message and press Enter. Type 'exit' or 'quit' to leave.\n")
while True:
user_input = input("You> ").strip()
# Skip empty inputs
if not user_input:
continue
# Let the user exit gracefully
if user_input.lower() in ("exit", "quit"):
print("Goodbye!")
break
# Send message and print the full response
response = chat.send_message(user_input)
print(response.text)
print()
What does this code do?
- The while True loop keeps the conversation going until you choose to stop.
- input("You> ") prints a prompt and waits for you to type a message. .strip() removes extra whitespace from both ends.
- chat.send_message() sends your message through the chat session and returns the full response at once.
- Typing exit or quit breaks out of the loop and ends the program.
- Save chat.py.
- Run the script by running this command:
python chat.py
- Type a message like What is Python? and press Enter. You should see the AI's full response appear all at once.
- Type exit to leave the chat loop.
Not seeing a response?
- Make sure your GEMINI_API_KEY environment variable is still set in this terminal session. If you opened a new terminal, you need to export it again.
- Check that you deleted all of the old one-shot code below the imports. Leftover lines from the previous step can cause errors.
- Make sure your virtual environment is still activated. You should see (venv) at the start of your terminal prompt.
Why isn't my chat session connecting to Gemini?
✔️ Awesome, I've got everything!
Great. Double check you have saved chat.py before moving on.
ⓧ I'd like to double check the full code
Here is the full chat.py at this point. Compare your file against this:
from google import genai
from google.genai import types
SYSTEM_PROMPT = (
"You are a helpful terminal assistant. "
"Keep responses concise and use markdown formatting for code."
)
MODEL = "gemini-2.5-flash"
client = genai.Client()
chat = client.chats.create(
model=MODEL,
config=types.GenerateContentConfig(system_instruction=SYSTEM_PROMPT)
)
print("Terminal AI Assistant (powered by Gemini)")
print("Type your message and press Enter. Type 'exit' or 'quit' to leave.\n")
while True:
user_input = input("You> ").strip()
if not user_input:
continue
if user_input.lower() in ("exit", "quit"):
print("Goodbye!")
break
response = chat.send_message(user_input)
print(response.text)
print()
Upgrade to streaming with graceful exit handling
The chat works, but the entire response loads before anything appears on screen. You already experienced streaming in the previous step. Time to bring that back and add some safety nets so the app handles interruptions cleanly.
- In chat.py, find the while True: loop and everything inside it.
- Find this line:
while True:
user_input = input("You> ").strip()
- Replace the entire while True loop (from while True: down to the end of the file) with this upgraded version:
while True:
try:
user_input = input("You> ").strip()
except (KeyboardInterrupt, EOFError):
# Handle Ctrl+C and unexpected input closure
print("\nGoodbye!")
break
if not user_input:
continue
if user_input.lower() in ("exit", "quit"):
print("Goodbye!")
break
try:
# Stream the response token-by-token
for chunk in chat.send_message_stream(user_input):
print(chunk.text, end="")
print()
except Exception as e:
print(f"Error: {e}")
print()
What changed in this loop?
- The input() call is now wrapped in a try/except that catches KeyboardInterrupt. If you press Ctrl+C, the app prints a friendly goodbye instead of a traceback.
- chat.send_message() is replaced with chat.send_message_stream(). This returns chunks one at a time, so text appears on screen as the AI generates it.
- A second try/except around the streaming call catches API errors. Instead of crashing the loop, it prints the error and lets you keep chatting.
- Save chat.py and run it again:
python chat.py
- Type a question and press Enter. You should see the response appear word-by-word instead of all at once.
- Press Ctrl+C while at the You> prompt. You should see Goodbye! printed cleanly instead of a Python traceback.
Response still appearing all at once?
- Make sure you replaced chat.send_message() with chat.send_message_stream(). The _stream suffix is what enables token-by-token output.
- Check that your for chunk in loop uses print(chunk.text, end=""). Without end="", each chunk prints on a new line.
Help me debug my streaming chat loop
✔️ Awesome, I've got everything!
Great. Make sure you have saved chat.py before continuing.
ⓧ I'd like to double check the full code
Here is the full chat.py at this point. Compare your file against this:
from google import genai
from google.genai import types
SYSTEM_PROMPT = (
"You are a helpful terminal assistant. "
"Keep responses concise and use markdown formatting for code."
)
MODEL = "gemini-2.5-flash"
client = genai.Client()
chat = client.chats.create(
model=MODEL,
config=types.GenerateContentConfig(system_instruction=SYSTEM_PROMPT)
)
print("Terminal AI Assistant (powered by Gemini)")
print("Type your message and press Enter. Type 'exit' or 'quit' to leave.\n")
while True:
try:
user_input = input("You> ").strip()
except (KeyboardInterrupt, EOFError):
print("\nGoodbye!")
break
if not user_input:
continue
if user_input.lower() in ("exit", "quit"):
print("Goodbye!")
break
try:
for chunk in chat.send_message_stream(user_input):
print(chunk.text, end="")
print()
except Exception as e:
print(f"Error: {e}")
print()
Test the conversation memory
The chat session is managing conversation history for you behind the scenes. Every time you call send_message_stream(), the SDK sends the entire conversation so far to Gemini. Time to prove it actually remembers.
- Run the script again:
python chat.py
- Type My name is Alex and press Enter.
- After the AI responds, type What is my name? and press Enter.
The AI should respond with Alex. That confirms the chat session is sending the full conversation history with each request.
How does conversation memory work?
The chat object stores every turn (your messages and the AI's responses) in an internal history list. You can inspect it at any time by calling chat.get_history(), which returns a list of Content objects with a .role (like user or model) and a .parts list containing the text.
The more turns you have, the more tokens each request uses. For long conversations, this is something to keep in mind.
- Type exit to leave the chat.
Your assistant can now hold a real conversation and streams its thinking in real-time. Next up, you will give the terminal output a visual upgrade with Rich markdown rendering and styled prompts.
Polish the UI with Rich
Your chatbot can hold full conversations and remember context across messages. The core logic works, but the raw terminal output could use some love.
Rich is a Python library that turns plain terminal text into styled output with colors, Markdown rendering, and loading indicators. In this step, you'll restructure chat.py and add Rich to create a polished chat experience.
In this step, get ready to:
- Set up Rich and restructure the script into reusable functions.
- Add Markdown rendering and a loading spinner for AI responses.
- Build a styled chat loop with colored prompts and error handling.
Set up Rich and restructure the file
Your current chat.py has all its code at the top level of the file. To add Rich properly, you'll restructure the script into functions. This makes the code cleaner and easier to extend.
- Clear the entire contents of chat.py and replace them with this code:
import sys
from google import genai
from google.genai import types
from rich.console import Console
from rich.markdown import Markdown
# Rich console replaces print() for styled terminal output
console = Console()
SYSTEM_PROMPT = (
"You are a helpful terminal assistant. "
"Keep responses concise and use markdown formatting for code."
)
MODEL = "gemini-2.5-flash"
def create_chat(client, system_prompt):
# Bundle the system instruction into a config object
config = types.GenerateContentConfig(
system_instruction=system_prompt
)
return client.chats.create(model=MODEL, config=config)
What does this code do?
- from rich.console import Console and from rich.markdown import Markdown bring in Rich's styled output and Markdown rendering.
- console = Console() creates a single Console instance that replaces print() throughout the script.
- create_chat() wraps the chat session setup into a reusable function. This makes it easy to create new sessions later, like when you reset a conversation.
You won't see a change yet. The next chunk adds the response rendering function.
Add Markdown rendering and a loading spinner
In the previous step, the AI's response streamed as raw, unformatted text. Rich can render Markdown so code blocks get syntax highlighting, headings appear bold, and lists are properly indented.
The trade-off is that the response collects fully before rendering instead of appearing token-by-token. To keep the experience responsive, you'll show a spinning "Thinking..." indicator while chunks are being collected.
- Below the create_chat function in chat.py, add the stream_response function:
def stream_response(chat, user_input):
full_response = ""
# Show a spinner while the API streams chunks
with console.status("[bold green]Thinking..."):
first_chunk = True
for chunk in chat.send_message_stream(user_input):
if first_chunk:
first_chunk = False
console.print() # Blank line after the spinner clears
if chunk.text:
full_response += chunk.text
# Render the complete response as formatted Markdown
if full_response:
console.print(Markdown(full_response))
return full_response
What does this code do?
- console.status("[bold green]Thinking...") shows a spinning indicator in the terminal while the API sends chunks back.
- The loop collects every chunk's text into full_response instead of printing each chunk immediately.
- Once the stream finishes, Markdown(full_response) renders the collected text with syntax highlighting, bold, italic, and bullet formatting.
- The function returns full_response so other parts of the code can access the AI's answer later.
Why collect the full response instead of streaming token-by-token?
Printing raw chunks as they arrive gives you a typewriter effect, but partial Markdown looks messy. A half-finished code block or a bullet list missing its last item is confusing to read.
Collecting first and rendering once gives you clean, properly formatted output every time. The spinner keeps the user informed while they wait.
This function needs a main() function to call it. You'll add that next.
Build the styled chat loop
Rich uses markup tags like [bold green] and [dim] to style text. You'll use these to create a colored welcome banner, a cyan prompt, and red error messages.
- Below the stream_response function, add the main function and the entry point:
def main():
client = genai.Client()
system_prompt = SYSTEM_PROMPT
chat = create_chat(client, system_prompt)
# Welcome banner with Rich color tags
console.print(
"[bold green]Terminal AI Assistant[/bold green] "
"[dim](powered by Gemini)[/dim]"
)
console.print(
"[dim]Type your message and press Enter. "
"Type 'exit' or 'quit' to leave.[/dim]\n"
)
while True:
try:
# Styled prompt with Rich markup
user_input = console.input("[bold cyan]You>[/bold cyan] ").strip()
except (KeyboardInterrupt, EOFError):
console.print("\n[dim]Goodbye![/dim]")
break
if not user_input:
continue
if user_input.lower() in ("exit", "quit"):
console.print("[dim]Goodbye![/dim]")
break
try:
stream_response(chat, user_input)
except Exception as e:
# Display API errors in red without crashing
console.print(f"[bold red]Error:[/bold red] {e}")
console.print()
# Only run main() when the script is executed directly
if __name__ == "__main__":
main()
What does this code do?
- console.print() with Rich markup tags like [bold green] adds color and weight to the welcome banner.
- console.input("[bold cyan]You>[/bold cyan] ") shows a colored prompt instead of the plain You> from before.
- The try/except around stream_response() catches API errors and displays them in red without crashing the loop.
- if __name__ == "__main__" ensures main() only runs when you execute the script directly, not when it's imported.
- Save chat.py.
- Run the script by entering this command:
python chat.py
- You should see a green Terminal AI Assistant banner and a cyan You> prompt.
- Type a message like Write a Python hello world program and press Enter.
- You should see a green "Thinking..." spinner, followed by a formatted response with syntax-highlighted code.
- Type exit to leave the chat.
Seeing a NameError or ImportError?
- Make sure your virtual environment is active. You should see (venv) at the start of your terminal prompt.
- Check that the imports at the top of chat.py match exactly. A typo in from rich.console import Console causes an ImportError.
- Make sure the if __name__ == "__main__": line and main() call are at the very bottom of the file with no indentation.
- My Rich-styled chat.py is throwing an error when I run it
✔️ Awesome, I've got everything!
Double check you've saved chat.py.
ⓧ I'd like to double check the full code
Here is the complete chat.py file. Make sure your version matches this exactly.
import sys
from google import genai
from google.genai import types
from rich.console import Console
from rich.markdown import Markdown
console = Console()
SYSTEM_PROMPT = (
"You are a helpful terminal assistant. "
"Keep responses concise and use markdown formatting for code."
)
MODEL = "gemini-2.5-flash"
def create_chat(client, system_prompt):
config = types.GenerateContentConfig(
system_instruction=system_prompt
)
return client.chats.create(model=MODEL, config=config)
def stream_response(chat, user_input):
full_response = ""
with console.status("[bold green]Thinking..."):
first_chunk = True
for chunk in chat.send_message_stream(user_input):
if first_chunk:
first_chunk = False
console.print()
if chunk.text:
full_response += chunk.text
if full_response:
console.print(Markdown(full_response))
return full_response
def main():
client = genai.Client()
system_prompt = SYSTEM_PROMPT
chat = create_chat(client, system_prompt)
console.print(
"[bold green]Terminal AI Assistant[/bold green] "
"[dim](powered by Gemini)[/dim]"
)
console.print(
"[dim]Type your message and press Enter. "
"Type 'exit' or 'quit' to leave.[/dim]\n"
)
while True:
try:
user_input = console.input("[bold cyan]You>[/bold cyan] ").strip()
except (KeyboardInterrupt, EOFError):
console.print("\n[dim]Goodbye![/dim]")
break
if not user_input:
continue
if user_input.lower() in ("exit", "quit"):
console.print("[dim]Goodbye![/dim]")
break
try:
stream_response(chat, user_input)
except Exception as e:
console.print(f"[bold red]Error:[/bold red] {e}")
console.print()
if __name__ == "__main__":
main()
Test the conversation memory too
Try asking "My name is Alex" and then "What is my name?" in your next run. The Rich styling is cosmetic, so the chat session's memory still works exactly as before.
Secret mission
Add Slash Commands
Your chat assistant works, but what if you could save conversations, change the AI's personality, or clear history with a single command? Add slash commands to turn your assistant into a true power tool.
Before you go
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally with a free API tier, so there are no ongoing costs.
Resources you used:
- Project directory on your Desktop containing chat.py and test_api.py.
- Python virtual environment with google-genai and rich packages installed.
- Google AI Studio API key stored as the GEMINI_API_KEY environment variable.
Keep everything running
No action needed. Choose this if you're still actively building or want to keep experimenting with your chat assistant.
- Your project directory, virtual environment, and API key stay as they are.
- You can re-run your assistant any time by activating your virtual environment and running python chat.py.
- The Gemini free tier allows up to 250 requests per day, so casual use costs nothing.
Pause - I'll come back to this later
Shut down any running processes to free up memory. Your files and API key stay intact for when you return.
- If your chat assistant is running, press Ctrl+C or type exit to stop it.
- Deactivate your virtual environment by running deactivate in your terminal.
- Your project folder, virtual environment, and API key remain on disk. When you come back, reactivate the virtual environment and you are good to go.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow these steps to clean everything up.
- Delete your entire project directory from the Desktop. This removes chat.py, test_api.py, and the virtual environment.
- Remove the GEMINI_API_KEY line from your ~/.bashrc or ~/.zshrc file if you added it there for persistence.
- Optionally, revoke your API key in Google AI Studio by visiting aistudio.google.com, navigating to your API keys, and deleting the key you created.
Mission accomplished
That's a Wrap!
Nice work! You just built a fully functional terminal AI assistant powered by Google's Gemini API.
You've learned how to:
- Connected to Google's Gemini API using the official Python SDK and implemented real-time streaming responses that appear token-by-token.
- Built multi-turn conversation memory so the AI remembers context across messages.
- Designed a polished terminal UI with Rich markdown rendering, syntax highlighting, and styled output.
- Secret Mission: Added slash commands for saving conversations, changing the AI's personality, and resetting chat history.
Ready to quiz yourself?