llm-universal
One async Rust client for many LLM APIs. Write your chat and tool-calling code once and switch providers by switching the constructor.
| Provider | Constructor | API |
|---|---|---|
| Anthropic (Claude) | AnthropicProvider::new(key) |
Messages API |
| OpenAI | OpenAiProvider::new(key) |
Chat Completions |
| Google Gemini | GeminiProvider::new(key) |
generateContent |
| Cohere | CohereProvider::new(key) |
Chat API v2 |
| Azure OpenAI | OpenAiProvider::azure(resource, key) |
Azure OpenAI v1 (api-key header) |
| Groq | OpenAiProvider::groq(key) |
OpenAI-compatible |
| Mistral | OpenAiProvider::mistral(key) |
OpenAI-compatible |
| DeepSeek | OpenAiProvider::deepseek(key) |
OpenAI-compatible |
| xAI (Grok) | OpenAiProvider::xai(key) |
OpenAI-compatible |
| OpenRouter | OpenAiProvider::openrouter(key) |
OpenAI-compatible |
| Together AI | OpenAiProvider::together(key) |
OpenAI-compatible |
| Fireworks AI | OpenAiProvider::fireworks(key) |
OpenAI-compatible |
| Perplexity | OpenAiProvider::perplexity(key) |
OpenAI-compatible |
| Ollama (local) | OpenAiProvider::ollama() |
OpenAI-compatible |
| LM Studio (local) | OpenAiProvider::lm_studio() |
OpenAI-compatible |
| Anything else OpenAI-compatible (vLLM, LiteLLM, …) | OpenAiProvider::new(key).with_base_url(url) |
OpenAI-compatible |
Features:
- Tool calling with one normalized format for every provider
- Errors modeled on the official SDKs (
BadRequest,Authentication,RateLimit, …, request IDs, retry rules) - Client-side validation before any network call
- Safe defaults:
- connect/request timeouts and a 32 MiB response cap
- no redirects, so the key never goes to another host
- keys are refused over plain
http://to non-local hosts - keys are redacted from
Debugand marked sensitive in headers - URLs are stripped from connection errors
Install
[]
= "0.2"
= { = "1", = ["macros", "rt-multi-thread"] }
Quick start
use ;
async
Local OpenAI-compatible server:
# use OpenAiProvider;
let ollama = new.with_base_url;
Tool calling
# use *;
# use json;
# async
A complete loop is in examples/tools.rs: cargo run --example tools.
Errors
LlmError variant |
When | SDK equivalent |
|---|---|---|
Api(Box<ApiError>) |
non-2xx response | APIStatusError and subclasses |
Connection |
DNS, TLS, dropped connection | APIConnectionError |
Timeout |
request timed out | APIConnectionTimeoutError |
Deserialize |
body is not JSON (content keeps the start of it; not printed) |
JSONDeserialize (async-openai) |
InvalidResponse |
JSON is missing expected fields | — |
InvalidArgument |
client-side validation failed | InvalidArgument (async-openai) |
ApiError carries status, kind (ApiErrorKind::RateLimit, …), message, error_type,
code, param, request_id and the JSON body. Its message format matches the SDKs:
401 invalid x-api-key
429 Rate limit reached for requests
invalid args: model must not be empty
# use *;
# async
is_retryable() follows the SDK retry rules: 408, 409, 429, >= 500, connection errors and timeouts.
Notes
- Anthropic newer models accept only
temperature = 1.0; leavetemperatureunset unless you need it. - Extended-thinking blocks are not part of the normalized message; if you enable thinking, keep the raw response to send signed thinking blocks back unchanged.
- Gemini tool calls without an id get a local id (
llm-universal-call-N) that is never sent back to the API. - Amazon Bedrock and Vertex AI need cloud-signed requests and are not supported yet.
- Streaming is not supported yet.
License
MIT-0 (MIT No Attribution): use, copy, modify and sell it for any purpose, commercial included, with no attribution required.