OpenAI-compatible proxy to OpenRouter, targeting Qwen 3.6 Plus.
Any client that speaks the OpenAI API (SDKs, curl, etc.) can point at Sozo and get responses from Qwen 3.6 Plus via OpenRouter — no client-side configuration changes needed. If the upstream is rate-limited, Sozo automatically retries with exponential backoff.
# Set your OpenRouter API key
export OPENROUTER_API_KEY=sk-or-...
# Run with Docker Compose
docker compose up -d
# Or build and run locally
make build
./bin/sozoSozo exposes a standard OpenAI-compatible endpoint:
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "Hello!"}],
"stream": false
}'Streaming works too:
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "Hello!"}],
"stream": true
}'Or use any OpenAI SDK by pointing it at Sozo:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8080/v1", api_key="unused")
response = client.chat.completions.create(
model="anything", # ignored — Sozo rewrites to Qwen 3.6 Plus
messages=[{"role": "user", "content": "Hello!"}],
)| Variable | Required | Default | Description |
|---|---|---|---|
OPENROUTER_API_KEY |
yes | — | Your OpenRouter API key |
LISTEN_ADDR |
no | :8080 |
Listen address |
OPENROUTER_BASE_URL |
no | https://openrouter.ai/api/v1 |
Upstream URL |
DEFAULT_MODEL |
no | qwen/qwen3.6-plus:free |
Model ID injected into requests |
| Method | Path | Description |
|---|---|---|
GET |
/ |
Redirects to /docs |
POST |
/v1/chat/completions |
Chat completions (streaming and non-streaming) |
GET |
/health |
Health check |
GET |
/swagger.json |
OpenAPI 3.0 spec |
GET |
/docs |
Swagger UI |
Sozo is designed to sit behind Traefik. The included docker-compose.yml has Traefik labels pre-configured for production. For local development, docker-compose.override.yml routes via sozo.localhost on the https entrypoint.
To deploy to production, update the host rule in docker-compose.yml:
labels:
- "traefik.http.routers.sozo.rule=Host(`your-domain.com`)"Both compose files join the external traefik_traefik network. The Docker image is ~14MB (multi-stage build to scratch).
make build # Build binary
make test # Run tests with race detector
make docker # Build Docker image
make up # docker compose up -d
make down # docker compose down
make smoke-test # Smoke test against running instanceTBD