aibridge

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 idProviderNotes
openaiOpenAIChat Completions API
anthropicAnthropicMessages API
geminiGoogle GeminiGemini API (AI Studio keys)
xaixAI (Grok)OpenAI-compatible
openrouterOpenRouterOpenAI-compatible, hundreds of models with one key
groqGroqOpenAI-compatible
deepseekDeepSeekOpenAI-compatible
mistralMistralOpenAI-compatible
togetherTogether AIOpenAI-compatible
openai_compatibleAny OpenAI-compatible URLvLLM, 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.

codeMeaning
authMissing, invalid or unauthorized key
rate_limitedThe provider throttled the request or the quota ran out
bad_requestThe provider rejected the request (bad parameters, unknown model)
not_foundModel or endpoint not found
upstreamProvider-side failure or overload
timeout, networkThe 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

VariableDefaultPurpose
AIBRIDGE_EXEC_API_TOKENSemptyComma-separated service tokens. Callers send Authorization: Bearer <token>. Empty means no authentication: only for local use.
AIBRIDGE_EXEC_PROVIDERSallComma-separated provider ids to expose
AIBRIDGE_EXEC_CORS_ORIGINSemptyBrowser origins allowed to call the server directly
AIBRIDGE_EXEC_ALLOW_CUSTOM_BASE_URLfalseLet 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 defaultServer-side URL override, for example AIBRIDGE_EXEC_BASE_URL_OPENAI
AIBRIDGE_EXEC_MAX_MESSAGES500Messages per request
AIBRIDGE_EXEC_MAX_TOTAL_CHARS1000000Characters 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_TOKENS whenever it's reachable from a network.
  • Keep AIBRIDGE_EXEC_ALLOW_CUSTOM_BASE_URL off 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