code-puppy 0.0.143__py3-none-any.whl → 0.0.145__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.
- code_puppy/model_factory.py +4 -1
- code_puppy/round_robin_model.py +39 -7
- {code_puppy-0.0.143.dist-info → code_puppy-0.0.145.dist-info}/METADATA +66 -1
- {code_puppy-0.0.143.dist-info → code_puppy-0.0.145.dist-info}/RECORD +8 -8
- {code_puppy-0.0.143.data → code_puppy-0.0.145.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.143.dist-info → code_puppy-0.0.145.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.143.dist-info → code_puppy-0.0.145.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.143.dist-info → code_puppy-0.0.145.dist-info}/licenses/LICENSE +0 -0
code_puppy/model_factory.py
CHANGED
|
@@ -255,6 +255,9 @@ class ModelFactory:
|
|
|
255
255
|
if not model_names or not isinstance(model_names, list):
|
|
256
256
|
raise ValueError(f"Round-robin model '{model_name}' requires a 'models' list in its configuration.")
|
|
257
257
|
|
|
258
|
+
# Get the rotate_every parameter (default: 1)
|
|
259
|
+
rotate_every = model_config.get("rotate_every", 1)
|
|
260
|
+
|
|
258
261
|
# Resolve each model name to an actual model instance
|
|
259
262
|
models = []
|
|
260
263
|
for name in model_names:
|
|
@@ -263,7 +266,7 @@ class ModelFactory:
|
|
|
263
266
|
models.append(model)
|
|
264
267
|
|
|
265
268
|
# Create and return the round-robin model
|
|
266
|
-
return RoundRobinModel(*models)
|
|
269
|
+
return RoundRobinModel(*models, rotate_every=rotate_every)
|
|
267
270
|
|
|
268
271
|
else:
|
|
269
272
|
raise ValueError(f"Unsupported model type: {model_type}")
|
code_puppy/round_robin_model.py
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
from contextlib import asynccontextmanager, suppress
|
|
1
3
|
from dataclasses import dataclass, field
|
|
2
4
|
from typing import Any, Callable, AsyncIterator, List
|
|
3
|
-
|
|
5
|
+
|
|
4
6
|
from pydantic_ai.models import Model, ModelMessage, ModelSettings, ModelRequestParameters, ModelResponse, StreamedResponse
|
|
5
7
|
from pydantic_ai.models.fallback import KnownModelName, infer_model, merge_model_settings
|
|
6
8
|
from pydantic_ai.result import RunContext
|
|
7
9
|
|
|
10
|
+
try:
|
|
11
|
+
from opentelemetry.context import get_current_span
|
|
12
|
+
except ImportError:
|
|
13
|
+
# If opentelemetry is not installed, provide a dummy implementation
|
|
14
|
+
def get_current_span():
|
|
15
|
+
class DummySpan:
|
|
16
|
+
def is_recording(self):
|
|
17
|
+
return False
|
|
18
|
+
def set_attributes(self, attributes):
|
|
19
|
+
pass
|
|
20
|
+
return DummySpan()
|
|
21
|
+
|
|
8
22
|
@dataclass(init=False)
|
|
9
23
|
class RoundRobinModel(Model):
|
|
10
24
|
"""A model that cycles through multiple models in a round-robin fashion.
|
|
@@ -16,26 +30,39 @@ class RoundRobinModel(Model):
|
|
|
16
30
|
models: List[Model]
|
|
17
31
|
_current_index: int = field(default=0, repr=False)
|
|
18
32
|
_model_name: str = field(repr=False)
|
|
33
|
+
_rotate_every: int = field(default=1, repr=False)
|
|
34
|
+
_request_count: int = field(default=0, repr=False)
|
|
19
35
|
|
|
20
36
|
def __init__(
|
|
21
37
|
self,
|
|
22
|
-
*models: Model
|
|
38
|
+
*models: Model,
|
|
39
|
+
rotate_every: int = 1,
|
|
40
|
+
settings: ModelSettings | None = None
|
|
23
41
|
):
|
|
24
42
|
"""Initialize a round-robin model instance.
|
|
25
43
|
|
|
26
44
|
Args:
|
|
27
|
-
models: The
|
|
45
|
+
models: The model instances to cycle through.
|
|
46
|
+
rotate_every: Number of requests before rotating to the next model (default: 1).
|
|
47
|
+
settings: Model settings that will be used as defaults for this model.
|
|
28
48
|
"""
|
|
29
|
-
super().__init__()
|
|
49
|
+
super().__init__(settings=settings)
|
|
30
50
|
if not models:
|
|
31
51
|
raise ValueError("At least one model must be provided")
|
|
32
|
-
|
|
52
|
+
if rotate_every < 1:
|
|
53
|
+
raise ValueError("rotate_every must be at least 1")
|
|
54
|
+
self.models = list(models)
|
|
33
55
|
self._current_index = 0
|
|
56
|
+
self._request_count = 0
|
|
57
|
+
self._rotate_every = rotate_every
|
|
34
58
|
|
|
35
59
|
@property
|
|
36
60
|
def model_name(self) -> str:
|
|
37
61
|
"""The model name showing this is a round-robin model with its candidates."""
|
|
38
|
-
|
|
62
|
+
base_name = f'round_robin:{",".join(model.model_name for model in self.models)}'
|
|
63
|
+
if self._rotate_every != 1:
|
|
64
|
+
return f'{base_name}:rotate_every={self._rotate_every}'
|
|
65
|
+
return base_name
|
|
39
66
|
|
|
40
67
|
@property
|
|
41
68
|
def system(self) -> str:
|
|
@@ -50,7 +77,10 @@ class RoundRobinModel(Model):
|
|
|
50
77
|
def _get_next_model(self) -> Model:
|
|
51
78
|
"""Get the next model in the round-robin sequence and update the index."""
|
|
52
79
|
model = self.models[self._current_index]
|
|
53
|
-
self.
|
|
80
|
+
self._request_count += 1
|
|
81
|
+
if self._request_count >= self._rotate_every:
|
|
82
|
+
self._current_index = (self._current_index + 1) % len(self.models)
|
|
83
|
+
self._request_count = 0
|
|
54
84
|
return model
|
|
55
85
|
|
|
56
86
|
async def request(
|
|
@@ -61,6 +91,7 @@ class RoundRobinModel(Model):
|
|
|
61
91
|
) -> ModelResponse:
|
|
62
92
|
"""Make a request using the next model in the round-robin sequence."""
|
|
63
93
|
current_model = self._get_next_model()
|
|
94
|
+
# Use the current model's settings as base, then merge with provided settings
|
|
64
95
|
merged_settings = merge_model_settings(current_model.settings, model_settings)
|
|
65
96
|
customized_model_request_parameters = current_model.customize_request_parameters(model_request_parameters)
|
|
66
97
|
|
|
@@ -83,6 +114,7 @@ class RoundRobinModel(Model):
|
|
|
83
114
|
) -> AsyncIterator[StreamedResponse]:
|
|
84
115
|
"""Make a streaming request using the next model in the round-robin sequence."""
|
|
85
116
|
current_model = self._get_next_model()
|
|
117
|
+
# Use the current model's settings as base, then merge with provided settings
|
|
86
118
|
merged_settings = merge_model_settings(current_model.settings, model_settings)
|
|
87
119
|
customized_model_request_parameters = current_model.customize_request_parameters(model_request_parameters)
|
|
88
120
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-puppy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.145
|
|
4
4
|
Summary: Code generation agent
|
|
5
5
|
Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
|
|
6
6
|
Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
|
|
@@ -189,6 +189,71 @@ If you need to run more exotic setups or connect to remote MCPs, just update you
|
|
|
189
189
|
|
|
190
190
|
---
|
|
191
191
|
|
|
192
|
+
## Round Robin Model Distribution
|
|
193
|
+
|
|
194
|
+
Code Puppy supports **Round Robin model distribution** to help you overcome rate limits and distribute load across multiple AI models. This feature automatically cycles through configured models with each request, maximizing your API usage while staying within rate limits.
|
|
195
|
+
|
|
196
|
+
### Configuration
|
|
197
|
+
Add a round-robin model configuration to your `extra_models.json` file:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
export CEREBRAS_API_KEY1=csk-...
|
|
201
|
+
export CEREBRAS_API_KEY2=csk-...
|
|
202
|
+
export CEREBRAS_API_KEY3=csk-...
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"qwen1": {
|
|
209
|
+
"type": "cerebras",
|
|
210
|
+
"name": "qwen-3-coder-480b",
|
|
211
|
+
"custom_endpoint": {
|
|
212
|
+
"url": "https://api.cerebras.ai/v1",
|
|
213
|
+
"api_key": "$CEREBRAS_API_KEY1"
|
|
214
|
+
},
|
|
215
|
+
"context_length": 131072
|
|
216
|
+
},
|
|
217
|
+
"qwen2": {
|
|
218
|
+
"type": "cerebras",
|
|
219
|
+
"name": "qwen-3-coder-480b",
|
|
220
|
+
"custom_endpoint": {
|
|
221
|
+
"url": "https://api.cerebras.ai/v1",
|
|
222
|
+
"api_key": "$CEREBRAS_API_KEY2"
|
|
223
|
+
},
|
|
224
|
+
"context_length": 131072
|
|
225
|
+
},
|
|
226
|
+
"qwen3": {
|
|
227
|
+
"type": "cerebras",
|
|
228
|
+
"name": "qwen-3-coder-480b",
|
|
229
|
+
"custom_endpoint": {
|
|
230
|
+
"url": "https://api.cerebras.ai/v1",
|
|
231
|
+
"api_key": "$CEREBRAS_API_KEY3"
|
|
232
|
+
},
|
|
233
|
+
"context_length": 131072
|
|
234
|
+
},
|
|
235
|
+
"cerebras_round_robin": {
|
|
236
|
+
"type": "round_robin",
|
|
237
|
+
"models": ["qwen1", "qwen2", "qwen3"],
|
|
238
|
+
"rotate_every": 5
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Then just use /model and tab to select your round-robin model!
|
|
244
|
+
|
|
245
|
+
The `rotate_every` parameter controls how many requests are made to each model before rotating to the next one. In this example, the round-robin model will use each Qwen model for 5 consecutive requests before moving to the next model in the sequence.
|
|
246
|
+
|
|
247
|
+
### Benefits
|
|
248
|
+
- **Rate Limit Protection**: Automatically distribute requests across multiple models
|
|
249
|
+
- **Load Balancing**: Share workload between different model providers
|
|
250
|
+
- **Fallback Resilience**: Continue working even if one model has temporary issues
|
|
251
|
+
- **Cost Optimization**: Use different models for different types of tasks
|
|
252
|
+
|
|
253
|
+
**NOTE:** Unlike fallback models, round-robin models distribute load but don't automatically retry with another model on failure. If a request fails, it will raise the exception directly.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
192
257
|
## Create your own Agent!!!
|
|
193
258
|
|
|
194
259
|
Code Puppy features a flexible agent system that allows you to work with specialized AI assistants tailored for different coding tasks. The system supports both built-in Python agents and custom JSON agents that you can create yourself.
|
|
@@ -6,10 +6,10 @@ code_puppy/config.py,sha256=9yWKHKjLJ2Ddl4frrBI9VRIwPvoWpIx1fAd1YpAvOSQ,15330
|
|
|
6
6
|
code_puppy/http_utils.py,sha256=BAvt4hed7fVMXglA7eS9gOb08h2YTuOyai6VmQq09fg,3432
|
|
7
7
|
code_puppy/main.py,sha256=Vv5HSJnkgZhCvvOoXrJ2zqM5P-i47-RcYAU00Z1Pfx0,21733
|
|
8
8
|
code_puppy/message_history_processor.py,sha256=O2rKp7W6YeIg93W8b0XySTUEQgIZm0f_06--_kzHugM,16145
|
|
9
|
-
code_puppy/model_factory.py,sha256
|
|
9
|
+
code_puppy/model_factory.py,sha256=-_0kJ7qYmRTDk0v3mfp-UrYf0IaDLyG8IMBweDECwJg,11917
|
|
10
10
|
code_puppy/models.json,sha256=dAfpMMI2EEeOMv0ynHSmMuJAYDLcZrs5gCLX3voC4-A,3252
|
|
11
11
|
code_puppy/reopenable_async_client.py,sha256=4UJRaMp5np8cbef9F0zKQ7TPKOfyf5U-Kv-0zYUWDho,8274
|
|
12
|
-
code_puppy/round_robin_model.py,sha256=
|
|
12
|
+
code_puppy/round_robin_model.py,sha256=VWGCbK0-0rLpnrdxlmND5JsKXiNZZWFMIjF4_OL3STE,5594
|
|
13
13
|
code_puppy/state_management.py,sha256=o4mNBCPblRyVrNBH-992-1YqffgH6AKHU7iZRqgP1LI,5925
|
|
14
14
|
code_puppy/status_display.py,sha256=F6eEAkGePDp4StM2BWj-uLLQTDGtJrf0IufzCeP1rRg,8336
|
|
15
15
|
code_puppy/summarization_agent.py,sha256=-e6yUGZ22ahSaF0y7QhgVcQBfx5ktNUkPxBIWQfPaA4,3275
|
|
@@ -126,9 +126,9 @@ code_puppy/tui/tests/test_sidebar_history_navigation.py,sha256=JGiyua8A2B8dLfwiE
|
|
|
126
126
|
code_puppy/tui/tests/test_status_bar.py,sha256=nYT_FZGdmqnnbn6o0ZuOkLtNUtJzLSmtX8P72liQ5Vo,1797
|
|
127
127
|
code_puppy/tui/tests/test_timestamped_history.py,sha256=nVXt9hExZZ_8MFP-AZj4L4bB_1Eo_mc-ZhVICzTuw3I,1799
|
|
128
128
|
code_puppy/tui/tests/test_tools.py,sha256=kgzzAkK4r0DPzQwHHD4cePpVNgrHor6cFr05Pg6DBWg,2687
|
|
129
|
-
code_puppy-0.0.
|
|
130
|
-
code_puppy-0.0.
|
|
131
|
-
code_puppy-0.0.
|
|
132
|
-
code_puppy-0.0.
|
|
133
|
-
code_puppy-0.0.
|
|
134
|
-
code_puppy-0.0.
|
|
129
|
+
code_puppy-0.0.145.data/data/code_puppy/models.json,sha256=dAfpMMI2EEeOMv0ynHSmMuJAYDLcZrs5gCLX3voC4-A,3252
|
|
130
|
+
code_puppy-0.0.145.dist-info/METADATA,sha256=i8WX2P4dcHB7_rGZGug23IafcwjtNPYsy1rcSbsKm9o,22019
|
|
131
|
+
code_puppy-0.0.145.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
132
|
+
code_puppy-0.0.145.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
|
|
133
|
+
code_puppy-0.0.145.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
|
134
|
+
code_puppy-0.0.145.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|