Use aibridge-exec in your project
aibridge-exec is the open-source Python package behind this playground. It gives you one interface to many LLM providers, with your own API key passed on every call. Use it as a library inside your Python code, or run it as a small HTTP service that any language can call.
It is stateless: it stores nothing, logs no keys and needs no database.
Supported providers
| Provider id | Provider | Notes |
|---|---|---|
openai | OpenAI | Chat Completions API |
anthropic | Anthropic | Messages API |
gemini | Google Gemini | Gemini API (AI Studio keys) |
xai | xAI (Grok) | OpenAI-compatible |
openrouter | OpenRouter | OpenAI-compatible, hundreds of models with one key |
groq | Groq | OpenAI-compatible |
deepseek | DeepSeek | OpenAI-compatible |
mistral | Mistral | OpenAI-compatible |
together | Together AI | OpenAI-compatible |
openai_compatible | Any OpenAI-compatible URL | vLLM, Ollama, LM Studio, gateways; you pass base_url |
Model ids change often, so the package doesn't hard-code them. Call list_models with your key
to get the ids your account can use right now.
Install
Python 3.11 or newer.
# Library only (depends on httpx)
pip install "aibridge-exec @ git+https://github.com/23ventures/aibridge-exec"
# Library plus the HTTP server (adds FastAPI and uvicorn)
pip install "aibridge-exec[server] @ git+https://github.com/23ventures/aibridge-exec"
In a requirements.txt, pin a tag or commit:
aibridge-exec @ git+https://github.com/23ventures/aibridge-exec@v0.2.0
With Poetry or uv:
poetry add "git+https://github.com/23ventures/aibridge-exec"
uv add "aibridge-exec @ git+https://github.com/23ventures/aibridge-exec"
Library quick start
import os
from aibridge_exec.providers import ChatClient
with ChatClient() as client:
models = client.list_models("anthropic", api_key=os.environ["ANTHROPIC_API_KEY"])
print([m.id for m in models][:5])
reply = client.chat(
"anthropic",
api_key=os.environ["ANTHROPIC_API_KEY"],
model=models[0].id,
system="Answer in one sentence.",
messages=[{"role": "user", "content": "What is a suspension bridge?"}],
)
print(reply.text)
print(reply.usage.input_tokens, reply.usage.output_tokens, reply.finish_reason)
Every provider takes the same arguments: model, messages (roles system, user,
assistant), and optionally system, temperature (0 to 2) and max_tokens. Differences
between providers are handled for you, for example Anthropic's separate system prompt and
required max_tokens, or Gemini's model role name.
Streaming
with ChatClient() as client:
for event in client.stream("openai", api_key=key, model="<model id>",
messages=[{"role": "user", "content": "Count to five"}]):
if event.type == "delta":
print(event.text, end="", flush=True)
else: # the last event is "done"
print("\n", event.finish_reason, event.usage)
Async
from aibridge_exec.providers import AsyncChatClient
async with AsyncChatClient() as client:
async for event in client.stream("gemini", api_key=key, model="<model id>", messages=messages):
...
reply = await client.chat("xai", api_key=key, model="<model id>", messages=messages)
Errors
All failures raise ProviderError with a stable code. Messages never contain your key.
code | Meaning |
|---|---|
auth | Missing, invalid or unauthorized key |
rate_limited | The provider throttled the request or the quota ran out |
bad_request | The provider rejected the request (bad parameters, unknown model) |
not_found | Model or endpoint not found |
upstream | Provider-side failure or overload |
timeout, network | The provider couldn't be reached in time |
from aibridge_exec.providers import ProviderError
try:
client.chat(...)
except ProviderError as error:
if error.code == "rate_limited":
retry_later()
else:
raise
Local models and gateways
client.chat("openai_compatible", api_key="unused", model="llama3.1",
base_url="http://localhost:11434/v1", messages=messages)
# Route a built-in provider through a proxy for every call
client = ChatClient(base_urls={"openai": "https://gateway.internal/openai/v1"})
Adding a provider
Adapters only describe HTTP requests and parse responses, so a new provider is one class:
subclass ProviderAdapter (or reuse OpenAICompatibleAdapter with a new ProviderInfo) and call
register_provider(MyAdapter()). See aibridge_exec/providers/anthropic.py for a complete example.
Run the HTTP server
pip install "aibridge-exec[server] @ git+https://github.com/23ventures/aibridge-exec"
export AIBRIDGE_EXEC_API_TOKENS="$(openssl rand -hex 32)"
aibridge-exec serve --host 0.0.0.0 --port 8000
Or with Docker, from a checkout of the repository:
docker build -t aibridge-exec .
docker run -p 8000:8000 -e AIBRIDGE_EXEC_API_TOKENS=change-me aibridge-exec
Interactive API docs are served at /docs.
Configuration
| Variable | Default | Purpose |
|---|---|---|
AIBRIDGE_EXEC_API_TOKENS | empty | Comma-separated service tokens. Callers send Authorization: Bearer <token>. Empty means no authentication: only for local use. |
AIBRIDGE_EXEC_PROVIDERS | all | Comma-separated provider ids to expose |
AIBRIDGE_EXEC_CORS_ORIGINS | empty | Browser origins allowed to call the server directly |
AIBRIDGE_EXEC_ALLOW_CUSTOM_BASE_URL | false | Let callers send base_url (enables openai_compatible). Leave off on public servers: it lets callers make the server request any URL. |
AIBRIDGE_EXEC_BASE_URL_<PROVIDER> | provider default | Server-side URL override, for example AIBRIDGE_EXEC_BASE_URL_OPENAI |
AIBRIDGE_EXEC_MAX_MESSAGES | 500 | Messages per request |
AIBRIDGE_EXEC_MAX_TOTAL_CHARS | 1000000 | Characters per request |
HTTP API
The provider key goes in the X-Provider-Key header, never in the URL or body. The service
token, if configured, goes in Authorization.
EXEC=http://localhost:8000
AUTH="Authorization: Bearer $AIBRIDGE_EXEC_API_TOKEN"
curl -s $EXEC/v1/providers -H "$AUTH"
curl -s $EXEC/v1/models -H "$AUTH" -H "X-Provider-Key: $OPENAI_API_KEY" \
-H 'Content-Type: application/json' -d '{"provider": "openai"}'
curl -N $EXEC/v1/chat -H "$AUTH" -H "X-Provider-Key: $OPENAI_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"provider": "openai", "model": "<model id>", "stream": true,
"messages": [{"role": "user", "content": "Hello"}]}'
A streamed reply is Server-Sent Events, one JSON object per event:
event: delta
data: {"type":"delta","text":"Hel"}
event: delta
data: {"type":"delta","text":"lo"}
event: done
data: {"type":"done","finish_reason":"stop","model":"...","usage":{"input_tokens":8,"output_tokens":2}}
If the provider fails mid-stream you get event: error with {"type":"error","error":{"code":...,"message":...}}.
Failures before the stream starts are normal HTTP errors with the same error object.
With "stream": false the response is {"text", "model", "finish_reason", "usage"}.
Security notes
- Keys are used for one request and then discarded. The server doesn't log request bodies or headers.
- Put the server behind TLS and set
AIBRIDGE_EXEC_API_TOKENSwhenever it's reachable from a network. - Keep
AIBRIDGE_EXEC_ALLOW_CUSTOM_BASE_URLoff unless every caller is trusted. - The package also contains a local Python code runtime (
aibridge_exec.code_runtime). It runs code as an ordinary process and is not a sandbox; don't expose it to untrusted input.
Want to try the providers first? Create a playground account