Fundamentals

OpenAI vs Gemini API for Python Beginners

Run the same prompt through the OpenAI and Google Gemini Python SDKs: system prompts, reading replies, images, free tiers, and one wrapper that switches.

By the end of this guide you will have the same question answered twice — once by OpenAI's gpt-4o-mini and once by Google's gemini-2.5-flash — plus one ask() function that talks to either provider through a single argument. Budget about thirty minutes, most of it spent collecting two API keys. You need no experience beyond having made one successful API call before.

An API key is a long secret string that identifies your account to a provider's servers, and an SDK (software development kit) is the Python package that wraps their web endpoints so you can call them like ordinary functions. OpenAI and Google each publish their own SDK, and the two look different enough on the page to make beginners think they are learning two unrelated skills. They are not. The differences fit in a short table, and this guide walks through every one of them with code you can run.

This is one guide in Understanding LLM APIs, written for people who can run a Python file but have never shipped production code. If you want the same treatment for a different pair, OpenAI vs Anthropic API for Beginners covers Claude.

Prerequisites

You need Python 3.10 or newer and a working virtual environment — an isolated folder of packages that keeps this project's libraries separate from the rest of your machine. If that phrase is new, work through Create a Python Virtual Environment for AI first, then come back.

Install both SDKs at once. The Gemini package is called google-genai; an older package named google-generativeai exists and is a different library with different function names, so be precise here.

python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install "openai>=1.40" "google-genai>=1.0" "python-dotenv>=1.0"
pip freeze > requirements.txt

Collect one key from each provider's console and put them side by side in a file named .env in your project folder:

OPENAI_API_KEY=sk-your-openai-key-here
GEMINI_API_KEY=your-google-ai-studio-key-here

Now add that file to .gitignore before you write another line:

echo ".env" >> .gitignore

Skipping this step is how people wake up to a four-figure bill. Automated scanners crawl public repositories for key patterns within minutes of a push, so treat the .gitignore line as part of the install, not as tidying up afterwards.

Step 1 — Make the call with the openai package

Start with the SDK you are most likely to have seen already. The openai package builds a client object from your key, and every text request goes through client.chat.completions.create.

import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()  # reads .env, which is listed in .gitignore

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "You explain things in plain English."},
        {"role": "user", "content": "What is an API key? Answer in two sentences."},
    ],
    max_tokens=200,
)

print(response.choices[0].message.content)
print("Tokens used:", response.usage.total_tokens)

Three things are worth naming. The instruction that sets the model's behaviour travels inside the messages list as a dictionary tagged "role": "system" — it is just another message, sitting before the user's. The reply arrives at response.choices[0].message.content, and that [0] exists because the endpoint can return several alternative answers in one call. Finally, max_tokens caps the length of the reply; a token is roughly three-quarters of a word, and both input and output tokens are billed.

Save the file as ask_openai.py and run python ask_openai.py. If you get a 401, your key never reached the client — Fix the 401 Unauthorized Error in OpenAI Python walks through the causes.

Step 2 — Make the identical call with google-genai

Now the same question, the same system instruction, the same length cap — written for Gemini. The import line is unusual: you import genai from the google namespace, and the helper classes come from google.genai.types.

import os
from dotenv import load_dotenv
from google import genai
from google.genai import types

load_dotenv()  # reads .env, which is listed in .gitignore

client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="What is an API key? Answer in two sentences.",
    config=types.GenerateContentConfig(
        system_instruction="You explain things in plain English.",
        max_output_tokens=200,
    ),
)

print(response.text)
print("Tokens used:", response.usage_metadata.total_token_count)

Compare the two files line by line and four differences stand out. The prompt goes into contents as a plain string rather than a list of role-tagged dictionaries — you may pass a list when you want a conversation, but a single turn needs no ceremony. The system instruction is not a message at all; it lives in a config object built from types.GenerateContentConfig. The length cap is spelled max_output_tokens inside that same config. And the reply is a plain attribute, response.text, with no index to remember.

Neither shape is better. OpenAI's list-of-messages is honest about the fact that a system instruction really is part of the conversation; Gemini's config object keeps per-call settings in one visible place, which is pleasant once you start tuning several of them at once. The diagram below traces the same prompt down both paths.

The two designs diverge again when you want a back-and-forth conversation. With OpenAI you keep appending to the messages list yourself: user turn, assistant turn, user turn, and you resend the whole list every time. Gemini supports the same manual approach — pass a list to contents instead of a string — but also offers client.chats.create(...), which returns a chat object that remembers the history for you and exposes a send_message method. Beginners often prefer the chat object; anyone building a real app usually goes back to managing the list by hand, because that is the only way to trim old turns when a conversation grows too long to send.

One prompt travelling through the OpenAI and Gemini Python SDKs A two-column data flow showing the same prompt entering each SDK, where the system instruction is attached in each, and which attribute holds the reply text at the end. One prompt plus a system rule OpenAI Gemini openai package chat.completions google-genai models.generate system message first in messages system_instruction inside config= same wording Read the reply choices[0].message Read the reply response.text same answer
The prompt text never changes; only the slot the system instruction sits in and the attribute that holds the finished reply differ between the two SDKs.

Step 3 — Send an image to each provider

Both providers accept pictures alongside text, which is the point at which the two SDKs feel most different. OpenAI expects the image encoded as base64 — a way of writing binary data using ordinary letters and digits — and pasted into a data URL inside the message's content list.

import base64
import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

with open("chart.png", "rb") as f:
    encoded = base64.b64encode(f.read()).decode("utf-8")

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Describe this chart in one sentence."},
            {"type": "image_url",
             "image_url": {"url": f"data:image/png;base64,{encoded}"}},
        ],
    }],
    max_tokens=200,
)

print(response.choices[0].message.content)

Gemini hands you a helper instead. types.Part.from_bytes takes the raw bytes and a MIME type — the short label such as image/png that tells a server what kind of file it is receiving — and you drop the result straight into the contents list next to your question.

import os
from dotenv import load_dotenv
from google import genai
from google.genai import types

load_dotenv()
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))

with open("chart.png", "rb") as f:
    image_bytes = f.read()

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents=[
        types.Part.from_bytes(data=image_bytes, mime_type="image/png"),
        "Describe this chart in one sentence.",
    ],
)

print(response.text)

The gap widens past images. Gemini models take audio and video as ordinary parts of the same generate_content call, so a clip and a question travel together. OpenAI splits that work across endpoints: speech goes to the transcription API first, and you feed the resulting text into a chat call. Neither approach is wrong, but the shape of your script changes depending on which you pick.

How each SDK accepts four kinds of input A comparison matrix with four rows for plain text, image files, audio clips and video clips, showing the argument or helper each provider expects for that input type. Input type OpenAI Gemini Plain text a chat prompt messages list role + content contents string or list of parts Image file png or jpeg base64 data URL in a content part Part.from_bytes or a file upload Audio clip mp3 or wav its own endpoint transcribe first same call works audio as a part Video clip an mp4 file not in this call send frames instead video as a part handled natively
Text and images are a wash between the two SDKs; audio and video are where Gemini's single generate_content call saves you a whole extra step.

Step 4 — Hide both behind one ask() function

Once you can write either call, stop choosing. Put both behind a function with a provider argument and the rest of your program stays provider-agnostic — you can swap models with one keyword and compare answers without touching any other code. The temperature argument is a dial from roughly 0 to 1 controlling how varied the wording is; What Is Temperature in an LLM API? explains what to set it to. Both providers accept it under the same name, which makes it easy to forward.

import os
from dotenv import load_dotenv
from openai import OpenAI
from google import genai
from google.genai import types

load_dotenv()  # both keys come from .env, which is in .gitignore

openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
gemini_client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))

SYSTEM = "You are a concise assistant. Answer in plain English."


def ask(prompt: str, provider: str = "openai", temperature: float = 0.2) -> str:
    """Send one prompt to either provider and return the reply as a string."""
    if provider == "openai":
        response = openai_client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": SYSTEM},
                {"role": "user", "content": prompt},
            ],
            temperature=temperature,
            max_tokens=400,
        )
        print(f"[openai tokens: {response.usage.total_tokens}]")
        return response.choices[0].message.content

    if provider == "gemini":
        response = gemini_client.models.generate_content(
            model="gemini-2.5-flash",
            contents=prompt,
            config=types.GenerateContentConfig(
                system_instruction=SYSTEM,
                temperature=temperature,
                max_output_tokens=400,
            ),
        )
        print(f"[gemini tokens: {response.usage_metadata.total_token_count}]")
        return response.text

    raise ValueError(f"Unknown provider: {provider!r}")


if __name__ == "__main__":
    question = "Name two things worth logging on every AI API call."
    print("OpenAI:", ask(question, provider="openai"))
    print("Gemini:", ask(question, provider="gemini"))

Run it with python ask_both.py. You get two answers to one question and a token count for each, printed in the same format, which is the fastest honest way to judge quality and cost on your own task rather than someone else's benchmark. Keep that print line: usage reporting is the habit that stops surprise bills, and Count Tokens in Python Before You Send shows how to estimate the cost before the call goes out.

Quick reference

Port a script from one SDK to the other and these eight rows are almost always the entire diff.

ConcernOpenAIGemini
Installpip install openaipip install google-genai
Importfrom openai import OpenAIfrom google import genai
ClientOpenAI(api_key=...)genai.Client(api_key=...)
Call methodclient.chat.completions.createclient.models.generate_content
Prompt argumentmessages=[{...}]contents="..."
System instructionmessage with role: "system"system_instruction in config
Reply textresponse.choices[0].message.contentresponse.text
Token countsresponse.usage.total_tokensresponse.usage_metadata

The output cap is the one extra trap: OpenAI calls it max_tokens at the top level, Gemini calls it max_output_tokens inside the config object.

Free tiers, honestly

This is the difference beginners feel first. Google's AI Studio hands you a key that works on a free tier for several Gemini models, limited by requests per minute and per day. It is enough to learn on, enough for a personal script, and not enough for anything with users. OpenAI's platform expects billing details before your first call; new accounts sometimes receive trial credit, but you should plan on paying from day one.

Do not treat any specific quota number you read online as current — including in tutorials that look freshly written. Both providers change limits and prices regularly, so open the provider's own rate-limit and pricing pages when you need a figure. If a genuinely free key is what you need right now, Best Free AI APIs for Beginners lists the options, and Groq vs OpenRouter Free Tier compares two more.

One practical consequence: free tiers rate-limit aggressively, so a loop over a spreadsheet will hit a wall long before your logic is wrong. Add a short pause between calls and retry on failure rather than assuming your code broke.

There is a second, less obvious consequence. Free tiers on any provider usually come with looser data-handling terms than paid usage — your prompts may be eligible for review or model training. That is fine for practice prompts and terrible for a client's contract or a customer's support ticket. Read the terms attached to the tier you are on before you paste anything you would not email to a stranger, and move to a paid key the moment real data enters your script.

Troubleshooting

  • ModuleNotFoundError: No module named 'google.genai' — you installed the older google-generativeai package, or you installed into a different environment from the one running your script. Activate the virtual environment, then run pip install google-genai. The general fix is in Fix ModuleNotFoundError: No Module Named openai.
  • TypeError: generate_content() got an unexpected keyword argument 'system_instruction' — you passed the system instruction directly to the call. It belongs inside config=types.GenerateContentConfig(system_instruction=...), and types must be imported from google.genai.
  • AttributeError: 'GenerateContentResponse' object has no attribute 'choices' — you copied OpenAI's reply-reading line into the Gemini script. Change it to response.text.
  • 429 RESOURCE_EXHAUSTED (Gemini) or a 429 rate-limit error (OpenAI) — you sent requests faster than the tier allows. Slow the loop down and retry with an increasing wait; Fix the 429 Rate-Limit Error in Python has the retry pattern.
  • response.text prints None — the model returned no usable candidate, usually because a safety filter stopped it or the output cap cut it off before any text was produced. Print response.candidates to see the finish reason before you change the prompt.

When to use this vs. alternatives

Neither provider wins outright, and anyone telling you otherwise is selling something. Match the choice to the job in front of you.

  • Reach for Gemini when you want to write code today without entering payment details, or when your input is a long document, an audio file or a video and you would rather send it in one call than build a pipeline around it.
  • Reach for OpenAI when you want the largest pool of tutorials and Stack Overflow answers behind you, or when your project also needs image generation, embeddings or speech synthesis under a single key and library.
  • Reach for a third option when neither fits: Claude is the strongest alternative for long careful writing and tool use, and free aggregator endpoints are better for pure experimentation.
A three-question decision path for picking OpenAI or Gemini first A decision tree asking whether you need speech or image generation, whether you want a free tier, and whether your inputs are long documents, with an outcome box on each branch. Need speech out? or generated images Start with OpenAI one key covers it yes no Free tier first? no card on file Start with Gemini AI Studio key yes no Long documents? many pages at once Try Gemini first big input window yes no Either one works pick on price
Three questions settle the choice for most beginner projects, and the last branch is the common one: for ordinary text work either provider is a fine default.

If you keep the ask() function from Step 4, none of this is permanent. Swap the provider argument, rerun your script, and compare the two answers on your own task — that beats every comparison article, including this one. Start with whichever key you can get in the next five minutes, write something that works end to end, and add the second provider the day a task actually needs it.

Back to Understanding LLM APIs.

Frequently asked questions

Which is easier to start with, the OpenAI or the Gemini Python SDK?

Both take about ten minutes. The openai package needs a payment method on your account before the first call, while Google gives you a key from AI Studio that works on a free tier straight away. If you want to write code tonight without entering card details, Gemini removes one step.

Can I send the same prompt to both OpenAI and Gemini?

Yes. The words you write transfer unchanged. What moves is where the system instruction sits and how you pull the reply text out of the response object. Wrap both calls in one function with a provider argument and the rest of your program never has to know which model answered.

Where does the system prompt go in the Gemini Python SDK?

It goes in the config argument, not in the prompt itself. You build types.GenerateContentConfig with a system_instruction value and pass it to client.models.generate_content. Passing system_instruction directly as a keyword to generate_content raises a TypeError, which is the single most common beginner mistake with this SDK.

Is the Gemini API really free?

Google AI Studio offers a free usage tier for several Gemini models, capped by requests per minute and per day. The caps are enough for learning and small scripts but not for a production app. Quotas change often, so read the current rate-limit page rather than trusting a number in any tutorial.

Should I learn both APIs or commit to one?

Learn one properly, ship something with it, then add the second when a task demands it. The mental model is shared, so the second SDK costs you an afternoon. Many working apps keep both wired up and route each job to whichever provider is cheaper or better suited.