Mirrowel commited on
Commit
79a70b2
·
1 Parent(s): aea7b14

feat: Refactor project into reusable API key rotation library and proxy

Browse files

This 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 CHANGED
@@ -1,74 +1,138 @@
1
- # API Key Proxy and Rotator [![License](https://licensebuttons.net/l/by-nc-sa/4.0/88x31.png)](https://creativecommons.org/licenses/by-nc-sa/4.0/) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/C0C0UZS4P)
2
 
3
- This project provides a simple and effective solution for managing and rotating API keys for services like Google Gemini, while exposing an OpenAI-compatible endpoint. It's designed to be a lightweight, self-hosted proxy that can help you:
4
 
5
- - **Secure your API keys**: Instead of embedding your keys directly in client-side applications, you can keep them on the server where they are more secure.
6
- - **Rotate keys to avoid rate limits**: The proxy can automatically rotate through a pool of API keys, reducing the chance of hitting rate limits on any single key.
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
- - **OpenAI-Compatible Endpoint**: Drop-in replacement for OpenAI's API.
13
- - **Smart Key Rotation**: Rotates keys based on usage to minimize errors.
14
- - **Usage Tracking**: Logs successes and failures for each key.
15
- - **Streaming and Non-Streaming Support**: Handles both response types seamlessly.
16
- - **Easy to Deploy**: Can be run with a simple `uvicorn` command.
 
17
 
18
- ## Getting Started
19
 
20
- ### Prerequisites
21
-
22
- - Python 3.8+
23
- - An `.env` file with your API keys (see `.env.example`).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- ### Installation
26
 
27
  1. **Clone the repository:**
 
28
  ```bash
29
- git clone <your-repo-url>
30
- cd <your-repo-name>
31
  ```
32
 
33
- 2. **Install dependencies:**
 
 
 
 
 
 
 
 
 
 
34
  ```bash
35
  pip install -r requirements.txt
36
  ```
37
 
38
- 3. **Configure your API keys:**
 
 
 
 
 
 
39
 
40
- Create a `.env` file in the project root and add your keys. You'll need a `PROXY_API_KEY` to secure your proxy and at least one `GEMINI_API_KEY`.
41
 
42
  ```
43
- # .env
44
  PROXY_API_KEY="your-secret-proxy-key"
45
- GEMINI_API_KEY_1="your-gemini-key-1"
46
- GEMINI_API_KEY_2="your-gemini-key-2"
47
- # Add more Gemini keys as needed
 
 
 
48
  ```
49
 
50
- ### Running the Proxy
51
 
52
- You can run the proxy using `uvicorn`:
53
 
54
  ```bash
55
- uvicorn src.proxy_app.main:app --host 0.0.0.0 --port 8000
56
  ```
57
 
58
- The proxy will now be running and accessible at `http://localhost:8000`.
59
 
60
- ## How to Use
61
 
62
- To use the proxy, make a POST request to the `/v1/chat/completions` endpoint, making sure to include your `PROXY_API_KEY` in the `Authorization` header.
63
 
64
- Here's an example using `curl`:
65
 
66
  ```bash
67
- curl -X POST http://localhost:8000/v1/chat/completions \
68
  -H "Content-Type: application/json" \
69
  -H "Authorization: Bearer your-secret-proxy-key" \
70
  -d '{
71
- "model": "gemini-pro",
72
- "messages": [{"role": "user", "content": "Hello, how are you?"}]
 
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`.
requirements.txt CHANGED
@@ -1,6 +1,4 @@
1
  fastapi
2
  uvicorn
3
  python-dotenv
4
- litellm
5
- requests
6
- filelock
 
1
  fastapi
2
  uvicorn
3
  python-dotenv
4
+ -e src/rotator_library
 
 
src/proxy_app/main.py CHANGED
@@ -7,10 +7,10 @@ import logging
7
  from pathlib import Path
8
  import sys
9
 
10
- # This is necessary for the app to find the rotator_library module
11
- sys.path.append(str(Path(__file__).resolve().parent.parent.parent))
12
 
13
- from src.rotator_library.client import RotatingClient
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)
src/rotator_library/README.md ADDED
@@ -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")
src/rotator_library/__init__.py ADDED
@@ -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
+ ]
src/rotator_library/client.py CHANGED
@@ -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
  """
src/rotator_library/pyproject.toml ADDED
@@ -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*"] }
staged_changes.txt ADDED
File without changes