keycycle 0.3.2__py3-none-any.whl
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.
- keycycle/__init__.py +48 -0
- keycycle/adapters/__init__.py +22 -0
- keycycle/adapters/generic_adapter.py +663 -0
- keycycle/adapters/openai_adapter.py +381 -0
- keycycle/config/__init__.py +25 -0
- keycycle/config/constants.py +35 -0
- keycycle/config/dataclasses.py +225 -0
- keycycle/config/enums.py +5 -0
- keycycle/config/loader.py +54 -0
- keycycle/config/log_config.py +53 -0
- keycycle/config/models/cerebras.yaml +36 -0
- keycycle/config/models/cohere.yaml +14 -0
- keycycle/config/models/gemini.yaml +69 -0
- keycycle/config/models/groq.yaml +161 -0
- keycycle/config/models/mistral.yaml +13 -0
- keycycle/config/models/moonshot.yaml +59 -0
- keycycle/config/models/openrouter.yaml +4 -0
- keycycle/config/models/openrouter_models.yaml +97 -0
- keycycle/config/models.py +53 -0
- keycycle/core/__init__.py +40 -0
- keycycle/core/backoff.py +79 -0
- keycycle/core/exceptions.py +94 -0
- keycycle/core/utils.py +480 -0
- keycycle/key_rotation/__init__.py +7 -0
- keycycle/key_rotation/rotating_mixin.py +392 -0
- keycycle/key_rotation/rotation_manager.py +231 -0
- keycycle/legacy_multi_provider_wrapper.py +734 -0
- keycycle/multi_client_wrapper.py +372 -0
- keycycle/py.typed +0 -0
- keycycle/usage/__init__.py +6 -0
- keycycle/usage/db_logic.py +98 -0
- keycycle/usage/usage_logger.py +89 -0
- keycycle/utils.py +27 -0
- keycycle-0.3.2.dist-info/METADATA +44 -0
- keycycle-0.3.2.dist-info/RECORD +37 -0
- keycycle-0.3.2.dist-info/WHEEL +5 -0
- keycycle-0.3.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import yaml
|
|
3
|
+
from typing import Dict, Any, List, TypedDict, Optional
|
|
4
|
+
from .dataclasses import RateLimits
|
|
5
|
+
|
|
6
|
+
class RateLimitConfig(TypedDict):
|
|
7
|
+
requests_per_minute: int
|
|
8
|
+
requests_per_hour: int
|
|
9
|
+
requests_per_day: int
|
|
10
|
+
tokens_per_minute: Optional[int]
|
|
11
|
+
tokens_per_hour: Optional[int]
|
|
12
|
+
tokens_per_day: Optional[int]
|
|
13
|
+
|
|
14
|
+
class ModelConfig(TypedDict):
|
|
15
|
+
name: str
|
|
16
|
+
id: str
|
|
17
|
+
max_context_length: int
|
|
18
|
+
|
|
19
|
+
def load_yaml_config(file_path: str) -> Any:
|
|
20
|
+
"""Loads a YAML configuration file."""
|
|
21
|
+
if not os.path.exists(file_path):
|
|
22
|
+
raise FileNotFoundError(f"Configuration file not found: {file_path}")
|
|
23
|
+
|
|
24
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
25
|
+
return yaml.safe_load(f)
|
|
26
|
+
|
|
27
|
+
def load_rate_limits_from_yaml(file_path: str) -> Dict[str, RateLimits]:
|
|
28
|
+
"""Loads rate limits from a YAML file and converts them to RateLimits objects."""
|
|
29
|
+
data = load_yaml_config(file_path)
|
|
30
|
+
if not isinstance(data, dict):
|
|
31
|
+
raise ValueError(f"Invalid rate limit config in {file_path}, expected a dictionary.")
|
|
32
|
+
|
|
33
|
+
limits = {}
|
|
34
|
+
for model_id, config in data.items():
|
|
35
|
+
try:
|
|
36
|
+
limits[model_id] = RateLimits(
|
|
37
|
+
requests_per_minute=config['requests_per_minute'],
|
|
38
|
+
requests_per_hour=config['requests_per_hour'],
|
|
39
|
+
requests_per_day=config['requests_per_day'],
|
|
40
|
+
tokens_per_minute=config.get('tokens_per_minute'),
|
|
41
|
+
tokens_per_hour=config.get('tokens_per_hour'),
|
|
42
|
+
tokens_per_day=config.get('tokens_per_day'),
|
|
43
|
+
)
|
|
44
|
+
except KeyError as e:
|
|
45
|
+
raise ValueError(f"Missing required field {e} for model {model_id} in {file_path}")
|
|
46
|
+
|
|
47
|
+
return limits
|
|
48
|
+
|
|
49
|
+
def load_openrouter_models(file_path: str) -> List[ModelConfig]:
|
|
50
|
+
"""Loads the OpenRouter models list."""
|
|
51
|
+
data = load_yaml_config(file_path)
|
|
52
|
+
if not isinstance(data, list):
|
|
53
|
+
raise ValueError(f"Invalid OpenRouter config in {file_path}, expected a list.")
|
|
54
|
+
return data
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import logging.config
|
|
3
|
+
from logging.handlers import RotatingFileHandler
|
|
4
|
+
from rich.logging import RichHandler
|
|
5
|
+
|
|
6
|
+
LOGGING_CONFIG = {
|
|
7
|
+
"version": 1,
|
|
8
|
+
"disable_existing_loggers": False,
|
|
9
|
+
"formatters": {
|
|
10
|
+
"standard": {
|
|
11
|
+
"format": "%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s",
|
|
12
|
+
"datefmt": "%Y-%m-%d %H:%M:%S",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
"handlers": {
|
|
17
|
+
"console": {
|
|
18
|
+
"class": "rich.logging.RichHandler",
|
|
19
|
+
"level": "DEBUG",
|
|
20
|
+
"rich_tracebacks": True, # Beautiful traceback formatting
|
|
21
|
+
"markup": True, # Allow "[bold]text[/]" inside log messages!
|
|
22
|
+
"show_path": False # Cleaner output (hides file path column)
|
|
23
|
+
},
|
|
24
|
+
"file": {
|
|
25
|
+
"class": "logging.handlers.RotatingFileHandler",
|
|
26
|
+
"level": "INFO",
|
|
27
|
+
"formatter": "standard",
|
|
28
|
+
"filename": "app.log",
|
|
29
|
+
"maxBytes": 10 * 1024 * 1024, # 10 MiB per file
|
|
30
|
+
"backupCount": 5,
|
|
31
|
+
"encoding": "utf-8",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
"root": {
|
|
36
|
+
"handlers": ["console", "file"],
|
|
37
|
+
"level": "WARNING",
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
"loggers": {
|
|
41
|
+
"key_manager": {
|
|
42
|
+
"handlers": ["console", "file"],
|
|
43
|
+
"level": "DEBUG",
|
|
44
|
+
"propagate": False,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def configure_logging():
|
|
50
|
+
logging.config.dictConfig(LOGGING_CONFIG)
|
|
51
|
+
|
|
52
|
+
configure_logging()
|
|
53
|
+
default_logger = logging.getLogger(__name__)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Cerebras free-tier (Free Trial) rate limits.
|
|
2
|
+
# Source: https://inference-docs.cerebras.ai/models/overview
|
|
3
|
+
# https://inference-docs.cerebras.ai/support/rate-limits
|
|
4
|
+
# Verified 2026-09-07: the Cerebras public Model Catalog now lists only two
|
|
5
|
+
# models available on the Free Trial tier — gpt-oss-120b and qwen-3.8-27b.
|
|
6
|
+
# All other entries previously listed here (llama3.1-8b, llama-3.3-70b,
|
|
7
|
+
# qwen-3-32b, qwen-3-235b-a22b-instruct-2507, zai-glm-4.7) have been removed
|
|
8
|
+
# from the public catalog and can no longer be served on free keys.
|
|
9
|
+
# Cerebras no longer publishes separate requests_per_hour/requests_per_day
|
|
10
|
+
# caps for this tier (only requests_per_minute plus token buckets), so those
|
|
11
|
+
# two fields below are derived as a sustained-RPM bound (rpm * 60, rpm * 60 * 24)
|
|
12
|
+
# rather than an independently-enforced Cerebras limit. tokens_per_minute uses
|
|
13
|
+
# the "uncached TPM" figure, the primary/binding token bucket per Cerebras docs.
|
|
14
|
+
gpt-oss-120b:
|
|
15
|
+
requests_per_minute: 5
|
|
16
|
+
requests_per_hour: 300
|
|
17
|
+
requests_per_day: 7200
|
|
18
|
+
tokens_per_minute: 30000
|
|
19
|
+
tokens_per_hour: 1000000
|
|
20
|
+
tokens_per_day: 1000000
|
|
21
|
+
|
|
22
|
+
qwen-3.8-27b:
|
|
23
|
+
requests_per_minute: 5
|
|
24
|
+
requests_per_hour: 300
|
|
25
|
+
requests_per_day: 7200
|
|
26
|
+
tokens_per_minute: 30000
|
|
27
|
+
tokens_per_hour: 1000000
|
|
28
|
+
tokens_per_day: 1000000
|
|
29
|
+
|
|
30
|
+
default:
|
|
31
|
+
requests_per_minute: 5
|
|
32
|
+
requests_per_hour: 300
|
|
33
|
+
requests_per_day: 7200
|
|
34
|
+
tokens_per_minute: 30000
|
|
35
|
+
tokens_per_hour: 1000000
|
|
36
|
+
tokens_per_day: 1000000
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
free:
|
|
2
|
+
requests_per_minute: 20
|
|
3
|
+
requests_per_hour: 1200
|
|
4
|
+
requests_per_day: 72000
|
|
5
|
+
|
|
6
|
+
pro:
|
|
7
|
+
requests_per_minute: 500
|
|
8
|
+
requests_per_hour: 30000
|
|
9
|
+
requests_per_day: 1440000
|
|
10
|
+
|
|
11
|
+
enterprise:
|
|
12
|
+
requests_per_minute: 1000
|
|
13
|
+
requests_per_hour: 60000
|
|
14
|
+
requests_per_day: 2880000
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
gemini-3-flash-preview:
|
|
2
|
+
requests_per_minute: 5
|
|
3
|
+
requests_per_hour: 300
|
|
4
|
+
requests_per_day: 20
|
|
5
|
+
tokens_per_minute: 250000
|
|
6
|
+
tokens_per_hour: 15000000
|
|
7
|
+
|
|
8
|
+
gemini-2.5-flash:
|
|
9
|
+
requests_per_minute: 5
|
|
10
|
+
requests_per_hour: 300
|
|
11
|
+
requests_per_day: 20
|
|
12
|
+
tokens_per_minute: 250000
|
|
13
|
+
tokens_per_hour: 15000000
|
|
14
|
+
|
|
15
|
+
gemini-2.5-flash-lite:
|
|
16
|
+
requests_per_minute: 10
|
|
17
|
+
requests_per_hour: 600
|
|
18
|
+
requests_per_day: 20
|
|
19
|
+
tokens_per_minute: 250000
|
|
20
|
+
tokens_per_hour: 15000000
|
|
21
|
+
|
|
22
|
+
gemini-2.5-flash-tts:
|
|
23
|
+
requests_per_minute: 3
|
|
24
|
+
requests_per_hour: 180
|
|
25
|
+
requests_per_day: 10
|
|
26
|
+
tokens_per_minute: 10000
|
|
27
|
+
tokens_per_hour: 600000
|
|
28
|
+
|
|
29
|
+
gemini-robotics-er-1.5-preview:
|
|
30
|
+
requests_per_minute: 10
|
|
31
|
+
requests_per_hour: 600
|
|
32
|
+
requests_per_day: 250
|
|
33
|
+
tokens_per_minute: 250000
|
|
34
|
+
tokens_per_hour: 15000000
|
|
35
|
+
|
|
36
|
+
gemma-3-12b-it:
|
|
37
|
+
requests_per_minute: 30
|
|
38
|
+
requests_per_hour: 1800
|
|
39
|
+
requests_per_day: 14400
|
|
40
|
+
tokens_per_minute: 15000
|
|
41
|
+
tokens_per_hour: 900000
|
|
42
|
+
|
|
43
|
+
gemma-3-1b-it:
|
|
44
|
+
requests_per_minute: 30
|
|
45
|
+
requests_per_hour: 1800
|
|
46
|
+
requests_per_day: 14400
|
|
47
|
+
tokens_per_minute: 15000
|
|
48
|
+
tokens_per_hour: 900000
|
|
49
|
+
|
|
50
|
+
gemma-3-27b-it:
|
|
51
|
+
requests_per_minute: 30
|
|
52
|
+
requests_per_hour: 1800
|
|
53
|
+
requests_per_day: 14400
|
|
54
|
+
tokens_per_minute: 15000
|
|
55
|
+
tokens_per_hour: 900000
|
|
56
|
+
|
|
57
|
+
gemma-3-2b-it:
|
|
58
|
+
requests_per_minute: 30
|
|
59
|
+
requests_per_hour: 1800
|
|
60
|
+
requests_per_day: 14400
|
|
61
|
+
tokens_per_minute: 15000
|
|
62
|
+
tokens_per_hour: 900000
|
|
63
|
+
|
|
64
|
+
gemma-3-4b-it:
|
|
65
|
+
requests_per_minute: 30
|
|
66
|
+
requests_per_hour: 1800
|
|
67
|
+
requests_per_day: 14400
|
|
68
|
+
tokens_per_minute: 15000
|
|
69
|
+
tokens_per_hour: 900000
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
allam-2-7b:
|
|
2
|
+
requests_per_minute: 30
|
|
3
|
+
requests_per_hour: 1800
|
|
4
|
+
requests_per_day: 7000
|
|
5
|
+
tokens_per_minute: 6000
|
|
6
|
+
tokens_per_hour: 360000
|
|
7
|
+
tokens_per_day: 500000
|
|
8
|
+
|
|
9
|
+
canopylabs/orpheus-arabic-saudi:
|
|
10
|
+
requests_per_minute: 10
|
|
11
|
+
requests_per_hour: 100
|
|
12
|
+
requests_per_day: 1200
|
|
13
|
+
tokens_per_minute: 3600
|
|
14
|
+
|
|
15
|
+
canopylabs/orpheus-v1-english:
|
|
16
|
+
requests_per_minute: 10
|
|
17
|
+
requests_per_hour: 100
|
|
18
|
+
requests_per_day: 1200
|
|
19
|
+
tokens_per_minute: 3600
|
|
20
|
+
|
|
21
|
+
groq/compound:
|
|
22
|
+
requests_per_minute: 30
|
|
23
|
+
requests_per_hour: 250
|
|
24
|
+
requests_per_day: 250
|
|
25
|
+
tokens_per_minute: 70000
|
|
26
|
+
|
|
27
|
+
groq/compound-mini:
|
|
28
|
+
requests_per_minute: 30
|
|
29
|
+
requests_per_hour: 250
|
|
30
|
+
requests_per_day: 250
|
|
31
|
+
tokens_per_minute: 70000
|
|
32
|
+
|
|
33
|
+
llama-3.1-8b-instant:
|
|
34
|
+
requests_per_minute: 30
|
|
35
|
+
requests_per_hour: 1800
|
|
36
|
+
requests_per_day: 14400
|
|
37
|
+
tokens_per_minute: 6000
|
|
38
|
+
tokens_per_hour: 360000
|
|
39
|
+
tokens_per_day: 500000
|
|
40
|
+
|
|
41
|
+
llama-3.3-70b-versatile:
|
|
42
|
+
requests_per_minute: 30
|
|
43
|
+
requests_per_hour: 1000
|
|
44
|
+
requests_per_day: 1000
|
|
45
|
+
tokens_per_minute: 12000
|
|
46
|
+
tokens_per_hour: 720000
|
|
47
|
+
tokens_per_day: 100000
|
|
48
|
+
|
|
49
|
+
meta-llama/llama-4-maverick-17b-128e-instruct:
|
|
50
|
+
requests_per_minute: 30
|
|
51
|
+
requests_per_hour: 1000
|
|
52
|
+
requests_per_day: 1000
|
|
53
|
+
tokens_per_minute: 6000
|
|
54
|
+
tokens_per_hour: 360000
|
|
55
|
+
tokens_per_day: 500000
|
|
56
|
+
|
|
57
|
+
meta-llama/llama-4-scout-17b-16e-instruct:
|
|
58
|
+
requests_per_minute: 30
|
|
59
|
+
requests_per_hour: 1000
|
|
60
|
+
requests_per_day: 1000
|
|
61
|
+
tokens_per_minute: 30000
|
|
62
|
+
tokens_per_hour: 1800000
|
|
63
|
+
tokens_per_day: 500000
|
|
64
|
+
|
|
65
|
+
meta-llama/llama-guard-4-12b:
|
|
66
|
+
requests_per_minute: 30
|
|
67
|
+
requests_per_hour: 1800
|
|
68
|
+
requests_per_day: 14400
|
|
69
|
+
tokens_per_minute: 15000
|
|
70
|
+
tokens_per_hour: 900000
|
|
71
|
+
tokens_per_day: 500000
|
|
72
|
+
|
|
73
|
+
meta-llama/llama-prompt-guard-2-22m:
|
|
74
|
+
requests_per_minute: 30
|
|
75
|
+
requests_per_hour: 1800
|
|
76
|
+
requests_per_day: 14400
|
|
77
|
+
tokens_per_minute: 15000
|
|
78
|
+
tokens_per_hour: 900000
|
|
79
|
+
tokens_per_day: 500000
|
|
80
|
+
|
|
81
|
+
meta-llama/llama-prompt-guard-2-86m:
|
|
82
|
+
requests_per_minute: 30
|
|
83
|
+
requests_per_hour: 1800
|
|
84
|
+
requests_per_day: 14400
|
|
85
|
+
tokens_per_minute: 15000
|
|
86
|
+
tokens_per_hour: 900000
|
|
87
|
+
tokens_per_day: 500000
|
|
88
|
+
|
|
89
|
+
moonshotai/kimi-k2-instruct:
|
|
90
|
+
requests_per_minute: 60
|
|
91
|
+
requests_per_hour: 1000
|
|
92
|
+
requests_per_day: 1000
|
|
93
|
+
tokens_per_minute: 10000
|
|
94
|
+
tokens_per_hour: 600000
|
|
95
|
+
tokens_per_day: 300000
|
|
96
|
+
|
|
97
|
+
moonshotai/kimi-k2-instruct-0905:
|
|
98
|
+
requests_per_minute: 60
|
|
99
|
+
requests_per_hour: 1000
|
|
100
|
+
requests_per_day: 1000
|
|
101
|
+
tokens_per_minute: 10000
|
|
102
|
+
tokens_per_hour: 600000
|
|
103
|
+
tokens_per_day: 300000
|
|
104
|
+
|
|
105
|
+
openai/gpt-oss-120b:
|
|
106
|
+
requests_per_minute: 30
|
|
107
|
+
requests_per_hour: 1000
|
|
108
|
+
requests_per_day: 1000
|
|
109
|
+
tokens_per_minute: 8000
|
|
110
|
+
tokens_per_hour: 480000
|
|
111
|
+
tokens_per_day: 200000
|
|
112
|
+
|
|
113
|
+
openai/gpt-oss-20b:
|
|
114
|
+
requests_per_minute: 30
|
|
115
|
+
requests_per_hour: 1000
|
|
116
|
+
requests_per_day: 1000
|
|
117
|
+
tokens_per_minute: 8000
|
|
118
|
+
tokens_per_hour: 480000
|
|
119
|
+
tokens_per_day: 200000
|
|
120
|
+
|
|
121
|
+
openai/gpt-oss-safeguard-20b:
|
|
122
|
+
requests_per_minute: 30
|
|
123
|
+
requests_per_hour: 1000
|
|
124
|
+
requests_per_day: 1000
|
|
125
|
+
tokens_per_minute: 8000
|
|
126
|
+
tokens_per_hour: 480000
|
|
127
|
+
tokens_per_day: 200000
|
|
128
|
+
|
|
129
|
+
playai-tts:
|
|
130
|
+
requests_per_minute: 10
|
|
131
|
+
requests_per_hour: 100
|
|
132
|
+
requests_per_day: 100
|
|
133
|
+
tokens_per_minute: 1200
|
|
134
|
+
tokens_per_hour: 72000
|
|
135
|
+
tokens_per_day: 3600
|
|
136
|
+
|
|
137
|
+
playai-tts-arabic:
|
|
138
|
+
requests_per_minute: 10
|
|
139
|
+
requests_per_hour: 100
|
|
140
|
+
requests_per_day: 100
|
|
141
|
+
tokens_per_minute: 1200
|
|
142
|
+
tokens_per_hour: 72000
|
|
143
|
+
tokens_per_day: 3600
|
|
144
|
+
|
|
145
|
+
qwen/qwen3-32b:
|
|
146
|
+
requests_per_minute: 60
|
|
147
|
+
requests_per_hour: 1000
|
|
148
|
+
requests_per_day: 1000
|
|
149
|
+
tokens_per_minute: 6000
|
|
150
|
+
tokens_per_hour: 360000
|
|
151
|
+
tokens_per_day: 500000
|
|
152
|
+
|
|
153
|
+
whisper-large-v3:
|
|
154
|
+
requests_per_minute: 20
|
|
155
|
+
requests_per_hour: 2000
|
|
156
|
+
requests_per_day: 2000
|
|
157
|
+
|
|
158
|
+
whisper-large-v3-turbo:
|
|
159
|
+
requests_per_minute: 20
|
|
160
|
+
requests_per_hour: 2000
|
|
161
|
+
requests_per_day: 2000
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Mistral API rate limits (dummy high limits, same posture as moonshot.yaml)
|
|
2
|
+
# Base URL: https://api.mistral.ai/v1 (OpenAI-compatible chat completions)
|
|
3
|
+
# Not independently researched against Mistral's published tier limits yet --
|
|
4
|
+
# a single key (NUM_MISTRAL=1, no rotation benefit) makes local throttling
|
|
5
|
+
# low-value for now; Mistral's own 429s still surface and rotate normally.
|
|
6
|
+
|
|
7
|
+
default:
|
|
8
|
+
requests_per_minute: 10000
|
|
9
|
+
requests_per_hour: 100000
|
|
10
|
+
requests_per_day: 1000000
|
|
11
|
+
tokens_per_minute: 10000000
|
|
12
|
+
tokens_per_hour: 100000000
|
|
13
|
+
tokens_per_day: 1000000000
|
|
@@ -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
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
|
|
2
|
+
- name: MiMo-V2-Flash
|
|
3
|
+
id: xiaomi/mimo-v2-flash:free
|
|
4
|
+
max_context_length: 262000
|
|
5
|
+
- name: Devstral 2 2512
|
|
6
|
+
id: mistralai/devstral-2512:free
|
|
7
|
+
max_context_length: 262000
|
|
8
|
+
- name: DeepSeek R1T2 Chimera
|
|
9
|
+
id: tngtech/deepseek-r1t2-chimera:free
|
|
10
|
+
max_context_length: 164000
|
|
11
|
+
- name: DeepSeek V3.1 Nex N1
|
|
12
|
+
id: deepseek-v3.1-nex-n1
|
|
13
|
+
max_context_length: 131000
|
|
14
|
+
- name: DeepSeek R1T Chimera
|
|
15
|
+
id: tngtech/deepseek-r1t-chimera:free
|
|
16
|
+
max_context_length: 164000
|
|
17
|
+
- name: Qwen3 Coder 480B A35B
|
|
18
|
+
id: qwen/qwen3-coder:free
|
|
19
|
+
max_context_length: 262000
|
|
20
|
+
- name: Nemotron 3 Nano 30B A3B
|
|
21
|
+
id: nvidia/nemotron-3-nano-30b-a3b:free
|
|
22
|
+
max_context_length: 256000
|
|
23
|
+
- name: Nemotron Nano 12B 2 VL
|
|
24
|
+
id: nvidia/nemotron-nano-12b-v2-vl:free
|
|
25
|
+
max_context_length: 128000
|
|
26
|
+
- name: GLM 4.5 Air
|
|
27
|
+
id: z-ai/glm-4.5-air:free
|
|
28
|
+
max_context_length: 131000
|
|
29
|
+
- name: DeepSeek R1 0528
|
|
30
|
+
id: deepseek/deepseek-r1-0528:free
|
|
31
|
+
max_context_length: 164000
|
|
32
|
+
- name: TNG R1T Chimera
|
|
33
|
+
id: tngtech/tng-r1t-chimera:free
|
|
34
|
+
max_context_length: 164000
|
|
35
|
+
- name: Gemma 3 27B
|
|
36
|
+
id: google/gemma-3-27b-it:free
|
|
37
|
+
max_context_length: 131000
|
|
38
|
+
- name: Llama 3.3 70B Instruct
|
|
39
|
+
id: meta-llama/llama-3.3-70b-instruct:free
|
|
40
|
+
max_context_length: 131000
|
|
41
|
+
- name: Venice Uncensored
|
|
42
|
+
id: cognitivecomputations/dolphin-mistral-24b-venice-edition:free
|
|
43
|
+
max_context_length: 33000
|
|
44
|
+
- name: Gemini 2.0 Flash Experimental
|
|
45
|
+
id: google/gemini-2.0-flash-exp:free
|
|
46
|
+
max_context_length: 1050000
|
|
47
|
+
- name: gpt-oss-120b
|
|
48
|
+
id: openai/gpt-oss-120b:free
|
|
49
|
+
max_context_length: 131000
|
|
50
|
+
- name: Hermes 3 405B Instruct
|
|
51
|
+
id: nousresearch/hermes-3-llama-3.1-405b:free
|
|
52
|
+
max_context_length: 131000
|
|
53
|
+
- name: Mistral 7B Instruct
|
|
54
|
+
id: mistralai/mistral-7b-instruct:free
|
|
55
|
+
max_context_length: 33000
|
|
56
|
+
- name: gpt-oss-20b
|
|
57
|
+
id: openai/gpt-oss-20b:free
|
|
58
|
+
max_context_length: 131000
|
|
59
|
+
- name: Mistral Small 3.1 24B
|
|
60
|
+
id: mistralai/mistral-small-3.1-24b-instruct:free
|
|
61
|
+
max_context_length: 128000
|
|
62
|
+
- name: Llama 3.1 405B Instruct
|
|
63
|
+
id: meta-llama/llama-3.1-405b-instruct:free
|
|
64
|
+
max_context_length: 131000
|
|
65
|
+
- name: Nemotron Nano 9B V2
|
|
66
|
+
id: nvidia/nemotron-nano-9b-v2:free
|
|
67
|
+
max_context_length: 128000
|
|
68
|
+
- name: Trinity Mini
|
|
69
|
+
id: arcee-ai/trinity-mini:free
|
|
70
|
+
max_context_length: 131000
|
|
71
|
+
- name: Llama 3.2 3B Instruct
|
|
72
|
+
id: meta-llama/llama-3.2-3b-instruct:free
|
|
73
|
+
max_context_length: 131000
|
|
74
|
+
- name: Qwen3 4B
|
|
75
|
+
id: qwen/qwen3-4b:free
|
|
76
|
+
max_context_length: 41000
|
|
77
|
+
- name: Qwen2.5-VL 7B Instruct
|
|
78
|
+
id: qwen/qwen-2.5-vl-7b-instruct:free
|
|
79
|
+
max_context_length: 33000
|
|
80
|
+
- name: Gemma 3n 2B
|
|
81
|
+
id: google/gemma-3n-e2b-it:free
|
|
82
|
+
max_context_length: 8000
|
|
83
|
+
- name: Gemma 3 4B
|
|
84
|
+
id: google/gemma-3-4b-it:free
|
|
85
|
+
max_context_length: 33000
|
|
86
|
+
- name: Gemma 3 12B
|
|
87
|
+
id: google/gemma-3-12b-it:free
|
|
88
|
+
max_context_length: 33000
|
|
89
|
+
- name: Gemma 3n 4B
|
|
90
|
+
id: google/gemma-3n-e4b-it:free
|
|
91
|
+
max_context_length: 8000
|
|
92
|
+
- name: AllenAI Molmo2 8b
|
|
93
|
+
id: allenai/molmo-2-8b:free
|
|
94
|
+
max_context_length: 37000
|
|
95
|
+
- name: Kimi K2 0711
|
|
96
|
+
id: moonshotai/kimi-k2:free
|
|
97
|
+
max_context_length: 33000
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from .dataclasses import RateLimits
|
|
3
|
+
from .enums import RateLimitStrategy
|
|
4
|
+
from typing import Any, TypedDict
|
|
5
|
+
from .loader import load_rate_limits_from_yaml, load_openrouter_models
|
|
6
|
+
|
|
7
|
+
# Get directory of this file
|
|
8
|
+
CONFIG_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
9
|
+
MODELS_DIR = os.path.join(CONFIG_DIR, 'models')
|
|
10
|
+
|
|
11
|
+
class ModelDict(TypedDict):
|
|
12
|
+
"""All the provider currrently supported"""
|
|
13
|
+
cerebras: Any
|
|
14
|
+
groq: Any
|
|
15
|
+
gemini: Any
|
|
16
|
+
openrouter: Any
|
|
17
|
+
cohere: Any
|
|
18
|
+
moonshot: Any
|
|
19
|
+
mistral: Any
|
|
20
|
+
|
|
21
|
+
PROVIDER_STRATEGIES: ModelDict = {
|
|
22
|
+
'cerebras': RateLimitStrategy.PER_MODEL,
|
|
23
|
+
'groq': RateLimitStrategy.PER_MODEL,
|
|
24
|
+
'gemini': RateLimitStrategy.PER_MODEL,
|
|
25
|
+
'openrouter': RateLimitStrategy.GLOBAL,
|
|
26
|
+
'cohere': RateLimitStrategy.PER_MODEL,
|
|
27
|
+
'moonshot': RateLimitStrategy.PER_MODEL,
|
|
28
|
+
'mistral': RateLimitStrategy.PER_MODEL
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Load Cohere tiers first to handle the env var logic
|
|
32
|
+
_cohere_tiers = load_rate_limits_from_yaml(os.path.join(MODELS_DIR, 'cohere.yaml'))
|
|
33
|
+
|
|
34
|
+
COHERE_TIERS = {
|
|
35
|
+
'free': _cohere_tiers['free'],
|
|
36
|
+
'pro': _cohere_tiers['pro'],
|
|
37
|
+
'enterprise': _cohere_tiers['enterprise'],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
MODEL_LIMITS: ModelDict = {
|
|
41
|
+
'cerebras': load_rate_limits_from_yaml(os.path.join(MODELS_DIR, 'cerebras.yaml')),
|
|
42
|
+
'groq': load_rate_limits_from_yaml(os.path.join(MODELS_DIR, 'groq.yaml')),
|
|
43
|
+
'gemini': load_rate_limits_from_yaml(os.path.join(MODELS_DIR, 'gemini.yaml')),
|
|
44
|
+
'openrouter': load_rate_limits_from_yaml(os.path.join(MODELS_DIR, 'openrouter.yaml')),
|
|
45
|
+
'cohere':{
|
|
46
|
+
# Same for every model
|
|
47
|
+
'default': COHERE_TIERS.get(os.getenv('COHERE_TIER', 'free').lower(), COHERE_TIERS['free'])
|
|
48
|
+
},
|
|
49
|
+
'moonshot': load_rate_limits_from_yaml(os.path.join(MODELS_DIR, 'moonshot.yaml')),
|
|
50
|
+
'mistral': load_rate_limits_from_yaml(os.path.join(MODELS_DIR, 'mistral.yaml'))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
OPENROUTER_MODELS = load_openrouter_models(os.path.join(MODELS_DIR, 'openrouter_models.yaml'))
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Core utilities and exceptions for keycycle."""
|
|
2
|
+
from .exceptions import (
|
|
3
|
+
KeycycleError,
|
|
4
|
+
KeycycleKeyError,
|
|
5
|
+
NoAvailableKeyError,
|
|
6
|
+
KeyNotFoundError,
|
|
7
|
+
InvalidKeyError,
|
|
8
|
+
RateLimitError,
|
|
9
|
+
ConfigurationError,
|
|
10
|
+
MissingEnvironmentVariableError,
|
|
11
|
+
InvalidConfigurationError,
|
|
12
|
+
)
|
|
13
|
+
from .utils import (
|
|
14
|
+
get_key_suffix,
|
|
15
|
+
is_rate_limit_error,
|
|
16
|
+
is_auth_error,
|
|
17
|
+
validate_api_key,
|
|
18
|
+
)
|
|
19
|
+
from .backoff import ExponentialBackoff, BackoffConfig
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
# Exceptions
|
|
23
|
+
"KeycycleError",
|
|
24
|
+
"KeycycleKeyError",
|
|
25
|
+
"NoAvailableKeyError",
|
|
26
|
+
"KeyNotFoundError",
|
|
27
|
+
"InvalidKeyError",
|
|
28
|
+
"RateLimitError",
|
|
29
|
+
"ConfigurationError",
|
|
30
|
+
"MissingEnvironmentVariableError",
|
|
31
|
+
"InvalidConfigurationError",
|
|
32
|
+
# Utilities
|
|
33
|
+
"get_key_suffix",
|
|
34
|
+
"is_rate_limit_error",
|
|
35
|
+
"is_auth_error",
|
|
36
|
+
"validate_api_key",
|
|
37
|
+
# Backoff
|
|
38
|
+
"ExponentialBackoff",
|
|
39
|
+
"BackoffConfig",
|
|
40
|
+
]
|