lmux-google 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lmux_google-0.7.0/PKG-INFO +141 -0
- lmux_google-0.7.0/README.md +121 -0
- lmux_google-0.7.0/pyproject.toml +31 -0
- lmux_google-0.7.0/src/lmux_google/__init__.py +37 -0
- lmux_google-0.7.0/src/lmux_google/_exceptions.py +66 -0
- lmux_google-0.7.0/src/lmux_google/_lazy.py +34 -0
- lmux_google-0.7.0/src/lmux_google/_mappers.py +424 -0
- lmux_google-0.7.0/src/lmux_google/auth.py +102 -0
- lmux_google-0.7.0/src/lmux_google/cost.py +243 -0
- lmux_google-0.7.0/src/lmux_google/params.py +56 -0
- lmux_google-0.7.0/src/lmux_google/provider.py +446 -0
- lmux_google-0.7.0/src/lmux_google/py.typed +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lmux-google
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Google (Gemini) provider for lmux
|
|
5
|
+
Keywords: llm,ai,google,gemini,vertex,language-model
|
|
6
|
+
Author: Connor Luebbehusen
|
|
7
|
+
Author-email: Connor Luebbehusen <connor@luebbehusen.dev>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
|
+
Classifier: Typing :: Typed
|
|
13
|
+
Requires-Dist: lmux~=0.7
|
|
14
|
+
Requires-Dist: google-genai~=1.0
|
|
15
|
+
Requires-Python: >=3.13
|
|
16
|
+
Project-URL: Homepage, https://github.com/cluebbehusen/lmux
|
|
17
|
+
Project-URL: Source, https://github.com/cluebbehusen/lmux/tree/main/packages/lmux-google
|
|
18
|
+
Project-URL: Issues, https://github.com/cluebbehusen/lmux/issues
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# lmux-google
|
|
22
|
+
|
|
23
|
+
Google (Gemini) provider for [lmux](https://github.com/cluebbehusen/lmux). Wraps the [google-genai](https://pypi.org/project/google-genai/) SDK, which serves Google's models through either backend:
|
|
24
|
+
|
|
25
|
+
- **Vertex AI** (default) — authenticated with Google Cloud credentials
|
|
26
|
+
- **Gemini Developer API** (AI Studio) — authenticated with an API key (`vertexai=False`)
|
|
27
|
+
|
|
28
|
+
Supports chat completions, streaming, and embeddings for Google-published models: Gemini and the Gemini/text embedding models.
|
|
29
|
+
|
|
30
|
+
Part of the [lmux](https://github.com/cluebbehusen/lmux) ecosystem: standardized interface, cost tracking on every response, and registry-based routing across providers.
|
|
31
|
+
|
|
32
|
+
## Auth
|
|
33
|
+
|
|
34
|
+
Three authentication methods:
|
|
35
|
+
|
|
36
|
+
### Application Default Credentials (default)
|
|
37
|
+
|
|
38
|
+
Uses `google.auth.default()`, which works with `GOOGLE_APPLICATION_CREDENTIALS`, `gcloud` CLI, or instance metadata.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from lmux_google import GoogleProvider
|
|
42
|
+
|
|
43
|
+
provider = GoogleProvider(project="my-project", location="us-central1")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Service Account
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from lmux_google import GoogleServiceAccountAuthProvider
|
|
50
|
+
|
|
51
|
+
provider = GoogleProvider(
|
|
52
|
+
project="my-project",
|
|
53
|
+
location="us-central1",
|
|
54
|
+
auth=GoogleServiceAccountAuthProvider(service_account_file="/path/to/key.json"),
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### API Key
|
|
59
|
+
|
|
60
|
+
Set `GOOGLE_API_KEY` in your environment:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from lmux_google import GoogleAPIKeyAuthProvider
|
|
64
|
+
|
|
65
|
+
provider = GoogleProvider(auth=GoogleAPIKeyAuthProvider(), vertexai=False)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
### Chat
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from lmux import UserMessage
|
|
74
|
+
|
|
75
|
+
response = provider.chat("gemini-2.5-pro", [UserMessage(content="Hello")])
|
|
76
|
+
print(response.content)
|
|
77
|
+
print(response.cost)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Streaming
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
for chunk in provider.chat_stream("gemini-2.5-pro", [UserMessage(content="Hello")]):
|
|
84
|
+
if chunk.delta:
|
|
85
|
+
print(chunk.delta, end="")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Embeddings
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
response = provider.embed("text-embedding-005", "Hello")
|
|
92
|
+
print(response.embeddings)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Async
|
|
96
|
+
|
|
97
|
+
All methods have async variants: `achat`, `achat_stream`, `aembed`.
|
|
98
|
+
|
|
99
|
+
### Registry
|
|
100
|
+
|
|
101
|
+
Use with the lmux registry to route across multiple providers:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from lmux import Registry
|
|
105
|
+
|
|
106
|
+
registry = Registry()
|
|
107
|
+
registry.register("google", provider)
|
|
108
|
+
response = registry.chat("google/gemini-2.5-pro", messages)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Provider Params
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from lmux_google import GoogleParams
|
|
115
|
+
|
|
116
|
+
response = provider.chat(
|
|
117
|
+
"gemini-2.5-pro",
|
|
118
|
+
messages,
|
|
119
|
+
provider_params=GoogleParams(thinking_config={"thinking_budget": 1024}),
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
| Parameter | Type | Description |
|
|
124
|
+
| ------------------- | --------------------- | -------------------------------- |
|
|
125
|
+
| `safety_settings` | `list[SafetySetting]` | Content safety thresholds |
|
|
126
|
+
| `presence_penalty` | `float` | Presence penalty |
|
|
127
|
+
| `frequency_penalty` | `float` | Frequency penalty |
|
|
128
|
+
| `seed` | `int` | Deterministic sampling seed |
|
|
129
|
+
| `labels` | `dict[str, str]` | Request labels |
|
|
130
|
+
| `thinking_config` | `dict` | Thinking/reasoning configuration |
|
|
131
|
+
|
|
132
|
+
## Constructor Options
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
GoogleProvider(
|
|
136
|
+
auth=..., # AuthProvider, default: GoogleADCAuthProvider()
|
|
137
|
+
project=..., # GCP project ID
|
|
138
|
+
location=..., # GCP region
|
|
139
|
+
vertexai=..., # Use Vertex AI (default: True) vs. AI Studio
|
|
140
|
+
)
|
|
141
|
+
```
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# lmux-google
|
|
2
|
+
|
|
3
|
+
Google (Gemini) provider for [lmux](https://github.com/cluebbehusen/lmux). Wraps the [google-genai](https://pypi.org/project/google-genai/) SDK, which serves Google's models through either backend:
|
|
4
|
+
|
|
5
|
+
- **Vertex AI** (default) — authenticated with Google Cloud credentials
|
|
6
|
+
- **Gemini Developer API** (AI Studio) — authenticated with an API key (`vertexai=False`)
|
|
7
|
+
|
|
8
|
+
Supports chat completions, streaming, and embeddings for Google-published models: Gemini and the Gemini/text embedding models.
|
|
9
|
+
|
|
10
|
+
Part of the [lmux](https://github.com/cluebbehusen/lmux) ecosystem: standardized interface, cost tracking on every response, and registry-based routing across providers.
|
|
11
|
+
|
|
12
|
+
## Auth
|
|
13
|
+
|
|
14
|
+
Three authentication methods:
|
|
15
|
+
|
|
16
|
+
### Application Default Credentials (default)
|
|
17
|
+
|
|
18
|
+
Uses `google.auth.default()`, which works with `GOOGLE_APPLICATION_CREDENTIALS`, `gcloud` CLI, or instance metadata.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from lmux_google import GoogleProvider
|
|
22
|
+
|
|
23
|
+
provider = GoogleProvider(project="my-project", location="us-central1")
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Service Account
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from lmux_google import GoogleServiceAccountAuthProvider
|
|
30
|
+
|
|
31
|
+
provider = GoogleProvider(
|
|
32
|
+
project="my-project",
|
|
33
|
+
location="us-central1",
|
|
34
|
+
auth=GoogleServiceAccountAuthProvider(service_account_file="/path/to/key.json"),
|
|
35
|
+
)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### API Key
|
|
39
|
+
|
|
40
|
+
Set `GOOGLE_API_KEY` in your environment:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from lmux_google import GoogleAPIKeyAuthProvider
|
|
44
|
+
|
|
45
|
+
provider = GoogleProvider(auth=GoogleAPIKeyAuthProvider(), vertexai=False)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### Chat
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from lmux import UserMessage
|
|
54
|
+
|
|
55
|
+
response = provider.chat("gemini-2.5-pro", [UserMessage(content="Hello")])
|
|
56
|
+
print(response.content)
|
|
57
|
+
print(response.cost)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Streaming
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
for chunk in provider.chat_stream("gemini-2.5-pro", [UserMessage(content="Hello")]):
|
|
64
|
+
if chunk.delta:
|
|
65
|
+
print(chunk.delta, end="")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Embeddings
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
response = provider.embed("text-embedding-005", "Hello")
|
|
72
|
+
print(response.embeddings)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Async
|
|
76
|
+
|
|
77
|
+
All methods have async variants: `achat`, `achat_stream`, `aembed`.
|
|
78
|
+
|
|
79
|
+
### Registry
|
|
80
|
+
|
|
81
|
+
Use with the lmux registry to route across multiple providers:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from lmux import Registry
|
|
85
|
+
|
|
86
|
+
registry = Registry()
|
|
87
|
+
registry.register("google", provider)
|
|
88
|
+
response = registry.chat("google/gemini-2.5-pro", messages)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Provider Params
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from lmux_google import GoogleParams
|
|
95
|
+
|
|
96
|
+
response = provider.chat(
|
|
97
|
+
"gemini-2.5-pro",
|
|
98
|
+
messages,
|
|
99
|
+
provider_params=GoogleParams(thinking_config={"thinking_budget": 1024}),
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Parameter | Type | Description |
|
|
104
|
+
| ------------------- | --------------------- | -------------------------------- |
|
|
105
|
+
| `safety_settings` | `list[SafetySetting]` | Content safety thresholds |
|
|
106
|
+
| `presence_penalty` | `float` | Presence penalty |
|
|
107
|
+
| `frequency_penalty` | `float` | Frequency penalty |
|
|
108
|
+
| `seed` | `int` | Deterministic sampling seed |
|
|
109
|
+
| `labels` | `dict[str, str]` | Request labels |
|
|
110
|
+
| `thinking_config` | `dict` | Thinking/reasoning configuration |
|
|
111
|
+
|
|
112
|
+
## Constructor Options
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
GoogleProvider(
|
|
116
|
+
auth=..., # AuthProvider, default: GoogleADCAuthProvider()
|
|
117
|
+
project=..., # GCP project ID
|
|
118
|
+
location=..., # GCP region
|
|
119
|
+
vertexai=..., # Use Vertex AI (default: True) vs. AI Studio
|
|
120
|
+
)
|
|
121
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "lmux-google"
|
|
3
|
+
version = "0.7.0"
|
|
4
|
+
description = "Google (Gemini) provider for lmux"
|
|
5
|
+
requires-python = ">=3.13"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
authors = [{ name = "Connor Luebbehusen", email = "connor@luebbehusen.dev" }]
|
|
9
|
+
keywords = ["llm", "ai", "google", "gemini", "vertex", "language-model"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
14
|
+
"Typing :: Typed",
|
|
15
|
+
]
|
|
16
|
+
dependencies = [
|
|
17
|
+
"lmux~=0.7",
|
|
18
|
+
"google-genai~=1.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/cluebbehusen/lmux"
|
|
23
|
+
Source = "https://github.com/cluebbehusen/lmux/tree/main/packages/lmux-google"
|
|
24
|
+
Issues = "https://github.com/cluebbehusen/lmux/issues"
|
|
25
|
+
|
|
26
|
+
[tool.uv.sources]
|
|
27
|
+
lmux = { workspace = true }
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["uv_build>=0.11.0,<0.12.0"]
|
|
31
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""lmux-google — Google (Gemini) provider for lmux."""
|
|
2
|
+
|
|
3
|
+
from lmux_google.auth import (
|
|
4
|
+
GoogleADCAuthProvider,
|
|
5
|
+
GoogleAPIKeyAuthProvider,
|
|
6
|
+
GoogleServiceAccountAuthProvider,
|
|
7
|
+
)
|
|
8
|
+
from lmux_google.cost import calculate_google_cost
|
|
9
|
+
from lmux_google.params import (
|
|
10
|
+
DynamicRetrievalConfig,
|
|
11
|
+
GoogleParams,
|
|
12
|
+
GoogleSearchConfig,
|
|
13
|
+
GoogleSearchRetrievalConfig,
|
|
14
|
+
GoogleSearchTypes,
|
|
15
|
+
SafetySetting,
|
|
16
|
+
)
|
|
17
|
+
from lmux_google.provider import GoogleProvider
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"DynamicRetrievalConfig",
|
|
21
|
+
"GoogleADCAuthProvider",
|
|
22
|
+
"GoogleAPIKeyAuthProvider",
|
|
23
|
+
"GoogleParams",
|
|
24
|
+
"GoogleProvider",
|
|
25
|
+
"GoogleSearchConfig",
|
|
26
|
+
"GoogleSearchRetrievalConfig",
|
|
27
|
+
"GoogleSearchTypes",
|
|
28
|
+
"GoogleServiceAccountAuthProvider",
|
|
29
|
+
"SafetySetting",
|
|
30
|
+
"calculate_google_cost",
|
|
31
|
+
"preload",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def preload() -> None:
|
|
36
|
+
"""Eagerly import the google-genai SDK."""
|
|
37
|
+
import google.genai # noqa: PLC0415, F401 # pyright: ignore[reportUnusedImport]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Map google-genai SDK exceptions to lmux exception hierarchy."""
|
|
2
|
+
|
|
3
|
+
from lmux.exceptions import (
|
|
4
|
+
AuthenticationError,
|
|
5
|
+
InvalidRequestError,
|
|
6
|
+
LmuxError,
|
|
7
|
+
NotFoundError,
|
|
8
|
+
ProviderError,
|
|
9
|
+
RateLimitError,
|
|
10
|
+
TimeoutError, # noqa: A004
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
PROVIDER = "google"
|
|
14
|
+
|
|
15
|
+
_STATUS_CODE_MAP: dict[int, type[LmuxError]] = {
|
|
16
|
+
400: InvalidRequestError,
|
|
17
|
+
401: AuthenticationError,
|
|
18
|
+
403: AuthenticationError,
|
|
19
|
+
404: NotFoundError,
|
|
20
|
+
408: TimeoutError,
|
|
21
|
+
429: RateLimitError,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def map_google_error(error: Exception) -> LmuxError:
|
|
26
|
+
"""Convert a google-genai SDK exception to the corresponding lmux exception."""
|
|
27
|
+
from google.genai import errors as genai_errors # noqa: PLC0415
|
|
28
|
+
|
|
29
|
+
if isinstance(error, genai_errors.ClientError):
|
|
30
|
+
return _map_client_error(error)
|
|
31
|
+
|
|
32
|
+
if isinstance(error, genai_errors.ServerError):
|
|
33
|
+
return ProviderError(str(error), provider=PROVIDER, status_code=error.code)
|
|
34
|
+
|
|
35
|
+
if isinstance(error, genai_errors.APIError):
|
|
36
|
+
return ProviderError(str(error), provider=PROVIDER, status_code=error.code)
|
|
37
|
+
|
|
38
|
+
# google.auth errors
|
|
39
|
+
auth_error = _check_auth_error(error)
|
|
40
|
+
if auth_error is not None:
|
|
41
|
+
return auth_error
|
|
42
|
+
|
|
43
|
+
return ProviderError(str(error), provider=PROVIDER)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _map_client_error(error: Exception) -> LmuxError:
|
|
47
|
+
"""Map a google-genai ClientError based on its HTTP status code."""
|
|
48
|
+
code: int | None = getattr(error, "code", None)
|
|
49
|
+
|
|
50
|
+
exc_cls = _STATUS_CODE_MAP.get(code) if code is not None else None
|
|
51
|
+
if exc_cls is not None:
|
|
52
|
+
if exc_cls is RateLimitError:
|
|
53
|
+
return RateLimitError(str(error), provider=PROVIDER, status_code=code)
|
|
54
|
+
return exc_cls(str(error), provider=PROVIDER, status_code=code)
|
|
55
|
+
|
|
56
|
+
return ProviderError(str(error), provider=PROVIDER, status_code=code)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _check_auth_error(error: Exception) -> AuthenticationError | None:
|
|
60
|
+
"""Check if the error is a google.auth exception and map it."""
|
|
61
|
+
import google.auth.exceptions # noqa: PLC0415
|
|
62
|
+
|
|
63
|
+
if isinstance(error, google.auth.exceptions.DefaultCredentialsError | google.auth.exceptions.RefreshError):
|
|
64
|
+
return AuthenticationError(str(error), provider=PROVIDER)
|
|
65
|
+
|
|
66
|
+
return None
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Lazy google-genai SDK loading internals.
|
|
2
|
+
|
|
3
|
+
Client creation is isolated here so tests can easily mock it
|
|
4
|
+
without patching sys.modules or using TYPE_CHECKING tricks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from google.auth.credentials import Credentials
|
|
11
|
+
from google.genai import Client
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_client(
|
|
15
|
+
*,
|
|
16
|
+
vertexai: bool = True,
|
|
17
|
+
project: str | None = None,
|
|
18
|
+
location: str | None = None,
|
|
19
|
+
credentials: "Credentials | None" = None,
|
|
20
|
+
api_key: str | None = None,
|
|
21
|
+
) -> "Client":
|
|
22
|
+
"""Create a google-genai Client."""
|
|
23
|
+
from google import genai # noqa: PLC0415
|
|
24
|
+
|
|
25
|
+
kwargs: dict[str, Any] = {"vertexai": vertexai}
|
|
26
|
+
if project is not None:
|
|
27
|
+
kwargs["project"] = project
|
|
28
|
+
if location is not None:
|
|
29
|
+
kwargs["location"] = location
|
|
30
|
+
if credentials is not None:
|
|
31
|
+
kwargs["credentials"] = credentials
|
|
32
|
+
if api_key is not None:
|
|
33
|
+
kwargs["api_key"] = api_key
|
|
34
|
+
return genai.Client(**kwargs)
|