Install
- Python
- TypeScript
- Rust
WarpLLM is synchronous; AsyncWarpLLM is
the await-able twin, and both take the same arguments.Constructing a client
- Python
- TypeScript
- Rust
AsyncWarpLLM takes the
same four.Configuration options
All timeouts are seconds, in every language. See Timeouts.
There is no client-wide base URL. To point a provider somewhere else — a
corporate proxy, or a test double — set
base_url on that provider’s entry
under Declaring providers. It affects that provider
alone.Declaring providers
By default a client serves warpllm’s whole roster and reads every provider’s environment variable. Declaring narrows both at once.- Python
- TypeScript
- Rust
ProviderOptions is a TypedDict with two optional fields, api_key and
base_url. To point a provider elsewhere:base_url overrides that provider’s address — a proxy, or a test
double — and wins over the roster’s own. It is per-provider: it never touches
any other.
It narrows two things:
1
What is read
Only the declared providers’ environment variables are consulted, so a key
exported for something else is not quietly adopted.
2
What is routable
A model under a provider you did not declare is refused at admission with
provider_not_declared, before any socket opens — so a client serving one
provider cannot be talked into billing another by a model string.Chat completions
- Python
- TypeScript
- Rust
Mapping, so any key type-checks.AsyncWarpLLM has the same method, awaited:DEEPSEEK_API_KEY” for deepseek/typo would send you after a credential you
already have.
Streaming
- Python
- TypeScript
- Rust
stream=True returns a ChatCompletionStream, matching the official OpenAI
SDK’s one-method shape. Dispatch is on the value, so behaviour is
correct wherever a checker cannot resolve the overload.AsyncWarpLLM returns an AsyncChatCompletionStream for async for. It
awaits its own native method rather than borrowing the sync client’s:
opening a stream is a request whose headers a provider may sit on, and
blocking on that inside a coroutine would stop the event loop.supported_apis.
The stream ending cleanly means the reply is complete. An upstream that
stopped early raises StreamTruncated after every chunk that did arrive, so a
truncated answer is never mistaken for a finished one. An error is terminal —
whatever produced it also ended the stream. See Streaming.
Errors
- Python
- TypeScript
- Rust
APIError → APIConnectionError, and APIError →
APIStatusError → each status class. So except APIStatusError catches
everything an upstream answered while letting a connection failure through.Types
- Python
- TypeScript
- Rust
TypedDicts, so they cost nothing at runtime and are optional.
chat_completions accepts any Mapping precisely because warpllm forwards
fields it does not model.Responses come back as plain dicts, so you subscript:
completion["choices"][0]["message"]["content"].Absent versus null
Response fields that are both optional and nullable distinguish a key the provider omitted from one it sent asnull.
- Python
- TypeScript
- Rust
A missing key versus a
None value. chunk["choices"][0]["delta"].get("content")
returns None for both — use in to tell them apart when it matters.Version
- Python
- TypeScript
- Rust
Shared behaviour
Everything below holds identically in all three:- Model strings are
provider/model, matched against a closed roster. - API keys are read from the environment once, when the client is built.
- Request fields warpllm does not model are forwarded to the provider untouched.
- The response’s
modelechoes your routing string, not the upstream name. - A stream that ends cleanly is a complete reply.
- There are no built-in retries and no fallback chain yet; both are planned.
Rust additionally has an unreleased
BalancedClientfor weighted load balancing — see Model routing.
Where the surfaces differ, and why
Rust splits streaming into its own method
Rust splits streaming into its own method
chat_completions with stream: Some(true) returns Error::InvalidInput
pointing at chat_completions_stream. A whole-reply return type cannot
carry chunks, so the entrypoint says where to go rather than quietly
serving the wrong shape.Python and Node dispatch on the value instead, matching the official OpenAI
SDK’s one-method shape in each language.Rust exposes warpllm's own error taxonomy
Rust exposes warpllm's own error taxonomy
warpllm::Error distinguishes RateLimited from QuotaExceeded, and
origin() says whether a provider ever answered. The bindings receive the
OpenAI rendering of that taxonomy instead.This is deliberate. A field on an SDK error object is a promise to keep
across versions; warpllm’s own vocabulary stays Rust-side, where changing
it costs a recompile instead of a major version. The
gateway’s HTTP envelope is the one
other surface that carries it.Responses are plain data, not wrapper objects
Responses are plain data, not wrapper objects
Rust has already parsed and validated the response. Rebuilding it into
Python or JavaScript objects would redo that work to hand back the same
fields under the same names.Python therefore subscripts (
completion["choices"][0]) while Node and
Rust use dot access.Static typing for streams is best-effort in Python
Static typing for streams is best-effort in Python
warpllm takes one mapping so unmodeled fields cross untouched, which means
the streaming overload has to match the mapping’s type — and a dict
literal carrying extra keys matches no
TypedDict. Runtime behaviour is
exact; where the annotation has to be right, call
chat_completions_stream.Next
Errors
The full taxonomy and what is worth retrying.
Run the gateway
The same core behind an HTTP surface.