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.
@@ -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,4 @@
1
+ default:
2
+ requests_per_minute: 20
3
+ requests_per_hour: 50
4
+ requests_per_day: 50
@@ -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
+ ]