keycycle 0.2.2__tar.gz → 0.2.4__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 (49) hide show
  1. {keycycle-0.2.2 → keycycle-0.2.4}/PKG-INFO +1 -1
  2. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/adapters/openai_adapter.py +1 -1
  3. keycycle-0.2.4/keycycle/config/models/moonshot.yaml +59 -0
  4. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/models.py +5 -2
  5. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/key_rotation/rotating_mixin.py +18 -10
  6. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/utils.py +2 -1
  7. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle.egg-info/PKG-INFO +1 -1
  8. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle.egg-info/SOURCES.txt +1 -0
  9. {keycycle-0.2.2 → keycycle-0.2.4}/pyproject.toml +2 -1
  10. {keycycle-0.2.2 → keycycle-0.2.4}/MANIFEST.in +0 -0
  11. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/__init__.py +0 -0
  12. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/adapters/__init__.py +0 -0
  13. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/adapters/generic_adapter.py +0 -0
  14. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/__init__.py +0 -0
  15. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/constants.py +0 -0
  16. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/dataclasses.py +0 -0
  17. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/enums.py +0 -0
  18. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/loader.py +0 -0
  19. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/log_config.py +0 -0
  20. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/models/cerebras.yaml +0 -0
  21. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/models/cohere.yaml +0 -0
  22. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/models/gemini.yaml +0 -0
  23. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/models/groq.yaml +0 -0
  24. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/models/openrouter.yaml +0 -0
  25. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/config/models/openrouter_models.yaml +0 -0
  26. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/core/__init__.py +0 -0
  27. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/core/backoff.py +0 -0
  28. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/core/exceptions.py +0 -0
  29. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/core/utils.py +0 -0
  30. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/key_rotation/__init__.py +0 -0
  31. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/key_rotation/rotation_manager.py +0 -0
  32. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/legacy_multi_provider_wrapper.py +0 -0
  33. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/multi_client_wrapper.py +0 -0
  34. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/py.typed +0 -0
  35. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/usage/__init__.py +0 -0
  36. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/usage/db_logic.py +0 -0
  37. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle/usage/usage_logger.py +0 -0
  38. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle.egg-info/dependency_links.txt +0 -0
  39. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle.egg-info/requires.txt +0 -0
  40. {keycycle-0.2.2 → keycycle-0.2.4}/keycycle.egg-info/top_level.txt +0 -0
  41. {keycycle-0.2.2 → keycycle-0.2.4}/setup.cfg +0 -0
  42. {keycycle-0.2.2 → keycycle-0.2.4}/tests/test_api_key_pinning.py +0 -0
  43. {keycycle-0.2.2 → keycycle-0.2.4}/tests/test_api_key_retrieval.py +0 -0
  44. {keycycle-0.2.2 → keycycle-0.2.4}/tests/test_cerebras_direct.py +0 -0
  45. {keycycle-0.2.2 → keycycle-0.2.4}/tests/test_custom_key_limits.py +0 -0
  46. {keycycle-0.2.2 → keycycle-0.2.4}/tests/test_multi_client_wrapper.py +0 -0
  47. {keycycle-0.2.2 → keycycle-0.2.4}/tests/test_rate_limit_logic.py +0 -0
  48. {keycycle-0.2.2 → keycycle-0.2.4}/tests/test_specific_key_retrieval.py +0 -0
  49. {keycycle-0.2.2 → keycycle-0.2.4}/tests/test_streams_recording.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keycycle
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A thread-safe key rotation and rate limiting manager for API keys.
5
5
  Author-email: William Jin <jinwilliam.jin@gmail.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -37,7 +37,7 @@ PROVIDER_BASE_URLS = {
37
37
  "cerebras": "https://api.cerebras.ai/v1",
38
38
  "groq": "https://api.groq.com/openai/v1",
39
39
  "cohere": "https://api.cohere.ai/compatibility/v1",
40
-
40
+ "moonshot": "https://api.moonshot.ai/v1",
41
41
  }
42
42
 
43
43
  class BaseRotatingClient:
@@ -0,0 +1,59 @@
1
+ # Moonshot API rate limits (dummy high limits)
2
+ # Base URL: https://api.moonshot.ai/v1
3
+ # Agno class: MoonShot (similar to OpenRouter special case)
4
+
5
+ default:
6
+ requests_per_minute: 10000
7
+ requests_per_hour: 100000
8
+ requests_per_day: 1000000
9
+ tokens_per_minute: 10000000
10
+ tokens_per_hour: 100000000
11
+ tokens_per_day: 1000000000
12
+
13
+ kimi-k2.5:
14
+ requests_per_minute: 10000
15
+ requests_per_hour: 100000
16
+ requests_per_day: 1000000
17
+ tokens_per_minute: 10000000
18
+ tokens_per_hour: 100000000
19
+ tokens_per_day: 1000000000
20
+
21
+ kimi-k2-0905-preview:
22
+ requests_per_minute: 10000
23
+ requests_per_hour: 100000
24
+ requests_per_day: 1000000
25
+ tokens_per_minute: 10000000
26
+ tokens_per_hour: 100000000
27
+ tokens_per_day: 1000000000
28
+
29
+ kimi-k2-0711-preview:
30
+ requests_per_minute: 10000
31
+ requests_per_hour: 100000
32
+ requests_per_day: 1000000
33
+ tokens_per_minute: 10000000
34
+ tokens_per_hour: 100000000
35
+ tokens_per_day: 1000000000
36
+
37
+ kimi-k2-turbo-preview:
38
+ requests_per_minute: 10000
39
+ requests_per_hour: 100000
40
+ requests_per_day: 1000000
41
+ tokens_per_minute: 10000000
42
+ tokens_per_hour: 100000000
43
+ tokens_per_day: 1000000000
44
+
45
+ kimi-k2-thinking:
46
+ requests_per_minute: 10000
47
+ requests_per_hour: 100000
48
+ requests_per_day: 1000000
49
+ tokens_per_minute: 10000000
50
+ tokens_per_hour: 100000000
51
+ tokens_per_day: 1000000000
52
+
53
+ kimi-k2-thinking-turbo:
54
+ requests_per_minute: 10000
55
+ requests_per_hour: 100000
56
+ requests_per_day: 1000000
57
+ tokens_per_minute: 10000000
58
+ tokens_per_hour: 100000000
59
+ tokens_per_day: 1000000000
@@ -15,13 +15,15 @@ class ModelDict(TypedDict):
15
15
  gemini: Any
16
16
  openrouter: Any
17
17
  cohere: Any
18
+ moonshot: Any
18
19
 
19
20
  PROVIDER_STRATEGIES: ModelDict = {
20
21
  'cerebras': RateLimitStrategy.PER_MODEL,
21
22
  'groq': RateLimitStrategy.PER_MODEL,
22
23
  'gemini': RateLimitStrategy.PER_MODEL,
23
24
  'openrouter': RateLimitStrategy.GLOBAL,
24
- 'cohere': RateLimitStrategy.PER_MODEL
25
+ 'cohere': RateLimitStrategy.PER_MODEL,
26
+ 'moonshot': RateLimitStrategy.PER_MODEL
25
27
  }
26
28
 
27
29
  # Load Cohere tiers first to handle the env var logic
@@ -41,7 +43,8 @@ MODEL_LIMITS: ModelDict = {
41
43
  'cohere':{
42
44
  # Same for every model
43
45
  'default': COHERE_TIERS.get(os.getenv('COHERE_TIER', 'free').lower(), COHERE_TIERS['free'])
44
- }
46
+ },
47
+ 'moonshot': load_rate_limits_from_yaml(os.path.join(MODELS_DIR, 'moonshot.yaml'))
45
48
  }
46
49
 
47
50
  OPENROUTER_MODELS = load_openrouter_models(os.path.join(MODELS_DIR, 'openrouter_models.yaml'))
@@ -14,7 +14,17 @@ from ..config.constants import (
14
14
  )
15
15
  from ..core.utils import is_rate_limit_error, is_temporary_rate_limit_error, get_key_suffix
16
16
  from ..core.backoff import ExponentialBackoff, BackoffConfig
17
- from agno.models.response import ModelResponse
17
+ if TYPE_CHECKING:
18
+ from agno.models.response import ModelResponse
19
+
20
+
21
+ class _UsageResponse:
22
+ """Minimal response holder for usage recording when agno is not available."""
23
+ __slots__ = ('response_usage',)
24
+
25
+ def __init__(self, usage=None):
26
+ self.response_usage = usage
27
+
18
28
 
19
29
  class RotatingCredentialsMixin:
20
30
  """
@@ -100,7 +110,7 @@ class RotatingCredentialsMixin:
100
110
  def _get_retry_limit(self) -> int:
101
111
  return min(self._max_retries, len(self.wrapper.manager.keys) - 1)
102
112
 
103
- def _record_usage(self, key_obj: KeyUsage, response: Optional[ModelResponse]):
113
+ def _record_usage(self, key_obj: KeyUsage, response: Optional["ModelResponse"]):
104
114
  """
105
115
  Extracts usage from the response and reports it to the manager.
106
116
  Falls back to estimated_tokens if response is None or usage is missing.
@@ -132,7 +142,7 @@ class RotatingCredentialsMixin:
132
142
  multiplier=TEMP_RATE_LIMIT_MULTIPLIER,
133
143
  ))
134
144
 
135
- def invoke(self, *args, **kwargs) -> ModelResponse:
145
+ def invoke(self, *args, **kwargs) -> "ModelResponse":
136
146
  limit = self._get_retry_limit()
137
147
 
138
148
  for attempt in range(limit + 1):
@@ -178,7 +188,7 @@ class RotatingCredentialsMixin:
178
188
  continue
179
189
  raise
180
190
 
181
- async def ainvoke(self, *args, **kwargs) -> ModelResponse:
191
+ async def ainvoke(self, *args, **kwargs) -> "ModelResponse":
182
192
  limit = self._get_retry_limit()
183
193
 
184
194
  for attempt in range(limit + 1):
@@ -224,7 +234,7 @@ class RotatingCredentialsMixin:
224
234
  continue
225
235
  raise
226
236
 
227
- def invoke_stream(self, *args, **kwargs) -> Iterator[ModelResponse]:
237
+ def invoke_stream(self, *args, **kwargs) -> Iterator["ModelResponse"]:
228
238
  limit = self._get_retry_limit()
229
239
 
230
240
  for attempt in range(limit + 1):
@@ -242,8 +252,7 @@ class RotatingCredentialsMixin:
242
252
  final_usage = chunk.response_usage
243
253
  yield chunk
244
254
  if final_usage:
245
- dummy_response = ModelResponse()
246
- dummy_response.response_usage = final_usage
255
+ dummy_response = _UsageResponse(final_usage)
247
256
  self._record_usage(key_usage, dummy_response)
248
257
  else:
249
258
  # If the stream didn't return usage data, fallback to estimation
@@ -280,7 +289,7 @@ class RotatingCredentialsMixin:
280
289
  continue
281
290
  raise
282
291
 
283
- async def ainvoke_stream(self, *args, **kwargs) -> AsyncIterator[ModelResponse]:
292
+ async def ainvoke_stream(self, *args, **kwargs) -> AsyncIterator["ModelResponse"]:
284
293
  limit = self._get_retry_limit()
285
294
 
286
295
  for attempt in range(limit + 1):
@@ -301,8 +310,7 @@ class RotatingCredentialsMixin:
301
310
 
302
311
  # Stream completed successfully. Record usage.
303
312
  if final_usage:
304
- dummy_response = ModelResponse()
305
- dummy_response.response_usage = final_usage
313
+ dummy_response = _UsageResponse(final_usage)
306
314
  self._record_usage(key_usage, dummy_response)
307
315
  else:
308
316
  self._record_usage(key_usage, None)
@@ -12,7 +12,8 @@ def get_agno_model_class(provider: str):
12
12
  "gemini": "Gemini",
13
13
  "azure": "AzureOpenAI",
14
14
  "aws": "Bedrock",
15
- "openrouter": "OpenRouter"
15
+ "openrouter": "OpenRouter",
16
+ "moonshot": "MoonShot"
16
17
  }
17
18
 
18
19
  class_name = overrides.get(p_low, p_low.capitalize())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keycycle
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A thread-safe key rotation and rate limiting manager for API keys.
5
5
  Author-email: William Jin <jinwilliam.jin@gmail.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -24,6 +24,7 @@ keycycle/config/models/cerebras.yaml
24
24
  keycycle/config/models/cohere.yaml
25
25
  keycycle/config/models/gemini.yaml
26
26
  keycycle/config/models/groq.yaml
27
+ keycycle/config/models/moonshot.yaml
27
28
  keycycle/config/models/openrouter.yaml
28
29
  keycycle/config/models/openrouter_models.yaml
29
30
  keycycle/core/__init__.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "keycycle"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  authors = [
9
9
  { name="William Jin", email="jinwilliam.jin@gmail.com" },
10
10
  ]
@@ -117,5 +117,6 @@ module = [
117
117
  "groq.*",
118
118
  "cerebras.*",
119
119
  "openrouter.*",
120
+ "moonshot.*",
120
121
  ]
121
122
  ignore_missing_imports = true
File without changes
File without changes
File without changes
File without changes