CoreMesh

Gemini API Python: Install, Chat and Stream With Code

Install google-genai, set GEMINI_API_KEY, generate text, hold a multi-turn chat and stream responses in Python, with copy-paste examples and common mistakes.

To use the Gemini API from Python, install the google-genai package, put your key in the GEMINI_API_KEY environment variable, create a genai.Client(), and call client.interactions.create() with a model and an input. The text comes back on interaction.output_text.

That's the whole core loop. The rest of this page covers the pieces people usually search for next: where to get a key, how to set it, how to chat across turns, how to stream, and the mistakes that cost the most time. Every example follows the official quickstart and text generation guide.

Install the Gemini API Python SDK#

The SDK is a single package:

bash
pip install -U google-genai

You import it as google.genai, which is the part that trips people up. The pip name has a hyphen, the import path doesn't:

python
from google import genai

I'd install it into a virtual environment per project, so a later upgrade doesn't break something else. The package page on PyPI is the place to check release history.

One note on naming. The current docs build everything on the Interactions API, exposed in Python as client.interactions. If you find older tutorials that use a different method name, compare them against the quickstart before copying them.

Get a key and set it#

You need an API key to authenticate requests, enforce security limits, and track usage to your account. Google AI Studio automatically creates a project and API key for new users, and you can copy it from the API keys page. If you need another one, click Create API key in AI Studio and follow the dialog to add a new key-project pair.

The quickstart sets the key as an environment variable:

bash
export GEMINI_API_KEY="YOUR_API_KEY"

The examples then call genai.Client() with no arguments, so keep that variable set in the shell where you run your script. That also answers the "how do I set the Gemini API key in Python" question without a key ever appearing in your source. I'd keep it that way. Hardcoding a key in a file is how keys end up in a public GitHub repo.

The quickstart doesn't show other ways of passing the key to the client, so if you need one (a secrets manager, say), check the SDK documentation.

Generate text: the smallest working example#

python
from google import genai

client = genai.Client()

interaction = client.interactions.create(
    model="gemini-3.8-flash",
    input="Explain how AI works in a few words"
)
print(interaction.output_text)

That's it: one client, one call, one property. The model name gemini-3.8-flash is what the docs use at the time of writing. Model names change, so check the current list on ai.google.dev before you pin one in production code.

What output_text actually returns#

interaction.output_text is a string with the last text blocks of the model's response. If the response is split across several consecutive text blocks, the SDK joins them. It does not include earlier text blocks that are separated by non-text content such as thoughts, images, audio, or tool calls. For interleaved or multimodal responses, iterate over interaction.steps yourself.

System instructions and generation config#

Set behavior with system_instruction:

python
interaction = client.interactions.create(
    model="gemini-3.8-flash",
    system_instruction="You are a cat. Your name is Neko.",
    input="Hello there"
)
print(interaction.output_text)

Override defaults such as max_output_tokens or thinking_level through generation_config:

python
interaction = client.interactions.create(
    model="gemini-3.8-flash",
    input="Explain how AI works",
    generation_config={
        "max_output_tokens": 500
    }
)
print(interaction.output_text)

Gemini models often have thinking enabled by default, which lets the model reason before it answers. The docs show "thinking_level": "low" in generation_config as a way to trade some reasoning for cost and latency. Each model supports different thinking configurations, so read the thinking guide for the model you pick. The docs also list temperature as a generation_config field.

Chat: multi-turn conversations in Python#

There are two ways to keep context across turns. Pick one per app.

Option 1: let the API hold the history#

Chain turns with previous_interaction_id. Each turn is a separate interaction, and the API manages the conversation history:

python
from google import genai

client = genai.Client()

interaction1 = client.interactions.create(
    model="gemini-3.8-flash",
    input="I have 2 dogs in my house.",
)
print(interaction1.output_text)

interaction2 = client.interactions.create(
    model="gemini-3.8-flash",
    input="How many paws are in my house?",
    previous_interaction_id=interaction1.id,
)
print(interaction2.output_text)

The second response returns only the new steps, but it's grounded in the first turn. For a simple chatbot loop, keep the latest interaction.id in a variable and pass it into each call.

Option 2: keep the history yourself#

Send store=False and pass the full history as a list on every call. You append each response's steps (via step.model_dump()) and your next user message:

python
history = [
    {
        "type": "user_input",
        "content": [{"type": "text", "text": "I have 2 dogs in my house."}]
    }
]

interaction1 = client.interactions.create(
    model="gemini-3.8-flash",
    store=False,
    input=history
)
print("Response 1:", interaction1.steps[-1].content[0].text)

for step in interaction1.steps:
    history.append(step.model_dump())

history.append({
    "type": "user_input",
    "content": [{"type": "text", "text": "How many paws are in my house?"}]
})

interaction2 = client.interactions.create(
    model="gemini-3.8-flash",
    store=False,
    input=history
)
print("Response 2:", interaction2.steps[-1].content[0].text)

I'd choose this one when you need to edit, trim, or persist the conversation in your own database. The first option is less code when you don't.

The history is just a list of dicts, so you can see its shape without calling the API. This runs offline and builds the same structure with a stand-in model step:

python
import json

history = [
    {"type": "user_input",
     "content": [{"type": "text", "text": "I have 2 dogs in my house."}]}
]

# stand-in for step.model_dump() from a real response
model_step = {"type": "model_output",
              "content": [{"type": "text", "text": "Nice! Dogs are great."}]}
history.append(model_step)

history.append({"type": "user_input",
                "content": [{"type": "text", "text": "How many paws are in my house?"}]})

for i, item in enumerate(history):
    print(i, item["type"], "->", item["content"][0]["text"])
print(len(json.dumps(history)), "bytes of JSON to send on the next turn")

Stream responses#

Pass stream=True and iterate. The quickstart's minimal version prints each raw event:

python
stream = client.interactions.create(
    model="gemini-3.8-flash",
    input="Explain how AI works",
    stream=True
)
for event in stream:
    print(event)

For a typewriter effect, filter to text deltas, as the text generation guide does:

python
for event in stream:
    if event.event_type == "step.delta":
        if event.delta.type == "text":
            print(event.delta.text, end="")

The filter matters. A stream contains more than text. Over REST the sequence is interaction.created, then step.start, step.delta, and step.stop for each step, then interaction.completed. The first step in the sample is a thought whose delta has type thought_signature, and the answer arrives in a later model_output step as text deltas. If you print every event, you'll see all of it.

Streaming combines with chat too. Pass previous_interaction_id and stream=True in the same call.

Here's the filtering logic as a self-contained script, using dicts shaped like the events from the docs:

python
events = [
    {"event_type": "interaction.created"},
    {"event_type": "step.start", "index": 0},
    {"event_type": "step.delta", "index": 0,
     "delta": {"type": "thought_signature", "signature": "EvEFCu4F..."}},
    {"event_type": "step.stop", "index": 0},
    {"event_type": "step.start", "index": 1},
    {"event_type": "step.delta", "index": 1,
     "delta": {"type": "text", "text": "AI "}},
    {"event_type": "step.delta", "index": 1,
     "delta": {"type": "text", "text": "works "}},
    {"event_type": "step.delta", "index": 1,
     "delta": {"type": "text", "text": "by learning patterns."}},
    {"event_type": "step.stop", "index": 1},
    {"event_type": "interaction.completed"},
]

parts = []
for event in events:
    if event["event_type"] == "step.delta" and event["delta"]["type"] == "text":
        parts.append(event["delta"]["text"])

print("".join(parts))
print(len(events), "events total,", len(parts), "carried text")

Calling it over REST instead#

If you'd rather not install the SDK, the same endpoint takes plain HTTP. The key goes in an x-goog-api-key header:

bash
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gemini-3.8-flash",
    "input": "Explain how AI works in a few words"
  }'

REST returns the full Interaction resource: an id, a status, usage token counts, and a steps array. The SDK exposes the same data and adds helpers like output_text on top. For streaming over REST, use the ?alt=sse URL and --no-buffer.

Common mistakes and gotchas#

  • Wrong package name. Install google-genai, import from google import genai.
  • Key not visible to the process. export only affects the current shell. If your script runs from an IDE, a cron job, or a container, set GEMINI_API_KEY there too.
  • Treating output_text as the whole answer. It returns only the trailing text blocks. With tools, images, or thoughts mixed in, walk interaction.steps.
  • Printing every stream event. Filter on event.event_type == "step.delta" and event.delta.type == "text", or your output is full of thought signatures and bookkeeping events.
  • Forgetting history in stateless mode. With store=False there's no previous_interaction_id to lean on. Send the full list each time, including the model's steps.
  • Hardcoding the model name everywhere. Put it in one constant. Names change, and the docs banner already points at newer models.
  • Copying old snippets. Tutorials written for earlier SDK interfaces may not match the client.interactions calls shown here.

Free tier, fees and where to look#

People search "gemini api python free" and "gemini api fees". The source docs used for this page don't state prices, rate limits, or free-tier terms, so I won't guess. Check the pricing and rate-limit pages linked from ai.google.dev for current numbers. The usage field in each response (total_input_tokens, total_output_tokens, total_tokens) shows what a given call consumed, which is useful for estimating cost.

The same goes for the Live API. The pages used here don't cover it, so look for it in the official docs rather than in a copied snippet.

Quick reference#

Task What to use
Install pip install -U google-genai
Import from google import genai
Key export GEMINI_API_KEY="..."
Client client = genai.Client()
Generate client.interactions.create(model=..., input=...)
Read text interaction.output_text
System prompt system_instruction="..."
Tuning generation_config={...}
Chat (API-held) previous_interaction_id=interaction.id
Chat (client-held) store=False plus full input history
Stream stream=True, filter step.delta with text

Checklist before you ship:

  1. Key comes from the environment, not the source file.
  2. Model name lives in one constant you can change.
  3. Streaming code filters on text deltas.
  4. You've decided whether the API or your app owns chat history.
  5. You've checked current pricing and limits on the official site.

Quick answers

How do I install the Gemini API in Python?

Run pip install -U google-genai, then import it with from google import genai and create a client with genai.Client().

How do I set the Gemini API key in Python?

The quickstart sets it as an environment variable with export GEMINI_API_KEY="YOUR_API_KEY" and then calls genai.Client() with no arguments. You can create a key in Google AI Studio.

How do I stream a Gemini response in Python?

Pass stream=True to client.interactions.create() and loop over the result. Print event.delta.text when event.event_type is step.delta and event.delta.type is text.

Is the Gemini API free to use from Python?

The documentation used for this article doesn't state pricing or free-tier terms. Check the official pricing and rate-limit pages on ai.google.dev for current details.

Did this solve it?

One tap. No account.

Saved you an hour? Send a tip.

USDC or ETH on Base. Keeps the notes free and the examples running.

Base
QR code with the CoreMesh tip address on Base
0x9dFD2Ee86182f92464b50809C6702B999a0Cd54f
Open in wallet · 5 USDC

On a computer? Scan the code with your phone wallet, or copy the address.

USDC or ETH on Base works best. Other tokens on Base, Ethereum or Arbitrum also reach this address. Tips are optional and unlock nothing; every guide stays free.

Discussion

Start the discussion

`code` and ``` blocks work

No account needed. Comments are read before they show up, usually the same day. Your email is never asked for.

Indra Wijaya

Builds and runs AI agents, bots and content pipelines, and writes down what works and what broke.

About · Email · RSS

Keep reading

New notes, no spam.

One post at a time, only when there's something worth reading. Follow in any feed reader.

Subscribe via RSS