Spaces:
Paused
feat: Add OpenRouter and Chutes.ai provider support
Browse filesThis commit introduces support for two new LLM providers: OpenRouter and Chutes.ai.
**Key Changes:**
- **OpenRouter Provider:**
- Added `openrouter_provider.py` to fetch the list of available models from the OpenRouter API.
- Updated `.env.example` to include `OPENROUTER_API_KEYS`.
- **Chutes.ai Provider:**
- Added `chutes_provider.py` to fetch models from the Chutes.ai API.
- Implemented special handling in `client.py` to treat Chutes.ai as a custom OpenAI-compatible endpoint. This involves setting the `api_base` to `https://llm.chutes.ai/v1` and remapping the model name for `litellm`.
- Updated `.env.example` to include `CHUTES_API_KEYS`.
- **Documentation:**
- Updated `DOCUMENTATION.md`, `README.md`, and `src/rotator_library/README.md` to reflect the addition of the new providers.
- Clarified the dynamic provider loading mechanism.
- Added details about the special handling for Chutes.ai.
- .env.example +4 -0
- DOCUMENTATION.md +10 -1
- README.md +6 -1
- src/rotator_library/README.md +19 -29
- src/rotator_library/client.py +10 -1
- src/rotator_library/providers/chutes_provider.py +23 -0
- src/rotator_library/providers/openrouter_provider.py +23 -0
|
@@ -2,6 +2,10 @@
|
|
| 2 |
# Add more keys by creating GEMINI_API_KEY_2, GEMINI_API_KEY_3, etc.
|
| 3 |
GEMINI_API_KEY_1="YOUR_GEMINI_API_KEY_1"
|
| 4 |
GEMINI_API_KEY_2="YOUR_GEMINI_API_KEY_2"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
# A secret key for your proxy server to authenticate requests
|
| 7 |
PROXY_API_KEY="YOUR_PROXY_API_KEY"
|
|
|
|
| 2 |
# Add more keys by creating GEMINI_API_KEY_2, GEMINI_API_KEY_3, etc.
|
| 3 |
GEMINI_API_KEY_1="YOUR_GEMINI_API_KEY_1"
|
| 4 |
GEMINI_API_KEY_2="YOUR_GEMINI_API_KEY_2"
|
| 5 |
+
OPENROUTER_API_KEY_1="YOUR_OPENROUTER_API_KEY_1"
|
| 6 |
+
OPENROUTER_API_KEY_2="YOUR_OPENROUTER_API_KEY_2"
|
| 7 |
+
CHUTES_API_KEY_1="YOUR_CHUTES_API_KEY_1"
|
| 8 |
+
CHUTES_API_KEY_2="YOUR_CHUTES_API_KEY_2"
|
| 9 |
|
| 10 |
# A secret key for your proxy server to authenticate requests
|
| 11 |
PROXY_API_KEY="YOUR_PROXY_API_KEY"
|
|
@@ -78,4 +78,13 @@ The provider plugin system allows for easy extension to support model list fetch
|
|
| 78 |
|
| 79 |
- **`provider_interface.py`**: Defines the abstract base class `ProviderPlugin` with a single abstract method, `get_models`. Any new provider plugin must inherit from this class and implement this method.
|
| 80 |
- **Implementations**: Each provider (e.g., `openai_provider.py`, `gemini_provider.py`) has its own file containing a class that implements the `ProviderPlugin` interface. The `get_models` method contains the specific logic to call the provider's API and return a list of their available models.
|
| 81 |
-
- **`__init__.py`**: This file
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
- **`provider_interface.py`**: Defines the abstract base class `ProviderPlugin` with a single abstract method, `get_models`. Any new provider plugin must inherit from this class and implement this method.
|
| 80 |
- **Implementations**: Each provider (e.g., `openai_provider.py`, `gemini_provider.py`) has its own file containing a class that implements the `ProviderPlugin` interface. The `get_models` method contains the specific logic to call the provider's API and return a list of their available models.
|
| 81 |
+
- **`__init__.py`**: This file contains a dynamic plugin system that automatically discovers and registers any provider implementation placed in the `providers/` directory.
|
| 82 |
+
|
| 83 |
+
### Special Provider: `chutes.ai`
|
| 84 |
+
|
| 85 |
+
The `chutes` provider is handled as a special case within the `RotatingClient`. Since `litellm` does not have native support for `chutes.ai`, the client performs the following modifications at runtime:
|
| 86 |
+
|
| 87 |
+
1. **Sets `api_base`**: It sets the `api_base` to `https://llm.chutes.ai/v1`.
|
| 88 |
+
2. **Remaps the Model**: It changes the model name from `chutes/some-model` to `openai/some-model` before passing the request to `litellm`.
|
| 89 |
+
|
| 90 |
+
This allows the system to use `chutes.ai` as if it were a custom OpenAI endpoint, while still leveraging the library's key rotation and management features.
|
|
@@ -84,6 +84,11 @@ The FastAPI proxy application exposes this functionality through an API endpoint
|
|
| 84 |
GEMINI_API_KEY_2="your-gemini-api-key-2"
|
| 85 |
|
| 86 |
OPENAI_API_KEY_1="your-openai-api-key-1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
```
|
| 88 |
|
| 89 |
## Running the Proxy
|
|
@@ -98,7 +103,7 @@ The proxy will be available at `http://127.0.0.1:8000`.
|
|
| 98 |
|
| 99 |
You can make requests to the proxy as if it were the OpenAI API. Remember to include your `PROXY_API_KEY` in the `Authorization` header.
|
| 100 |
|
| 101 |
-
The `model` parameter must be specified in the format `provider/model_name` (e.g., `gemini/gemini-2.5-flash-preview-05-20`, `openai/gpt-4`).
|
| 102 |
|
| 103 |
### Example with `curl` (Non-Streaming):
|
| 104 |
```bash
|
|
|
|
| 84 |
GEMINI_API_KEY_2="your-gemini-api-key-2"
|
| 85 |
|
| 86 |
OPENAI_API_KEY_1="your-openai-api-key-1"
|
| 87 |
+
|
| 88 |
+
OPENROUTER_API_KEY_1="your-openrouter-api-key-1"
|
| 89 |
+
|
| 90 |
+
# chutes.ai is used as a custom OpenAI endpoint
|
| 91 |
+
CHUTES_API_KEY_1="your-chutes-api-key-1"
|
| 92 |
```
|
| 93 |
|
| 94 |
## Running the Proxy
|
|
|
|
| 103 |
|
| 104 |
You can make requests to the proxy as if it were the OpenAI API. Remember to include your `PROXY_API_KEY` in the `Authorization` header.
|
| 105 |
|
| 106 |
+
The `model` parameter must be specified in the format `provider/model_name` (e.g., `gemini/gemini-2.5-flash-preview-05-20`, `openai/gpt-4`, `openrouter/google/gemini-flash-1.5`, `chutes/deepseek-ai/DeepSeek-R1-0528`).
|
| 107 |
|
| 108 |
### Example with `curl` (Non-Streaming):
|
| 109 |
```bash
|
|
@@ -46,7 +46,7 @@ client = RotatingClient(
|
|
| 46 |
|
| 47 |
This is the primary method for making API calls. It's a wrapper around `litellm.acompletion` that adds key rotation and retry logic.
|
| 48 |
|
| 49 |
-
- **Parameters**: Accepts the same keyword arguments as `litellm.acompletion` (e.g., `messages`, `stream`). The `model` parameter is required and must be a string in the format `provider/model_name` (e.g., `"gemini/gemini-2.5-flash-preview-05-20"`).
|
| 50 |
- **Returns**:
|
| 51 |
- For non-streaming requests, it returns the `litellm` response object.
|
| 52 |
- For streaming requests, it returns an async generator that yields OpenAI-compatible Server-Sent Events (SSE).
|
|
@@ -104,39 +104,29 @@ Cooldowns are managed by the `UsageManager` on a per-model basis, preventing fai
|
|
| 104 |
|
| 105 |
## Extending with Provider Plugins
|
| 106 |
|
| 107 |
-
|
| 108 |
|
| 109 |
-
1. **Create a new provider file** in `src/rotator_library/providers
|
| 110 |
-
2. **Implement the `
|
| 111 |
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
3. **Register the plugin** in `src/rotator_library/providers/__init__.py`:
|
| 125 |
|
| 126 |
-
|
| 127 |
-
# src/rotator_library/providers/__init__.py
|
| 128 |
-
from .openai_provider import OpenAIProvider
|
| 129 |
-
from .gemini_provider import GeminiProvider
|
| 130 |
-
from .my_provider import MyProvider # Import your new provider
|
| 131 |
|
| 132 |
-
|
| 133 |
-
"openai": OpenAIProvider,
|
| 134 |
-
"gemini": GeminiProvider,
|
| 135 |
-
"my_provider": MyProvider, # Add it to the dictionary
|
| 136 |
-
}
|
| 137 |
-
```
|
| 138 |
|
| 139 |
-
The `RotatingClient`
|
| 140 |
|
| 141 |
## Detailed Documentation
|
| 142 |
|
|
|
|
| 46 |
|
| 47 |
This is the primary method for making API calls. It's a wrapper around `litellm.acompletion` that adds key rotation and retry logic.
|
| 48 |
|
| 49 |
+
- **Parameters**: Accepts the same keyword arguments as `litellm.acompletion` (e.g., `messages`, `stream`). The `model` parameter is required and must be a string in the format `provider/model_name` (e.g., `"gemini/gemini-2.5-flash-preview-05-20"`, `"openrouter/google/gemini-flash-1.5"`, `"chutes/deepseek-ai/DeepSeek-R1-0528"`).
|
| 50 |
- **Returns**:
|
| 51 |
- For non-streaming requests, it returns the `litellm` response object.
|
| 52 |
- For streaming requests, it returns an async generator that yields OpenAI-compatible Server-Sent Events (SSE).
|
|
|
|
| 104 |
|
| 105 |
## Extending with Provider Plugins
|
| 106 |
|
| 107 |
+
The library uses a dynamic plugin system. To add support for a new provider, you only need to do two things:
|
| 108 |
|
| 109 |
+
1. **Create a new provider file** in `src/rotator_library/providers/` (e.g., `my_provider.py`). The name of the file (without `_provider.py`) will be used as the provider name (e.g., `my_provider`).
|
| 110 |
+
2. **Implement the `ProviderInterface`**: Inside your new file, create a class that inherits from `ProviderInterface` and implements the `get_models` method.
|
| 111 |
|
| 112 |
+
```python
|
| 113 |
+
# src/rotator_library/providers/my_provider.py
|
| 114 |
+
from .provider_interface import ProviderInterface
|
| 115 |
+
from typing import List
|
| 116 |
+
|
| 117 |
+
class MyProvider(ProviderInterface):
|
| 118 |
+
async def get_models(self, api_key: str) -> List[str]:
|
| 119 |
+
# Logic to fetch and return a list of model names
|
| 120 |
+
# The model names should be prefixed with the provider name.
|
| 121 |
+
# e.g., ["my-provider/model-1", "my-provider/model-2"]
|
| 122 |
+
pass
|
| 123 |
+
```
|
|
|
|
| 124 |
|
| 125 |
+
The system will automatically discover and register your new provider when the library is imported.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
+
### Special Case: `chutes.ai`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
+
The `chutes` provider is handled as a special case. Since `litellm` does not support it directly, the `RotatingClient` modifies the request by setting the `api_base` to `https://llm.chutes.ai/v1` and remapping the model from `chutes/model-name` to `openai/model-name`. This allows `chutes.ai` to be used as a custom OpenAI-compatible endpoint.
|
| 130 |
|
| 131 |
## Detailed Documentation
|
| 132 |
|
|
@@ -81,7 +81,16 @@ class RotatingClient:
|
|
| 81 |
for attempt in range(self.max_retries):
|
| 82 |
try:
|
| 83 |
print(f"Attempting call with key ...{current_key[-4:]} (Attempt {attempt + 1}/{self.max_retries})")
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
if is_streaming:
|
| 87 |
# For streams, we return a wrapper generator that logs usage on completion.
|
|
|
|
| 81 |
for attempt in range(self.max_retries):
|
| 82 |
try:
|
| 83 |
print(f"Attempting call with key ...{current_key[-4:]} (Attempt {attempt + 1}/{self.max_retries})")
|
| 84 |
+
|
| 85 |
+
# Create a copy of kwargs to modify for the litellm call
|
| 86 |
+
litellm_kwargs = kwargs.copy()
|
| 87 |
+
|
| 88 |
+
# Handle chutes.ai as a special case
|
| 89 |
+
if provider == "chutes":
|
| 90 |
+
litellm_kwargs["model"] = f"openai/{model.split('/', 1)[1]}"
|
| 91 |
+
litellm_kwargs["api_base"] = "https://llm.chutes.ai/v1"
|
| 92 |
+
|
| 93 |
+
response = await litellm.acompletion(api_key=current_key, **litellm_kwargs)
|
| 94 |
|
| 95 |
if is_streaming:
|
| 96 |
# For streams, we return a wrapper generator that logs usage on completion.
|
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import logging
|
| 3 |
+
from typing import List
|
| 4 |
+
from .provider_interface import ProviderInterface
|
| 5 |
+
|
| 6 |
+
class ChutesProvider(ProviderInterface):
|
| 7 |
+
"""
|
| 8 |
+
Provider implementation for the chutes.ai API.
|
| 9 |
+
"""
|
| 10 |
+
async def get_models(self, api_key: str) -> List[str]:
|
| 11 |
+
"""
|
| 12 |
+
Fetches the list of available models from the chutes.ai API.
|
| 13 |
+
"""
|
| 14 |
+
try:
|
| 15 |
+
response = requests.get(
|
| 16 |
+
"https://llm.chutes.ai/v1/models",
|
| 17 |
+
headers={"Authorization": f"Bearer {api_key}"}
|
| 18 |
+
)
|
| 19 |
+
response.raise_for_status()
|
| 20 |
+
return [f"chutes/{model['id']}" for model in response.json().get("data", [])]
|
| 21 |
+
except requests.RequestException as e:
|
| 22 |
+
logging.error(f"Failed to fetch chutes.ai models: {e}")
|
| 23 |
+
return []
|
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import logging
|
| 3 |
+
from typing import List
|
| 4 |
+
from .provider_interface import ProviderInterface
|
| 5 |
+
|
| 6 |
+
class OpenRouterProvider(ProviderInterface):
|
| 7 |
+
"""
|
| 8 |
+
Provider implementation for the OpenRouter API.
|
| 9 |
+
"""
|
| 10 |
+
async def get_models(self, api_key: str) -> List[str]:
|
| 11 |
+
"""
|
| 12 |
+
Fetches the list of available models from the OpenRouter API.
|
| 13 |
+
"""
|
| 14 |
+
try:
|
| 15 |
+
response = requests.get(
|
| 16 |
+
"https://openrouter.ai/api/v1/models",
|
| 17 |
+
headers={"Authorization": f"Bearer {api_key}"}
|
| 18 |
+
)
|
| 19 |
+
response.raise_for_status()
|
| 20 |
+
return [f"openrouter/{model['id']}" for model in response.json().get("data", [])]
|
| 21 |
+
except requests.RequestException as e:
|
| 22 |
+
logging.error(f"Failed to fetch OpenRouter models: {e}")
|
| 23 |
+
return []
|