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

Usage

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 enabled (e.g. gemini-3-flash-preview), and you can set the thinking level when running 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 separate 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, gets 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 conversion 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
}

PartialUser = partial.partial_model(User) # name, email, and age are now optional
print(PartialUser.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.3.tar.gz (13.1 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.3-py3-none-any.whl (19.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: ggmw-0.1.3.tar.gz
  • Upload date:
  • Size: 13.1 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.3.tar.gz
Algorithm Hash digest
SHA256 6b7a29a47848d82c5bce02d46a7ae1dd78c2e40c912dc3046c0ef7ec2069758b
MD5 ba4a79c8d201c3d31320129ba518d3b7
BLAKE2b-256 23c2836f9403a92162ba94bb5be604eb5ce2cc91e15485d53555bd67c50fbf88

See more details on using hashes here.

File details

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

File metadata

  • Download URL: ggmw-0.1.3-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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 4b9cf1c29ae693598b69053baed41d30696461677492f8bc398d0ebecf27b559
MD5 3992b22eeade1f1d8d14b1143a5cfe61
BLAKE2b-256 92e6d46d70299c4a3d6bd4251466085d02584d715bcf981fb6478e3409a8c080

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