Proxy / README.md
CJHauser's picture
Update README.md
f69bc37 verified
|
Raw
History Blame Contribute Delete
6.22 kB
---
title: Unified AI Proxy
emoji: πŸ”€
colorFrom: blue
colorTo: indigo
sdk: docker
pinned: false
---
# πŸ”€ Unified AI Proxy
A self-hosted, OpenAI-compatible API gateway that routes requests to any AI provider from a single endpoint. Manage all your provider keys through a clean dashboard and hit one URL for everything.
---
## Features
- **Single endpoint** for OpenAI, Anthropic, Groq, Together, Mistral, Cohere, DeepSeek, OpenRouter, or any OpenAI-compatible API
- **Provider routing** via `providerName/model-name` prefix in the model field
- **Streaming support** β€” SSE responses forwarded without buffering
- **Unified model list** β€” `GET /v1/models` aggregates all providers, prefixed by name
- **Dashboard** β€” add/edit/delete providers, view request logs, manage your master key
- **Persistent SQLite** β€” data survives Space restarts (mount `/data` as persistent storage)
- **Env var seeding** β€” pre-seed providers via `PROVIDER_<NAME>_URL` + `PROVIDER_<NAME>_KEY`
---
## Quick Start
### 1. Deploy to Hugging Face Spaces
1. Create a new Space β†’ SDK: **Docker**
2. Upload these files: `Dockerfile`, `main.py`, `requirements.txt`, `README.md`
3. Enable **Persistent Storage** in Space settings β†’ mount at `/data`
4. The Space starts and prints your **Master API Key** to the build logs β€” copy it
### 2. Open the Dashboard
Navigate to your Space URL. Enter your master key to unlock the dashboard.
### 3. Add Providers
In the **Providers** tab, add any OpenAI-compatible backend:
| Provider | Base URL |
|----------|----------|
| OpenAI | `https://api.openai.com/v1` |
| Anthropic | `https://api.anthropic.com/v1` |
| Groq | `https://api.groq.com/openai/v1` |
| Together | `https://api.together.xyz/v1` |
| Mistral | `https://api.mistral.ai/v1` |
| Cohere | `https://api.cohere.ai/compatibility/v1` |
| DeepSeek | `https://api.deepseek.com/v1` |
| OpenRouter | `https://openrouter.ai/api/v1` |
### 4. Make Requests
Use your Space URL as the base URL in any OpenAI-compatible client:
```bash
# Route to a specific provider using providerName/ prefix
curl https://your-space.hf.space/v1/chat/completions \
-H "Authorization: Bearer YOUR_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "groq/llama3-8b-8192",
"messages": [{"role": "user", "content": "Hello!"}]
}'
# Together AI with a nested model path
curl https://your-space.hf.space/v1/chat/completions \
-H "Authorization: Bearer YOUR_MASTER_KEY" \
-d '{
"model": "together/meta-llama/Llama-3-70b-chat-hf",
"messages": [{"role": "user", "content": "Hi"}],
"stream": true
}'
# Use default provider (no prefix needed)
curl https://your-space.hf.space/v1/chat/completions \
-H "Authorization: Bearer YOUR_MASTER_KEY" \
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]}'
# List all models across all providers
curl https://your-space.hf.space/v1/models \
-H "Authorization: Bearer YOUR_MASTER_KEY"
```
### Use with the OpenAI Python SDK
```python
from openai import OpenAI
client = OpenAI(
base_url="https://your-space.hf.space/v1",
api_key="YOUR_MASTER_KEY",
)
# Route to Groq
response = client.chat.completions.create(
model="groq/llama3-8b-8192",
messages=[{"role": "user", "content": "Hello!"}],
)
# Route to Anthropic (via OpenAI-compat layer)
response = client.chat.completions.create(
model="anthropic/claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "Hello!"}],
)
```
---
## Environment Variable Seeding
Pre-seed providers without touching the dashboard by setting Space secrets:
```
PROVIDER_OPENAI_URL=https://api.openai.com/v1
PROVIDER_OPENAI_KEY=sk-...
PROVIDER_GROQ_URL=https://api.groq.com/openai/v1
PROVIDER_GROQ_KEY=gsk_...
PROVIDER_TOGETHER_URL=https://api.together.xyz/v1
PROVIDER_TOGETHER_KEY=...
```
These are imported on startup if the provider name doesn't already exist in the database.
---
## API Reference
| Route | Auth | Description |
|-------|------|-------------|
| `GET /` | Master key (dashboard login) | Web dashboard |
| `GET /health` | None | Health check + provider count |
| `GET /v1/models` | Master key | Aggregated model list from all providers |
| `POST /v1/chat/completions` | Master key | Proxied chat completions |
| `POST /v1/completions` | Master key | Proxied text completions |
| `POST /v1/embeddings` | Master key | Proxied embeddings |
| `GET /v1/*` | Master key | Any other OpenAI-compatible endpoint |
---
## Model Routing
The proxy determines which provider to use based on the `model` field:
1. **With prefix** β€” `groq/llama3-8b-8192` β†’ routes to the provider named `Groq`, forwards model as `llama3-8b-8192`
2. **Nested paths** β€” `together/meta-llama/Llama-3-70b` β†’ routes to `Together`, forwards `meta-llama/Llama-3-70b`
3. **No prefix** β€” uses the provider marked as **Default** in Settings
4. **No default set** β€” uses the first enabled provider
Matching is **case-insensitive**: `GROQ/`, `groq/`, and `Groq/` all route to a provider named `Groq`.
---
## Dashboard Tabs
### Providers
Add, edit, enable/disable, and delete providers. Set one as the default. API keys are stored encrypted in SQLite and only the last 4 characters are ever displayed.
### Logs
Auto-refreshing table (every 5s) of the last 200 requests: timestamp, provider, model, endpoint, status code, and latency. Automatically truncated at 500 stored rows.
### Settings
View and copy your master key, regenerate it (invalidates immediately), and set the default provider.
---
## Security Notes
- Provider API keys are **never returned** by any API endpoint β€” only the last 4 characters are shown in the dashboard
- The master key protects all `/v1/*` endpoints and the dashboard
- CORS is open (`*`) so the proxy can be called from any frontend β€” restrict this in production if needed
- The dashboard login stores the master key in `sessionStorage` (cleared on tab close)
---
## Local Development
```bash
pip install fastapi uvicorn httpx sqlalchemy
uvicorn main:app --reload --port 7860
# Open http://localhost:7860
# Master key printed to terminal on first run
```
Data is stored in `./db.sqlite` when `/data` doesn't exist.