Thanks for the recent import-cost work (lazy interactions, _gaos behind TYPE_CHECKING, defer_build=True in 2.18.0). It cut RSS roughly in half for us. Home Assistant's Gemini integration often runs on small devices, such as a Raspberry Pi with 1–2 GB of RAM, where every megabyte counts.
After 2.18, most of what's left of the SDK's own import cost is types.py (863 KB of source). About a third of that file is 457 TypedDict classes (*Dict) plus ~470 *OrDict / *UnionDict aliases built from them. They exist for type hints only: no pydantic field uses them, and the SDK only reads them in annotations, in error messages, and in the Live API.
Measurement (2.25.0, CPython 3.14, RSS of a fresh process, shared deps preloaded):
|
stock |
TypedDicts lazy |
import google.genai |
18.2 MiB |
15.7 MiB |
| import + Client + chats.create with a typical GenerateContentConfig |
25.2 MiB |
22.5 MiB |
That's about 2.7 MiB, or ~15% of the SDK's own import cost.
What we tried (as a build-time patch on our side): move the *Dict classes and the aliases that depend on them into a private module (_types_dicts.py, same source text), and load it from the existing module-level __getattr__ in types.py on first access. types.ContentDict and from google.genai.types import ContentDict keep working. A small set stays eager: the names other SDK modules import at load time (HttpOptionsDict, HttpOptionsOrDict, ContentDict, ContentOrDict, GenerateContentConfigOrDict, PartUnionDict) and everything their definitions and annotations reference, about 100 names.
Risks and mitigations:
-
Behavioral equivalence: All 1381 public names in google.genai.types are verified identical to stock: same kind, fields, docstrings, __module__, get_type_hints() outcome, and dir(). The bundled test suite gives identical per-test outcomes with the patch applied.
-
__module__ attribute: Each moved TypedDict has its __module__ set back to google.genai.types after loading, so pickle, inspect, and tooling see the same origin as before. Code doing from google.genai.types import SomeDict continues to work.
-
First-access latency: The lazy module loads on first use of any deferred name. This is a one-time cost (~5 ms measured) that moves from import time to first use. After that, all names are cached in the module globals, and access is a normal attribute lookup.
-
Python < 3.14: The savings rely on PEP 649 (3.14+): function annotations such as config: Optional[types.GenerateContentConfigOrDict] in models.py are no longer evaluated at definition time. On 3.10–3.13 those annotations would trigger the load at import. That is still correct but saves nothing, unless the SDK's modules adopt from __future__ import annotations.
-
Generated code: Since types.py is generated, the natural place for this is the generator. The patch is a build-time transform that could be folded into the code generation pipeline.
Proof of concept: rafaelborja/python-genai@main...lazy-load-typedicts. Happy to open a pull request if that helps, or it can serve as a reference for a generator change on your side.
A further step would be splitting the pydantic models themselves. A typical text + tools + thinking caller reaches only 137 of the 465 models, so that could save roughly another 5 MiB (estimated, not yet measured).
Happy to share the measurement scripts and help with testing.
Thanks for the recent import-cost work (lazy
interactions,_gaosbehindTYPE_CHECKING,defer_build=Truein 2.18.0). It cut RSS roughly in half for us. Home Assistant's Gemini integration often runs on small devices, such as a Raspberry Pi with 1–2 GB of RAM, where every megabyte counts.After 2.18, most of what's left of the SDK's own import cost is
types.py(863 KB of source). About a third of that file is 457TypedDictclasses (*Dict) plus ~470*OrDict/*UnionDictaliases built from them. They exist for type hints only: no pydantic field uses them, and the SDK only reads them in annotations, in error messages, and in the Live API.Measurement (2.25.0, CPython 3.14, RSS of a fresh process, shared deps preloaded):
import google.genaiThat's about 2.7 MiB, or ~15% of the SDK's own import cost.
What we tried (as a build-time patch on our side): move the
*Dictclasses and the aliases that depend on them into a private module (_types_dicts.py, same source text), and load it from the existing module-level__getattr__intypes.pyon first access.types.ContentDictandfrom google.genai.types import ContentDictkeep working. A small set stays eager: the names other SDK modules import at load time (HttpOptionsDict,HttpOptionsOrDict,ContentDict,ContentOrDict,GenerateContentConfigOrDict,PartUnionDict) and everything their definitions and annotations reference, about 100 names.Risks and mitigations:
Behavioral equivalence: All 1381 public names in
google.genai.typesare verified identical to stock: same kind, fields, docstrings,__module__,get_type_hints()outcome, anddir(). The bundled test suite gives identical per-test outcomes with the patch applied.__module__attribute: Each moved TypedDict has its__module__set back togoogle.genai.typesafter loading, sopickle,inspect, and tooling see the same origin as before. Code doingfrom google.genai.types import SomeDictcontinues to work.First-access latency: The lazy module loads on first use of any deferred name. This is a one-time cost (~5 ms measured) that moves from import time to first use. After that, all names are cached in the module globals, and access is a normal attribute lookup.
Python < 3.14: The savings rely on PEP 649 (3.14+): function annotations such as
config: Optional[types.GenerateContentConfigOrDict]inmodels.pyare no longer evaluated at definition time. On 3.10–3.13 those annotations would trigger the load at import. That is still correct but saves nothing, unless the SDK's modules adoptfrom __future__ import annotations.Generated code: Since
types.pyis generated, the natural place for this is the generator. The patch is a build-time transform that could be folded into the code generation pipeline.Proof of concept: rafaelborja/python-genai@main...lazy-load-typedicts. Happy to open a pull request if that helps, or it can serve as a reference for a generator change on your side.
A further step would be splitting the pydantic models themselves. A typical text + tools + thinking caller reaches only 137 of the 465 models, so that could save roughly another 5 MiB (estimated, not yet measured).
Happy to share the measurement scripts and help with testing.