✨ An elegant async Python wrapper for Google Gemini web app
Project description
Gemini-API
A reverse-engineered asynchronous Python wrapper for the Google Gemini web app (formerly Bard).
Features
- Persistent Cookies - Automatically refreshes cookies in background. Optimized for always-on services.
- Image Generation - Natively supports generating and editing images with natural language.
- System Prompt - Supports customizing the model's system prompt with Gemini Gems.
- Extension Support - Supports generating content with Gemini extensions, such as YouTube and Gmail.
- Classified Outputs - Categorizes text, thoughts, web images, and AI-generated images in the response.
- Streaming Mode - Supports stream generation, yielding partial outputs as they are generated.
- Official Flavor - Provides a simple and elegant interface inspired by Google Generative AI's official API.
- Asynchronous - Utilizes
asyncioto run generation tasks and return outputs efficiently.
Table of Contents
- Features
- Table of Contents
- Installation
- Authentication
- Usage
- Initialization
- Generate Content
- Generate Content with Files
- Conversations Across Multiple Turns
- Continue Previous Conversations
- Read Conversation History
- Delete Previous Conversations from Gemini History
- Temporary Mode
- Streaming Mode
- Select Language Model
- Apply System Prompt with Gemini Gems
- Manage Custom Gems
- Retrieve Model's Thought Process
- Retrieve Images in Response
- Generate and Edit Images
- Generate Content with Gemini Extensions
- Check and Switch to Other Reply Candidates
- Logging Configuration
- References
- Stargazers
Installation
[!NOTE]
This package requires Python 3.10 or higher.
Install or update the package with pip.
pip install -U gemini_webapi
Optionally, the package offers a way to automatically import cookies from your local browser via optional dependency browser-cookie3. To enable this feature, install gemini_webapi[browser] instead. Supported platforms and browsers can be found here.
pip install -U gemini_webapi[browser]
Authentication
[!TIP]
If
browser-cookie3is installed, you can skip this step and go directly to the usage section. Just make sure you are logged in to https://gemini.google.com in your browser.
- Go to https://gemini.google.com and log in with your Google account
- Press F12 to open the web inspector, go to the
Networktab, and refresh the page - Click any request and copy the cookie values of
__Secure-1PSIDand__Secure-1PSIDTS
[!NOTE]
If your application is deployed in a containerized environment (e.g. Docker), you may want to persist the cookies with a volume to avoid re-authentication every time the container rebuilds. You can set
GEMINI_COOKIE_PATHenvironment variable to specify the path where auto-refreshed cookies are stored. Make sure the path is writable by the application.Here's part of a sample
docker-compose.ymlfile:
services:
main:
environment:
GEMINI_COOKIE_PATH: /tmp/gemini_webapi
volumes:
- ./gemini_cookies:/tmp/gemini_webapi
[!NOTE]
The API's auto-cookie-refreshing feature doesn't require
browser-cookie3and is enabled by default. It allows you to keep the API service running without worrying about cookie expiration.This feature may require you to log in to your Google account again in the browser. This is expected behavior and won't affect the API's functionality.
To avoid this, it's recommended to get cookies from a separate browser session and close it as soon as possible for best utilization (e.g. a fresh login in the browser's private mode). More details can be found here.
Usage
Initialization
Import the required packages and initialize a client with your cookies from the previous step. After successful initialization, the API will automatically refresh __Secure-1PSIDTS in the background as long as the process is alive.
import asyncio
from gemini_webapi import GeminiClient
# Replace "COOKIE VALUE HERE" with your actual cookie values.
# Leave Secure_1PSIDTS empty if it's not available for your account.
Secure_1PSID = "COOKIE VALUE HERE"
Secure_1PSIDTS = "COOKIE VALUE HERE"
async def main():
# If browser-cookie3 is installed, simply use `client = GeminiClient()`
client = GeminiClient(Secure_1PSID, Secure_1PSIDTS, proxy=None)
await client.init(timeout=30, auto_close=False, close_delay=300, auto_refresh=True)
asyncio.run(main())
[!TIP]
auto_closeandclose_delayare optional arguments for automatically closing the client after a certain period of inactivity. This feature is disabled by default. In an always-on service like a chatbot, it's recommended to setauto_closetoTruewith a reasonableclose_delayvalue for better resource management.
Generate Content
Ask a single-turn question by calling GeminiClient.generate_content, which returns a gemini_webapi.ModelOutput object containing the generated text, images, thoughts, and conversation metadata.
async def main():
response = await client.generate_content("Hello World!")
print(response.text)
asyncio.run(main())
[!TIP]
Simply use
print(response)to get the same output if you just want to see the response text.
Generate Content with Files
Gemini supports file input, including images and documents. Optionally, you can pass files as a list of paths in str or pathlib.Path to GeminiClient.generate_content together with a text prompt.
async def main():
response = await client.generate_content(
"Introduce the contents of these two files. Is there any connection between them?",
files=["assets/sample.pdf", Path("assets/banner.png")],
)
print(response.text)
asyncio.run(main())
Conversations Across Multiple Turns
If you want to keep the conversation continuous, use GeminiClient.start_chat to create a gemini_webapi.ChatSession object and send messages through it. The conversation history will be handled automatically and updated after each turn.
async def main():
chat = client.start_chat()
response1 = await chat.send_message(
"Introduce the contents of these two files. Is there any connection between them?",
files=["assets/sample.pdf", Path("assets/banner.png")],
)
print(response1.text)
response2 = await chat.send_message(
"Use image generation tool to modify the banner with another font and design."
)
print(response2.text, response2.images, sep="\n\n----------------------------------\n\n")
asyncio.run(main())
[!TIP]
Same as
GeminiClient.generate_content,ChatSession.send_messagealso acceptsimageas an optional argument.
Continue Previous Conversations
To manually retrieve previous conversations, you can pass a previous ChatSession's metadata to GeminiClient.start_chat when creating a new ChatSession. Alternatively, you can persist previous metadata to a file or database if you need to access it after the current Python process has exited.
async def main():
# Start a new chat session
chat = client.start_chat()
response = await chat.send_message("Fine weather today")
# Save chat's metadata
previous_session = chat.metadata
# Load the previous conversation
previous_chat = client.start_chat(metadata=previous_session)
response = await previous_chat.send_message("What was my previous message?")
print(response)
asyncio.run(main())
Read Conversation History
You can read the conversation history of a specific chat by calling GeminiClient.read_chat with the chat ID.
async def main():
chat = client.start_chat()
await chat.send_message("What is the capital of France?")
# Read the chat history
history = await client.read_chat(chat.cid)
for turn in history:
print(
f"Input: {turn.user_prompt}\n"
f"Output: {turn.assistant_response}"
"\n\n----------------------------------\n\n"
)
asyncio.run(main())
Delete Previous Conversations from Gemini History
You can delete a specific chat from Gemini history on the server by calling GeminiClient.delete_chat with the chat ID.
async def main():
# Start a new chat session
chat = client.start_chat()
await chat.send_message("This is a temporary conversation.")
# Delete the chat
await client.delete_chat(chat.cid)
print(f"Chat deleted: {chat.cid}")
asyncio.run(main())
Temporary Mode
You can start a temporary chat by passing temporary=True to GeminiClient.generate_content or ChatSession.send_message. Temporary chats won't be saved in Gemini history.
async def main():
response = await client.generate_content("Hello World!", temporary=True)
print(response.text, "\n\n----------------------------------\n\n")
chat = client.start_chat()
await chat.send_message("Fine weather today", temporary=False)
response2 = await chat.send_message("What's my last message?", temporary=True)
print(response2.text)
asyncio.run(main())
Streaming Mode
For longer responses, you can use streaming mode to receive partial outputs as they are generated. This provides a more responsive user experience, especially for real-time applications like chatbots.
The generate_content_stream method yields ModelOutput objects where the text_delta attribute contains only the new characters received since the last yield, making it easy to display incremental updates.
async def main():
async for chunk in client.generate_content_stream(
"What's the difference between 'await' and 'async for'?"
):
print(chunk.text_delta, end="", flush=True)
print()
asyncio.run(main())
[!TIP]
Streaming mode accepts the same arguments as
generate_content. You can also use streaming mode in multi-turn conversations withChatSession.send_message_stream.
Select Language Model
You can specify which language model to use by passing the model argument to GeminiClient.generate_content or GeminiClient.start_chat. The default value is unspecified.
Currently available models (as of November 20, 2025):
unspecified- Default modelgemini-3.0-pro- Gemini 3.0 Progemini-3.0-flash- Gemini 3.0 Flashgemini-3.0-flash-thinking- Gemini 3.0 Flash Thinking
from gemini_webapi.constants import Model
async def main():
response1 = await client.generate_content(
"What's your language model version? Reply with the version number only.",
model=Model.G_3_0_FLASH,
)
print(f"Model version ({Model.G_3_0_FLASH.model_name}): {response1.text}")
chat = client.start_chat(model="gemini-2.5-pro")
response2 = await chat.send_message("What's your language model version? Reply with the version number only.")
print(f"Model version (gemini-2.5-pro): {response2.text}")
asyncio.run(main())
You can also pass custom model header strings directly to access models that are not listed above.
# "model_name" and "model_header" keys must be present
custom_model = {
"model_name": "xxx",
"model_header": {
"x-goog-ext-525001261-jspb": "[1,null,null,null,'e6fa609c3fa255c0',null,null,null,[4]]"
},
}
response = await client.generate_content(
"What's your model version?",
model=custom_model
)
Apply System Prompt with Gemini Gems
System prompts can be applied to conversations via Gemini Gems. To use a gem, you can pass the gem argument to GeminiClient.generate_content or GeminiClient.start_chat. gem can be either a gem ID string or a gemini_webapi.Gem object. Only one gem can be applied to a single conversation.
[!TIP]
There are some system predefined gems that are not shown to users by default (and therefore may not work properly). Use
client.fetch_gems(include_hidden=True)to include them in the fetch result.
async def main():
# Fetch all gems for the current account, including both predefined and user-created ones
await client.fetch_gems(include_hidden=False, language="en")
# Once fetched, gems will be cached in `GeminiClient.gems`
gems = client.gems
# Get the gem you want to use
system_gems = gems.filter(predefined=True)
coding_partner = system_gems.get(id="coding-partner")
response1 = await client.generate_content(
"What's your system prompt?",
model=Model.G_3_0_FLASH,
gem=coding_partner,
)
print(response1.text)
# Another example with a user-created custom gem
# Gem ids are consistent strings. Store them somewhere to avoid fetching gems every time
your_gem = gems.get(name="Your Gem Name")
your_gem_id = your_gem.id
chat = client.start_chat(gem=your_gem_id)
response2 = await chat.send_message("What's your system prompt?")
print(response2)
Manage Custom Gems
You can create, update, and delete your custom gems programmatically with the API. Note that predefined system gems cannot be modified or deleted.
Create a Custom Gem
Create a new custom gem with a name, system prompt (instructions), and optional description:
async def main():
# Create a new custom gem
new_gem = await client.create_gem(
name="Python Tutor",
prompt="You are a helpful Python programming tutor.",
description="A specialized gem for Python programming"
)
print(f"Custom gem created: {new_gem}")
# Use the newly created gem in a conversation
response = await client.generate_content(
"Explain how list comprehensions work in Python",
gem=new_gem
)
print(response.text)
asyncio.run(main())
Update an Existing Gem
[!NOTE]
When updating a gem, you must provide all parameters (name, prompt, description) even if you only want to change one of them.
async def main():
# Get a custom gem (assuming you have one named "Python Tutor")
await client.fetch_gems()
python_tutor = client.gems.get(name="Python Tutor")
# Update the gem with new instructions
updated_gem = await client.update_gem(
gem=python_tutor, # Can also pass gem ID string
name="Advanced Python Tutor",
prompt="You are an expert Python programming tutor.",
description="An advanced Python programming assistant"
)
print(f"Custom gem updated: {updated_gem}")
asyncio.run(main())
Delete a Custom Gem
async def main():
# Get the gem to delete
await client.fetch_gems()
gem_to_delete = client.gems.get(name="Advanced Python Tutor")
# Delete the gem
await client.delete_gem(gem_to_delete) # Can also pass gem ID string
print(f"Custom gem deleted: {gem_to_delete.name}")
asyncio.run(main())
Retrieve Model's Thought Process
When using models with thinking capabilities, the model's thought process will be populated in ModelOutput.thoughts.
async def main():
response = await client.generate_content(
"What's 1+1?", model="gemini-2.5-pro"
)
print(response.thoughts)
print(response.text)
asyncio.run(main())
Retrieve Images in Response
Images in the API's output are stored as a list of gemini_webapi.Image objects. You can access the image title, URL, and description by calling Image.title, Image.url and Image.alt respectively.
async def main():
response = await client.generate_content("Send me some pictures of cats")
for image in response.images:
print(image, "\n\n----------------------------------\n\n")
asyncio.run(main())
Generate and Edit Images
You can ask Gemini to generate and edit images with Nano Banana, Google's latest image model, using natural language.
[!IMPORTANT]
Google has some limitations on Gemini's image generation feature, so availability may vary by region/account. Here's a summary copied from official documentation (as of Sep 10, 2025):
This feature’s availability in any specific Gemini app is also limited to the supported languages and countries of that app.
For now, this feature isn’t available to users under 18.
To use this feature, you must be signed in to Gemini Apps.
You can save images returned from Gemini locally by calling Image.save(). Optionally, you can specify the file path and file name by passing path and filename arguments to the function, and skip images with invalid file names by passing skip_invalid_filename=True. This works for both WebImage and GeneratedImage.
async def main():
response = await client.generate_content("Generate some pictures of cats")
for i, image in enumerate(response.images):
await image.save(path="temp/", filename=f"cat_{i}.png", verbose=True)
print(image, "\n\n----------------------------------\n\n")
asyncio.run(main())
[!NOTE]
By default, when asked to send images (like in the previous example), Gemini will send images fetched from the web instead of generating images with an AI model, unless you specifically ask it to "generate" images in your prompt. In this package, web images and generated images are treated differently as
WebImageandGeneratedImage, and are automatically categorized in the output.
Generate Content with Gemini Extensions
[!IMPORTANT]
To access Gemini extensions in the API, you must activate them on the Gemini website first. As with image generation, Google also has limitations on the availability of Gemini extensions. Here's a summary copied from official documentation (as of March 19, 2025):
To connect apps to Gemini, you must have Gemini Apps Activity on.
To use this feature, you must be signed in to Gemini Apps.
Important: If you’re under 18, Google Workspace and Maps apps currently only work with English prompts in Gemini.
After activating extensions for your account, you can access them in your prompts either in natural language or by starting your prompt with "@" followed by the extension keyword.
async def main():
response1 = await client.generate_content("@Gmail What's the latest message in my mailbox?")
print(response1, "\n\n----------------------------------\n\n")
response2 = await client.generate_content("@Youtube What's the latest activity of Taylor Swift?")
print(response2, "\n\n----------------------------------\n\n")
asyncio.run(main())
[!NOTE]
For region availability, your Google account's preferred language only needs to be set to one of the three supported languages listed above. You can change your language settings here.
Check and Switch to Other Reply Candidates
A response from Gemini sometimes contains multiple reply candidates with different generated content. You can check all candidates and choose one to continue the conversation. By default, the first candidate is chosen.
async def main():
# Start a conversation and list all reply candidates
chat = client.start_chat()
response = await chat.send_message("Recommend a science fiction book for me.")
for candidate in response.candidates:
print(candidate, "\n\n----------------------------------\n\n")
if len(response.candidates) > 1:
# Control the ongoing conversation flow by choosing candidate manually
new_candidate = chat.choose_candidate(index=1) # Choose the second candidate here
followup_response = await chat.send_message("Tell me more about it.") # Will generate content based on the chosen candidate
print(new_candidate, followup_response, sep="\n\n----------------------------------\n\n")
else:
print("Only one candidate available.")
asyncio.run(main())
Logging Configuration
This package uses loguru for logging and exposes a function set_log_level to control the log level. You can set the log level to one of the following values: DEBUG, INFO, WARNING, ERROR, and CRITICAL. The default value is INFO.
from gemini_webapi import set_log_level
set_log_level("DEBUG")
[!NOTE]
Calling
set_log_levelfor the first time will globally remove all existing loguru handlers. You may want to configure logging directly with loguru to avoid this issue and have more advanced control over logging behaviors.
References
Stargazers
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gemini_webapi-1.21.0.tar.gz.
File metadata
- Download URL: gemini_webapi-1.21.0.tar.gz
- Upload date:
- Size: 272.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0b75c29b5380b109f663b35c8df76f5244dae819b12a472bd25df9016a46b76c
|
|
| MD5 |
c59c68f9d4cd8d14952a6f53389184ba
|
|
| BLAKE2b-256 |
b31eec164b7af828b2561a6d0d6235d895e2997698d16fcbbcc652b20ca0ba49
|
Provenance
The following attestation bundles were made for gemini_webapi-1.21.0.tar.gz:
Publisher:
pypi-publish.yml on HanaokaYuzu/Gemini-API
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gemini_webapi-1.21.0.tar.gz -
Subject digest:
0b75c29b5380b109f663b35c8df76f5244dae819b12a472bd25df9016a46b76c - Sigstore transparency entry: 1053154542
- Sigstore integration time:
-
Permalink:
HanaokaYuzu/Gemini-API@ffa53dac9e411107fabc9a4240e0ec40cb008bdf -
Branch / Tag:
refs/tags/v1.21.0 - Owner: https://github.com/HanaokaYuzu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@ffa53dac9e411107fabc9a4240e0ec40cb008bdf -
Trigger Event:
push
-
Statement type:
File details
Details for the file gemini_webapi-1.21.0-py3-none-any.whl.
File metadata
- Download URL: gemini_webapi-1.21.0-py3-none-any.whl
- Upload date:
- Size: 69.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04fbaef555537892c2c824ea127796770d210dbe65df4f178723897dd969b72e
|
|
| MD5 |
d303e4f7101a2269cea8e62cabbe5d25
|
|
| BLAKE2b-256 |
a8c71e94cbfdf7c6add59f61897d0069e83ca099eafb0affeac7a56881c4298e
|
Provenance
The following attestation bundles were made for gemini_webapi-1.21.0-py3-none-any.whl:
Publisher:
pypi-publish.yml on HanaokaYuzu/Gemini-API
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gemini_webapi-1.21.0-py3-none-any.whl -
Subject digest:
04fbaef555537892c2c824ea127796770d210dbe65df4f178723897dd969b72e - Sigstore transparency entry: 1053154561
- Sigstore integration time:
-
Permalink:
HanaokaYuzu/Gemini-API@ffa53dac9e411107fabc9a4240e0ec40cb008bdf -
Branch / Tag:
refs/tags/v1.21.0 - Owner: https://github.com/HanaokaYuzu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@ffa53dac9e411107fabc9a4240e0ec40cb008bdf -
Trigger Event:
push
-
Statement type: