LLM providers
An LLM provider is the service that writes a bot's replies. LiteBot ships with seven: Groq, OpenRouter, OpenAI, Gemini through AI Studio, Gemini through Vertex AI, Sarvam and Ollama. This chapter covers the variables each one reads, how a bot picks its provider and model, what happens when a provider fails, and how a tenant brings its own key. Read it before you set keys in .env, and again when replies start failing or coming from an unexpected model.
Realtime voice over /voice talks to Gemini Live directly and does not go through these providers; see Voice.
Choosing
A provider registers at startup only when its key (or, for Vertex and Ollama, its settings) is present. The log lists what registered:
Initialized 3 LLM provider(s): openrouter, gemini, gemini-vertex (default: gemini-vertex)
| Provider name | Registers when | Default model | Best for |
|---|---|---|---|
groq |
GROQ_API_KEY is set |
llama-3.3-70b-versatile |
Fast open models |
openrouter |
OPENROUTER_API_KEY is set |
meta-llama/llama-3.1-70b-instruct |
One key for many vendors' models |
gemini |
GEMINI_API_KEY is set |
gemini-1.5-flash when GEMINI_MODEL is unset |
Gemini with an API key |
gemini-vertex |
VERTEX_PROJECT_ID and a credentials file are both set |
VERTEX_MODEL, else GEMINI_MODEL, else models/gemini-2.5-flash |
Gemini from a Google Cloud region, lower first-token latency from India |
openai |
OPENAI_API_KEY is set |
gpt-4-turbo-preview |
OpenAI models |
sarvam |
SARVAM_API_KEY is set |
sarvam-30b |
Indian languages |
ollama |
OLLAMA_ENABLED=true or OLLAMA_BASE_URL is set |
llama3.2 |
Self-hosted models, no per-token cost |
The gemini fallback model gemini-1.5-flash is an old model. Always set GEMINI_MODEL; .env.example sets models/gemini-2.5-flash. Keep the models/ prefix as shown there.
Every provider supports both plain and streaming replies, so any of them works with "stream": true on the chat API (Chat API).
How a bot picks its provider and model
Each bot names a provider and, optionally, a model (Bot configuration reference).
{ "id": "support", "provider": "gemini-vertex", "model": "models/gemini-2.5-flash" }
providerdefaults togroqwhen the bot omits it. If Groq is not registered on your server, the bot always runs on a substitute, so setprovideron every bot.modelis optional. Without it, the provider's default model answers.temperature(default 0.7) andmaxTokens(default 1000) are passed with every request.
For each turn LiteBot resolves the provider in this order:
- The bot's tenant has its own key for that provider: a tenant-owned instance answers (Per-tenant provider keys).
- The named provider is registered and usable: it answers.
- Otherwise
DEFAULT_LLM_PROVIDER, if it is set, registered and usable. - Otherwise the first usable provider in registration order: groq, openrouter, gemini, gemini-vertex, openai, sarvam, ollama.
- If nothing is usable, the first registered provider is tried anyway, with the log line "All providers unhealthy, attempting with first available provider".
| Variable | Default | Meaning |
|---|---|---|
DEFAULT_LLM_PROVIDER |
unset (registration order) | Preferred substitute when a bot's provider is missing or unhealthy, and first choice for failover. A name that is not registered is ignored with a warning |
Model name on a substitute
When a different provider answers than the one the bot asked for, LiteBot drops the bot's model (and any Gemini cache handle) so the substitute answers with its own default model. Sending models/gemini-2.5-flash to OpenRouter would fail with a 400. The log shows:
Provider gemini unavailable; openrouter serving with its default model
Set each provider's *_MODEL variable to a model you are happy to be served by, because that is what callers get during an outage. Analytics record the provider that actually answered, not the one requested.
Groq, OpenRouter, OpenAI, Sarvam
| Variable | Default | Meaning |
|---|---|---|
GROQ_API_KEY |
unset | Registers Groq |
GROQ_MODEL |
llama-3.3-70b-versatile |
Default model |
OPENROUTER_API_KEY |
unset | Registers OpenRouter. A key shorter than 10 characters is rejected |
OPENROUTER_MODEL |
meta-llama/llama-3.1-70b-instruct |
Default model |
OPENROUTER_APP_NAME |
Bitvoice AI |
App name sent to OpenRouter |
OPENROUTER_SITE_URL |
https://bitvoice.ai |
Site URL sent to OpenRouter |
OPENROUTER_SORT |
unset | price, throughput or latency routing among OpenRouter's upstream hosts. Unset uses OpenRouter's own load balancing |
OPENROUTER_IDLE_TIMEOUT_MS |
30000 | Abort a stream that sends nothing for this long |
OPENAI_API_KEY |
unset | Registers OpenAI (and the OpenAI embeddings option, see Knowledge base) |
OPENAI_MODEL |
gpt-4-turbo-preview |
Default model |
OPENAI_ORGANIZATION |
unset | Organisation id, when your key needs one |
OPENAI_SERVICE_TIER |
auto |
OpenAI service tier |
SARVAM_API_KEY |
unset | Registers Sarvam (OpenAI-compatible API at api.sarvam.ai) |
SARVAM_MODEL |
sarvam-30b |
Default model |
Gemini (AI Studio)
| Variable | Default | Meaning |
|---|---|---|
GEMINI_API_KEY |
unset | Registers gemini |
GEMINI_MODEL |
gemini-1.5-flash |
Default model. Set it, for example models/gemini-2.5-flash |
GEMINI_HEDGE_DELAY_MS |
6000 | After this many milliseconds without an answer, send an identical second request and keep whichever answers first. 0 turns hedging off |
GEMINI_CACHE_ENABLED |
true | Context caching, see below. false turns it off |
GEMINI_CACHE_TTL |
3600s |
Lifetime of each cache |
GEMINI_CACHE_MIN_TOKENS |
2048 | Skip caching for prompts estimated below this size |
These behaviours apply to both Gemini transports.
- Context cache. For a bot with a system prompt, LiteBot caches the system prompt and tools on Google's side and reuses the cache on later turns. The first turn that misses starts creating the cache in the background and runs uncached. Prompts estimated below
GEMINI_CACHE_MIN_TOKENS(about four characters per token) are never cached, because Google rejects small caches. - Hedged requests. Gemini Flash usually answers in about a second but sometimes stalls for many seconds. Hedging fires a duplicate after the delay. Keep the delay well above your normal reply time, or you pay for two requests on healthy turns.
- Safety settings. All four harm categories are set to
BLOCK_ONLY_HIGH. The stricter medium setting blocked ordinary Malayalam healthcare speech. A blocked reply is reported with finish reasoncontent_filter. This is not configurable. - Thinking. Gemini 2.x Flash runs with a thinking budget of 0 and Gemini 3 Flash with thinking level
minimal, to keep first-token latency low. Text chat has no setting to change this;VOICE_THINKING_BUDGETapplies only to realtime voice. - Thought signatures. Gemini 3 models sign each tool call, and the signature must be sent back when the call is replayed in history or the request fails with a 400. LiteBot stores and replays it. For tool calls in old history without one (or made by another provider), it sends Google's documented bypass value so the turn still works.
Gemini on Vertex AI
gemini-vertex serves the same Gemini models from a Google Cloud region, authenticated with a service account instead of an API key. Bots opt in with "provider": "gemini-vertex". Measured from Mumbai, first token arrives in about 0.7 s on asia-south1 against about 1.2 s through AI Studio.
VERTEX_PROJECT_ID=my-gcp-project
VERTEX_LOCATION=asia-south1
VERTEX_CREDENTIALS_FILE=/opt/litebot/vertex-sa.json
VERTEX_MODEL=models/gemini-2.5-flash
DEFAULT_LLM_PROVIDER=gemini-vertex
| Variable | Default | Meaning |
|---|---|---|
VERTEX_PROJECT_ID |
unset | Google Cloud project. Required |
VERTEX_CREDENTIALS_FILE |
unset | Path to the service-account JSON key. Required unless GOOGLE_APPLICATION_CREDENTIALS is set |
GOOGLE_APPLICATION_CREDENTIALS |
unset | Used when VERTEX_CREDENTIALS_FILE is unset |
VERTEX_LOCATION |
asia-south1 |
Region; global uses the global endpoint |
VERTEX_MODEL |
GEMINI_MODEL, else models/gemini-2.5-flash |
Default model |
Before you start: enable the Vertex AI API on the project, create a service account with the Vertex AI User role (roles/aiplatform.user), download its JSON key, and make it readable by the user LiteBot runs as. The file must be a service_account key with client_email and private_key.
Setting only one of the two required values logs "gemini-vertex needs both VERTEX_PROJECT_ID and VERTEX_CREDENTIALS_FILE" and skips the provider. An unreadable or invalid key logs "gemini-vertex not registered" and leaves the other providers running.
Region to global fallback
Regions do not serve every model, and new models reach regions weeks after global. When the configured region returns 404 for a model, LiteBot remembers that model as global-only for the life of the process, retries once on the global endpoint, and logs "Vertex: model not served in the configured region; using the global endpoint for it". The first turn on such a model pays the extra round trip; later turns go straight to global. A restart forgets the mapping.
Context caching works on Vertex too. Knowledge-base embeddings follow the same Vertex settings automatically (Knowledge base).
Ollama
| Variable | Default | Meaning |
|---|---|---|
OLLAMA_ENABLED |
false | true registers Ollama. Setting OLLAMA_BASE_URL also registers it |
OLLAMA_BASE_URL |
http://localhost:11434 |
Ollama server |
OLLAMA_MODEL |
llama3.2 |
Default model |
OLLAMA_API_KEY |
unset | Bearer token for Ollama Cloud or an authenticating proxy |
OLLAMA_REQUEST_TIMEOUT |
120000 | Request timeout in milliseconds; cloud models cold-start slowly |
OLLAMA_KEEP_ALIVE |
30m |
How long the model stays loaded after a request (-1 forever, 0 unload) |
OLLAMA_THINKING |
false | true for thinking models |
LiteBot uses Ollama's native /api/chat endpoint. Ollama cannot take a per-tenant key.
Timeouts and the circuit breaker
Each provider call runs inside a circuit breaker. After a run of failures the breaker opens and rejects calls for a minute, then lets a probe through; two successful probes close it again.
| Variable | Default | Meaning |
|---|---|---|
ENABLE_CIRCUIT_BREAKER |
off | Must be exactly true. When off, calls run with no breaker, no breaker timeout and no in-request failover |
CIRCUIT_BREAKER_THRESHOLD |
5 | Failures before the breaker opens |
CIRCUIT_BREAKER_TIMEOUT |
unset | Timeout for every breaker, LLM and tools alike. When set it overrides all the values below |
<PROVIDER>_REQUEST_TIMEOUT |
unset | Per-provider timeout: the name upper-cased with non-alphanumerics as _, for example GEMINI_VERTEX_REQUEST_TIMEOUT |
LLM_REQUEST_TIMEOUT |
30000 | Timeout when no per-provider value is set (Ollama: 120000) |
DISABLE_PROVIDER_FALLBACK |
false | true turns off in-request failover. .env.example ships it as true |
Known gap: .env.example sets CIRCUIT_BREAKER_TIMEOUT=30000, which silently overrides LLM_REQUEST_TIMEOUT, the per-provider timeouts and every tool's metadata.timeout. Remove it if you rely on those. A breaker is created on first use with the settings of that moment, so timeout changes need a restart.
Failover within a request
With the breaker on and DISABLE_PROVIDER_FALLBACK not true, a failed or rejected call is retried once on another provider: DEFAULT_LLM_PROVIDER first, then registration order, skipping the one that failed. The bot's model is dropped so the substitute uses its default. There is no further fallback after that one retry. Streaming calls never fail over within the request; they rely on health marking for the next turn.
Health and recovery
Separately from the breaker, LiteBot tracks each provider's health.
- Three consecutive failed calls mark a provider unhealthy, with "Provider gemini marked unhealthy after 3 failures" in the log. Any success resets the count.
- A stream cut off mid-reply is treated as a network blip and does not count as a failure.
- An unhealthy provider is skipped when choosing a provider for new turns, so bots that name it move to a substitute.
- After
PROVIDER_RETRY_MS(default 60000) one request is allowed through as a probe ("Probing unhealthy provider gemini"). Success makes the provider healthy again; failure starts a new window. Only one request probes per window. POST /api/bots/providers/health-check(admin) probes every provider immediately and updates health.
PROVIDER_RETRY_MS and the probe are present in the current working tree but not yet committed or listed in .env.example. Without them, an unhealthy provider stays out until a restart or a manual health check.
curl -s http://127.0.0.1:3345/api/bots/providers/status -H "Authorization: Bearer <token>"
curl -s -X POST http://127.0.0.1:3345/api/bots/providers/health-check -H "Authorization: Bearer <token>"
{ "success": true, "data": { "available": ["gemini", "gemini-vertex"], "total": 2, "healthy": 1,
"providers": { "gemini": { "healthy": false, "consecutiveFailures": 3, "lastError": "Request failed with status code 402" } } } }
Responses to requests without tools are cached in Redis for 10 minutes when Redis is connected. The cache key includes the bot, provider, model, system prompt and messages.
Per-tenant provider keys
A tenant can bring its own key for groq, openrouter, gemini, openai or sarvam. Bots of that tenant then run on the tenant's key; other tenants keep using the global keys. gemini-vertex and ollama cannot take a tenant key.
curl -s -X PUT http://127.0.0.1:3345/api/tenants/acme/providers/gemini \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{"apiKey":"<tenant-gemini-key>","model":"models/gemini-2.5-flash"}'
{ "success": true, "data": { "provider": "gemini", "keyHint": "x9Qz", "model": "models/gemini-2.5-flash" } }
GET /api/tenants/:id/providerslists the providers with the last four characters of each key only.DELETE /api/tenants/:id/providers/:providerremoves a key.- Keys are encrypted at rest with a key derived from
JWT_SECRET. If you changeJWT_SECRET, stored keys can no longer be decrypted; LiteBot logs a warning and the tenant's bots fall back to the global key. - A tenant key that fails is never replaced by the global key: the error surfaces, the tenant instance is outside health marking and failover, and it has its own breaker.
modelis optional; without it the global default model for that provider is used.- Setting or removing a key takes effect at once on the node that received the request. Another node that had no key for that tenant notices a new one within a minute; a node that already built an instance with the old key keeps using it until it restarts.
Where to go next
- Set a bot's provider and model: Bot configuration reference
- Give the model tools to call: Tools
- Tenants and who may set keys: Users, roles and tenants
- What each model costs and how fast it answers: Cost and performance
- Replies failing or coming from the wrong model: Troubleshooting