Mirrowel commited on
Commit
df5a3da
·
1 Parent(s): c2eea0c

feat: Add OpenRouter and Chutes.ai provider support

Browse files

This 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 CHANGED
@@ -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"
DOCUMENTATION.md CHANGED
@@ -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 acts as a registry for the available plugins. The `PROVIDER_PLUGINS` dictionary maps provider names to their corresponding plugin classes. The `RotatingClient` uses this dictionary to instantiate the correct plugin at runtime.
 
 
 
 
 
 
 
 
 
 
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.
README.md CHANGED
@@ -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
src/rotator_library/README.md CHANGED
@@ -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
- You can add support for fetching model lists from new providers by creating a custom provider plugin.
108
 
109
- 1. **Create a new provider file** in `src/rotator_library/providers/`, for example, `my_provider.py`.
110
- 2. **Implement the `ProviderPlugin` interface**:
111
 
112
- ```python
113
- # src/rotator_library/providers/my_provider.py
114
- from .provider_interface import ProviderPlugin
115
- from typing import List
116
-
117
- class MyProvider(ProviderPlugin):
118
- async def get_models(self, api_key: str) -> List[str]:
119
- # Logic to fetch and return a list of model names
120
- # e.g., ["my-provider/model-1", "my-provider/model-2"]
121
- pass
122
- ```
123
-
124
- 3. **Register the plugin** in `src/rotator_library/providers/__init__.py`:
125
 
126
- ```python
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
- PROVIDER_PLUGINS = {
133
- "openai": OpenAIProvider,
134
- "gemini": GeminiProvider,
135
- "my_provider": MyProvider, # Add it to the dictionary
136
- }
137
- ```
138
 
139
- The `RotatingClient` will automatically use your new plugin when `get_available_models` is called for `"my_provider"`.
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
 
src/rotator_library/client.py CHANGED
@@ -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
- response = await litellm.acompletion(api_key=current_key, **kwargs)
 
 
 
 
 
 
 
 
 
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.
src/rotator_library/providers/chutes_provider.py ADDED
@@ -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 []
src/rotator_library/providers/openrouter_provider.py ADDED
@@ -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 []