Skip to main content

A minimal wrapper for the google gemini (google-genai) API

Project description

GGMW: Google Gemini Minimal Wrapper

Provides a minimally abstracted way to use the Gemini API.

Installation

uv add ggmw
pip install ggmw

Usages

Text complete response

# main.py
from google.genai import client as genai_client

from ggmw import Client, Message

client = Client(
    genai_client.Client(api_key="<API_KEY_HERE>"),
    model="gemini-2.5-flash"
)

messages = [
    Message("system", "Answer in rhyme"),
    Message("user", "What is recursion")
]
 
response = client.text_run(messages)

print(response)

Output:

 uv run main.py                     
A function starts, a task to do,
Then deep within, it calls anew.
Not quite the same, a smaller plight,
A mirrored problem, day and night.

It solves a piece, then calls once more,
Until a simple, clear-cut shore,
A 'base case' known, it needs no aid,
The final step, no more afraid.

Then upwards climb the answers true,
From smallest part, right back to you.
Unwinding logic, sharp and neat,
Making the complex task complete!

Text streaming

# main.py
from google.genai import client as genai_client

from ggmw import Client, Message

client = Client(
    genai_client.Client(api_key="<API_KEY_HERE>"),
    model="gemini-2.5-flash"
)

messages = [
    Message("system", "Answer in rhyme"),
    Message("user", "What is recursion")
]
 
for chunk in client.text_stream(messages):
    print(chunk)
    print("-"*20)

Output:

 uv run main.py
It's a curious process, a mystical art,
Where a function calls itself, playing its part.
It takes a big problem, and breaks it with care,
Into smaller versions, floating in air.

Each call then proceeds, with
--------------------
 a sub-task so slight,
Until a "base case" at last comes in sight.
This simple condition, a must for its plight,
Ensures it won't loop through the day and the night.

Once that is
--------------------
 fulfilled, the process turns 'round,
The answers unwind, with a satisfying sound.
From the smallest of pieces, the whole starts to grow,
A powerful pattern, for all coders to know!
--------------------

Structured complete response

# main.py
from google.genai import client as genai_client
from pydantic import BaseModel, Field

from ggmw import Client, Message

class Poem(BaseModel):
    """Represents a poem with 3 stanzas"""
    first_stanza:  str = Field(description="The first stanza of the poem")
    second_stanza: str = Field(description="The second stanza of the poem")    
    third_stanza:  str = Field(description="The third stanza of the poem")

client = Client(
    genai_client.Client(api_key="<API_KEY_HERE>"),
    model="gemini-2.5-flash"
)
messages = [
    Message("system", "Answer in rhyme"),
    Message("user", "What is recursion")
]
 
response = client.structured_run(messages, Poem)

print(repr(response))

Output:

 uv run main.py
Poem(first_stanza='A function calling its own name,To solve a problem, light a flame.It does its task, then calls anew,Until a simpler path shines through.', second_stanza='There must be a stop, a base so clear,To quell the endless stack, my dear.If not, it calls, and calls with might,Until memory fades from sight.', third_stanza='For trees and fractals, tasks so grand,It helps to walk throughout the land.A loop unseen, a nested quest,Recursion puts logic to the test.')

Structured streaming

# main.py
from google.genai import client as genai_client
from pydantic import BaseModel, Field

from ggmw import Client, Message

class Poem(BaseModel):
    """Represents a poem with 3 stanzas"""
    first_stanza:  str = Field(description="The first stanza of the poem")
    second_stanza: str = Field(description="The second stanza of the poem")    
    third_stanza:  str = Field(description="The third stanza of the poem")

client = Client(
    genai_client.Client(api_key="<API_KEY_HERE>"),
    model="gemini-2.5-flash"
)
messages = [
    Message("system", "Answer in rhyme"),
    Message("user", "What is recursion")
]
 
for partial in client.structured_stream(messages, Poem):
    print(repr(partial))
    print("-"*20)

Output:

 uv run main.py
PartialPoem(first_stanza='A puzzle deep, a loop untold,A function brave, both new and old.It calls itself, a clever trick,To solve a problem, oh so quick', second_stanza=None, third_stanza=None)
--------------------
PartialPoem(first_stanza='A puzzle deep, a loop untold,A function brave, both new and old.It calls itself, a clever trick,To solve a problem, oh so quick.', second_stanza="But lest it run forever on,A base case waits, till cycle's gone.A simple truth, it must discern,So from its endless path it can turn.", third_stanza='')
--------------------
PartialPoem(first_stanza='A puzzle deep, a loop untold,A function brave, both new and old.It calls itself, a clever trick,To solve a problem, oh so quick.', second_stanza="But lest it run forever on,A base case waits, till cycle's gone.A simple truth, it must discern,So from its endless path it can turn.", third_stanza="With each call down, a smaller part,It breaks the task, a work of art.Then builds it back, from finish line,Recursion's power, truly divine.")
--------------------

Async client

You can also use the async client, which has the same interfaces as the above sync one

# main.py
import asyncio
from google.genai import client as genai_client
from pydantic import BaseModel, Field

from ggmw import AsyncClient, Message

class Poem(BaseModel):
    """Represents a poem with 3 stanzas"""
    first_stanza:  str = Field(description="The first stanza of the poem")
    second_stanza: str = Field(description="The second stanza of the poem")    
    third_stanza:  str = Field(description="The third stanza of the poem")

client = AsyncClient(
    genai_client.Client(api_key="<API_KEY_HERE>"),
    model="gemini-2.5-flash"
)
messages = [
    Message("system", "Answer in rhyme"),
    Message("user", "What is recursion")
]

async def main():
    async for partial in client.structured_stream(messages, Poem):
        print(repr(partial))
        print("-"*20)

if __name__ == "__main__":
    asyncio.run(main())

Output:

 uv run main.py
PartialPoem(first_stanza='A curious loop, a self-same call,A function runs, then gives its all,By asking for its own dear name,To play again the very game.', second_stanza=None, third_stanza=None)
--------------------
PartialPoem(first_stanza='A curious loop, a self-same call,A function runs, then gives its all,By asking for its own dear name,To play again the very game.', second_stanza='It needs a stop, a simple truth,A base case known from early youth,To break the chain, to end the quest,And put the calling thoughts to rest.', third_stanza='Like mirrors deep')
--------------------
PartialPoem(first_stanza='A curious loop, a self-same call,A function runs, then gives its all,By asking for its own dear name,To play again the very game.', second_stanza='It needs a stop, a simple truth,A base case known from early youth,To break the chain, to end the quest,And put the calling thoughts to rest.', third_stanza='Like mirrors deep, reflecting bright,Or Russian dolls, a wondrous sight,It solves a task, then calls again,Until the simplest point is plain.')
--------------------

Additional Notes:

  • Some Gemini models have thinking enable (e.g. gemini-3-flash-preview), and you can set the thinking level when run those models
response = client.text_run(messages, thinking_level="LOW")

The Gemini API also has the ability to return the model thoughts, but it returns the thoughts along side the answer, and I don't know how to seperate them effectively so it is disabled (for now)

  • In order for the structured output streaming to work, the provided output schema, which is a pydantic model, get converted to a partial version (same structure but all the fields are set to optional). This behavior is implicit to keep the client simple.

Maybe in the future, change the interfaces so that the caller must pass in the converted models to be more explicit? For now it is implicit for simplicity The convertion function is called partial_model, and you can use it yourself

# main.py
from pydantic import BaseModel

from ggmw import partial

class User(BaseModel):
    name: str   # name, email and age are required 
    email: str
    age: int   
data = {
    "name": "foo",
    "email": "foo@example.com"
    # no age
}

ParitalUser = partial.partial_model(User) # name, email, and are are now optional
print(ParitalUser.model_validate(data))

Output:

 uv run main.py
name='foo' email='foo@example.com' age=None

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ggmw-0.1.1.tar.gz (13.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ggmw-0.1.1-py3-none-any.whl (19.8 kB view details)

Uploaded Python 3

File details

Details for the file ggmw-0.1.1.tar.gz.

File metadata

  • Download URL: ggmw-0.1.1.tar.gz
  • Upload date:
  • Size: 13.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ggmw-0.1.1.tar.gz
Algorithm Hash digest
SHA256 d18568645aba5aea70b04a14ee59231fe242cf110ed6199242e51d1db2948f49
MD5 66a252a8b6317c859fda139f8e73aaa8
BLAKE2b-256 1f029b02a34645f35fc4d720d916381177b7520f1f6a66af22cf92c7369577b5

See more details on using hashes here.

File details

Details for the file ggmw-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: ggmw-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 19.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ggmw-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 de1d599ac2540f41a6517386fc8645a3d1833369858c576967cc2b62935498a5
MD5 265c36cec8ae3a43aff09c13d77f3c8a
BLAKE2b-256 9b2a85d9ef18d8437d903eeda7e6196ee1f8ba45cecbebf303d28a5102620f72

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page