Spaces:
Paused
feat: Refactor project into reusable API key rotation library and proxy
Browse filesThis commit introduces a significant refactoring of the project structure:
- **Extracted `rotating-api-key-client` library**: The core API key rotation, usage tracking, and error handling logic has been moved into a new, standalone Python library located in `src/rotator_library`. This library is now designed for independent reuse.
- **Updated FastAPI proxy**: The `proxy_app` now consumes the `rotating-api-key-client` library as a local dependency, simplifying its `main.py` and leveraging the new package structure.
- **Enhanced documentation**:
- A new `src/rotator_library/README.md` provides detailed usage instructions for the standalone library.
- The main `README.md` has been extensively updated to reflect the new project architecture, provide clearer setup and running instructions, and include examples for both the proxy and the library.
- **Improved dependency management**: `requirements.txt` now installs the `rotator_library` in editable mode, and `pyproject.toml` has been added to the library for proper package definition.
- **Configurable usage file path**: The `RotatingClient` now allows specifying a custom path for the usage tracking file.
- README.md +101 -37
- requirements.txt +1 -3
- src/proxy_app/main.py +3 -3
- src/rotator_library/README.md +53 -0
- src/rotator_library/__init__.py +17 -0
- src/rotator_library/client.py +2 -2
- src/rotator_library/pyproject.toml +29 -0
- staged_changes.txt +0 -0
|
@@ -1,74 +1,138 @@
|
|
| 1 |
-
# API Key Proxy
|
| 2 |
|
| 3 |
-
This project provides
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
- **Monitor key usage**: The system tracks the usage of each key, providing insights into your API consumption.
|
| 8 |
-
- **Provide a unified endpoint**: Exposes an OpenAI-compatible `/v1/chat/completions` endpoint, allowing you to use it with a wide range of existing tools and libraries.
|
| 9 |
|
| 10 |
## Features
|
| 11 |
|
| 12 |
-
-
|
| 13 |
-
-
|
| 14 |
-
-
|
| 15 |
-
-
|
| 16 |
-
-
|
|
|
|
| 17 |
|
| 18 |
-
##
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
| 26 |
|
| 27 |
1. **Clone the repository:**
|
|
|
|
| 28 |
```bash
|
| 29 |
-
git clone <
|
| 30 |
-
cd <
|
| 31 |
```
|
| 32 |
|
| 33 |
-
2. **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
```bash
|
| 35 |
pip install -r requirements.txt
|
| 36 |
```
|
| 37 |
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
-
|
| 41 |
|
| 42 |
```
|
| 43 |
-
#
|
| 44 |
PROXY_API_KEY="your-secret-proxy-key"
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
#
|
|
|
|
|
|
|
|
|
|
| 48 |
```
|
| 49 |
|
| 50 |
-
|
| 51 |
|
| 52 |
-
|
| 53 |
|
| 54 |
```bash
|
| 55 |
-
uvicorn src.proxy_app.main:app --
|
| 56 |
```
|
| 57 |
|
| 58 |
-
The proxy will
|
| 59 |
|
| 60 |
-
##
|
| 61 |
|
| 62 |
-
|
| 63 |
|
| 64 |
-
|
| 65 |
|
| 66 |
```bash
|
| 67 |
-
curl -X POST http://
|
| 68 |
-H "Content-Type: application/json" \
|
| 69 |
-H "Authorization: Bearer your-secret-proxy-key" \
|
| 70 |
-d '{
|
| 71 |
-
|
| 72 |
-
|
|
|
|
| 73 |
}'
|
| 74 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# API Key Proxy with Rotating Key Library
|
| 2 |
|
| 3 |
+
This project provides two main components:
|
| 4 |
|
| 5 |
+
1. A reusable Python library (`rotating-api-key-client`) for intelligently rotating API keys.
|
| 6 |
+
2. A FastAPI proxy application that uses this library to provide an OpenAI-compatible endpoint for various LLM providers.
|
|
|
|
|
|
|
| 7 |
|
| 8 |
## Features
|
| 9 |
|
| 10 |
+
- **Smart Key Rotation**: The library automatically uses the least-used key to distribute load.
|
| 11 |
+
- **Automatic Retries**: Retries requests on transient server errors.
|
| 12 |
+
- **Cooldowns**: Puts keys on a temporary cooldown after rate limit or authentication errors.
|
| 13 |
+
- **Usage Tracking**: Tracks daily and global usage for each key.
|
| 14 |
+
- **Provider Agnostic**: Works with any provider supported by `litellm`.
|
| 15 |
+
- **OpenAI-Compatible Proxy**: The proxy provides a familiar API for interacting with different models.
|
| 16 |
|
| 17 |
+
## Project Structure
|
| 18 |
|
| 19 |
+
```
|
| 20 |
+
.
|
| 21 |
+
├── logs/ # Logs for failed requests
|
| 22 |
+
├── src/
|
| 23 |
+
│ ├── proxy_app/ # The FastAPI proxy application
|
| 24 |
+
│ │ └── main.py
|
| 25 |
+
│ └── rotator_library/ # The rotating-api-key-client library
|
| 26 |
+
│ ├── __init__.py
|
| 27 |
+
│ ├── client.py
|
| 28 |
+
│ ├── error_handler.py
|
| 29 |
+
│ ├── failure_logger.py
|
| 30 |
+
│ ├── usage_manager.py
|
| 31 |
+
│ ├── pyproject.toml
|
| 32 |
+
│ └── README.md
|
| 33 |
+
├── .env.example
|
| 34 |
+
├── .gitignore
|
| 35 |
+
├── README.md
|
| 36 |
+
└── requirements.txt
|
| 37 |
+
```
|
| 38 |
|
| 39 |
+
## Setup and Installation
|
| 40 |
|
| 41 |
1. **Clone the repository:**
|
| 42 |
+
|
| 43 |
```bash
|
| 44 |
+
git clone <repository-url>
|
| 45 |
+
cd <repository-name>
|
| 46 |
```
|
| 47 |
|
| 48 |
+
2. **Create a virtual environment:**
|
| 49 |
+
|
| 50 |
+
```bash
|
| 51 |
+
python -m venv venv
|
| 52 |
+
source venv/bin/activate # On Windows, use `venv\Scripts\activate`
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
3. **Install the dependencies:**
|
| 56 |
+
|
| 57 |
+
The `requirements.txt` file includes the proxy's dependencies and installs the `rotator_library` in editable mode (`-e`), so you can develop both simultaneously.
|
| 58 |
+
|
| 59 |
```bash
|
| 60 |
pip install -r requirements.txt
|
| 61 |
```
|
| 62 |
|
| 63 |
+
4. **Configure environment variables:**
|
| 64 |
+
|
| 65 |
+
Create a `.env` file by copying the `.env.example`:
|
| 66 |
+
|
| 67 |
+
```bash
|
| 68 |
+
cp .env.example .env
|
| 69 |
+
```
|
| 70 |
|
| 71 |
+
Edit the `.env` file with your API keys:
|
| 72 |
|
| 73 |
```
|
| 74 |
+
# A secret key for your proxy to prevent unauthorized access
|
| 75 |
PROXY_API_KEY="your-secret-proxy-key"
|
| 76 |
+
|
| 77 |
+
# Add one or more API keys from your chosen provider (e.g., Gemini)
|
| 78 |
+
# The keys will be tried in order.
|
| 79 |
+
GEMINI_API_KEY_1="your-gemini-api-key-1"
|
| 80 |
+
GEMINI_API_KEY_2="your-gemini-api-key-2"
|
| 81 |
+
# ...and so on
|
| 82 |
```
|
| 83 |
|
| 84 |
+
## Running the Proxy
|
| 85 |
|
| 86 |
+
To run the proxy application:
|
| 87 |
|
| 88 |
```bash
|
| 89 |
+
uvicorn src.proxy_app.main:app --reload
|
| 90 |
```
|
| 91 |
|
| 92 |
+
The proxy will be available at `http://127.0.0.1:8000`.
|
| 93 |
|
| 94 |
+
## Using the Proxy
|
| 95 |
|
| 96 |
+
You can make requests to the proxy as if it were the OpenAI API. Make sure to include your `PROXY_API_KEY` in the `Authorization` header.
|
| 97 |
|
| 98 |
+
### Example with `curl`:
|
| 99 |
|
| 100 |
```bash
|
| 101 |
+
curl -X POST http://127.0.0.1:8000/v1/chat/completions \
|
| 102 |
-H "Content-Type: application/json" \
|
| 103 |
-H "Authorization: Bearer your-secret-proxy-key" \
|
| 104 |
-d '{
|
| 105 |
+
"model": "gemini/gemini-pro",
|
| 106 |
+
"messages": [{"role": "user", "content": "What is the capital of France?"}],
|
| 107 |
+
"stream": false
|
| 108 |
}'
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
### Example with Python `requests`:
|
| 112 |
+
|
| 113 |
+
```python
|
| 114 |
+
import requests
|
| 115 |
+
import json
|
| 116 |
+
|
| 117 |
+
proxy_url = "http://127.0.0.1:8000/v1/chat/completions"
|
| 118 |
+
proxy_key = "your-secret-proxy-key"
|
| 119 |
+
|
| 120 |
+
headers = {
|
| 121 |
+
"Content-Type": "application/json",
|
| 122 |
+
"Authorization": f"Bearer {proxy_key}"
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
data = {
|
| 126 |
+
"model": "gemini/gemini-pro",
|
| 127 |
+
"messages": [{"role": "user", "content": "What is the capital of France?"}],
|
| 128 |
+
"stream": False
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
response = requests.post(proxy_url, headers=headers, data=json.dumps(data))
|
| 132 |
+
|
| 133 |
+
print(response.json())
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
## Using the Library in Other Projects
|
| 137 |
+
|
| 138 |
+
The `rotating-api-key-client` library is designed to be reusable. You can find more information on how to use it in its own `README.md` file located at `src/rotator_library/README.md`.
|
|
@@ -1,6 +1,4 @@
|
|
| 1 |
fastapi
|
| 2 |
uvicorn
|
| 3 |
python-dotenv
|
| 4 |
-
|
| 5 |
-
requests
|
| 6 |
-
filelock
|
|
|
|
| 1 |
fastapi
|
| 2 |
uvicorn
|
| 3 |
python-dotenv
|
| 4 |
+
-e src/rotator_library
|
|
|
|
|
|
|
@@ -7,10 +7,10 @@ import logging
|
|
| 7 |
from pathlib import Path
|
| 8 |
import sys
|
| 9 |
|
| 10 |
-
#
|
| 11 |
-
sys.path.append(str(Path(__file__).resolve().parent.parent
|
| 12 |
|
| 13 |
-
from
|
| 14 |
|
| 15 |
# Configure logging
|
| 16 |
logging.basicConfig(level=logging.INFO)
|
|
|
|
| 7 |
from pathlib import Path
|
| 8 |
import sys
|
| 9 |
|
| 10 |
+
# Add the 'src' directory to the Python path to allow importing 'rotating_api_key_client'
|
| 11 |
+
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 12 |
|
| 13 |
+
from rotator_library import RotatingClient
|
| 14 |
|
| 15 |
# Configure logging
|
| 16 |
logging.basicConfig(level=logging.INFO)
|
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rotating API Key Client
|
| 2 |
+
|
| 3 |
+
A simple, thread-safe client that intelligently rotates and retries API keys for use with `litellm`.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- **Smart Key Rotation**: Automatically uses the least-used key to distribute load.
|
| 8 |
+
- **Automatic Retries**: Retries requests on transient server errors.
|
| 9 |
+
- **Cooldowns**: Puts keys on a temporary cooldown after rate limit or authentication errors.
|
| 10 |
+
- **Usage Tracking**: Tracks daily and global usage for each key.
|
| 11 |
+
- **Provider Agnostic**: Works with any provider supported by `litellm`.
|
| 12 |
+
|
| 13 |
+
## Installation
|
| 14 |
+
|
| 15 |
+
To install the library, you can install it directly from a Git repository or a local path.
|
| 16 |
+
|
| 17 |
+
### From a local path:
|
| 18 |
+
|
| 19 |
+
```bash
|
| 20 |
+
pip install -e .
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
## Usage
|
| 24 |
+
|
| 25 |
+
Here's a simple example of how to use the `RotatingClient`:
|
| 26 |
+
|
| 27 |
+
```python
|
| 28 |
+
import asyncio
|
| 29 |
+
from rotating_api_key_client import RotatingClient
|
| 30 |
+
|
| 31 |
+
async def main():
|
| 32 |
+
# List of your API keys
|
| 33 |
+
api_keys = ["key1", "key2", "key3"]
|
| 34 |
+
|
| 35 |
+
# Initialize the client
|
| 36 |
+
client = RotatingClient(api_keys=api_keys)
|
| 37 |
+
|
| 38 |
+
# Make a request
|
| 39 |
+
response = await client.acompletion(
|
| 40 |
+
model="gemini/gemini-pro",
|
| 41 |
+
messages=[{"role": "user", "content": "Hello, how are you?"}]
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
print(response)
|
| 45 |
+
|
| 46 |
+
if __name__ == "__main__":
|
| 47 |
+
asyncio.run(main())
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
By default, the client will store usage data in a `key_usage.json` file in the current working directory. You can customize this by passing the `usage_file_path` parameter:
|
| 51 |
+
|
| 52 |
+
```python
|
| 53 |
+
client = RotatingClient(api_keys=api_keys, usage_file_path="/path/to/your/usage.json")
|
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Rotating API Key Client
|
| 3 |
+
"""
|
| 4 |
+
from .client import RotatingClient
|
| 5 |
+
from .usage_manager import UsageManager
|
| 6 |
+
from .error_handler import is_authentication_error, is_rate_limit_error, is_server_error, is_unrecoverable_error
|
| 7 |
+
from .failure_logger import log_failure
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"RotatingClient",
|
| 11 |
+
"UsageManager",
|
| 12 |
+
"is_authentication_error",
|
| 13 |
+
"is_rate_limit_error",
|
| 14 |
+
"is_server_error",
|
| 15 |
+
"is_unrecoverable_error",
|
| 16 |
+
"log_failure",
|
| 17 |
+
]
|
|
@@ -18,12 +18,12 @@ class RotatingClient:
|
|
| 18 |
A client that intelligently rotates and retries API keys using LiteLLM,
|
| 19 |
with support for both streaming and non-streaming responses.
|
| 20 |
"""
|
| 21 |
-
def __init__(self, api_keys: List[str], max_retries: int = 2):
|
| 22 |
if not api_keys:
|
| 23 |
raise ValueError("API keys list cannot be empty.")
|
| 24 |
self.api_keys = api_keys
|
| 25 |
self.max_retries = max_retries
|
| 26 |
-
self.usage_manager = UsageManager()
|
| 27 |
|
| 28 |
async def _streaming_wrapper(self, stream: Any, key: str, model: str) -> AsyncGenerator[Any, None]:
|
| 29 |
"""
|
|
|
|
| 18 |
A client that intelligently rotates and retries API keys using LiteLLM,
|
| 19 |
with support for both streaming and non-streaming responses.
|
| 20 |
"""
|
| 21 |
+
def __init__(self, api_keys: List[str], max_retries: int = 2, usage_file_path: str = "key_usage.json"):
|
| 22 |
if not api_keys:
|
| 23 |
raise ValueError("API keys list cannot be empty.")
|
| 24 |
self.api_keys = api_keys
|
| 25 |
self.max_retries = max_retries
|
| 26 |
+
self.usage_manager = UsageManager(file_path=usage_file_path)
|
| 27 |
|
| 28 |
async def _streaming_wrapper(self, stream: Any, key: str, model: str) -> AsyncGenerator[Any, None]:
|
| 29 |
"""
|
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=61.0"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "rotating-api-key-client"
|
| 7 |
+
version = "0.1.0"
|
| 8 |
+
authors = [
|
| 9 |
+
{ name="Mirrowel", email="you@example.com" },
|
| 10 |
+
]
|
| 11 |
+
description = "A client that intelligently rotates and retries API keys using LiteLLM."
|
| 12 |
+
readme = "README.md"
|
| 13 |
+
requires-python = ">=3.7"
|
| 14 |
+
classifiers = [
|
| 15 |
+
"Programming Language :: Python :: 3",
|
| 16 |
+
"License :: OSI Approved :: MIT License",
|
| 17 |
+
"Operating System :: OS Independent",
|
| 18 |
+
]
|
| 19 |
+
dependencies = [
|
| 20 |
+
"litellm",
|
| 21 |
+
"filelock",
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
[project.urls]
|
| 25 |
+
"Homepage" = "https://github.com/Mirrowel/LLM-API-Key-Proxy"
|
| 26 |
+
"Bug Tracker" = "https://github.com/Mirrowel/LLM-API-Key-Proxy/issues"
|
| 27 |
+
|
| 28 |
+
[tool.setuptools.packages]
|
| 29 |
+
find = { where = ["."], include = ["rotator_library*"] }
|
|
File without changes
|