Skip to content

Configuration

Liter-llm reads configuration from three sources, applied in priority order (lower numbers win):

  1. Constructor arguments — passed directly to the client factory (create_client(...) / ClientConfigBuilder).
  2. JSON string — create_client_from_json(json) for bindings without a builder.
  3. TOML file — liter-llm.toml, auto-discovered by walking up from the current working directory.

The Rust core exposes the same surface to every language binding via the C FFI. Methods like chat, embed, etc. are called on the client instance returned by the factory; there is no top-level LlmClient class to subclass or pre-configure in the bindings.

Place a liter-llm.toml file in your project directory. FileConfig::discover() (Rust) and the proxy server walk up the directory tree looking for it.

api_key = "sk-..."
base_url = "https://api.openai.com/v1"
model_hint = "openai"
timeout_secs = 120
max_retries = 5
cooldown_secs = 30
health_check_secs = 60
cost_tracking = true
tracing = true
[cache]
max_entries = 512
ttl_seconds = 600
backend = "memory"
[budget]
global_limit = 50.0
enforcement = "hard"
[budget.model_limits]
"openai/gpt-4o" = 25.0
[rate_limit]
rpm = 60
tpm = 100000
window_seconds = 60
[in_flight_limit]
max_in_flight = 8
[[providers]]
name = "my-provider"
base_url = "https://my-llm.example.com/v1"
auth_header = "Bearer"
model_prefixes = ["my-provider/"]
Field Type Description
api_key string Provider API key. Wrapped in SecretString internally.
base_url string Override the provider’s base URL.
model_hint string Pre-resolve a provider (e.g. "openai"); skips prefix lookup.
timeout_secs int Per-request timeout in seconds (default: 60).
max_retries int Retries on 429/5xx with exponential backoff (default: 3).
cooldown_secs int Circuit-breaker cooldown after transient errors.
health_check_secs int Interval for background health checks.
cost_tracking bool Enable per-request cost calculation.
tracing bool Emit tracing spans for each request (see Observability).
extra_headers map Additional headers attached to every outgoing request.
Field Type Description
max_entries int Maximum cached responses (default: 256).
ttl_seconds int Time-to-live for each entry in seconds (default: 300).
backend string "memory" (default, in-process LRU) or an OpenDAL scheme ("redis", "s3", "fs", "gcs", …). Prefix with opendal: to select any OpenDAL service explicitly, e.g. "opendal:memory".
backend_config map OpenDAL backend-specific key-value config.

The backend and backend_config fields are only honored when liter-llm is compiled with the opendal-cache feature.

Field Type Description
global_limit float Maximum total spend in USD across all models.
model_limits map Per-model spend limits (model name → USD).
enforcement string "hard" (reject over-budget) or "soft" (warn only).
Field Type Description
rpm int Maximum requests per window.
tpm int Maximum tokens per window.
window_seconds int Window duration in seconds (default: 60).
Field Type Description
max_in_flight int Maximum outstanding provider requests. Must be greater than 0.

Array of custom provider definitions. Each entry contains:

Field Type Description
name string Unique provider name.
base_url string Provider’s API base URL.
auth_header string "Bearer" (default), "none", or a header name.
model_prefixes string[] Model name prefixes that route to this provider.

The constructors below match the actual binding surface. Other bindings (Go, Java, Kotlin Android, C#, Ruby, PHP, Elixir, Dart, Swift, Zig, WebAssembly) expose the same scalar arguments through their generated wrappers; JVM Kotlin applications use the Java binding from Kotlin. Use the matching API page under Reference for exact language signatures.

use liter_llm::{ClientConfigBuilder, DefaultClient};
use std::time::Duration;
let config = ClientConfigBuilder::new("sk-...")
.base_url("https://api.openai.com/v1")
.timeout(Duration::from_secs(120))
.max_retries(5)
.build();
let client = DefaultClient::new(config, Some("openai"))?;

Load from a liter-llm.toml:

use liter_llm::{FileConfig, DefaultClient};
if let Some(file) = FileConfig::discover()? {
let config = file.into_builder().build();
let client = DefaultClient::new(config, None)?;
}

ManagedClient::new(config, model_hint) is available with the tower feature and wires the full middleware stack (cache, budget, rate limit, cooldown, health, hooks, tracing) from the same ClientConfig.

create_client_from_json (createClientFromJson in TypeScript) accepts the same snake_case keys as liter-llm.toml, as a JSON object. Unknown keys are rejected.

Key Effect
api_key, base_url, model_hint, timeout_secs, max_retries, extra_headers Client settings, applied to the underlying HTTP client.
cache, budget, cooldown_secs, rate_limit, in_flight_limit, health_check_secs, cost_tracking, tracing Middleware. When any is set, chat, chat_stream, embed, list_models, image_generate, speech, transcribe, moderate, rerank, search and ocr run through the managed Tower stack. File, batch and response operations bypass it.
providers Each entry is registered process-wide as a custom provider (auth_header: "Bearer"/"Authorization"/unset sends a bearer token, "none" sends no auth header, any other value names a header that carries the raw key).

Hooks are not expressible in JSON; build a ClientConfig in Rust to use them. A build compiled without the tower feature rejects the middleware keys with an error instead of ignoring them.

Client configured with a custom base URL routes all requests to that endpoint

Python
import asyncio
import os
from liter_llm import create_client
from liter_llm._internal_bindings import ChatCompletionRequest
async def main() -> None:
client = create_client(api_key=os.environ["API_KEY"])
req = ChatCompletionRequest.from_json("{\"messages\":[{\"content\":\"Hello\",\"role\":\"user\"}],\"model\":\"local-model\"}")
result = await client.chat(req)
print(result.choices)
print(result.choices[0].message.content)
print(result.choices[0].finish_reason)
print(result.model)
asyncio.run(main())
Option Type Default Description
api_key string required Provider API key. Wrapped in SecretString internally.
base_url string from registry Override the provider’s base URL.
model_hint string none Pre-resolve a provider at construction (e.g. "openai").
timeout_secs int 60 Request timeout in seconds.
max_retries int 3 Retries on 429/5xx responses with exponential backoff.

API keys passed to the constructor are wrapped in secrecy::SecretString. They are never logged, serialized, or included in error messages. If the constructor is given an empty api_key, the builder reads the standard environment variable for the active provider:

Provider Environment variable
OpenAI OPENAI_API_KEY
Anthropic ANTHROPIC_API_KEY
Google (Gemini) GEMINI_API_KEY
Groq GROQ_API_KEY
Mistral MISTRAL_API_KEY
Cohere CO_API_KEY

Override base_url to point at a local inference server or a corporate proxy:

# Ollama running locally
base_url = "http://localhost:11434/v1"
let config = ClientConfigBuilder::new("unused")
.base_url("http://localhost:11434/v1")
.build();

The response cache is wired through the Tower middleware stack. In Rust, attach CacheLayer directly; in other languages, configure it via TOML (and use ManagedClient indirectly through the bindings’ high-level clients).

[cache]
max_entries = 256
ttl_seconds = 300
backend = "memory"

With the opendal-cache feature, the cache backend can be any OpenDAL service — Redis, S3, GCS, Azure Blob, local filesystem, and more.

[cache]
ttl_seconds = 3600
backend = "redis"
[cache.backend_config]
endpoint = "redis://localhost:6379"

Track and enforce spending limits per model and globally. Costs are recomputed after every successful response using liter_llm::cost::completion_cost.

[budget]
global_limit = 10.0
enforcement = "hard"
[budget.model_limits]
"openai/gpt-4o" = 5.0

Enforcement::Hard rejects requests with LiterLlmError::BudgetExceeded once the limit is reached. Enforcement::Soft emits a tracing::warn! but allows the request through.

Per-model RPM (requests per minute) and TPM (tokens per minute) limits using a fixed window.

[rate_limit]
rpm = 60
tpm = 100000
window_seconds = 60

Bound simultaneous provider requests across all operations that share a managed client. Requests wait for a permit; cached responses do not consume one.

[in_flight_limit]
max_in_flight = 8

Hooks are implemented via the Rust LlmHook trait and are wired into the Tower stack through HooksLayer. They are not currently exposed through the language bindings.

use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use liter_llm::error::{LiterLlmError, Result};
use liter_llm::tower::hooks::{HooksLayer, LlmHook};
use liter_llm::tower::types::{LlmRequest, LlmResponse};
struct LoggingHook;
impl LlmHook for LoggingHook {
fn on_request(&self, _req: &LlmRequest)
-> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>>
{
Box::pin(async {
tracing::info!("dispatching request");
Ok(())
})
}
fn on_response(&self, _req: &LlmRequest, _resp: &LlmResponse)
-> Pin<Box<dyn Future<Output = ()> + Send + '_>>
{
Box::pin(async {
tracing::info!("response received");
})
}
fn on_error(&self, _req: &LlmRequest, err: &LiterLlmError)
-> Pin<Box<dyn Future<Output = ()> + Send + '_>>
{
let message = err.to_string();
Box::pin(async move {
tracing::error!(error = %message, "request failed");
})
}
}
let hooks: Vec<Arc<dyn LlmHook>> = vec![Arc::new(LoggingHook)];
let layer = HooksLayer::new(hooks);

Returning Err from on_request short-circuits the service chain, so hooks can implement guardrails (content filtering, budget gating, etc.).

Custom providers are registered in a process-wide global registry. Once registered, any request whose model name matches one of the provider’s model_prefixes is routed there.

use liter_llm::{register_custom_provider, CustomProviderConfig, AuthHeaderFormat};
register_custom_provider(CustomProviderConfig {
name: "my-provider".into(),
base_url: "https://my-llm.example.com/v1".into(),
auth_header: AuthHeaderFormat::Bearer,
model_prefixes: vec!["my-provider/".into()],
})?;

Unregister with unregister_custom_provider("my-provider") (returns Result<bool>: Ok(true) if a provider with that name existed).

AuthHeaderFormat has three variants: Bearer (sends Authorization: Bearer <key>), ApiKey(header_name) (sends a custom header), and None (no auth header). FileProviderConfig::auth_header (from TOML or JSON) is a string; to_custom_provider_config() maps unset, "Bearer" and "Authorization" to Bearer, "none" to None, and any other value to ApiKey with that header name.

The tracing reference has moved to Observability. That page covers span attributes, OTEL exporter setup, cost tracking, and Tower layer composition.