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.
Files changed (32) hide show
  1. {greycloud-0.4.0/greycloud.egg-info → greycloud-0.4.2}/PKG-INFO +3 -3
  2. {greycloud-0.4.0 → greycloud-0.4.2}/README.md +1 -1
  3. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/__init__.py +1 -1
  4. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/cache.py +2 -2
  5. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/config.py +1 -1
  6. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/rate_limiter.py +10 -1
  7. {greycloud-0.4.0 → greycloud-0.4.2/greycloud.egg-info}/PKG-INFO +3 -3
  8. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud.egg-info/requires.txt +1 -1
  9. {greycloud-0.4.0 → greycloud-0.4.2}/pyproject.toml +2 -2
  10. {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_batch.py +8 -8
  11. {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_cache.py +10 -10
  12. {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_config.py +1 -1
  13. {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_init.py +1 -1
  14. {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_rate_limiter.py +39 -0
  15. {greycloud-0.4.0 → greycloud-0.4.2}/LICENSE +0 -0
  16. {greycloud-0.4.0 → greycloud-0.4.2}/MANIFEST.in +0 -0
  17. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/async_client.py +0 -0
  18. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/auth.py +0 -0
  19. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/batch.py +0 -0
  20. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/client.py +0 -0
  21. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud/grounding.py +0 -0
  22. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud.egg-info/SOURCES.txt +0 -0
  23. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud.egg-info/dependency_links.txt +0 -0
  24. {greycloud-0.4.0 → greycloud-0.4.2}/greycloud.egg-info/top_level.txt +0 -0
  25. {greycloud-0.4.0 → greycloud-0.4.2}/setup.cfg +0 -0
  26. {greycloud-0.4.0 → greycloud-0.4.2}/setup.py +0 -0
  27. {greycloud-0.4.0 → greycloud-0.4.2}/tests/__init__.py +0 -0
  28. {greycloud-0.4.0 → greycloud-0.4.2}/tests/conftest.py +0 -0
  29. {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_async_client.py +0 -0
  30. {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_auth.py +0 -0
  31. {greycloud-0.4.0 → greycloud-0.4.2}/tests/test_client.py +0 -0
  32. {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.0
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>=1.59.0
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-preview",
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-preview",
138
+ model="gemini-3.8-flash",
139
139
  )
140
140
 
141
141
  # Create client
@@ -13,7 +13,7 @@ from .cache import GreyCloudCache
13
13
  from .rate_limiter import VertexRateLimiter
14
14
  from .async_client import GreyCloudAsyncClient
15
15
 
16
- __version__ = "0.4.0"
16
+ __version__ = "0.4.2"
17
17
  __all__ = [
18
18
  "GreyCloudConfig",
19
19
  "GreyCloudClient",
@@ -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.5-flash": 1024,
31
- "gemini-3.1-pro": 4096,
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-preview"
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
- await self._tpm_limiter.acquire(prompt_tokens_est)
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.0
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>=1.59.0
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-preview",
180
+ model="gemini-3.8-flash",
181
181
  )
182
182
 
183
183
  # Create client
@@ -1,4 +1,4 @@
1
- google-genai>=1.59.0
1
+ google-genai>=2.31.0
2
2
  google-auth>=2.0.0
3
3
  google-cloud-storage>=3.8.0
4
4
  aiolimiter>=1.1.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "greycloud"
7
- version = "0.4.0"
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>=1.59.0",
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-3.1-pro-preview",
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-3.1-pro-preview"
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-3.1-pro-preview",
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-3.1-pro-preview",
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-3.1-pro-preview",
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-3.1-pro-preview",
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-3.1-pro-preview",
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-3.1-pro-preview",
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 test_gemini_3_flash(self):
31
- """Gemini 3 Flash requires 1024 tokens"""
32
- assert get_min_tokens_for_model("gemini-3.5-flash") == 1024
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 test_gemini_3_pro(self):
35
- """Gemini 3 Pro requires 4096 tokens"""
36
- assert get_min_tokens_for_model("gemini-3.1-pro") == 4096
37
- assert get_min_tokens_for_model("gemini-3.1-pro-preview") == 4096
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-3.1-pro") == 4096
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.5-flash"
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.5-flash"
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-preview"
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
@@ -16,7 +16,7 @@ class TestPackageInit:
16
16
 
17
17
  def test_version(self):
18
18
  """Test package version"""
19
- assert __version__ == "0.4.0"
19
+ assert __version__ == "0.4.2"
20
20
 
21
21
  def test_all_exports(self):
22
22
  """Test __all__ exports"""
@@ -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