greycloud 0.4.0__tar.gz → 0.4.2__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.
- {greycloud-0.4.0/greycloud.egg-info → greycloud-0.4.2}/PKG-INFO +3 -3
- {greycloud-0.4.0 → greycloud-0.4.2}/README.md +1 -1
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/__init__.py +1 -1
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/cache.py +2 -2
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/config.py +1 -1
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/rate_limiter.py +10 -1
- {greycloud-0.4.0 → greycloud-0.4.2/greycloud.egg-info}/PKG-INFO +3 -3
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud.egg-info/requires.txt +1 -1
- {greycloud-0.4.0 → greycloud-0.4.2}/pyproject.toml +2 -2
- {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_batch.py +8 -8
- {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_cache.py +10 -10
- {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_config.py +1 -1
- {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_init.py +1 -1
- {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_rate_limiter.py +39 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/LICENSE +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/MANIFEST.in +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/async_client.py +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/auth.py +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/batch.py +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/client.py +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/grounding.py +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud.egg-info/SOURCES.txt +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud.egg-info/dependency_links.txt +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/greycloud.egg-info/top_level.txt +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/setup.cfg +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/setup.py +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/tests/__init__.py +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/tests/conftest.py +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_async_client.py +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_auth.py +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_client.py +0 -0
- {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_grounding.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: greycloud
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Reusable Google GenAI/Vertex AI Client Module
|
|
5
5
|
Author: GreyCloud Contributors
|
|
6
6
|
License: MIT
|
|
@@ -21,7 +21,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
21
21
|
Requires-Python: >=3.10
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
|
-
Requires-Dist: google-genai>=
|
|
24
|
+
Requires-Dist: google-genai>=2.31.0
|
|
25
25
|
Requires-Dist: google-auth>=2.0.0
|
|
26
26
|
Requires-Dist: google-cloud-storage>=3.8.0
|
|
27
27
|
Requires-Dist: aiolimiter>=1.1.0
|
|
@@ -177,7 +177,7 @@ config = GreyCloudConfig(
|
|
|
177
177
|
project_id="your-project-id",
|
|
178
178
|
location="us-central1",
|
|
179
179
|
# Default model is a Gemini 3 flash model; you can override if desired.
|
|
180
|
-
model="gemini-3-flash
|
|
180
|
+
model="gemini-3.8-flash",
|
|
181
181
|
)
|
|
182
182
|
|
|
183
183
|
# Create client
|
|
@@ -135,7 +135,7 @@ config = GreyCloudConfig(
|
|
|
135
135
|
project_id="your-project-id",
|
|
136
136
|
location="us-central1",
|
|
137
137
|
# Default model is a Gemini 3 flash model; you can override if desired.
|
|
138
|
-
model="gemini-3-flash
|
|
138
|
+
model="gemini-3.8-flash",
|
|
139
139
|
)
|
|
140
140
|
|
|
141
141
|
# Create client
|
|
@@ -27,8 +27,8 @@ MIN_TOKENS_BY_MODEL = {
|
|
|
27
27
|
"gemini-2.0": 2048,
|
|
28
28
|
"gemini-2.5-flash": 1024,
|
|
29
29
|
"gemini-2.5-pro": 4096,
|
|
30
|
-
"gemini-3.
|
|
31
|
-
"gemini-
|
|
30
|
+
"gemini-3.8-flash": 1024,
|
|
31
|
+
"gemini-4-argon": 4096,
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
# Default minimum if model not recognized
|
|
@@ -48,7 +48,7 @@ class GreyCloudConfig:
|
|
|
48
48
|
# Model configuration
|
|
49
49
|
# Default to a generally available Gemini 3 flash model.
|
|
50
50
|
# You can override this per-request when calling GreyCloudClient.
|
|
51
|
-
model: str = "gemini-3-flash
|
|
51
|
+
model: str = "gemini-3.8-flash"
|
|
52
52
|
endpoint: str = "https://aiplatform.googleapis.com"
|
|
53
53
|
api_version: str = "v1"
|
|
54
54
|
|
|
@@ -44,7 +44,16 @@ class VertexRateLimiter:
|
|
|
44
44
|
Whatever the underlying API call raises.
|
|
45
45
|
"""
|
|
46
46
|
await self._rpm_limiter.acquire()
|
|
47
|
-
|
|
47
|
+
# Clamp the TPM acquisition to the bucket capacity: a single request
|
|
48
|
+
# cannot be split, so one whose estimate exceeds the per-minute budget
|
|
49
|
+
# must still go through (it waits for a fresh window at worst) rather
|
|
50
|
+
# than being categorically rejected. Without the clamp, any request
|
|
51
|
+
# estimated above `tpm` — e.g. long-context prompts carrying a large
|
|
52
|
+
# injected knowledge block — raises aiolimiter's
|
|
53
|
+
# "Can't acquire more than the maximum capacity" ValueError on every
|
|
54
|
+
# attempt and the client retries until the caller gives up. The
|
|
55
|
+
# server's real TPM quota is enforced by Vertex itself.
|
|
56
|
+
await self._tpm_limiter.acquire(min(prompt_tokens_est, self.tpm))
|
|
48
57
|
async with self._semaphore:
|
|
49
58
|
return await api_call_coro
|
|
50
59
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: greycloud
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Reusable Google GenAI/Vertex AI Client Module
|
|
5
5
|
Author: GreyCloud Contributors
|
|
6
6
|
License: MIT
|
|
@@ -21,7 +21,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
21
21
|
Requires-Python: >=3.10
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
|
-
Requires-Dist: google-genai>=
|
|
24
|
+
Requires-Dist: google-genai>=2.31.0
|
|
25
25
|
Requires-Dist: google-auth>=2.0.0
|
|
26
26
|
Requires-Dist: google-cloud-storage>=3.8.0
|
|
27
27
|
Requires-Dist: aiolimiter>=1.1.0
|
|
@@ -177,7 +177,7 @@ config = GreyCloudConfig(
|
|
|
177
177
|
project_id="your-project-id",
|
|
178
178
|
location="us-central1",
|
|
179
179
|
# Default model is a Gemini 3 flash model; you can override if desired.
|
|
180
|
-
model="gemini-3-flash
|
|
180
|
+
model="gemini-3.8-flash",
|
|
181
181
|
)
|
|
182
182
|
|
|
183
183
|
# Create client
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "greycloud"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.2"
|
|
8
8
|
description = "Reusable Google GenAI/Vertex AI Client Module"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# google-genai >= 1.48.0 requires Python >= 3.10.
|
|
@@ -28,7 +28,7 @@ classifiers = [
|
|
|
28
28
|
|
|
29
29
|
dependencies = [
|
|
30
30
|
# Use a modern google-genai that aligns with Python >= 3.10.
|
|
31
|
-
"google-genai>=
|
|
31
|
+
"google-genai>=2.31.0",
|
|
32
32
|
"google-auth>=2.0.0",
|
|
33
33
|
"google-cloud-storage>=3.8.0",
|
|
34
34
|
"aiolimiter>=1.1.0",
|
|
@@ -227,7 +227,7 @@ class TestGreyCloudBatch:
|
|
|
227
227
|
"""Test batch requests upload"""
|
|
228
228
|
batch_requests = [
|
|
229
229
|
types.InlinedRequest(
|
|
230
|
-
model="gemini-
|
|
230
|
+
model="gemini-4-argon",
|
|
231
231
|
contents=[{"role": "user", "parts": [{"text": "Test"}]}],
|
|
232
232
|
config=types.GenerateContentConfig(temperature=0.2),
|
|
233
233
|
)
|
|
@@ -261,7 +261,7 @@ class TestGreyCloudBatch:
|
|
|
261
261
|
obj = json.loads(lines[0])
|
|
262
262
|
assert "request" in obj
|
|
263
263
|
req = obj["request"]
|
|
264
|
-
assert req["model"] == "gemini-
|
|
264
|
+
assert req["model"] == "gemini-4-argon"
|
|
265
265
|
assert "contents" in req
|
|
266
266
|
# Config should be serialized as generationConfig (not "config")
|
|
267
267
|
assert "generationConfig" in req
|
|
@@ -274,7 +274,7 @@ class TestGreyCloudBatch:
|
|
|
274
274
|
"""Test that JSONL serialization uses camelCase field names"""
|
|
275
275
|
batch_requests = [
|
|
276
276
|
types.InlinedRequest(
|
|
277
|
-
model="gemini-
|
|
277
|
+
model="gemini-4-argon",
|
|
278
278
|
contents=[{"role": "user", "parts": [{"text": "Test"}]}],
|
|
279
279
|
config=types.GenerateContentConfig(
|
|
280
280
|
temperature=0.5,
|
|
@@ -325,7 +325,7 @@ class TestGreyCloudBatch:
|
|
|
325
325
|
"""Test that systemInstruction is serialized as top-level request field"""
|
|
326
326
|
batch_requests = [
|
|
327
327
|
types.InlinedRequest(
|
|
328
|
-
model="gemini-
|
|
328
|
+
model="gemini-4-argon",
|
|
329
329
|
contents=[{"role": "user", "parts": [{"text": "Hello"}]}],
|
|
330
330
|
config=types.GenerateContentConfig(
|
|
331
331
|
system_instruction="You are a helpful assistant.",
|
|
@@ -368,7 +368,7 @@ class TestGreyCloudBatch:
|
|
|
368
368
|
thinking = types.ThinkingConfig(thinking_budget=2048)
|
|
369
369
|
batch_requests = [
|
|
370
370
|
types.InlinedRequest(
|
|
371
|
-
model="gemini-
|
|
371
|
+
model="gemini-4-argon",
|
|
372
372
|
contents=[{"role": "user", "parts": [{"text": "Think"}]}],
|
|
373
373
|
config=types.GenerateContentConfig(
|
|
374
374
|
temperature=0.5,
|
|
@@ -404,7 +404,7 @@ class TestGreyCloudBatch:
|
|
|
404
404
|
"""Test that None config values are omitted from JSONL"""
|
|
405
405
|
batch_requests = [
|
|
406
406
|
types.InlinedRequest(
|
|
407
|
-
model="gemini-
|
|
407
|
+
model="gemini-4-argon",
|
|
408
408
|
contents=[{"role": "user", "parts": [{"text": "Test"}]}],
|
|
409
409
|
config=types.GenerateContentConfig(temperature=0.5),
|
|
410
410
|
)
|
|
@@ -437,7 +437,7 @@ class TestGreyCloudBatch:
|
|
|
437
437
|
"""Test that InlinedRequest.metadata is NOT forwarded to JSONL"""
|
|
438
438
|
batch_requests = [
|
|
439
439
|
types.InlinedRequest(
|
|
440
|
-
model="gemini-
|
|
440
|
+
model="gemini-4-argon",
|
|
441
441
|
contents=[{"role": "user", "parts": [{"text": "Test"}]}],
|
|
442
442
|
metadata={"request_id": "123"},
|
|
443
443
|
)
|
|
@@ -469,7 +469,7 @@ class TestGreyCloudBatch:
|
|
|
469
469
|
"""Test batch job creation"""
|
|
470
470
|
batch_requests = [
|
|
471
471
|
types.InlinedRequest(
|
|
472
|
-
model="gemini-
|
|
472
|
+
model="gemini-4-argon",
|
|
473
473
|
contents=[{"role": "user", "parts": [{"text": "Test"}]}],
|
|
474
474
|
config=types.GenerateContentConfig(),
|
|
475
475
|
)
|
|
@@ -27,14 +27,14 @@ class TestGetMinTokensForModel:
|
|
|
27
27
|
assert get_min_tokens_for_model("gemini-2.5-pro") == 4096
|
|
28
28
|
assert get_min_tokens_for_model("gemini-2.5-pro-preview") == 4096
|
|
29
29
|
|
|
30
|
-
def
|
|
31
|
-
"""Gemini 3 Flash requires 1024 tokens"""
|
|
32
|
-
assert get_min_tokens_for_model("gemini-3.
|
|
30
|
+
def test_gemini_3_8_flash(self):
|
|
31
|
+
"""Gemini 3.8 Flash requires 1024 tokens"""
|
|
32
|
+
assert get_min_tokens_for_model("gemini-3.8-flash") == 1024
|
|
33
33
|
|
|
34
|
-
def
|
|
35
|
-
"""Gemini
|
|
36
|
-
assert get_min_tokens_for_model("gemini-
|
|
37
|
-
assert get_min_tokens_for_model("gemini-
|
|
34
|
+
def test_gemini_4_argon(self):
|
|
35
|
+
"""Gemini 4 Argon requires 4096 tokens"""
|
|
36
|
+
assert get_min_tokens_for_model("gemini-4-argon") == 4096
|
|
37
|
+
assert get_min_tokens_for_model("gemini-4-argon-preview") == 4096
|
|
38
38
|
|
|
39
39
|
def test_gemini_2_0(self):
|
|
40
40
|
"""Gemini 2.0 requires 2048 tokens"""
|
|
@@ -47,7 +47,7 @@ class TestGetMinTokensForModel:
|
|
|
47
47
|
def test_case_insensitive(self):
|
|
48
48
|
"""Model name matching is case insensitive"""
|
|
49
49
|
assert get_min_tokens_for_model("GEMINI-2.5-FLASH") == 1024
|
|
50
|
-
assert get_min_tokens_for_model("gemini-
|
|
50
|
+
assert get_min_tokens_for_model("gemini-4-argon") == 4096
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
class TestGreyCloudCacheInit:
|
|
@@ -580,7 +580,7 @@ class TestGreyCloudCacheGetCacheInfo:
|
|
|
580
580
|
mock_cache = MagicMock(spec=types.CachedContent)
|
|
581
581
|
mock_cache.name = "cachedContents/test-cache"
|
|
582
582
|
mock_cache.display_name = "Test Cache"
|
|
583
|
-
mock_cache.model = "gemini-3.
|
|
583
|
+
mock_cache.model = "gemini-3.8-flash"
|
|
584
584
|
mock_cache.create_time = datetime.datetime(2024, 1, 1, 12, 0, 0)
|
|
585
585
|
mock_cache.expire_time = datetime.datetime(2024, 1, 1, 13, 0, 0)
|
|
586
586
|
|
|
@@ -592,7 +592,7 @@ class TestGreyCloudCacheGetCacheInfo:
|
|
|
592
592
|
|
|
593
593
|
assert info["name"] == "cachedContents/test-cache"
|
|
594
594
|
assert info["display_name"] == "Test Cache"
|
|
595
|
-
assert info["model"] == "gemini-3.
|
|
595
|
+
assert info["model"] == "gemini-3.8-flash"
|
|
596
596
|
assert info["total_token_count"] == 50000
|
|
597
597
|
|
|
598
598
|
|
|
@@ -55,7 +55,7 @@ class TestGreyCloudConfig:
|
|
|
55
55
|
config = GreyCloudConfig(project_id="test-project")
|
|
56
56
|
|
|
57
57
|
# Defaults
|
|
58
|
-
assert config.model == "gemini-3-flash
|
|
58
|
+
assert config.model == "gemini-3.8-flash"
|
|
59
59
|
assert config.temperature == 1.0
|
|
60
60
|
assert config.top_p == 0.95
|
|
61
61
|
assert config.max_output_tokens == 65535
|
|
@@ -79,3 +79,42 @@ class TestVertexRateLimiterEstimateTokens:
|
|
|
79
79
|
def test_estimate_empty_string(self):
|
|
80
80
|
"""Empty string returns 0"""
|
|
81
81
|
assert VertexRateLimiter.estimate_tokens("") == 0
|
|
82
|
+
|
|
83
|
+
@pytest.mark.asyncio
|
|
84
|
+
async def test_tpm_acquisition_clamped_to_capacity(self, monkeypatch):
|
|
85
|
+
"""A request whose token estimate exceeds the TPM bucket capacity must
|
|
86
|
+
still proceed (clamped to capacity, waiting for a fresh window at
|
|
87
|
+
worst) instead of raising aiolimiter's 'Can't acquire more than the
|
|
88
|
+
maximum capacity' ValueError — a single request cannot be split, and
|
|
89
|
+
the server's real TPM quota is enforced by Vertex itself. Regression
|
|
90
|
+
for BlackSheep's long-context prompts (injected knowledge block plus
|
|
91
|
+
case history estimated above the 250K/min default bucket)."""
|
|
92
|
+
limiter = VertexRateLimiter(rpm=60, tpm=250_000, max_concurrency=1)
|
|
93
|
+
mock_coro = AsyncMock(return_value="done")
|
|
94
|
+
|
|
95
|
+
# Estimate far above capacity: must not raise.
|
|
96
|
+
result = await limiter.call_with_limits(500_000, mock_coro())
|
|
97
|
+
assert result == "done"
|
|
98
|
+
mock_coro.assert_awaited_once()
|
|
99
|
+
|
|
100
|
+
# The bucket was fully consumed by the clamped acquisition: a second
|
|
101
|
+
# request in the same window must wait for capacity to replenish
|
|
102
|
+
# (not fail), proving the clamp actually acquired.
|
|
103
|
+
# The clamped acquisition amount: patch the bucket class to record
|
|
104
|
+
# the requested amount, asserting the request acquired exactly the
|
|
105
|
+
# bucket capacity, not the raw estimate.
|
|
106
|
+
import aiolimiter
|
|
107
|
+
|
|
108
|
+
acquired = []
|
|
109
|
+
real_acquire = aiolimiter.AsyncLimiter.acquire
|
|
110
|
+
|
|
111
|
+
async def spy_acquire(self, amount=1):
|
|
112
|
+
acquired.append(amount)
|
|
113
|
+
await real_acquire(self, amount)
|
|
114
|
+
|
|
115
|
+
monkeypatch.setattr(aiolimiter.AsyncLimiter, "acquire", spy_acquire)
|
|
116
|
+
limiter2 = VertexRateLimiter(rpm=60, tpm=250_000, max_concurrency=1)
|
|
117
|
+
await limiter2.call_with_limits(500_000, AsyncMock(return_value="x")())
|
|
118
|
+
# acquired[0] is the RPM bucket's acquire(1); the TPM acquisition
|
|
119
|
+
# (clamped to capacity, not the 500K estimate) comes last.
|
|
120
|
+
assert acquired[-1] == 250_000
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|