Spaces:
Paused
Paused
Merge remote-tracking branch 'origin/feature/filtering-tool' into dev
Browse files- DOCUMENTATION.md +81 -0
- requirements.txt +6 -0
- src/proxy_app/build.py +12 -4
- src/proxy_app/model_filter_gui.py +0 -0
- src/proxy_app/settings_tool.py +29 -8
- src/rotator_library/client.py +33 -34
- src/rotator_library/pyproject.toml +2 -6
DOCUMENTATION.md
CHANGED
|
@@ -10,6 +10,7 @@ The project is a monorepo containing two primary components:
|
|
| 10 |
* **Batch Manager**: Optimizes high-volume embedding requests.
|
| 11 |
* **Detailed Logger**: Provides per-request file logging for debugging.
|
| 12 |
* **OpenAI-Compatible Endpoints**: `/v1/chat/completions`, `/v1/embeddings`, etc.
|
|
|
|
| 13 |
2. **The Resilience Library (`rotator_library`)**: This is the core engine that provides high availability. It is consumed by the proxy app to manage a pool of API keys, handle errors gracefully, and ensure requests are completed successfully even when individual keys or provider endpoints face issues.
|
| 14 |
|
| 15 |
This architecture cleanly separates the API interface from the resilience logic, making the library a portable and powerful tool for any application needing robust API key management.
|
|
@@ -1452,3 +1453,83 @@ stats = cache.get_stats()
|
|
| 1452 |
# Includes: {"disk_available": True, "disk_errors": 0, ...}
|
| 1453 |
```
|
| 1454 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
* **Batch Manager**: Optimizes high-volume embedding requests.
|
| 11 |
* **Detailed Logger**: Provides per-request file logging for debugging.
|
| 12 |
* **OpenAI-Compatible Endpoints**: `/v1/chat/completions`, `/v1/embeddings`, etc.
|
| 13 |
+
* **Model Filter GUI**: Visual interface for configuring model ignore/whitelist rules per provider (see Section 6).
|
| 14 |
2. **The Resilience Library (`rotator_library`)**: This is the core engine that provides high availability. It is consumed by the proxy app to manage a pool of API keys, handle errors gracefully, and ensure requests are completed successfully even when individual keys or provider endpoints face issues.
|
| 15 |
|
| 16 |
This architecture cleanly separates the API interface from the resilience logic, making the library a portable and powerful tool for any application needing robust API key management.
|
|
|
|
| 1453 |
# Includes: {"disk_available": True, "disk_errors": 0, ...}
|
| 1454 |
```
|
| 1455 |
|
| 1456 |
+
---
|
| 1457 |
+
|
| 1458 |
+
## 6. Model Filter GUI
|
| 1459 |
+
|
| 1460 |
+
The Model Filter GUI (`model_filter_gui.py`) provides a visual interface for configuring model ignore and whitelist rules per provider. It replaces the need to manually edit `IGNORE_MODELS_*` and `WHITELIST_MODELS_*` environment variables.
|
| 1461 |
+
|
| 1462 |
+
### 6.1. Overview
|
| 1463 |
+
|
| 1464 |
+
**Purpose**: Visually manage which models are exposed via the `/v1/models` endpoint for each provider.
|
| 1465 |
+
|
| 1466 |
+
**Launch**:
|
| 1467 |
+
```bash
|
| 1468 |
+
python -c "from src.proxy_app.model_filter_gui import run_model_filter_gui; run_model_filter_gui()"
|
| 1469 |
+
```
|
| 1470 |
+
|
| 1471 |
+
Or via the launcher TUI if integrated.
|
| 1472 |
+
|
| 1473 |
+
### 6.2. Features
|
| 1474 |
+
|
| 1475 |
+
#### Core Functionality
|
| 1476 |
+
|
| 1477 |
+
- **Provider Selection**: Dropdown to switch between available providers with automatic model fetching
|
| 1478 |
+
- **Ignore Rules**: Pattern-based rules (supports wildcards like `*-preview`, `gpt-4*`) to exclude models
|
| 1479 |
+
- **Whitelist Rules**: Pattern-based rules to explicitly include models, overriding ignore rules
|
| 1480 |
+
- **Real-time Preview**: Typing in rule input fields highlights affected models before committing
|
| 1481 |
+
- **Rule-Model Linking**: Click a model to highlight the affecting rule; click a rule to highlight all affected models
|
| 1482 |
+
- **Persistence**: Rules saved to `.env` file in standard `IGNORE_MODELS_<PROVIDER>` and `WHITELIST_MODELS_<PROVIDER>` format
|
| 1483 |
+
|
| 1484 |
+
#### Dual-Pane Model View
|
| 1485 |
+
|
| 1486 |
+
The interface displays two synchronized lists:
|
| 1487 |
+
|
| 1488 |
+
| Left Pane | Right Pane |
|
| 1489 |
+
|-----------|------------|
|
| 1490 |
+
| All fetched models (plain text) | Same models with color-coded status |
|
| 1491 |
+
| Shows total count | Shows available/ignored count |
|
| 1492 |
+
| Scrolls in sync with right pane | Color indicates affecting rule |
|
| 1493 |
+
|
| 1494 |
+
**Color Coding**:
|
| 1495 |
+
- **Green**: Model is available (no rule affects it, or whitelisted)
|
| 1496 |
+
- **Red/Orange tones**: Model is ignored (color matches the specific ignore rule)
|
| 1497 |
+
- **Blue/Teal tones**: Model is explicitly whitelisted (color matches the whitelist rule)
|
| 1498 |
+
|
| 1499 |
+
#### Rule Management
|
| 1500 |
+
|
| 1501 |
+
- **Comma-separated input**: Add multiple rules at once (e.g., `*-preview, *-beta, gpt-3.5*`)
|
| 1502 |
+
- **Wildcard support**: `*` matches any characters (e.g., `gemini-*-preview`)
|
| 1503 |
+
- **Affected count**: Each rule shows how many models it affects
|
| 1504 |
+
- **Tooltips**: Hover over a rule to see the list of affected models
|
| 1505 |
+
- **Instant delete**: Click the × button to remove a rule immediately
|
| 1506 |
+
|
| 1507 |
+
### 6.3. Keyboard Shortcuts
|
| 1508 |
+
|
| 1509 |
+
| Shortcut | Action |
|
| 1510 |
+
|----------|--------|
|
| 1511 |
+
| `Ctrl+S` | Save changes to `.env` |
|
| 1512 |
+
| `Ctrl+R` | Refresh models from provider |
|
| 1513 |
+
| `Ctrl+F` | Focus search field |
|
| 1514 |
+
| `F1` | Show help dialog |
|
| 1515 |
+
| `Escape` | Clear search / Clear highlights |
|
| 1516 |
+
|
| 1517 |
+
### 6.4. Context Menu
|
| 1518 |
+
|
| 1519 |
+
Right-click on any model to access:
|
| 1520 |
+
|
| 1521 |
+
- **Add to Ignore List**: Creates an ignore rule for the exact model name
|
| 1522 |
+
- **Add to Whitelist**: Creates a whitelist rule for the exact model name
|
| 1523 |
+
- **View Affecting Rule**: Highlights the rule that affects this model
|
| 1524 |
+
- **Copy Model Name**: Copies the full model ID to clipboard
|
| 1525 |
+
|
| 1526 |
+
### 6.5. Integration with Proxy
|
| 1527 |
+
|
| 1528 |
+
The GUI modifies the same environment variables that the `RotatingClient` reads:
|
| 1529 |
+
|
| 1530 |
+
1. **GUI saves rules** → Updates `.env` file
|
| 1531 |
+
2. **Proxy reads on startup** → Loads `IGNORE_MODELS_*` and `WHITELIST_MODELS_*`
|
| 1532 |
+
3. **Proxy applies rules** → `get_available_models()` filters based on rules
|
| 1533 |
+
|
| 1534 |
+
**Note**: The proxy must be restarted to pick up rule changes made via the GUI (or use the Launcher TUI's reload functionality if available).
|
| 1535 |
+
|
requirements.txt
CHANGED
|
@@ -19,3 +19,9 @@ aiohttp
|
|
| 19 |
colorlog
|
| 20 |
|
| 21 |
rich
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
colorlog
|
| 20 |
|
| 21 |
rich
|
| 22 |
+
|
| 23 |
+
# GUI for model filter configuration
|
| 24 |
+
customtkinter
|
| 25 |
+
|
| 26 |
+
# For building the executable
|
| 27 |
+
pyinstaller
|
src/proxy_app/build.py
CHANGED
|
@@ -3,6 +3,7 @@ import sys
|
|
| 3 |
import platform
|
| 4 |
import subprocess
|
| 5 |
|
|
|
|
| 6 |
def get_providers():
|
| 7 |
"""
|
| 8 |
Scans the 'src/rotator_library/providers' directory to find all provider modules.
|
|
@@ -24,6 +25,7 @@ def get_providers():
|
|
| 24 |
hidden_imports.append(f"--hidden-import={module_name}")
|
| 25 |
return hidden_imports
|
| 26 |
|
|
|
|
| 27 |
def main():
|
| 28 |
"""
|
| 29 |
Constructs and runs the PyInstaller command to build the executable.
|
|
@@ -47,22 +49,27 @@ def main():
|
|
| 47 |
"--collect-data",
|
| 48 |
"litellm",
|
| 49 |
# Optimization: Exclude unused heavy modules
|
| 50 |
-
"--exclude-module=tkinter",
|
| 51 |
"--exclude-module=matplotlib",
|
| 52 |
"--exclude-module=IPython",
|
| 53 |
"--exclude-module=jupyter",
|
| 54 |
"--exclude-module=notebook",
|
| 55 |
"--exclude-module=PIL.ImageTk",
|
| 56 |
# Optimization: Enable UPX compression (if available)
|
| 57 |
-
"--upx-dir=upx"
|
|
|
|
|
|
|
| 58 |
# Optimization: Strip debug symbols (smaller binary)
|
| 59 |
-
"--strip"
|
|
|
|
|
|
|
| 60 |
]
|
| 61 |
|
| 62 |
# Add hidden imports for providers
|
| 63 |
provider_imports = get_providers()
|
| 64 |
if not provider_imports:
|
| 65 |
-
print(
|
|
|
|
|
|
|
| 66 |
command.extend(provider_imports)
|
| 67 |
|
| 68 |
# Add the main script
|
|
@@ -80,5 +87,6 @@ def main():
|
|
| 80 |
except FileNotFoundError:
|
| 81 |
print("Error: PyInstaller is not installed or not in the system's PATH.")
|
| 82 |
|
|
|
|
| 83 |
if __name__ == "__main__":
|
| 84 |
main()
|
|
|
|
| 3 |
import platform
|
| 4 |
import subprocess
|
| 5 |
|
| 6 |
+
|
| 7 |
def get_providers():
|
| 8 |
"""
|
| 9 |
Scans the 'src/rotator_library/providers' directory to find all provider modules.
|
|
|
|
| 25 |
hidden_imports.append(f"--hidden-import={module_name}")
|
| 26 |
return hidden_imports
|
| 27 |
|
| 28 |
+
|
| 29 |
def main():
|
| 30 |
"""
|
| 31 |
Constructs and runs the PyInstaller command to build the executable.
|
|
|
|
| 49 |
"--collect-data",
|
| 50 |
"litellm",
|
| 51 |
# Optimization: Exclude unused heavy modules
|
|
|
|
| 52 |
"--exclude-module=matplotlib",
|
| 53 |
"--exclude-module=IPython",
|
| 54 |
"--exclude-module=jupyter",
|
| 55 |
"--exclude-module=notebook",
|
| 56 |
"--exclude-module=PIL.ImageTk",
|
| 57 |
# Optimization: Enable UPX compression (if available)
|
| 58 |
+
"--upx-dir=upx"
|
| 59 |
+
if platform.system() != "Darwin"
|
| 60 |
+
else "--noupx", # macOS has issues with UPX
|
| 61 |
# Optimization: Strip debug symbols (smaller binary)
|
| 62 |
+
"--strip"
|
| 63 |
+
if platform.system() != "Windows"
|
| 64 |
+
else "--console", # Windows gets clean console
|
| 65 |
]
|
| 66 |
|
| 67 |
# Add hidden imports for providers
|
| 68 |
provider_imports = get_providers()
|
| 69 |
if not provider_imports:
|
| 70 |
+
print(
|
| 71 |
+
"Warning: No providers found. The build might not include any LLM providers."
|
| 72 |
+
)
|
| 73 |
command.extend(provider_imports)
|
| 74 |
|
| 75 |
# Add the main script
|
|
|
|
| 87 |
except FileNotFoundError:
|
| 88 |
print("Error: PyInstaller is not installed or not in the system's PATH.")
|
| 89 |
|
| 90 |
+
|
| 91 |
if __name__ == "__main__":
|
| 92 |
main()
|
src/proxy_app/model_filter_gui.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/proxy_app/settings_tool.py
CHANGED
|
@@ -749,23 +749,20 @@ class SettingsTool:
|
|
| 749 |
self.console.print(" 3. ⚡ Concurrency Limits")
|
| 750 |
self.console.print(" 4. 🔄 Rotation Modes")
|
| 751 |
self.console.print(" 5. 🔬 Provider-Specific Settings")
|
| 752 |
-
self.console.print(" 6.
|
| 753 |
-
self.console.print(" 7.
|
|
|
|
| 754 |
|
| 755 |
self.console.print()
|
| 756 |
self.console.print("━" * 70)
|
| 757 |
|
| 758 |
self.console.print(self._get_pending_status_text())
|
| 759 |
|
| 760 |
-
self.console.print()
|
| 761 |
-
self.console.print(
|
| 762 |
-
"[dim]⚠️ Model filters not supported - edit .env for IGNORE_MODELS_* / WHITELIST_MODELS_*[/dim]"
|
| 763 |
-
)
|
| 764 |
self.console.print()
|
| 765 |
|
| 766 |
choice = Prompt.ask(
|
| 767 |
"Select option",
|
| 768 |
-
choices=["1", "2", "3", "4", "5", "6", "7"],
|
| 769 |
show_choices=False,
|
| 770 |
)
|
| 771 |
|
|
@@ -780,8 +777,10 @@ class SettingsTool:
|
|
| 780 |
elif choice == "5":
|
| 781 |
self.manage_provider_settings()
|
| 782 |
elif choice == "6":
|
| 783 |
-
self.
|
| 784 |
elif choice == "7":
|
|
|
|
|
|
|
| 785 |
self.exit_without_saving()
|
| 786 |
|
| 787 |
def manage_custom_providers(self):
|
|
@@ -1393,6 +1392,28 @@ class SettingsTool:
|
|
| 1393 |
|
| 1394 |
input("Press Enter to return...")
|
| 1395 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1396 |
def manage_provider_settings(self):
|
| 1397 |
"""Manage provider-specific settings (Antigravity, Gemini CLI)"""
|
| 1398 |
while True:
|
|
|
|
| 749 |
self.console.print(" 3. ⚡ Concurrency Limits")
|
| 750 |
self.console.print(" 4. 🔄 Rotation Modes")
|
| 751 |
self.console.print(" 5. 🔬 Provider-Specific Settings")
|
| 752 |
+
self.console.print(" 6. 🎯 Model Filters (Ignore/Whitelist)")
|
| 753 |
+
self.console.print(" 7. 💾 Save & Exit")
|
| 754 |
+
self.console.print(" 8. 🚫 Exit Without Saving")
|
| 755 |
|
| 756 |
self.console.print()
|
| 757 |
self.console.print("━" * 70)
|
| 758 |
|
| 759 |
self.console.print(self._get_pending_status_text())
|
| 760 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
self.console.print()
|
| 762 |
|
| 763 |
choice = Prompt.ask(
|
| 764 |
"Select option",
|
| 765 |
+
choices=["1", "2", "3", "4", "5", "6", "7", "8"],
|
| 766 |
show_choices=False,
|
| 767 |
)
|
| 768 |
|
|
|
|
| 777 |
elif choice == "5":
|
| 778 |
self.manage_provider_settings()
|
| 779 |
elif choice == "6":
|
| 780 |
+
self.launch_model_filter_gui()
|
| 781 |
elif choice == "7":
|
| 782 |
+
self.save_and_exit()
|
| 783 |
+
elif choice == "8":
|
| 784 |
self.exit_without_saving()
|
| 785 |
|
| 786 |
def manage_custom_providers(self):
|
|
|
|
| 1392 |
|
| 1393 |
input("Press Enter to return...")
|
| 1394 |
|
| 1395 |
+
def launch_model_filter_gui(self):
|
| 1396 |
+
"""Launch the Model Filter GUI for managing ignore/whitelist rules"""
|
| 1397 |
+
clear_screen()
|
| 1398 |
+
self.console.print("\n[cyan]Launching Model Filter GUI...[/cyan]\n")
|
| 1399 |
+
self.console.print(
|
| 1400 |
+
"[dim]The GUI will open in a separate window. Close it to return here.[/dim]\n"
|
| 1401 |
+
)
|
| 1402 |
+
|
| 1403 |
+
try:
|
| 1404 |
+
from proxy_app.model_filter_gui import run_model_filter_gui
|
| 1405 |
+
|
| 1406 |
+
run_model_filter_gui() # Blocks until GUI closes
|
| 1407 |
+
except ImportError as e:
|
| 1408 |
+
self.console.print(f"\n[red]Failed to launch Model Filter GUI: {e}[/red]")
|
| 1409 |
+
self.console.print()
|
| 1410 |
+
self.console.print(
|
| 1411 |
+
"[yellow]Make sure 'customtkinter' is installed:[/yellow]"
|
| 1412 |
+
)
|
| 1413 |
+
self.console.print(" [cyan]pip install customtkinter[/cyan]")
|
| 1414 |
+
self.console.print()
|
| 1415 |
+
input("Press Enter to continue...")
|
| 1416 |
+
|
| 1417 |
def manage_provider_settings(self):
|
| 1418 |
"""Manage provider-specific settings (Antigravity, Gemini CLI)"""
|
| 1419 |
while True:
|
src/rotator_library/client.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import asyncio
|
|
|
|
| 2 |
import json
|
| 3 |
import re
|
| 4 |
import codecs
|
|
@@ -297,7 +298,14 @@ class RotatingClient:
|
|
| 297 |
def _is_model_ignored(self, provider: str, model_id: str) -> bool:
|
| 298 |
"""
|
| 299 |
Checks if a model should be ignored based on the ignore list.
|
| 300 |
-
Supports
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
"""
|
| 302 |
model_provider = model_id.split("/")[0]
|
| 303 |
if model_provider not in self.ignore_models:
|
|
@@ -314,52 +322,43 @@ class RotatingClient:
|
|
| 314 |
provider_model_name = model_id
|
| 315 |
|
| 316 |
for ignored_pattern in ignore_list:
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
else:
|
| 323 |
-
# Exact match against the full proxy ID OR the provider's model name
|
| 324 |
-
if (
|
| 325 |
-
model_id == ignored_pattern
|
| 326 |
-
or provider_model_name == ignored_pattern
|
| 327 |
-
):
|
| 328 |
-
return True
|
| 329 |
return False
|
| 330 |
|
| 331 |
def _is_model_whitelisted(self, provider: str, model_id: str) -> bool:
|
| 332 |
"""
|
| 333 |
Checks if a model is explicitly whitelisted.
|
| 334 |
-
Supports
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
"""
|
| 336 |
model_provider = model_id.split("/")[0]
|
| 337 |
if model_provider not in self.whitelist_models:
|
| 338 |
return False
|
| 339 |
|
| 340 |
whitelist = self.whitelist_models[model_provider]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
for whitelisted_pattern in whitelist:
|
| 342 |
-
|
|
|
|
|
|
|
|
|
|
| 343 |
return True
|
| 344 |
-
|
| 345 |
-
try:
|
| 346 |
-
# This is the model name as the provider sees it (e.g., "gpt-4" or "google/gemma-7b")
|
| 347 |
-
provider_model_name = model_id.split("/", 1)[1]
|
| 348 |
-
except IndexError:
|
| 349 |
-
provider_model_name = model_id
|
| 350 |
-
|
| 351 |
-
if whitelisted_pattern.endswith("*"):
|
| 352 |
-
match_pattern = whitelisted_pattern[:-1]
|
| 353 |
-
# Match wildcard against the provider's model name
|
| 354 |
-
if provider_model_name.startswith(match_pattern):
|
| 355 |
-
return True
|
| 356 |
-
else:
|
| 357 |
-
# Exact match against the full proxy ID OR the provider's model name
|
| 358 |
-
if (
|
| 359 |
-
model_id == whitelisted_pattern
|
| 360 |
-
or provider_model_name == whitelisted_pattern
|
| 361 |
-
):
|
| 362 |
-
return True
|
| 363 |
return False
|
| 364 |
|
| 365 |
def _sanitize_litellm_log(self, log_data: dict) -> dict:
|
|
|
|
| 1 |
import asyncio
|
| 2 |
+
import fnmatch
|
| 3 |
import json
|
| 4 |
import re
|
| 5 |
import codecs
|
|
|
|
| 298 |
def _is_model_ignored(self, provider: str, model_id: str) -> bool:
|
| 299 |
"""
|
| 300 |
Checks if a model should be ignored based on the ignore list.
|
| 301 |
+
Supports full glob/fnmatch patterns for both full model IDs and model names.
|
| 302 |
+
|
| 303 |
+
Pattern examples:
|
| 304 |
+
- "gpt-4" - exact match
|
| 305 |
+
- "gpt-4*" - prefix wildcard (matches gpt-4, gpt-4-turbo, etc.)
|
| 306 |
+
- "*-preview" - suffix wildcard (matches gpt-4-preview, o1-preview, etc.)
|
| 307 |
+
- "*-preview*" - contains wildcard (matches anything with -preview)
|
| 308 |
+
- "*" - match all
|
| 309 |
"""
|
| 310 |
model_provider = model_id.split("/")[0]
|
| 311 |
if model_provider not in self.ignore_models:
|
|
|
|
| 322 |
provider_model_name = model_id
|
| 323 |
|
| 324 |
for ignored_pattern in ignore_list:
|
| 325 |
+
# Use fnmatch for full glob pattern support
|
| 326 |
+
if fnmatch.fnmatch(provider_model_name, ignored_pattern) or fnmatch.fnmatch(
|
| 327 |
+
model_id, ignored_pattern
|
| 328 |
+
):
|
| 329 |
+
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
return False
|
| 331 |
|
| 332 |
def _is_model_whitelisted(self, provider: str, model_id: str) -> bool:
|
| 333 |
"""
|
| 334 |
Checks if a model is explicitly whitelisted.
|
| 335 |
+
Supports full glob/fnmatch patterns for both full model IDs and model names.
|
| 336 |
+
|
| 337 |
+
Pattern examples:
|
| 338 |
+
- "gpt-4" - exact match
|
| 339 |
+
- "gpt-4*" - prefix wildcard (matches gpt-4, gpt-4-turbo, etc.)
|
| 340 |
+
- "*-preview" - suffix wildcard (matches gpt-4-preview, o1-preview, etc.)
|
| 341 |
+
- "*-preview*" - contains wildcard (matches anything with -preview)
|
| 342 |
+
- "*" - match all
|
| 343 |
"""
|
| 344 |
model_provider = model_id.split("/")[0]
|
| 345 |
if model_provider not in self.whitelist_models:
|
| 346 |
return False
|
| 347 |
|
| 348 |
whitelist = self.whitelist_models[model_provider]
|
| 349 |
+
|
| 350 |
+
try:
|
| 351 |
+
# This is the model name as the provider sees it (e.g., "gpt-4" or "google/gemma-7b")
|
| 352 |
+
provider_model_name = model_id.split("/", 1)[1]
|
| 353 |
+
except IndexError:
|
| 354 |
+
provider_model_name = model_id
|
| 355 |
+
|
| 356 |
for whitelisted_pattern in whitelist:
|
| 357 |
+
# Use fnmatch for full glob pattern support
|
| 358 |
+
if fnmatch.fnmatch(
|
| 359 |
+
provider_model_name, whitelisted_pattern
|
| 360 |
+
) or fnmatch.fnmatch(model_id, whitelisted_pattern):
|
| 361 |
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
return False
|
| 363 |
|
| 364 |
def _sanitize_litellm_log(self, log_data: dict) -> dict:
|
src/rotator_library/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "rotator_library"
|
| 7 |
-
version = "1.
|
| 8 |
authors = [
|
| 9 |
{ name="Mirrowel", email="nuh@uh.com" },
|
| 10 |
]
|
|
@@ -16,11 +16,7 @@ classifiers = [
|
|
| 16 |
"License :: OSI Approved :: MIT License",
|
| 17 |
"Operating System :: OS Independent",
|
| 18 |
]
|
| 19 |
-
dependencies = [
|
| 20 |
-
"litellm",
|
| 21 |
-
"filelock",
|
| 22 |
-
"httpx"
|
| 23 |
-
]
|
| 24 |
|
| 25 |
[project.urls]
|
| 26 |
"Homepage" = "https://github.com/Mirrowel/LLM-API-Key-Proxy"
|
|
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "rotator_library"
|
| 7 |
+
version = "1.05"
|
| 8 |
authors = [
|
| 9 |
{ name="Mirrowel", email="nuh@uh.com" },
|
| 10 |
]
|
|
|
|
| 16 |
"License :: OSI Approved :: MIT License",
|
| 17 |
"Operating System :: OS Independent",
|
| 18 |
]
|
| 19 |
+
dependencies = []
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
[project.urls]
|
| 22 |
"Homepage" = "https://github.com/Mirrowel/LLM-API-Key-Proxy"
|