Choose a provider¶
Talk to Anthropic Cloud¶
The default. Config::default() already selects it:
use rtb_chat::{AiClient, Config, Provider};
use secrecy::SecretString;
let client = AiClient::new(Config {
provider: Provider::Anthropic,
model: "claude-opus-4-7".into(),
api_key: SecretString::from(std::env::var("ANTHROPIC_API_KEY")?),
..Config::default()
})?;
This is the backend that supports prompt caching, extended thinking, citations and typed auth and rate-limit errors. Prefer it when you have the choice.
Talk to an Anthropic-compatible gateway¶
Use Provider::AnthropicLocal and set base_url. Without the override it
sends to api.anthropic.com, exactly like Provider::Anthropic:
use url::Url;
let client = AiClient::new(Config {
provider: Provider::AnthropicLocal,
model: "claude-opus-4-7".into(),
base_url: Some(Url::parse("https://gateway.internal/anthropic")?),
api_key: SecretString::from(std::env::var("GATEWAY_TOKEN")?),
..Config::default()
})?;
/v1/messages is appended to whatever you supply, with any trailing slash
trimmed first.
Talk to OpenAI or Gemini¶
let client = AiClient::new(Config {
provider: Provider::OpenAi,
model: "gpt-4.1".into(),
api_key: SecretString::from(std::env::var("OPENAI_API_KEY")?),
..Config::default()
})?;
One thing to know before you ship this: AiClient::new writes the key into the
process environment as OPENAI_API_KEY (or GEMINI_API_KEY for Gemini). Build
one client per process; a second client for the same provider overwrites the
first one's key. See
What rtb-chat does not do.
Talk to a local Ollama¶
let client = AiClient::new(Config {
provider: Provider::Ollama,
model: "llama3".into(),
// Required to be non-empty, never sent anywhere.
api_key: SecretString::from("unused".to_string()),
..Config::default()
})?;
The endpoint is genai's default, http://localhost:11434/. To reach Ollama on
another host, set base_url, and allow_insecure_base_url: true if it serves
plain http://.
Point at an OpenAI-compatible endpoint¶
Use Provider::OpenAiCompatible and set base_url to the endpoint's base, the
part before chat/completions:
let client = AiClient::new(Config {
provider: Provider::OpenAiCompatible,
model: "meta-llama/Llama-3-70b-chat-hf".into(),
base_url: Some(Url::parse("https://api.together.xyz/v1")?),
api_key: SecretString::from(std::env::var("TOGETHER_API_KEY")?),
..Config::default()
})?;
The request goes to https://api.together.xyz/v1/chat/completions through
genai's OpenAI adapter, whatever the model is called. The key travels the same way
as for Provider::OpenAi: through OPENAI_API_KEY in the process environment.
Without base_url there is no endpoint to send to, and genai falls back to
routing on the model name. The next section shows how to spot that.
Check the request went where you meant¶
On the genai path with no base_url, routing is quiet: no error, just an answer
from somewhere else, or a connection refused on localhost.
Two ways to confirm:
Read the INFO log. AiClient::new emits one line with the provider and the
host it will use:
The literal openai-compatible is the tell that there is no real endpoint behind
this client.
Read the error. genai names the adapter it chose when a call fails:
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)
adapter: Ollama on a provider you did not set to Ollama means the model name
matched nothing and fell through to the default.