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 age 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.2.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.2-py3-none-any.whl (19.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: ggmw-0.1.2.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.2.tar.gz
Algorithm Hash digest
SHA256 192b9d19ba8b4db2b12e1419dcfc459734675e69f6c86acd107067c299016e02
MD5 1519994e4af9637c201ff9ca60f69f77
BLAKE2b-256 43835b7013e7b03978c746e4e3c83af65e25dc7b249827d8f2a54e41da48ab67

See more details on using hashes here.

File details

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

File metadata

  • Download URL: ggmw-0.1.2-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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0c8a1d0c15fd0dabd3bf260c091aa2919f4c22d5286521337e57e419165b78ac
MD5 b6ac592221e4b961904b3d379bf64a8b
BLAKE2b-256 331d8a647481d063b676fa4d5e2197434e85a319668b99f123309d30e8a9a9bb

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