Skip to content

Providers

Provider selects one of two backends. Everything else about a provider — the endpoint, the auth mechanism, which request fields survive — follows from that choice.

pub enum Provider {
    Anthropic,
    AnthropicLocal,
    OpenAi,
    OpenAiCompatible,
    Gemini,
    Ollama,
}

The enum is #[non_exhaustive]: a match on it needs a _ arm, and new variants can arrive in a minor release.

Which provider uses which backend

Provider::is_anthropic() returns true for Anthropic and AnthropicLocal and false for the rest. That is the whole dispatch rule.

Variant Backend Default endpoint
Anthropic Anthropic-direct (reqwest) https://api.anthropic.com/v1/messages
AnthropicLocal Anthropic-direct (reqwest) https://api.anthropic.com/v1/messages — see below
OpenAi genai https://api.openai.com/v1/
OpenAiCompatible genai whatever the model name resolves to
Gemini genai https://generativelanguage.googleapis.com/v1beta/
Ollama genai http://localhost:11434/

AnthropicLocal does not default to a local endpoint

AnthropicLocal is for a self-hosted Anthropic-compatible endpoint — an in-house proxy, a gateway, Claude Code Local. The wire format is identical to Cloud, which is why it shares the backend.

What it does not do is change the default endpoint. With base_url: None, Provider::AnthropicLocal sends to https://api.anthropic.com/v1/messages, exactly like Provider::Anthropic. Always set base_url when you use it.

The variant still matters: it documents intent, and it is the one to keep an eye on if a future release starts treating the two differently.

Feature support by backend

Everything in this table was checked against the request builders in src/client.rs and src/anthropic.rs.

Capability Anthropic-direct genai
ChatRequest::system yes yes
ChatRequest::messages yes yes
ChatRequest::temperature yes dropped
ChatRequest::max_tokens yes (defaults to 1024) dropped
ChatRequest::cache_control yes no — Anthropic-only feature
ChatRequest::thinking yes no — Anthropic-only feature
Config::base_url yes ignored
Config::timeout yes ignored
ChatResponse::citations yes, when the model emits them always empty
Usage cache fields yes always 0
AiError::Auth on 401/403 yes no — arrives as AiError::Provider
AiError::RateLimited on 429 yes no — arrives as AiError::Provider
ChatStreamEvent::ThinkingToken yes, from thinking_delta yes, from genai reasoning chunks
chat_structured yes yes

The four entries in bold are the ones that surprise people, because the field exists, compiles, and does nothing. They are covered in What rtb-chat does not do.

The model name picks the adapter

On the genai backend, Provider decides which environment variable the key is written to — and nothing else. Routing is genai's, and genai routes on the model name:

Model name genai adapter
gpt-*, o1*, o3*, o4*, chatgpt*, codex* OpenAI — or OpenAI Responses for gpt-5* and the codex/pro variants
gemini* Gemini
claude* Anthropic
grok*, command*, deepseek-*, moonshot-*, … that vendor's adapter
namespace::model the named adapter
anything else Ollama, at http://localhost:11434/

The fallback is the trap. Provider::OpenAiCompatible with a model name like llama-3.1-70b-instruct does not go to your OpenAI-compatible endpoint. It goes to localhost, because the name matches nothing and Ollama is genai's catch-all:

provider error: Web call failed for model 'llama-3.1-70b-instruct (adapter: Ollama)'.
Cause: Reqwest error: error sending request for url (http://localhost:11434/api/chat)

If you need a specific adapter, name it with genai's namespace:: prefix — for example together::meta-llama/Llama-3-70b. That gets the routing right, but not the key: Provider::OpenAiCompatible writes Config::api_key to OPENAI_API_KEY, and each namespaced adapter reads its own variable (TOGETHER_API_KEY, GROQ_API_KEY), so the call fails with ApiKeyEnvNotFound unless you export that variable yourself. It still cannot change the endpoint, which is the underlying limitation.

Where the API key goes

Provider Mechanism
Anthropic, AnthropicLocal x-api-key header on each request
OpenAi, OpenAiCompatible std::env::set_var("OPENAI_API_KEY", …) in AiClient::new
Gemini std::env::set_var("GEMINI_API_KEY", …) in AiClient::new
Ollama nothing is set; the key is still required to be non-empty

The environment writes are process-wide and permanent for the life of the process. Read The genai backend puts your key in the process environment before building a second client.

Anthropic-direct request headers

Every Anthropic-direct call sends:

Header Value
x-api-key the exposed Config::api_key
anthropic-version 2023-06-01 — pinned in the crate, not configurable
content-type application/json
user-agent rtb-chat/<crate version>
accept text/event-stream, on chat_stream only

anthropic-version is a compile-time constant. There is no config field for it, so a Messages API feature gated behind a newer version string needs a crate release.