smithery/majiayu000

config-architecture

LiteLLM-RS Configuration Architecture. Covers YAML loading with ${VAR} environment substitution, strict serde config models, startup validation via the Validate trait, and the LITELLM_* env-only fallback. Use when changing config models or serde defaults, debugging env variable substitution failures, updating validation rules, altering config loading behavior, or troubleshooting gateway.yaml.

Installation

$ npx skills add smithery/majiayu000 --skill config-architecture

Summary

  • LiteLLM-RS Configuration Architecture.
  • Covers YAML loading with ${VAR} environment substitution, strict serde config models, startup validation via the Validate trait, and the LITELLM_* env-only fallback.
  • Use when changing config models or serde defaults, debugging env variable substitution failures, updating validation rules, altering config loading behavior, or troubleshooting gateway.yaml.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/majiayu000 · top by installs.

npx skills add smithery/majiayu000

Browse all from smithery/majiayu000

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 13,775 B
  • docs SUMMARY.md 182 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Configuration Architecture Guide

Overview

LiteLLM-RS loads a single YAML file into type-safe Rust models (GatewayConfig in src/config/models/gateway.rs and its children), substitutes ${VAR} / $VAR tokens from the process environment during load, fills missing fields from serde defaults, then validates the whole tree once at startup.

There is no hot reload. Configuration is read once at startup; src/utils/config/optimized.rs states this explicitly ("Hot reload is not supported"). Restart the process to pick up changes. AppState.config (src/server/state.rs) is atomically swappable by explicit code paths, but nothing watches the file.

Load Order

┌─────────────────────────────────────────────────────────────────┐
│  1. YAML file                                                   │
│     --config PATH (default: "config/gateway.yaml", src/main.rs) │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  2. Environment substitution (substitute_env_vars)              │
│     ${VAR} = required (hard error if unset)                     │
│     $VAR   = best-effort (left literal if unset)                │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  3. serde parse + defaults                                      │
│     #[serde(default ...)] fills omitted fields;                 │
│     deny_unknown_fields rejects keys on structs that opt in     │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  4. Validation (Validate trait)                                 │
│     runs inside Config::from_file / Config::from_env            │
│     failure aborts startup                                      │
└─────────────────────────────────────────────────────────────────┘

After loading, explicit --host / --port CLI values overwrite server.host / server.port, then the config is re-validated (loadconfigwith_overrides in src/main.rs).

Fallback rule: when the gateway starts without an explicit --config and the default file fails to load, runserverwithdefaultconfigoverrides retries with Config::fromenv(), which builds the config from LITELLM* environment variables only (src/config/models/gateway.rs::GatewayConfig::fromenv). An explicit --config path that fails is a hard error — no env fallback (loadexplicitconfig in src/server/builder.rs).

There is no search-path chain: no /etc/litellm/..., no gateway.yaml in cwd. The only default is the single constant DEFAULTCONFIGPATH = "config/gateway.yaml" relative to the process working directory (src/main.rs).


YAML Configuration Structure

The canonical example is config/gateway.yaml.example; unit tests assert it parses against the current schema. Top-level sections of GatewayConfig:

schema_version: "1.0"        # only "1.0" is accepted by validation

server:                      # host/port/workers/timeout/max_body_size/tls/cors/features/...
providers:                   # LIST of provider entries, not a map
  - name: "openai-primary"
    provider_type: "openai"
    api_key: "${OPENAI_API_KEY}"
    base_url: "https://api.openai.com/v1"
    models: ["gpt-4o"]
    priority: 0              # lower wins under priority_based routing

model_aliases:               # public alias -> canonical model or another alias
  production-chat: "gpt-4o"

router:                      # strategy, circuit_breaker, load_balancer
storage:                     # database, redis, vector_db
auth:                        # enable_jwt, enable_api_key, jwt_secret, rbac, allow_anonymous
monitoring:                  # metrics, tracing, health, callbacks
cache:                       # deterministic response cache (+ semantic_cache flag)
rate_limit:                  # token bucket strategy, redis_failure_mode
guardrails:                  # content safety; prompt-injection protection on by default
ip_access:                   # allowlist/blocklist; empty/default rules allow all
enterprise:                  # sso, audit_logging
pricing:                     # source, unpriced_model_policy

Unknown top-level fields are rejected at parse time (#[serde(denyunknownfields)] on GatewayConfig), as are unknown keys in the nested YAML models that carry the same attribute. This is not a universal serde guarantee: providers[].settings is intentionally open-ended, and some provider/callback backend payload structs do not deny unknown fields. There are no top-level logging, routing, or observability sections — logging lives under monitoring.logging, routing under router.


Type-Safe Configuration Models

Root Configuration

src/config/models/gateway.rs:

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GatewayConfig {
    pub schema_version: String,                    // default "1.0"
    pub server: ServerConfig,
    pub providers: Vec<ProviderConfig>,            // Vec, not a map
    pub model_aliases: HashMap<String, String>,    // default empty
    pub router: GatewayRouterConfig,
    pub storage: StorageConfig,
    pub auth: AuthConfig,
    pub monitoring: MonitoringConfig,
    pub cache: CacheConfig,                        // default
    pub rate_limit: RateLimitConfig,               // default
    pub guardrails: GuardrailConfig,               // default = enabled
    pub ip_access: IpAccessConfig,                 // default
    pub enterprise: EnterpriseConfig,              // default
    pub pricing: GatewayPricingConfig,             // default
}

Server Configuration

src/config/models/server.rs:

#[serde(deny_unknown_fields)]
pub struct ServerConfig {
    pub host: String,                      // default "0.0.0.0"
    pub port: u16,                         // default 8000
    pub workers: Option<usize>,            // None -> CPU count at runtime
    pub max_connections: Option<usize>,
    pub timeout: u64,                      // seconds, default 30
    pub max_body_size: usize,              // bytes, default 10 MiB
    pub dev_mode: bool,                    // default false
    pub tls: Option<TlsConfig>,
    pub cors: CorsConfig,
    pub features: Vec<String>,
    pub trusted_proxies: Vec<String>,      // X-Forwarded-For trust list
    pub stream_idle_timeout: u64,          // SSE idle timeout, default 300s; 0 disables
}

Shared defaults live in src/config/models/mod.rs: defaultport() returns 8000, defaulttimeout() returns 30, defaultmaxbodysize() returns 10 1024 1024. There is no keepalive, requesttimeout, or string-form maxrequest_size field.

Provider Configuration

src/config/models/provider.rs — one entry per deployment, stored as Vec<ProviderConfig>:

#[serde(deny_unknown_fields)]
pub struct ProviderConfig {
    pub name: String,                      // must be unique across entries
    pub provider_type: String,             // e.g. "openai", "anthropic"
    pub api_key: String,                   // required unless registry skips key checks
    pub base_url: Option<String>,
    pub endpoint_access: ProviderEndpointAccess, // default PublicOnly; "private_network" opt-in
    pub api_version: Option<String>,
    pub organization: Option<String>,
    pub project: Option<String>,
    pub weight: f32,                       // default 1.0, valid range (0, 100]
    pub priority: u32,                     // default 0; lower wins in priority_based
    pub rpm: u32,                          // default 1000
    pub tpm: u32,                          // default 100_000
    pub max_concurrent_requests: u32,      // default 10
    pub timeout: u64,                      // seconds, default 30, max 300
    pub max_retries: u32,                  // default 3
    pub retry: RetryConfig,                // base_delay/max_delay/backoff_multiplier/jitter
    pub health_check: ProviderHealthCheckConfig,
    pub settings: HashMap<String, serde_json::Value>, // provider-specific keys
    pub models: Vec<String>,
    pub tags: Vec<String>,
    pub enabled: bool,                     // default true
}

This is the YAML-facing model. The separate ProviderConfig trait that provider implementations satisfy lives in src/core/traits/provider/config.rs — see [reference/provider-config-errors.md](reference/provider-config-errors.md).


Environment Variable Substitution

substituteenvvars in src/config/mod.rs processes the raw file text before parsing:

  • ${VAR_NAME} — substituted with the env value. If unset, loading fails: all missing braced variables are collected, deduplicated, sorted, and reported in one error (Missing environment variables referenced by config: A, B). Placeholders can never silently reach runtime as literal strings.
  • $VAR_NAME — shell-style bare form, also substituted. If unset, the token is left literal so ordinary dollar-containing values pass through untouched.
  • ${VAR:-default} is not supported and is silently dangerous: neither recognized pattern matches a token containing :-, so the whole string stays literal in the parsed value — no substitution and no error.
  • Substitution is line-based and quote/comment aware: text after an unquoted # (a YAML comment) is never substituted, and values pulled from the environment are not re-expanded.
  • Errors surface as GatewayError::Config (the runtime loader does not use ConfigError; see [reference/provider-config-errors.md](reference/provider-config-errors.md)).
# Works
api_key: "${OPENAI_API_KEY}"        # fails startup if OPENAI_API_KEY is unset
url: "$REDIS_URL"                   # left literal if REDIS_URL is unset

# Does NOT work - no fallback syntax; url becomes the literal
# string "${REDIS_URL:-redis://localhost:6379}"
url: "${REDIS_URL:-redis://localhost:6379}"

Separately, when the implicit-default serve path falls back to env-only configuration, GatewayConfig::fromenv reads dedicated LITELLM* variables (LITELLMHOST, LITELLMPORT, LITELLMDATABASEURL, LITELLMJWTSECRET, LITELLM_PROVIDERS, ... — see the constants atop src/config/models/gateway.rs). This is independent of ${VAR} substitution inside YAML.


Configuration Loading

Actual chain, Config::from_file in src/config/mod.rs:

pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
    let content = tokio::fs::read_to_string(path).await
        .map_err(|e| GatewayError::Config(format!("Failed to read config file: {}", e)))?;
    let content = substitute_env_vars(&content)?;          // hard error on missing ${VAR}
    let gateway: GatewayConfig = serde_yml::from_str(&content)
        .map_err(|e| GatewayError::Config(format!("Failed to parse config: {}", e)))?;
    let config = Self { gateway };
    config.validate()?;                                    // Validate trait, single entry point
    Ok(config)
}

Entry points:

Path Behavior
gateway --config FILE ... loads exactly that file; failure is fatal
gateway serve (no --config) tries config/gateway.yaml; on any load error retries via Config::from_env()
gateway validate-config loads + validates only, prints result

Secret redaction happens only on export: tojson / toyaml replace known secrets (provider API keys, jwt_secret, HMAC secret, S3/vector/SSO credentials) with [REDACTED] before serializing.

References

  • [reference/validation.md](reference/validation.md) — Validate trait, validation module layout, and per-section validation rules.
  • [reference/provider-config-errors.md](reference/provider-config-errors.md) — Provider configuration trait and ConfigError variants.
  • [reference/best-practices.md](reference/best-practices.md) — Defaults, early validation, secret handling, type safety, and documentation practices.