Files
LocalAI/core/config/application_config.go
T
Ettore Di Giacinto 082fd7ee7d feat(addons): add schemaVersion 2 host policy with a deny list
A policy file may now carry a deny object. Deny rules name an add-on ID
or "*" and list prefixes, exact operation names or the single token "*".
Schema 1 files parse as before; a deny key in a schema 1 file, or a
version above 2, is an error that names the problem.

The selected file is now always opened, even when a flat
--addon-host-ops list replaces its grants, because a deny must not
vanish when someone adds a flag. An invalid selected file still stops
startup.

Add --addon-host-deny (LOCALAI_ADDON_HOST_DENY), unioned with the file,
and the --addon-host-blanket and --addon-host-ui-grants flags with their
config options.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-03 21:22:56 +00:00

1702 lines
59 KiB
Go

package config
import (
"context"
"encoding/json"
"regexp"
"sync/atomic"
"time"
"github.com/mudler/LocalAI/core/services/addons/gateway"
"github.com/mudler/LocalAI/core/services/addons/hostsettings"
"github.com/mudler/LocalAI/pkg/addons/contract"
"github.com/mudler/LocalAI/pkg/diagnostics"
"github.com/mudler/LocalAI/pkg/modelartifacts"
"github.com/mudler/LocalAI/pkg/system"
"github.com/mudler/LocalAI/pkg/vrambudget"
"github.com/mudler/LocalAI/pkg/xsysinfo"
"github.com/mudler/xlog"
)
// AddonConfig is startup-only deployment configuration. Authority is injected
// by the embedding host and cannot be loaded from runtime settings.
type AddonConfig struct {
Transport *gateway.CarrierConfig `json:"-" yaml:"-"`
ListenAddress string
// Frontend is set for the frontend role, also when add-ons are off. A
// frontend keeps no owner state, so it never opens the library store.
Frontend bool
// DedicatedOwner has no implicit legacy policy source. Migration must name
// an explicitly selected owner configuration; frontend state is never imported.
DedicatedOwner bool
// MigrateHostSettings selects only this owner process's effective legacy policy.
MigrateHostSettings bool
HostSettingsMigration *hostsettings.Migration `json:"-" yaml:"-"`
Enabled bool
InstanceID, OwnerID, OwnerAddress, StorageID, Root string
SharedStorage bool
// OwnershipMode is "named-user" (the default when empty) or "local-owner".
// Local-owner mode is never inferred from a request. The CLI sets it as the
// documented default when authentication is off and the operator chose no
// mode. Every caller that reaches the addon routes shares one authority and
// sharing is disabled.
// Every frontend of a deployment must use the same value.
OwnershipMode string
// OfficialLibrary registers the official add-on library on the first start,
// when the owner has no sources.json yet. A sources.json that exists is
// never changed by it, so a library the admin removed stays removed.
OfficialLibrary bool
// OfficialBackendLibrary also registers the backend library on the first
// start, under the same rule: a sources.json that exists is never changed,
// so a library the admin removed stays removed. It is on by default, but
// whether it is enabled depends on the build (core/library.BackendSource):
// a build that reads the backend gallery lists the library and leaves it
// off.
OfficialBackendLibrary bool
// BackendMaxBytes bounds the compressed size of one backend image
// installed from a library; zero is 8 GiB. Add-ons keep their own limit.
BackendMaxBytes int64
Host contract.HostCompatibility
AdminCheck func(context.Context, contract.Actor) error `json:"-" yaml:"-"`
// BackupKeep is how many backups of one add-on the owner keeps; zero is
// three. BackupMaxAge removes older ones; zero keeps them by count only.
// The newest verified backup and the recovery copy of an upgrade are
// never removed by either.
BackupKeep int
BackupMaxAge time.Duration
// UpdateCacheTTL is how long an answer of the update check, and the
// library data under it, counts as fresh; zero is 15 minutes.
// UpdateRefreshInterval is the least time between two explicit refreshes
// of one add-on and of one library; zero is 15 seconds.
UpdateCacheTTL time.Duration
UpdateRefreshInterval time.Duration
// ArtifactExpiry is how long a private file result stays downloadable;
// zero is the contract default of one hour. It is a programmatic option
// that no flag sets: the acceptance fixture shortens it to test expiry.
ArtifactExpiry time.Duration
}
// IsFrontend says whether this process is an add-on frontend: the role was set,
// or the instance is not the owner it forwards to.
func (c AddonConfig) IsFrontend() bool {
return c.Frontend || (c.Enabled && c.OwnerID != "" && c.InstanceID != c.OwnerID)
}
func WithAddons(c AddonConfig) AppOption {
return func(o *ApplicationConfig) { o.Addons = c }
}
type ApplicationConfig struct {
chatSelection *chatSelectionState
// OfficialLibraries is the switch for the shipped default library sources
// of every kind. On by default; off keeps them listed but unread.
OfficialLibraries bool
Addons AddonConfig `json:"-" yaml:"-"`
// AddonHostOps maps an add-on ID to the host operation prefixes it may
// call. It is startup-only. Nil or empty means no add-on gets a host
// operation endpoint.
AddonHostOps map[string][]string `json:"-" yaml:"-"`
// AddonHostDeny maps an add-on ID, or "*" for all, to host operation
// prefixes, exact names or "*" that no grant may enable. Startup-only;
// deny set in the UI is stored separately and applies live.
AddonHostDeny map[string][]string `json:"-" yaml:"-"`
// AddonHostBlanket is the blanket approval mode set by the deployment
// ("off", "official", "any"). Empty leaves the choice to the UI.
AddonHostBlanket string `json:"-" yaml:"-"`
// AddonHostUIGrantsLocked makes the UI grant store read-only, so the
// policy file and flags are the only source of grants.
AddonHostUIGrantsLocked bool `json:"-" yaml:"-"`
// DiagnosticsRecorder is startup-only and local to this frontend process.
DiagnosticsRecorder *diagnostics.Recorder `json:"-" yaml:"-"`
// ProxyAPIKeyEnvLookup resolves upstream credentials at the CLI boundary.
ProxyAPIKeyEnvLookup func(string) string `json:"-" yaml:"-"`
Context context.Context
ConfigFile string
SystemState *system.SystemState
ExternalBackends []string
// ModelArtifactMaterializer is a controller-only acquisition capability.
// It is excluded from serialization so credentials captured by its concrete
// implementation cannot enter persisted settings or distributed payloads.
ModelArtifactMaterializer ArtifactMaterializer `json:"-" yaml:"-"`
ModelPreloadRenderMode string `json:"-" yaml:"-"`
DisableModelPreloadColor bool `json:"-" yaml:"-"`
// WebRTCNAT1To1IPs, when set, are advertised as the host ICE candidates for
// /v1/realtime WebRTC instead of every local interface address. Needed when
// the routable address differs from what pion gathers — e.g. Docker host
// networking (where pion also offers unreachable bridge IPs) or NAT.
WebRTCNAT1To1IPs []string
// WebRTCICEInterfaces, when set, restricts ICE candidate gathering to these
// network interfaces (e.g. eth0), filtering out docker0/veth noise.
WebRTCICEInterfaces []string
// WebRTCUDPPort, when positive, is the shared UDP port used by all WebRTC
// peer connections. Zero keeps pion's default ephemeral-port behavior.
WebRTCUDPPort int
UploadLimitMB, Threads, ContextSize int
ArtifactDownloadConcurrency int
// MaxConcurrentBackendOperations bounds how many backend install, upgrade
// and delete operations run at once. Always >= 1.
MaxConcurrentBackendOperations int
// liveMaxBackendOps mirrors MaxConcurrentBackendOperations for readers on
// other goroutines, since a runtime settings change can land at any time.
liveMaxBackendOps atomic.Int64
F16 bool
Debug bool
LogClient bool
TrustedProxies string // comma-separated IPs/CIDRs whose forwarding headers are believed
EnableTracing bool
MaxConcurrentBackendRequests int
TracingMaxItems int
TracingMaxBodyBytes int // Per-body cap for captured request/response bodies; 0 disables the cap
EnableBackendLogging bool
GeneratedContentDir string
UploadDir string
DataPath string // Persistent data directory for collectiondb, agents, etc.
DynamicConfigsDir string
DynamicConfigsDirPollInterval time.Duration
CORS bool
DisableCSRF bool
// DisableHTTPCompression turns off the gzip response middleware. Gzip is
// on by default because the React UI bundle and the admin JSON APIs are
// text-heavy; turn it off only when a fronting proxy already compresses.
DisableHTTPCompression bool
// HTTPCompressionMinLength is the response-size floor (bytes) below which
// gzip is skipped. 0 keeps middleware.DefaultCompressionMinLength.
HTTPCompressionMinLength int
PreloadJSONModels string
PreloadModelsFromPath string
CORSAllowOrigins string
ApiKeys []string
P2PEnableLimits bool
P2PToken string
P2PNetworkID string
Federated bool
// ExternalBaseURL is the externally visible base URL of this instance
// (scheme+host[:port]), set via LOCALAI_BASE_URL. When non-empty it is
// authoritative for every self-referential URL LocalAI emits (OAuth
// callbacks, generated image/video links, async job StatusURLs),
// overriding proxy-header detection. Empty = derive from request headers.
ExternalBaseURL string
// DisableStats turns off per-request token tracking. By default the
// routing module's billing recorder runs in every mode (including
// no-auth single-user) so dashboards and `/api/usage` are immediately
// useful; set this to opt out of that, e.g., for ephemeral CI runs
// or privacy-strict deployments where no token-count history should
// touch disk or memory.
DisableStats bool
// RouterDecisionLog retains the bounded in-memory routing log even when
// billing stats are disabled. It does not enable token usage recording.
RouterDecisionLog bool
// MITMListen is the address (host:port) the cloudproxy MITM
// listener binds on. Empty disables the MITM proxy entirely.
// Use case: redacting PII from Claude Code / Codex CLI traffic
// without LocalAI holding the upstream API key. Clients set
// HTTPS_PROXY=http://localai:port and trust the CA cert
// LocalAI exposes at /api/middleware/proxy-ca.crt.
MITMListen string
// PIIDefaultDetectors lists token-classification (NER) detector model
// names applied to any PII-enabled model that does not name its own
// pii.detectors. This makes cloud-proxy / MITM redaction work out of the
// box (those default to PII-enabled but carry no detector list) and lets
// an operator set one detector for the whole instance. Set at runtime via
// POST /api/settings; read live by Application.ResolvePIIPolicy.
PIIDefaultDetectors []string
// MITMCADir holds the persisted MITM proxy CA cert and private
// key. The CA is generated on first start; subsequent starts
// reload it so clients keep trusting the same root. The key
// file is mode 0600.
MITMCADir string
DisableWebUI bool
OllamaAPIRootEndpoint bool
EnforcePredownloadScans bool
OpaqueErrors bool
UseSubtleKeyComparison bool
DisableApiKeyRequirementForHttpGet bool
DisableMetrics bool
HttpGetExemptedEndpoints []*regexp.Regexp
DisableGalleryEndpoint bool
DisableMCP bool
LoadToMemory []string
Galleries []Gallery
BackendGalleries []Gallery
ExternalGRPCBackends map[string]string
AutoloadGalleries, AutoloadBackendGalleries bool
VRAMPersistentCache bool
AutoUpgradeBackends bool
AutoUpgradeAddons bool
PreferDevelopmentBackends bool
// RequireBackendIntegrity promotes a missing SHA256 (tarball/HTTP URIs)
// or missing verification policy (OCI URIs) from a warning to a hard
// failure during backend install/upgrade. Off by default to keep
// upgrades non-breaking; operators opt in explicitly via
// --require-backend-integrity / LOCALAI_REQUIRE_BACKEND_INTEGRITY.
RequireBackendIntegrity bool
// DisableBackendInstall mirrors SystemState.DisableBackendInstall for the
// HTTP layer: admission guards and the capability surfaces read it here.
DisableBackendInstall bool
SingleBackend bool // Deprecated: use MaxActiveBackends = 1 instead
MaxActiveBackends int // Maximum number of active backends (0 = unlimited, 1 = single backend mode)
WatchDogIdle bool
WatchDogBusy bool
WatchDog bool
// Memory Reclaimer settings (works with GPU if available, otherwise RAM)
MemoryReclaimerEnabled bool // Enable memory threshold monitoring
MemoryReclaimerThreshold float64 // Threshold 0.0-1.0 (e.g., 0.95 = 95%)
// VRAMBudget optionally caps how much VRAM this instance uses for model
// allocation, as "80%" or "12GB". Empty = use full detected VRAM.
VRAMBudget string
// Eviction settings
ForceEvictionWhenBusy bool // Force eviction even when models have active API calls (default: false for safety)
SizeAwareEviction bool // Evict largest models first rather than least-recently-used (default: false)
LRUEvictionMaxRetries int // Maximum number of retries when waiting for busy models to become idle (default: 30)
LRUEvictionRetryInterval time.Duration // Interval between retries when waiting for busy models (default: 1s)
// ModelLoadFailureCooldown is the base cooldown applied after a model load
// fails: new load attempts for that model are refused (HTTP 503 + Retry-After)
// until it elapses, doubling per consecutive failure up to a 5m cap and reset
// on success. Prevents a client polling a broken model from respawning a
// crashing backend on every request. 0 disables it. Default: 10s.
ModelLoadFailureCooldown time.Duration
ModelsURL []string
WatchDogBusyTimeout, WatchDogIdleTimeout time.Duration
WatchDogInterval time.Duration // Interval between watchdog checks
MachineTag string
APIAddress string
LlamaCPPTunnelCallback func(tunnels []string)
MLXTunnelCallback func(tunnels []string)
DisableRuntimeSettings bool
OpenResponsesStoreTTL time.Duration // TTL for Open Responses store (0 = no expiration)
PathWithoutAuth []string
// Collections (the knowledge base)
Collections CollectionsConfig
// ChatsVisionModel and ChatsTranscriptionModel are the models the chats'
// media tools use to describe images and transcribe audio. Empty: the first
// installed model with that capability.
ChatsVisionModel string
ChatsTranscriptionModel string
// Authentication & Authorization
Auth AuthConfig
// Distributed / Horizontal Scaling
Distributed DistributedConfig
// ExposeNodeHeader, when true, activates middleware.ExposeNodeHeader on
// the inference routes (OpenAI chat/completions/embeddings, Anthropic
// /v1/messages, Ollama /api/chat,/api/generate,/api/embed). The
// middleware wraps the response writer and attaches an "X-LocalAI-Node"
// response header carrying the ID of the distributed-mode worker node
// that served the request. Off by default because the node ID is
// internal topology that can aid attacker reconnaissance if surfaced on
// a public endpoint; operators opt in explicitly via
// --expose-node-header / LOCALAI_EXPOSE_NODE_HEADER for debugging,
// observability and load-balancer attribution.
ExposeNodeHeader bool
// LocalAI Assistant chat modality. Hard-disable the in-process admin MCP
// server with this flag; runtime-toggleable via /api/settings.
DisableLocalAIAssistant bool
// DisableSharedPages turns off the pages of shared folders: they are not
// listed as runnable, do not open and their bridge refuses every call.
// Runtime-toggleable via /api/settings (shared_pages_enabled).
DisableSharedPages bool
// ChatsHostExec is who gets the chats' built-in bash, which runs commands
// on this machine with no isolation: off, ask (administrators, with a card
// for each command) or on. Empty: off with accounts, ask without.
// Runtime-toggleable via /api/settings (chats_host_exec).
ChatsHostExec string
// ChatsToolProviders pins, for a built-in chat tool or skill that several
// add-ons replace, the add-on that serves it (tool or skill name -> add-on
// ID). Tool and skill names never overlap.
ChatsToolProviders map[string]string
// DisableChats turns off the whole chats surface: the /api/chats API, saved
// agents, triggers, connectors and the agent tasks and jobs. Nothing starts
// (no loop, bot or trigger runner).
DisableChats bool
// DisableTriggers and DisableConnectors turn off one part of the chats:
// the trigger runner, or the connector bots. The API for the part answers
// 404.
DisableTriggers bool
DisableConnectors bool
// DisableConnectorTalkback hides the tool with which an agent writes to a
// chat of its connection and stops the reports of triggers to a channel.
// Owner notices keep working.
DisableConnectorTalkback bool
// SkipBindingCapsMigration keeps the one-time grant of the usual
// permissions to old connector bindings from running.
SkipBindingCapsMigration bool
// AgentJobRetentionDays is how long finished agent jobs are kept. Zero
// means DefaultAgentJobRetentionDays.
AgentJobRetentionDays int
// MCPCIJobTimeout is the longest an agent job may run. Zero means
// DefaultMCPCIJobTimeout.
MCPCIJobTimeout time.Duration
// TriggerRunKeep is the number of finished runs kept for each trigger
// (beyond the last two days). Zero means triggers.DefaultKeepRuns, a negative
// value keeps every run.
TriggerRunKeep int
// TriggerRunRetentionDays is how long finished trigger runs are kept. Zero
// means 30 days (triggers.DefaultRunMaxAge), a negative value keeps them forever.
TriggerRunRetentionDays int
// MCPConnectorsAllowPrivate lets the MCP connectors that users define reach
// addresses on private networks (loopback, link-local, RFC 1918, metadata).
// Off by default: such an address lets a user probe the internal network
// through the server. Runtime-toggleable via /api/settings.
MCPConnectorsAllowPrivate bool
// The built-in web tools of the chats. Each is runtime-toggleable via
// /api/settings and read at each turn. DisableChatsWebSearch and
// DisableChatsWebFetch are stored as the negation (chats_web_search,
// chats_web_fetch), so both tools are on by default. ChatsWebSearXNGURL
// is the address of a SearXNG instance, used alone unless
// ChatsWebSearchProviders lists other providers.
// ChatsWebAllowPrivate lets web_fetch reach addresses on private networks
// (never the cloud metadata addresses). ChatsWebPorts is a comma list of
// the ports web_fetch may use; empty means 80 and 443.
DisableChatsWebSearch bool
DisableChatsWebFetch bool
ChatsWebSearXNGURL string
// ChatsWebSearchProviders is the order of the search providers of
// web_search, comma separated (see ParseSearchProviders). Empty means
// the default order.
ChatsWebSearchProviders string
ChatsWebAllowPrivate bool
ChatsWebPorts string
// The network of the commands that chats run in the Sandbox, through the
// host proxy. Each is runtime-toggleable via /api/settings and read at
// each check, so no restart. ChatsEgressCeiling is the widest preset
// (off, registries, all) any chat may use; ChatsEgressDefault the preset
// of a new chat, capped by the ceiling. ChatsEgressAllowPrivate lets an
// administrator's rule or card answer reach private addresses (never the
// metadata addresses). ChatsEgressPorts is a comma list of the ports a
// command may reach (empty: 80 and 443), ChatsEgressExtraRegistries a
// comma list of host names added to the package registries, and
// ChatsEgressAllowedHosts exact hosts (host or host:port, comma separated)
// that any chat with the network on may always reach;
// ChatsEgressAuditDays how long the audit keeps a line (0: 30 days).
ChatsEgressCeiling string
ChatsEgressDefault string
ChatsEgressAllowPrivate bool
ChatsEgressPorts string
ChatsEgressExtraRegistries string
ChatsEgressAllowedHosts string
ChatsEgressAuditDays int
// ChatsMaxIterations is the step limit of the tool loop of one chat turn:
// how many rounds of tool calls the model may make before it must answer.
// A turn that reaches it ends as paused, and the person can let it go on.
// 0: DefaultChatsMaxIterations. A model's agent.max_iterations wins.
// Runtime-settable via /api/settings (chats_max_iterations).
ChatsMaxIterations int
// ChatsGoalMaxTurns is the most turns the server starts by itself for one
// goal of a chat, and ChatsGoalMaxSteps the most model requests the turns
// of one goal make, before the goal pauses. 0: the defaults. Runtime-settable
// via /api/settings (chats_goal_max_turns, chats_goal_max_steps).
ChatsGoalMaxTurns int
ChatsGoalMaxSteps int
// The limits of the loops of the chats (a prompt a chat repeats by
// itself). Zero means the default. Runtime-settable via /api/settings
// (chats_loop_min_interval, chats_loop_max_runs, chats_loop_max_per_chat,
// chats_loop_max_per_user, chats_loop_daily_runs); read at each use.
ChatsLoopMinInterval time.Duration
ChatsLoopMaxRuns int
ChatsLoopMaxPerChat int
ChatsLoopMaxPerUser int
ChatsLoopDailyRuns int
// DisableChatsSavedAgents switches off, for every chat, the saved agents
// as sub-agents (list_agents and the agent argument of spawn_agent).
// Stored as the negation (chats_saved_agents), so it is on by default;
// each chat still has to switch it on. Read at each turn.
DisableChatsSavedAgents bool
// DisableChatsWorkFolder stops the steering of the chats' working files:
// the prompt no longer names the chat's work folder (Chats/<chat>/work)
// for scripts, logs and drafts, and a helper file saved elsewhere gets no
// nudge. Stored as the negation (chats_work_folder), so it is on by
// default. Read at each turn.
DisableChatsWorkFolder bool
// DisableChatsIndexFiles stops the reading of the text of PDF, Word,
// Excel and PowerPoint files in the chats' files, which search_files and
// the Files page search. Stored as the negation (chats_index_files), so
// it is on by default. ChatsIndexMaxTextBytes is the most text kept for
// one file (chats_index_max_text_bytes; 0: 2 MiB). Both are read at each
// pass of the indexer, so no restart.
DisableChatsIndexFiles bool
ChatsIndexMaxTextBytes int
// DisableChatSuggestions stops the assistant from offering things (to
// remember a name, to make a shortcut, to update a widget on a schedule)
// to users who did not choose for themselves. Runtime-toggleable via
// /api/settings as chats_suggestions (stored as the negation).
DisableChatSuggestions bool
// LegacyNavigation shows the previous sidebar. See WithLegacyNavigation.
LegacyNavigation bool
// Branding / whitelabeling — runtime-mutable via /api/settings (text) and
// /api/branding/asset/:kind (binary uploads). All values optional; empty
// strings fall back to bundled LocalAI defaults.
Branding BrandingConfig
}
const DefaultMaxConcurrentBackendRequests = 1024
// BrandingConfig holds the whitelabel/branding configuration of the instance.
// Text fields are exposed via the public GET /api/branding endpoint so the
// login page can read them before authentication. Binary asset filenames
// (logo, horizontal logo, favicon) are stored as basenames; the actual files
// live under {DynamicConfigsDir}/branding/.
type BrandingConfig struct {
InstanceName string
InstanceTagline string
LogoFile string
LogoHorizontalFile string
FaviconFile string
}
// AuthConfig holds configuration for user authentication and authorization.
type AuthConfig struct {
Enabled bool
DatabaseURL string // "postgres://..." or file path for SQLite
GitHubClientID string
GitHubClientSecret string
OIDCIssuer string // OIDC issuer URL for auto-discovery (e.g. https://accounts.google.com)
OIDCClientID string
OIDCClientSecret string
AdminEmail string // auto-promote to admin on login
RegistrationMode string // "open", "approval" (default when empty), "invite"
DisableLocalAuth bool // disable local email/password registration and login
APIKeyHMACSecret string // HMAC secret for API key hashing; auto-generated if empty
DefaultAPIKeyExpiry string // default expiry duration for API keys (e.g. "90d"); empty = no expiry
}
// CollectionsConfig holds the configuration of the collections service.
type CollectionsConfig struct {
Enabled bool // default: true (disabled by LOCALAI_DISABLE_COLLECTIONS=true)
StateDir string // default: DynamicConfigsDir (LocalAI configuration folder)
APIURL string // default: self-referencing LocalAI (http://127.0.0.1:<port>)
APIKey string // default: first API key from LocalAI config
DefaultModel string
CollectionDBPath string
VectorEngine string // default: "chromem"
EmbeddingModel string // default: "granite-embedding-107m-multilingual"
MaxChunkingSize int // default: 400
ChunkOverlap int // default: 0
DatabaseURL string
}
type AppOption func(*ApplicationConfig)
func WithProxyAPIKeyEnvLookup(lookup func(string) string) AppOption {
return func(o *ApplicationConfig) { o.ProxyAPIKeyEnvLookup = lookup }
}
func NewApplicationConfig(o ...AppOption) *ApplicationConfig {
opt := &ApplicationConfig{
Context: context.Background(),
Debug: true,
ModelArtifactMaterializer: modelartifacts.NewDefaultManager(),
OfficialLibraries: true,
ModelPreloadRenderMode: "dark",
// Capture backend process stdout/stderr into the per-model
// BackendLogStore by default so the UI "Backend Logs" page works out
// of the box in single mode, matching worker/distributed mode (which
// force-enables it). It's a small in-memory ring buffer; the Settings
// toggle can still turn it off (a persisted false wins - see
// loadRuntimeSettingsFromFile).
EnableBackendLogging: true,
VRAMPersistentCache: true,
ArtifactDownloadConcurrency: modelartifacts.DefaultDownloadConcurrency,
MaxConcurrentBackendOperations: 1,
LRUEvictionMaxRetries: 30, // Default: 30 retries
LRUEvictionRetryInterval: 1 * time.Second, // Default: 1 second
ModelLoadFailureCooldown: 10 * time.Second, // Default: 10s base cooldown after a failed load
// WatchDogInterval is intentionally left at the zero value here.
// The startup loader applies a persisted runtime_settings.json value
// only when the interval is still 0 (its "not set by env var"
// heuristic, matching the idle/busy timeouts); a non-zero baseline
// default would defeat that and silently revert a UI-saved Check
// Interval to the default on every restart (#10601). The effective
// 500ms default is supplied at the watchdog layer (DefaultWatchdogInterval)
// when the value is still 0.
MaxConcurrentBackendRequests: DefaultMaxConcurrentBackendRequests,
TracingMaxItems: 1024,
TracingMaxBodyBytes: 64 * 1024, // 64 KiB - caps each request/response body in the trace buffer
Collections: CollectionsConfig{
Enabled: true,
VectorEngine: "chromem",
EmbeddingModel: "granite-embedding-107m-multilingual",
MaxChunkingSize: 400,
},
PathWithoutAuth: []string{},
}
for _, oo := range o {
oo(opt)
}
opt.SetMaxConcurrentBackendOperations(opt.MaxConcurrentBackendOperations)
opt.selectionState()
return opt
}
func WithModelsURL(urls ...string) AppOption {
return func(o *ApplicationConfig) {
o.ModelsURL = urls
}
}
func WithSystemState(state *system.SystemState) AppOption {
return func(o *ApplicationConfig) {
o.SystemState = state
}
}
func WithExternalBackends(backends ...string) AppOption {
return func(o *ApplicationConfig) {
o.ExternalBackends = backends
}
}
func WithWebRTCNAT1To1IPs(ips ...string) AppOption {
return func(o *ApplicationConfig) {
o.WebRTCNAT1To1IPs = ips
}
}
func WithWebRTCICEInterfaces(interfaces ...string) AppOption {
return func(o *ApplicationConfig) {
o.WebRTCICEInterfaces = interfaces
}
}
func WithWebRTCUDPPort(port int) AppOption {
return func(o *ApplicationConfig) {
o.WebRTCUDPPort = port
}
}
func WithMachineTag(tag string) AppOption {
return func(o *ApplicationConfig) {
o.MachineTag = tag
}
}
func WithCors(b bool) AppOption {
return func(o *ApplicationConfig) {
o.CORS = b
}
}
func WithP2PNetworkID(s string) AppOption {
return func(o *ApplicationConfig) {
o.P2PNetworkID = s
}
}
func WithDisableCSRF(b bool) AppOption {
return func(o *ApplicationConfig) {
o.DisableCSRF = b
}
}
func WithDisableHTTPCompression(b bool) AppOption {
return func(o *ApplicationConfig) {
o.DisableHTTPCompression = b
}
}
func WithHTTPCompressionMinLength(n int) AppOption {
return func(o *ApplicationConfig) {
o.HTTPCompressionMinLength = n
}
}
func WithP2PToken(s string) AppOption {
return func(o *ApplicationConfig) {
o.P2PToken = s
}
}
var EnableWatchDog = func(o *ApplicationConfig) {
o.WatchDog = true
}
var EnableTracing = func(o *ApplicationConfig) {
o.EnableTracing = true
}
var EnableBackendLogging = func(o *ApplicationConfig) {
o.EnableBackendLogging = true
}
var EnableWatchDogIdleCheck = func(o *ApplicationConfig) {
o.WatchDog = true
o.WatchDogIdle = true
}
var DisableGalleryEndpoint = func(o *ApplicationConfig) {
o.DisableGalleryEndpoint = true
}
var DisableMCP = func(o *ApplicationConfig) {
o.DisableMCP = true
}
var EnableWatchDogBusyCheck = func(o *ApplicationConfig) {
o.WatchDog = true
o.WatchDogBusy = true
}
var DisableWebUI = func(o *ApplicationConfig) {
o.DisableWebUI = true
}
var EnableOllamaAPIRootEndpoint = func(o *ApplicationConfig) {
o.OllamaAPIRootEndpoint = true
}
var DisableRuntimeSettings = func(o *ApplicationConfig) {
o.DisableRuntimeSettings = true
}
func SetWatchDogBusyTimeout(t time.Duration) AppOption {
return func(o *ApplicationConfig) {
o.WatchDogBusyTimeout = t
}
}
func SetWatchDogIdleTimeout(t time.Duration) AppOption {
return func(o *ApplicationConfig) {
o.WatchDogIdleTimeout = t
}
}
func SetWatchDogInterval(t time.Duration) AppOption {
return func(o *ApplicationConfig) {
o.WatchDogInterval = t
}
}
// EnableMemoryReclaimer enables memory threshold monitoring.
// When enabled, the watchdog will evict backends if memory usage exceeds the threshold.
// Works with GPU VRAM if available, otherwise uses system RAM.
var EnableMemoryReclaimer = func(o *ApplicationConfig) {
o.MemoryReclaimerEnabled = true
o.WatchDog = true // Memory reclaimer requires watchdog infrastructure
}
// SetMemoryReclaimerThreshold sets the memory usage threshold (0.0-1.0).
// When memory usage exceeds this threshold, backends will be evicted using LRU strategy.
func SetMemoryReclaimerThreshold(threshold float64) AppOption {
return func(o *ApplicationConfig) {
if threshold > 0 && threshold <= 1.0 {
o.MemoryReclaimerThreshold = threshold
o.MemoryReclaimerEnabled = true
o.WatchDog = true // Memory reclaimer requires watchdog infrastructure
}
}
}
// SetVRAMBudget sets the VRAM allocation cap ("80%" or "12GB", "" = no cap).
func SetVRAMBudget(v string) AppOption {
return func(o *ApplicationConfig) {
o.VRAMBudget = v
}
}
// WithMemoryReclaimer configures the memory reclaimer with the given settings
func WithMemoryReclaimer(enabled bool, threshold float64) AppOption {
return func(o *ApplicationConfig) {
o.MemoryReclaimerEnabled = enabled
if threshold > 0 && threshold <= 1.0 {
o.MemoryReclaimerThreshold = threshold
}
if enabled {
o.WatchDog = true // Memory reclaimer requires watchdog infrastructure
}
}
}
// EnableSingleBackend is deprecated: use SetMaxActiveBackends(1) instead.
// This is kept for backward compatibility.
var EnableSingleBackend = func(o *ApplicationConfig) {
o.SingleBackend = true
o.MaxActiveBackends = 1
}
// SetMaxActiveBackends sets the maximum number of active backends.
// 0 = unlimited, 1 = single backend mode (replaces EnableSingleBackend)
func SetMaxActiveBackends(n int) AppOption {
return func(o *ApplicationConfig) {
o.MaxActiveBackends = n
// For backward compatibility, also set SingleBackend if n == 1
if n == 1 {
o.SingleBackend = true
}
}
}
// GetEffectiveMaxActiveBackends returns the effective max active backends limit.
// It considers both MaxActiveBackends and the deprecated SingleBackend setting.
// If MaxActiveBackends is set (> 0), it takes precedence.
// If SingleBackend is true and MaxActiveBackends is 0, returns 1.
// Otherwise returns 0 (unlimited).
func (o *ApplicationConfig) GetEffectiveMaxActiveBackends() int {
if o.MaxActiveBackends > 0 {
return o.MaxActiveBackends
}
if o.SingleBackend {
return 1
}
return 0
}
// WatchdogShouldRun reports whether the live watchdog process should be
// running for the current config. It mirrors the gating in
// (*Application).startWatchdog so the /api/settings start/stop decision and
// the startup path agree on a single source of truth: the watchdog runs when
// idle/busy checks are enabled (WatchDog), when LRU eviction is active
// (effective max active backends > 0), or when the memory reclaimer is on.
func (o *ApplicationConfig) WatchdogShouldRun() bool {
return o.WatchDog || o.GetEffectiveMaxActiveBackends() > 0 || o.MemoryReclaimerEnabled
}
// WithForceEvictionWhenBusy sets whether to force eviction even when models have active API calls
func WithForceEvictionWhenBusy(enabled bool) AppOption {
return func(o *ApplicationConfig) {
o.ForceEvictionWhenBusy = enabled
}
}
// WithSizeAwareEviction enables size-aware eviction ordering.
// When true, the watchdog evicts the largest loaded model first rather than the
// least-recently-used one, keeping small utility models resident and maximizing
// memory freed per eviction.
func WithSizeAwareEviction(enabled bool) AppOption {
return func(o *ApplicationConfig) {
o.SizeAwareEviction = enabled
}
}
// WithLRUEvictionMaxRetries sets the maximum number of retries when waiting for busy models to become idle
func WithLRUEvictionMaxRetries(maxRetries int) AppOption {
return func(o *ApplicationConfig) {
if maxRetries > 0 {
o.LRUEvictionMaxRetries = maxRetries
}
}
}
// WithLRUEvictionRetryInterval sets the interval between retries when waiting for busy models
func WithLRUEvictionRetryInterval(interval time.Duration) AppOption {
return func(o *ApplicationConfig) {
if interval > 0 {
o.LRUEvictionRetryInterval = interval
}
}
}
// WithModelLoadFailureCooldown sets the base cooldown applied after a failed
// model load. 0 disables the cooldown, so unlike most options it accepts any
// non-negative value.
func WithModelLoadFailureCooldown(cooldown time.Duration) AppOption {
return func(o *ApplicationConfig) {
if cooldown >= 0 {
o.ModelLoadFailureCooldown = cooldown
}
}
}
var EnableGalleriesAutoload = func(o *ApplicationConfig) {
o.AutoloadGalleries = true
}
var EnableBackendGalleriesAutoload = func(o *ApplicationConfig) {
o.AutoloadBackendGalleries = true
}
func WithOfficialLibraries(v bool) AppOption {
return func(o *ApplicationConfig) { o.OfficialLibraries = v }
}
func WithAutoUpgradeBackends(v bool) AppOption {
return func(o *ApplicationConfig) { o.AutoUpgradeBackends = v }
}
func WithAutoUpgradeAddons(v bool) AppOption {
return func(o *ApplicationConfig) { o.AutoUpgradeAddons = v }
}
func WithVRAMPersistentCache(v bool) AppOption {
return func(o *ApplicationConfig) { o.VRAMPersistentCache = v }
}
func WithRequireBackendIntegrity(v bool) AppOption {
return func(o *ApplicationConfig) { o.RequireBackendIntegrity = v }
}
func WithDisableBackendInstall(v bool) AppOption {
return func(o *ApplicationConfig) { o.DisableBackendInstall = v }
}
func WithPreferDevelopmentBackends(v bool) AppOption {
return func(o *ApplicationConfig) { o.PreferDevelopmentBackends = v }
}
var EnableFederated = func(o *ApplicationConfig) {
o.Federated = true
}
func WithExternalBackend(name string, uri string) AppOption {
return func(o *ApplicationConfig) {
if o.ExternalGRPCBackends == nil {
o.ExternalGRPCBackends = make(map[string]string)
}
o.ExternalGRPCBackends[name] = uri
}
}
func WithCorsAllowOrigins(b string) AppOption {
return func(o *ApplicationConfig) {
o.CORSAllowOrigins = b
}
}
func WithStringGalleries(galls string) AppOption {
return func(o *ApplicationConfig) {
if galls == "" {
o.Galleries = []Gallery{}
return
}
var galleries []Gallery
if err := json.Unmarshal([]byte(galls), &galleries); err != nil {
xlog.Error("failed loading galleries", "error", err)
}
o.Galleries = append(o.Galleries, galleries...)
}
}
func WithBackendGalleries(galls string) AppOption {
return func(o *ApplicationConfig) {
if galls == "" {
o.BackendGalleries = []Gallery{}
return
}
var galleries []Gallery
if err := json.Unmarshal([]byte(galls), &galleries); err != nil {
xlog.Error("failed loading galleries", "error", err)
}
o.BackendGalleries = append(o.BackendGalleries, galleries...)
}
}
func WithGalleries(galleries []Gallery) AppOption {
return func(o *ApplicationConfig) {
o.Galleries = append(o.Galleries, galleries...)
}
}
func WithContext(ctx context.Context) AppOption {
return func(o *ApplicationConfig) {
o.Context = ctx
}
}
// WithModelArtifactMaterializer injects the controller-only model acquisition capability.
func WithModelArtifactMaterializer(materializer ArtifactMaterializer) AppOption {
return func(o *ApplicationConfig) {
if materializer != nil {
o.ModelArtifactMaterializer = materializer
}
}
}
// WithMaxConcurrentBackendOperations sets how many backend operations run at
// once. Values below 1 fall back to 1.
func WithMaxConcurrentBackendOperations(n int) AppOption {
return func(o *ApplicationConfig) {
if n < 1 {
n = 1
}
o.SetMaxConcurrentBackendOperations(n)
}
}
// SetMaxConcurrentBackendOperations stores the limit, clamped to at least 1.
func (o *ApplicationConfig) SetMaxConcurrentBackendOperations(n int) {
if n < 1 {
n = 1
}
o.MaxConcurrentBackendOperations = n
o.liveMaxBackendOps.Store(int64(n))
}
// BackendOperationLimit returns how many backend operations may run at once.
// It is safe to call while a runtime settings change is applied.
func (o *ApplicationConfig) BackendOperationLimit() int {
if n := o.liveMaxBackendOps.Load(); n >= 1 {
return int(n)
}
if o.MaxConcurrentBackendOperations >= 1 {
return o.MaxConcurrentBackendOperations
}
return 1
}
func WithArtifactDownloadConcurrency(concurrency int) AppOption {
return func(o *ApplicationConfig) {
if concurrency < 1 {
concurrency = modelartifacts.DefaultDownloadConcurrency
}
o.ArtifactDownloadConcurrency = concurrency
}
}
// WithModelPreloadDisplay configures terminal rendering for model preload output.
func WithModelPreloadDisplay(renderMode string, disableColor bool) AppOption {
return func(o *ApplicationConfig) {
if renderMode != "" {
o.ModelPreloadRenderMode = renderMode
}
o.DisableModelPreloadColor = disableColor
}
}
func WithYAMLConfigPreload(configFile string) AppOption {
return func(o *ApplicationConfig) {
o.PreloadModelsFromPath = configFile
}
}
func WithJSONStringPreload(configFile string) AppOption {
return func(o *ApplicationConfig) {
o.PreloadJSONModels = configFile
}
}
func WithConfigFile(configFile string) AppOption {
return func(o *ApplicationConfig) {
o.ConfigFile = configFile
}
}
// DefaultUploadLimitMB is the body limit, in MB, of every route that has no
// limit of its own, when the operator sets none.
const DefaultUploadLimitMB = 15
// UploadLimitSet reports whether the operator chose an upload limit. Zero
// means "not set", so a route with a larger limit of its own can keep it.
func (o *ApplicationConfig) UploadLimitSet() bool { return o.UploadLimitMB > 0 }
// GeneralUploadLimitMB is the body limit, in MB, of the routes that have no
// limit of their own: the operator's value, or the default.
func (o *ApplicationConfig) GeneralUploadLimitMB() int {
if o.UploadLimitSet() {
return o.UploadLimitMB
}
return DefaultUploadLimitMB
}
func WithUploadLimitMB(limit int) AppOption {
return func(o *ApplicationConfig) {
o.UploadLimitMB = limit
}
}
// WithThreads sets the thread-count hint. 0 means "unset": the effective
// physical-core default is resolved in application.New() only after
// persisted runtime settings merge, so a file-saved value is not masked by
// an eager fallback (#10845).
func WithThreads(threads int) AppOption {
return func(o *ApplicationConfig) {
o.Threads = threads
}
}
func WithContextSize(ctxSize int) AppOption {
return func(o *ApplicationConfig) {
o.ContextSize = ctxSize
}
}
func WithLlamaCPPTunnelCallback(callback func(tunnels []string)) AppOption {
return func(o *ApplicationConfig) {
o.LlamaCPPTunnelCallback = callback
}
}
func WithMLXTunnelCallback(callback func(tunnels []string)) AppOption {
return func(o *ApplicationConfig) {
o.MLXTunnelCallback = callback
}
}
func WithF16(f16 bool) AppOption {
return func(o *ApplicationConfig) {
o.F16 = f16
}
}
func WithDebug(debug bool) AppOption {
return func(o *ApplicationConfig) {
o.Debug = debug
}
}
// WithTrustedProxies sets the proxies whose X-Forwarded-For is believed.
func WithTrustedProxies(list string) AppOption {
return func(o *ApplicationConfig) {
o.TrustedProxies = list
}
}
// WithLogClient adds the peer address and user agent to the HTTP access log.
func WithLogClient(on bool) AppOption {
return func(o *ApplicationConfig) {
o.LogClient = on
}
}
func WithTracingMaxItems(items int) AppOption {
return func(o *ApplicationConfig) {
o.TracingMaxItems = items
}
}
func WithMaxConcurrentBackendRequests(requests int) AppOption {
return func(o *ApplicationConfig) {
if requests <= 0 {
requests = DefaultMaxConcurrentBackendRequests
}
o.MaxConcurrentBackendRequests = requests
}
}
func WithTracingMaxBodyBytes(bytes int) AppOption {
return func(o *ApplicationConfig) {
o.TracingMaxBodyBytes = bytes
}
}
func WithGeneratedContentDir(generatedContentDir string) AppOption {
return func(o *ApplicationConfig) {
o.GeneratedContentDir = generatedContentDir
}
}
func WithUploadDir(uploadDir string) AppOption {
return func(o *ApplicationConfig) {
o.UploadDir = uploadDir
}
}
func WithDataPath(dataPath string) AppOption {
return func(o *ApplicationConfig) {
o.DataPath = dataPath
}
}
// WithDisableStats turns off the billing recorder. CLI: --disable-stats.
func WithDisableStats(disable bool) AppOption {
return func(o *ApplicationConfig) {
o.DisableStats = disable
}
}
// WithMITMListen sets the address the cloudproxy MITM listener
// binds on. Empty = disabled. CLI: --mitm-listen.
func WithMITMListen(addr string) AppOption {
return func(o *ApplicationConfig) {
o.MITMListen = addr
}
}
// WithMITMCADir sets the directory used to persist the MITM proxy
// CA cert + key. CLI: --mitm-ca-dir.
func WithMITMCADir(dir string) AppOption {
return func(o *ApplicationConfig) {
o.MITMCADir = dir
}
}
// WithPIIDefaultDetectors sets the instance-wide default PII/secret detector
// model names applied to any PII-enabled model (chiefly cloud-proxy / MITM
// models) that names no pii.detectors of its own. CLI/env:
// LOCALAI_PII_DEFAULT_DETECTORS. Empty leaves the value to
// runtime_settings.json / the Middleware UI; a non-empty value takes
// precedence over the file (env > file).
func WithPIIDefaultDetectors(detectors []string) AppOption {
return func(o *ApplicationConfig) {
o.PIIDefaultDetectors = detectors
}
}
func WithDynamicConfigDir(dynamicConfigsDir string) AppOption {
return func(o *ApplicationConfig) {
o.DynamicConfigsDir = dynamicConfigsDir
}
}
func WithDynamicConfigDirPollInterval(interval time.Duration) AppOption {
return func(o *ApplicationConfig) {
o.DynamicConfigsDirPollInterval = interval
}
}
func WithApiKeys(apiKeys []string) AppOption {
return func(o *ApplicationConfig) {
o.ApiKeys = apiKeys
}
}
func WithOpenResponsesStoreTTL(ttl time.Duration) AppOption {
return func(o *ApplicationConfig) {
o.OpenResponsesStoreTTL = ttl
}
}
func WithEnforcedPredownloadScans(enforced bool) AppOption {
return func(o *ApplicationConfig) {
o.EnforcePredownloadScans = enforced
}
}
func WithOpaqueErrors(opaque bool) AppOption {
return func(o *ApplicationConfig) {
o.OpaqueErrors = opaque
}
}
func WithLoadToMemory(models []string) AppOption {
return func(o *ApplicationConfig) {
o.LoadToMemory = models
}
}
func WithSubtleKeyComparison(subtle bool) AppOption {
return func(o *ApplicationConfig) {
o.UseSubtleKeyComparison = subtle
}
}
func WithDisableApiKeyRequirementForHttpGet(required bool) AppOption {
return func(o *ApplicationConfig) {
o.DisableApiKeyRequirementForHttpGet = required
}
}
func WithAPIAddress(address string) AppOption {
return func(o *ApplicationConfig) {
o.APIAddress = address
}
}
var DisableMetricsEndpoint AppOption = func(o *ApplicationConfig) {
o.DisableMetrics = true
}
func WithHttpGetExemptedEndpoints(endpoints []string) AppOption {
return func(o *ApplicationConfig) {
o.HttpGetExemptedEndpoints = []*regexp.Regexp{}
for _, epr := range endpoints {
r, err := regexp.Compile(epr)
if err == nil && r != nil {
o.HttpGetExemptedEndpoints = append(o.HttpGetExemptedEndpoints, r)
} else {
xlog.Warn("Error while compiling HTTP Get Exemption regex, skipping this entry.", "error", err, "regex", epr)
}
}
}
}
// Agent Pool options
var DisableCollections = func(o *ApplicationConfig) {
o.Collections.Enabled = false
}
func WithCollectionsAPIURL(url string) AppOption {
return func(o *ApplicationConfig) {
o.Collections.APIURL = url
}
}
func WithCollectionsAPIKey(key string) AppOption {
return func(o *ApplicationConfig) {
o.Collections.APIKey = key
}
}
func WithCollectionsDefaultModel(model string) AppOption {
return func(o *ApplicationConfig) {
o.Collections.DefaultModel = model
}
}
func WithCollectionsStateDir(dir string) AppOption {
return func(o *ApplicationConfig) {
o.Collections.StateDir = dir
}
}
func WithCollectionsVectorEngine(engine string) AppOption {
return func(o *ApplicationConfig) {
o.Collections.VectorEngine = engine
}
}
func WithCollectionsEmbeddingModel(model string) AppOption {
return func(o *ApplicationConfig) {
o.Collections.EmbeddingModel = model
}
}
func WithCollectionsDatabaseURL(url string) AppOption {
return func(o *ApplicationConfig) {
o.Collections.DatabaseURL = url
}
}
func WithCollectionsMaxChunkingSize(size int) AppOption {
return func(o *ApplicationConfig) {
o.Collections.MaxChunkingSize = size
}
}
func WithCollectionsChunkOverlap(overlap int) AppOption {
return func(o *ApplicationConfig) {
o.Collections.ChunkOverlap = overlap
}
}
func WithCollectionsCollectionDBPath(path string) AppOption {
return func(o *ApplicationConfig) {
o.Collections.CollectionDBPath = path
}
}
// Auth options
func WithAuthEnabled(enabled bool) AppOption {
return func(o *ApplicationConfig) {
o.Auth.Enabled = enabled
}
}
func WithAuthDatabaseURL(url string) AppOption {
return func(o *ApplicationConfig) {
o.Auth.DatabaseURL = url
}
}
func WithAuthGitHubClientID(clientID string) AppOption {
return func(o *ApplicationConfig) {
o.Auth.GitHubClientID = clientID
}
}
func WithAuthGitHubClientSecret(clientSecret string) AppOption {
return func(o *ApplicationConfig) {
o.Auth.GitHubClientSecret = clientSecret
}
}
func WithExternalBaseURL(url string) AppOption {
return func(o *ApplicationConfig) {
o.ExternalBaseURL = url
}
}
func WithAuthAdminEmail(email string) AppOption {
return func(o *ApplicationConfig) {
o.Auth.AdminEmail = email
}
}
func WithAuthRegistrationMode(mode string) AppOption {
return func(o *ApplicationConfig) {
o.Auth.RegistrationMode = mode
}
}
func WithAuthDisableLocalAuth(disable bool) AppOption {
return func(o *ApplicationConfig) {
o.Auth.DisableLocalAuth = disable
}
}
func WithAuthOIDCIssuer(issuer string) AppOption {
return func(o *ApplicationConfig) {
o.Auth.OIDCIssuer = issuer
}
}
func WithAuthOIDCClientID(clientID string) AppOption {
return func(o *ApplicationConfig) {
o.Auth.OIDCClientID = clientID
}
}
func WithAuthOIDCClientSecret(clientSecret string) AppOption {
return func(o *ApplicationConfig) {
o.Auth.OIDCClientSecret = clientSecret
}
}
func WithAuthAPIKeyHMACSecret(secret string) AppOption {
return func(o *ApplicationConfig) {
o.Auth.APIKeyHMACSecret = secret
}
}
func WithAuthDefaultAPIKeyExpiry(expiry string) AppOption {
return func(o *ApplicationConfig) {
o.Auth.DefaultAPIKeyExpiry = expiry
}
}
// WithLegacyNavigation brings the old sidebar back without a rebuild. The UI
// reads it from /api/features. It is a rollback switch and is removed one
// release after the add-on navigation ships.
func WithLegacyNavigation(on bool) AppOption {
return func(o *ApplicationConfig) {
o.LegacyNavigation = on
}
}
// DisableChats, DisableTriggers and DisableConnectors are the options of the
// operator switches LOCALAI_DISABLE_CHATS, LOCALAI_DISABLE_TRIGGERS and
// LOCALAI_DISABLE_CONNECTORS.
var (
DisableChats = func(o *ApplicationConfig) { o.DisableChats = true }
DisableTriggers = func(o *ApplicationConfig) { o.DisableTriggers = true }
DisableConnectors = func(o *ApplicationConfig) { o.DisableConnectors = true }
// DisableConnectorTalkback is the option of LOCALAI_DISABLE_CONNECTOR_TALKBACK.
DisableConnectorTalkback = func(o *ApplicationConfig) { o.DisableConnectorTalkback = true }
// SkipBindingCapsMigration is the option of LOCALAI_SKIP_BINDING_CAPS_MIGRATION.
SkipBindingCapsMigration = func(o *ApplicationConfig) { o.SkipBindingCapsMigration = true }
)
// WithAgentJobRetentionDays sets how long finished agent jobs are kept.
func WithAgentJobRetentionDays(days int) AppOption {
return func(o *ApplicationConfig) { o.AgentJobRetentionDays = days }
}
// WithMCPCIJobTimeout sets the longest an agent job may run.
func WithMCPCIJobTimeout(d time.Duration) AppOption {
return func(o *ApplicationConfig) { o.MCPCIJobTimeout = d }
}
// WithTriggerRunRetention sets how many finished runs are kept for each
// trigger, and for how many days.
func WithTriggerRunRetention(keep, days int) AppOption {
return func(o *ApplicationConfig) { o.TriggerRunKeep, o.TriggerRunRetentionDays = keep, days }
}
// Defaults of the agent jobs.
const (
DefaultAgentJobRetentionDays = 30
DefaultMCPCIJobTimeout = 10 * time.Minute
)
// WithChatsHostExec sets who gets the chats' built-in bash: off, ask or on.
func WithChatsHostExec(mode string) AppOption {
return func(o *ApplicationConfig) {
o.SetChatsHostExec(mode)
}
}
// WithDisableSharedPages turns off the pages of shared folders.
func WithDisableSharedPages(disabled bool) AppOption {
return func(o *ApplicationConfig) {
o.DisableSharedPages = disabled
}
}
// WithDisableLocalAIAssistant hard-disables the in-process admin MCP server.
// When set, the chat-handler branch for metadata.localai_assistant=true
// returns a "feature unavailable" error.
func WithDisableLocalAIAssistant(disabled bool) AppOption {
return func(o *ApplicationConfig) {
o.DisableLocalAIAssistant = disabled
}
}
// WithExposeNodeHeader enables the X-LocalAI-Node response header on
// inference endpoints. Default off; the node ID reveals internal cluster
// topology and is opt-in for that reason.
func WithExposeNodeHeader(enabled bool) AppOption {
return func(o *ApplicationConfig) {
o.ExposeNodeHeader = enabled
}
}
// ToConfigLoaderOptions returns a slice of ConfigLoader Option.
// Some options defined at the application level are going to be passed as defaults for
// all the configuration for the models.
// This includes for instance the context size or the number of threads.
// If a model doesn't set configs directly to the config model file
// it will use the defaults defined here.
func (o *ApplicationConfig) ToConfigLoaderOptions() []ConfigLoaderOption {
return []ConfigLoaderOption{
LoadOptionContextSize(o.ContextSize),
LoadOptionDebug(o.Debug),
LoadOptionF16(o.F16),
LoadOptionThreads(o.Threads),
ModelPath(o.SystemState.Model.ModelsPath),
LoadOptionGalleryFiles(o.Galleries...),
// Select later YAML/default phases; early phases use WithReloadDiagnostics
// at loader construction so callbacks retain their original execution point.
LoadOptionDiagnostics(o.DiagnosticsRecorder),
}
}
// ToRuntimeSettings converts ApplicationConfig to RuntimeSettings for API
// responses and JSON serialization. Field coverage comes entirely from the
// runtimeSettingsFields registry (runtime_settings_registry.go).
func (o *ApplicationConfig) ToRuntimeSettings() RuntimeSettings {
var s RuntimeSettings
for _, f := range runtimeSettingsFields {
f.snapshot(o, &s)
}
return s
}
// ApplyRuntimeSettings applies RuntimeSettings to ApplicationConfig (the
// live POST /api/settings path). Only non-nil fields are applied. Returns
// true when legacy service-reconfiguration fields were accepted (see each
// row's restartRequired flag in runtimeSettingsFields). The settings endpoint
// consumes this as a watchdog reconfiguration signal, not a server restart.
func (o *ApplicationConfig) ApplyRuntimeSettings(settings *RuntimeSettings) (requireRestart bool) {
if settings == nil {
return false
}
for _, f := range runtimeSettingsFields {
if f.snapshotOnly || !f.isSet(settings) {
continue
}
if f.apply(o, settings) && f.requiresRestart {
requireRestart = true
}
}
// The React Settings "Enable Watchdog" master toggle manages only the
// idle/busy checks - watchdog_enabled is vestigial in that UI. Whenever
// either idle/busy field is present in the body, derive the run-state
// from idle||busy so a cold enable starts the watchdog and a full
// disable stops it, instead of trusting the stale watchdog_enabled the
// UI never updates. An API client posting only watchdog_enabled
// (idle/busy absent) keeps its explicit value. (#9125)
if settings.WatchdogIdleEnabled != nil || settings.WatchdogBusyEnabled != nil {
o.WatchDog = o.WatchDogIdle || o.WatchDogBusy
}
// The memory reclaimer needs the watchdog run loop.
if settings.MemoryReclaimerEnabled != nil && *settings.MemoryReclaimerEnabled {
o.WatchDog = true
}
// VRAM budget live-apply: a cross-cutting process-global side effect
// (xsysinfo), kept out of the registry row like the watchdog/reclaimer
// invariants above - rows only own config members. An empty string
// clears the cap; a malformed value is rejected by the settings
// endpoint, but stay fail-open here too so a bad persisted value
// cannot wedge apply.
if settings.VRAMBudget != nil {
if b, err := vrambudget.Parse(o.VRAMBudget); err == nil {
xsysinfo.SetDefaultVRAMBudget(b)
}
}
if settings.ArtifactDownloadConcurrency != nil {
if configurable, ok := o.ModelArtifactMaterializer.(interface{ SetDownloadConcurrency(int) }); ok {
configurable.SetDownloadConcurrency(o.ArtifactDownloadConcurrency)
}
}
// Note: ApiKeys need env-merge handling (MergeAPIKeys) - done by the
// caller, because the env-provided keys live on the startup config.
return requireRestart
}
// func WithMetrics(meter *metrics.Metrics) AppOption {
// return func(o *StartupOptions) {
// o.Metrics = meter
// }
// }
// WithRouterDecisionLog retains routing decisions independently of billing stats.
func WithRouterDecisionLog(enabled bool) AppOption {
return func(o *ApplicationConfig) { o.RouterDecisionLog = enabled }
}
// WithDiagnosticsRecorder installs the optional startup-only timing recorder.
func WithDiagnosticsRecorder(r *diagnostics.Recorder) AppOption {
return func(o *ApplicationConfig) { o.DiagnosticsRecorder = r }
}
// DefaultChatsMaxIterations is the step limit of a chat turn when
// chats_max_iterations is not set. Multi-step work (read files, run commands,
// write a result) often needs a few dozen rounds of tool calls.
const DefaultChatsMaxIterations = 40
// MaxChatsMaxIterations is the highest step limit chats_max_iterations takes.
const MaxChatsMaxIterations = 500
// Bounds and defaults of the loop settings of the chats.
const (
MinChatsLoopInterval = 30 * time.Second
MaxChatsLoopInterval = 24 * time.Hour
DefaultChatsLoopMinInterval = time.Minute
DefaultChatsLoopMaxRuns = 100
MaxChatsLoopMaxRuns = 10000
DefaultChatsLoopMaxPerChat = 3
MaxChatsLoopMaxPerChat = 20
DefaultChatsLoopMaxPerUser = 10
MaxChatsLoopMaxPerUser = 200
DefaultChatsLoopDailyRuns = 200
MaxChatsLoopDailyRuns = 100000
)
// EffectiveChatsLoopMinInterval is chats_loop_min_interval as it applies now.
func (o *ApplicationConfig) EffectiveChatsLoopMinInterval() time.Duration {
if d := o.ChatsLoopMinInterval; d >= MinChatsLoopInterval && d <= MaxChatsLoopInterval {
return d
}
return DefaultChatsLoopMinInterval
}
// inRange is n when it is from 1 to hi, else def.
func inRange(n, hi, def int) int {
if n >= 1 && n <= hi {
return n
}
return def
}
// EffectiveChatsLoopMaxRuns is chats_loop_max_runs as it applies now.
func (o *ApplicationConfig) EffectiveChatsLoopMaxRuns() int {
return inRange(o.ChatsLoopMaxRuns, MaxChatsLoopMaxRuns, DefaultChatsLoopMaxRuns)
}
// EffectiveChatsLoopMaxPerChat is chats_loop_max_per_chat as it applies now.
func (o *ApplicationConfig) EffectiveChatsLoopMaxPerChat() int {
return inRange(o.ChatsLoopMaxPerChat, MaxChatsLoopMaxPerChat, DefaultChatsLoopMaxPerChat)
}
// EffectiveChatsLoopMaxPerUser is chats_loop_max_per_user as it applies now.
func (o *ApplicationConfig) EffectiveChatsLoopMaxPerUser() int {
return inRange(o.ChatsLoopMaxPerUser, MaxChatsLoopMaxPerUser, DefaultChatsLoopMaxPerUser)
}
// EffectiveChatsLoopDailyRuns is chats_loop_daily_runs as it applies now.
func (o *ApplicationConfig) EffectiveChatsLoopDailyRuns() int {
return inRange(o.ChatsLoopDailyRuns, MaxChatsLoopDailyRuns, DefaultChatsLoopDailyRuns)
}
// Bounds of chats_index_max_text_bytes.
const (
MinChatsIndexMaxTextBytes = 64 << 10
MaxChatsIndexMaxTextBytes = 16 << 20
DefaultChatsIndexMaxTextBytes = 2 << 20
)
// EffectiveChatsIndexMaxTextBytes is the text cap of one indexed file that
// applies: the setting, or the default when it is unset or out of range.
func (o *ApplicationConfig) EffectiveChatsIndexMaxTextBytes() int {
if n := o.ChatsIndexMaxTextBytes; n >= MinChatsIndexMaxTextBytes && n <= MaxChatsIndexMaxTextBytes {
return n
}
return DefaultChatsIndexMaxTextBytes
}
// DefaultChatsEgressAuditDays is how long the Sandbox network audit keeps a
// line while chats_egress_audit_days is unset.
const DefaultChatsEgressAuditDays = 30
// EffectiveChatsEgressAuditDays is chats_egress_audit_days as it applies now.
func (o *ApplicationConfig) EffectiveChatsEgressAuditDays() int {
if o.ChatsEgressAuditDays < 1 || o.ChatsEgressAuditDays > 365 {
return DefaultChatsEgressAuditDays
}
return o.ChatsEgressAuditDays
}
// Defaults and bounds of chats_goal_max_turns and chats_goal_max_steps.
const (
DefaultChatsGoalMaxTurns = 20
MaxChatsGoalMaxTurns = 200
DefaultChatsGoalMaxSteps = 400
MaxChatsGoalMaxSteps = 10000
)
// EffectiveChatsGoalMaxTurns is chats_goal_max_turns as it applies now.
func (o *ApplicationConfig) EffectiveChatsGoalMaxTurns() int {
if o.ChatsGoalMaxTurns < 1 || o.ChatsGoalMaxTurns > MaxChatsGoalMaxTurns {
return DefaultChatsGoalMaxTurns
}
return o.ChatsGoalMaxTurns
}
// EffectiveChatsGoalMaxSteps is chats_goal_max_steps as it applies now.
func (o *ApplicationConfig) EffectiveChatsGoalMaxSteps() int {
if o.ChatsGoalMaxSteps < 1 || o.ChatsGoalMaxSteps > MaxChatsGoalMaxSteps {
return DefaultChatsGoalMaxSteps
}
return o.ChatsGoalMaxSteps
}
// EffectiveChatsMaxIterations is chats_max_iterations as it applies now.
func (o *ApplicationConfig) EffectiveChatsMaxIterations() int {
if o.ChatsMaxIterations < 1 || o.ChatsMaxIterations > MaxChatsMaxIterations {
return DefaultChatsMaxIterations
}
return o.ChatsMaxIterations
}
func WithP2PEnableLimits(enabled bool) AppOption {
return func(o *ApplicationConfig) { o.P2PEnableLimits = enabled }
}