Skip to content

Commit ecbdbb2

Browse files
committed
✨ keypool logic
1 parent e4fb2bb commit ecbdbb2

File tree

12 files changed

+565
-42
lines changed

12 files changed

+565
-42
lines changed

README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,16 @@ $ python run.py
9191

9292
| Provider | Models (Assorted) | Execution | Rating |
9393
|--------------------------------|-----------------------------------------------|-------------|---------|
94-
| Anthropic (Coming Soon) | To be specified shortly | API | None |
95-
| Cerebras (Recommended) | To be specified shortly | API | None |
96-
| TPEOficial (Coming Soon) | To be specified shortly | API | None |
97-
| Google (Coming Soon) | To be specified shortly | API | None |
98-
| Groq (Recommended \| Default) | To be specified shortly | API | None |
99-
| Meta (Coming Soon) | To be specified shortly | API | None |
100-
| Ollama (Coming Soon) | To be specified shortly | Local | None |
101-
| OpenAI (Coming Soon) | To be specified shortly | API | None |
102-
| OpenRouter (Coming Soon) | To be specified shortly | API | None |
103-
| Perplexity (Coming Soon) | To be specified shortly | API | None |
94+
| Anthropic (Coming Soon) | Exec `/models` command | API | None |
95+
| Cerebras (Recommended) | Exec `/models` command | API | None |
96+
| TPEOficial (Coming Soon) | Exec `/models` command | API | None |
97+
| Google (Coming Soon) | Exec `/models` command | API | None |
98+
| Groq (Recommended \| Default) | Exec `/models` command | API | None |
99+
| Meta (Coming Soon) | Exec `/models` command | API | None |
100+
| Ollama (Coming Soon) | Exec `/models` command | Local | None |
101+
| OpenAI (Coming Soon) | Exec `/models` command | API | None |
102+
| OpenRouter (Coming Soon) | Exec `/models` command | API | None |
103+
| Perplexity (Coming Soon) | Exec `/models` command | API | None |
104104

105105
And coming soon...
106106

@@ -114,6 +114,8 @@ And coming soon...
114114
This system automatically consumes the different API keys and providers that you define in order to avoid consumption limitations from external providers.
115115

116116
You can view your API Keys by running the command `/apikeys`.
117+
118+
You can configure the logic of the Multi-Key Pool System using the `/keypool` command.
117119
</details>
118120

119121
<details>

build.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import platform
1616
from pathlib import Path
1717

18-
1918
def get_platform_info():
2019
"""Get current platform information"""
2120
system = platform.system().lower()
@@ -25,7 +24,6 @@ def get_platform_info():
2524
elif system == "darwin": return "macos", "", f"dymo-code-macos-{machine}"
2625
else: return "linux", "", f"dymo-code-linux-{machine}"
2726

28-
2927
def clean_build():
3028
"""Clean build artifacts"""
3129
project_root = Path(__file__).parent
@@ -46,7 +44,6 @@ def clean_build():
4644

4745
print("Clean complete!")
4846

49-
5047
def check_dependencies():
5148
"""Check if required build dependencies are installed"""
5249
try:
@@ -58,7 +55,6 @@ def check_dependencies():
5855
subprocess.check_call([sys.executable, "-m", "pip", "install", "pyinstaller"])
5956
return True
6057

61-
6258
def build():
6359
"""Build the executable"""
6460
project_root = Path(__file__).parent
@@ -70,10 +66,10 @@ def build():
7066
print(f"Building Dymo Code for {platform_name.upper()}")
7167
print(f"{'='*60}\n")
7268

73-
# Check dependencies
69+
# Check dependencies.
7470
check_dependencies()
7571

76-
# Run PyInstaller
72+
# Run PyInstaller.
7773
cmd = [
7874
sys.executable, "-m", "PyInstaller",
7975
"--clean",
@@ -108,7 +104,6 @@ def build():
108104
print(f"{'='*60}\n")
109105
else: print(f"\nWarning: Expected output file not found at {original}")
110106

111-
112107
def show_help():
113108
"""Show help message"""
114109
print(__doc__)
@@ -119,7 +114,6 @@ def show_help():
119114
print("\nNote: Cross-compilation is not supported.")
120115
print(" Build on each target platform separately.")
121116

122-
123117
def main():
124118
args = sys.argv[1:]
125119

@@ -137,5 +131,4 @@ def main():
137131

138132
build()
139133

140-
141134
if __name__ == "__main__": main()

docs/images/icon.ico

15.5 KB
Binary file not shown.

docs/images/logo.png

13.3 KB
Loading

dymo-code.spec

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ except ImportError:
2323
datas = [
2424
# Include version.json for update checking
2525
(str(project_root / 'static-api'), 'static-api'),
26+
# Include logo image for documentation
27+
(str(project_root / 'docs' / 'images' / 'logo.png'), 'docs/images'),
2628
]
2729

2830
# Add certifi certificates if available
@@ -132,5 +134,5 @@ exe = EXE(
132134
target_arch=None,
133135
codesign_identity=None,
134136
entitlements_file=None,
135-
icon=str(project_root / 'static-api' / 'icon.ico') if (project_root / 'static-api' / 'icon.ico').exists() else None,
137+
icon=str(project_root / 'docs' / 'images' / 'icon.ico') if (project_root / 'docs' / 'images' / 'icon.ico').exists() else None,
136138
)

src/agent.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,16 @@
2020
from .config import COLORS, AVAILABLE_MODELS, DEFAULT_MODEL, get_system_prompt, ModelProvider
2121
from .clients import ClientManager, StreamChunk, ToolCall, ExecutedTool
2222
from .lib.prompts import mode_manager
23-
from .api_key_manager import api_key_manager, is_rate_limit_error, is_credit_error
23+
from .api_key_manager import (
24+
api_key_manager, is_rate_limit_error, is_credit_error,
25+
set_rotation_callbacks, model_fallback_manager
26+
)
2427
from .tools import TOOL_DEFINITIONS, execute_tool, TOOLS, get_all_tool_definitions
25-
from .ui import console, display_tool_call, display_tool_result, display_executed_tool, display_code_execution_result, display_info, display_warning
28+
from .ui import (
29+
console, display_tool_call, display_tool_result, display_executed_tool,
30+
display_code_execution_result, display_info, display_warning,
31+
display_key_rotation_notice, display_model_fallback_notice, display_provider_exhausted_notice
32+
)
2633
from .logger import log_error, log_api_error, log_tool_error, log_debug
2734
from .history import history_manager
2835
from .name_detector import detect_and_save_name
@@ -202,11 +209,30 @@ def __init__(self, model_key: str = DEFAULT_MODEL):
202209
self._init_system_prompt()
203210
# Start a new conversation
204211
history_manager.start_new_conversation()
212+
# Set up rotation and fallback callbacks for user notifications
213+
self._setup_rotation_callbacks()
205214

206215
def set_status_callback(self, callback: StatusCallback):
207216
"""Set a callback function for status updates"""
208217
self._status_callback = callback
209218

219+
def _setup_rotation_callbacks(self):
220+
"""Set up callbacks for API key rotation and model fallback notifications"""
221+
def on_key_rotated(provider: str, old_key: str, new_key: str):
222+
display_key_rotation_notice(provider, "rate limit or quota exceeded")
223+
224+
def on_model_fallback(provider: str, old_model: str, new_model: str):
225+
display_model_fallback_notice(provider, old_model, new_model)
226+
227+
def on_provider_exhausted(provider: str):
228+
display_provider_exhausted_notice(provider)
229+
230+
set_rotation_callbacks(
231+
on_key_rotated=on_key_rotated,
232+
on_model_fallback=on_model_fallback,
233+
on_provider_exhausted=on_provider_exhausted
234+
)
235+
210236
def _update_status(self, status: str, detail: str = ""):
211237
"""Send a status update if callback is set"""
212238
if self._status_callback:
@@ -815,11 +841,31 @@ def chat(self, user_input: str, _retry_count: int = 0) -> str:
815841
self.messages.append({"role": "user", "content": user_input})
816842
return self.chat(user_input, _retry_count=_retry_count + 1)
817843

818-
# Check if this is a quota/rate limit error - try to switch provider
844+
# Check if this is a quota/rate limit error - try model fallback first, then provider switch
819845
current_provider = AVAILABLE_MODELS[self.model_key].provider.value
820846
if is_quota_or_rate_error(error_str):
821847
log_debug(f"Quota/rate error detected for {current_provider}")
822848

849+
# First, try to fallback to a simpler model within the same provider (if enabled)
850+
if model_fallback_manager.is_enabled() and _retry_count < 2:
851+
fallback_model_id = model_fallback_manager.get_fallback_model(current_provider, model_id)
852+
if fallback_model_id:
853+
# Find the model key for this fallback model
854+
for key, config in AVAILABLE_MODELS.items():
855+
if config.id == fallback_model_id and config.provider.value == current_provider:
856+
# Activate the fallback with notification
857+
model_fallback_manager.activate_fallback(
858+
current_provider,
859+
model_id,
860+
fallback_model_id,
861+
duration_minutes=5
862+
)
863+
old_model_key = self.model_key
864+
self.model_key = key
865+
log_debug(f"Model fallback: {model_id} -> {fallback_model_id}")
866+
# Retry with simpler model
867+
return self.chat(user_input, _retry_count=_retry_count + 1)
868+
823869
# Show friendly message
824870
friendly_msg = get_friendly_quota_message(current_provider)
825871
console.print()

0 commit comments

Comments
 (0)