mirror of
https://github.com/mudler/LocalAI.git
synced 2026-04-19 06:20:42 -04:00
* feat: add backend versioning data model foundation Add Version, URI, and Digest fields to BackendMetadata for tracking installed backend versions and enabling upgrade detection. Add Version field to GalleryBackend. Add UpgradeAvailable/AvailableVersion fields to SystemBackend. Implement GetImageDigest() for lightweight OCI digest lookups via remote.Head. Record version, URI, and digest at install time in InstallBackend() and propagate version through meta backends. * feat: add backend upgrade detection and execution logic Add CheckBackendUpgrades() to compare installed backend versions/digests against gallery entries, and UpgradeBackend() to perform atomic upgrades with backup-based rollback on failure. Includes Agent A's data model changes (Version/URI/Digest fields, GetImageDigest). * feat: add AutoUpgradeBackends config and runtime settings Add configuration and runtime settings for backend auto-upgrade: - RuntimeSettings field for dynamic config via API/JSON - ApplicationConfig field, option func, and roundtrip conversion - CLI flag with LOCALAI_AUTO_UPGRADE_BACKENDS env var - Config file watcher support for runtime_settings.json - Tests for ToRuntimeSettings, ApplyRuntimeSettings, and roundtrip * feat(ui): add backend version display and upgrade support - Add upgrade check/trigger API endpoints to config and api module - Backends page: version badge, upgrade indicator, upgrade button - Manage page: version in metadata, context-aware upgrade/reinstall button - Settings page: auto-upgrade backends toggle * feat: add upgrade checker service, API endpoints, and CLI command - UpgradeChecker background service: checks every 6h, auto-upgrades when enabled - API endpoints: GET /backends/upgrades, POST /backends/upgrades/check, POST /backends/upgrade/:name - CLI: `localai backends upgrade` command, version display in `backends list` - BackendManager interface: add UpgradeBackend and CheckUpgrades methods - Wire upgrade op through GalleryService backend handler - Distributed mode: fan-out upgrade to worker nodes via NATS * fix: use advisory lock for upgrade checker in distributed mode In distributed mode with multiple frontend instances, use PostgreSQL advisory lock (KeyBackendUpgradeCheck) so only one instance runs periodic upgrade checks and auto-upgrades. Prevents duplicate upgrade operations across replicas. Standalone mode is unchanged (simple ticker loop). * test: add e2e tests for backend upgrade API - Test GET /api/backends/upgrades returns 200 (even with no upgrade checker) - Test POST /api/backends/upgrade/:name accepts request and returns job ID - Test full upgrade flow: trigger upgrade via API, wait for job completion, verify run.sh updated to v2 and metadata.json has version 2.0.0 - Test POST /api/backends/upgrades/check returns 200 - Fix nil check for applicationInstance in upgrade API routes
1147 lines
32 KiB
Go
1147 lines
32 KiB
Go
package config
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/mudler/LocalAI/pkg/system"
|
|
"github.com/mudler/LocalAI/pkg/xsysinfo"
|
|
"github.com/mudler/xlog"
|
|
)
|
|
|
|
type ApplicationConfig struct {
|
|
Context context.Context
|
|
ConfigFile string
|
|
SystemState *system.SystemState
|
|
ExternalBackends []string
|
|
UploadLimitMB, Threads, ContextSize int
|
|
F16 bool
|
|
Debug bool
|
|
EnableTracing bool
|
|
TracingMaxItems int
|
|
EnableBackendLogging bool
|
|
GeneratedContentDir string
|
|
|
|
UploadDir string
|
|
DataPath string // Persistent data directory for collectiondb, agents, etc.
|
|
|
|
DynamicConfigsDir string
|
|
DynamicConfigsDirPollInterval time.Duration
|
|
CORS bool
|
|
DisableCSRF bool
|
|
PreloadJSONModels string
|
|
PreloadModelsFromPath string
|
|
CORSAllowOrigins string
|
|
ApiKeys []string
|
|
P2PToken string
|
|
P2PNetworkID string
|
|
Federated bool
|
|
|
|
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
|
|
AutoUpgradeBackends 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%)
|
|
|
|
// Eviction settings
|
|
ForceEvictionWhenBusy bool // Force eviction even when models have active API calls (default: false for safety)
|
|
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)
|
|
|
|
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
|
|
|
|
AgentJobRetentionDays int // Default: 30 days
|
|
|
|
OpenResponsesStoreTTL time.Duration // TTL for Open Responses store (0 = no expiration)
|
|
|
|
PathWithoutAuth []string
|
|
|
|
// Agent Pool (LocalAGI integration)
|
|
AgentPool AgentPoolConfig
|
|
|
|
// Authentication & Authorization
|
|
Auth AuthConfig
|
|
|
|
// Distributed / Horizontal Scaling
|
|
Distributed DistributedConfig
|
|
}
|
|
|
|
// 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
|
|
BaseURL string // for OAuth callback URLs (e.g. "http://localhost:8080")
|
|
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
|
|
}
|
|
|
|
// AgentPoolConfig holds configuration for the LocalAGI agent pool integration.
|
|
type AgentPoolConfig struct {
|
|
Enabled bool // default: true (disabled by LOCALAI_DISABLE_AGENTS=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
|
|
MultimodalModel string
|
|
TranscriptionModel string
|
|
TranscriptionLanguage string
|
|
TTSModel string
|
|
Timeout string // default: "5m"
|
|
EnableSkills bool
|
|
EnableLogs bool
|
|
CustomActionsDir string
|
|
CollectionDBPath string
|
|
VectorEngine string // default: "chromem"
|
|
EmbeddingModel string // default: "granite-embedding-107m-multilingual"
|
|
MaxChunkingSize int // default: 400
|
|
ChunkOverlap int // default: 0
|
|
DatabaseURL string
|
|
AgentHubURL string // default: "https://agenthub.localai.io"
|
|
}
|
|
|
|
type AppOption func(*ApplicationConfig)
|
|
|
|
func NewApplicationConfig(o ...AppOption) *ApplicationConfig {
|
|
opt := &ApplicationConfig{
|
|
Context: context.Background(),
|
|
UploadLimitMB: 15,
|
|
Debug: true,
|
|
AgentJobRetentionDays: 30, // Default: 30 days
|
|
LRUEvictionMaxRetries: 30, // Default: 30 retries
|
|
LRUEvictionRetryInterval: 1 * time.Second, // Default: 1 second
|
|
WatchDogInterval: 500 * time.Millisecond, // Default: 500ms
|
|
TracingMaxItems: 1024,
|
|
AgentPool: AgentPoolConfig{
|
|
Enabled: true,
|
|
Timeout: "5m",
|
|
VectorEngine: "chromem",
|
|
EmbeddingModel: "granite-embedding-107m-multilingual",
|
|
MaxChunkingSize: 400,
|
|
AgentHubURL: "https://agenthub.localai.io",
|
|
},
|
|
PathWithoutAuth: []string{
|
|
"/static/",
|
|
"/generated-audio/",
|
|
"/generated-images/",
|
|
"/generated-videos/",
|
|
"/favicon.svg",
|
|
"/readyz",
|
|
"/healthz",
|
|
"/api/auth/",
|
|
"/assets/",
|
|
},
|
|
}
|
|
for _, oo := range o {
|
|
oo(opt)
|
|
}
|
|
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 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 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
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
}
|
|
|
|
var EnableGalleriesAutoload = func(o *ApplicationConfig) {
|
|
o.AutoloadGalleries = true
|
|
}
|
|
|
|
var EnableBackendGalleriesAutoload = func(o *ApplicationConfig) {
|
|
o.AutoloadBackendGalleries = true
|
|
}
|
|
|
|
func WithAutoUpgradeBackends(v bool) AppOption {
|
|
return func(o *ApplicationConfig) { o.AutoUpgradeBackends = 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
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
func WithUploadLimitMB(limit int) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.UploadLimitMB = limit
|
|
}
|
|
}
|
|
|
|
func WithThreads(threads int) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
if threads == 0 { // 0 is not allowed
|
|
threads = xsysinfo.CPUPhysicalCores()
|
|
}
|
|
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
|
|
}
|
|
}
|
|
|
|
func WithTracingMaxItems(items int) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.TracingMaxItems = items
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
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 WithAgentJobRetentionDays(days int) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentJobRetentionDays = days
|
|
}
|
|
}
|
|
|
|
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 DisableAgentPool = func(o *ApplicationConfig) {
|
|
o.AgentPool.Enabled = false
|
|
}
|
|
|
|
func WithAgentPoolAPIURL(url string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.APIURL = url
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolAPIKey(key string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.APIKey = key
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolDefaultModel(model string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.DefaultModel = model
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolMultimodalModel(model string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.MultimodalModel = model
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolTranscriptionModel(model string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.TranscriptionModel = model
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolTranscriptionLanguage(lang string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.TranscriptionLanguage = lang
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolTTSModel(model string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.TTSModel = model
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolStateDir(dir string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.StateDir = dir
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolTimeout(timeout string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.Timeout = timeout
|
|
}
|
|
}
|
|
|
|
var EnableAgentPoolSkills = func(o *ApplicationConfig) {
|
|
o.AgentPool.EnableSkills = true
|
|
}
|
|
|
|
func WithAgentPoolVectorEngine(engine string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.VectorEngine = engine
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolEmbeddingModel(model string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.EmbeddingModel = model
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolCustomActionsDir(dir string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.CustomActionsDir = dir
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolDatabaseURL(url string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.DatabaseURL = url
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolMaxChunkingSize(size int) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.MaxChunkingSize = size
|
|
}
|
|
}
|
|
|
|
func WithAgentPoolChunkOverlap(overlap int) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.ChunkOverlap = overlap
|
|
}
|
|
}
|
|
|
|
var EnableAgentPoolLogs = func(o *ApplicationConfig) {
|
|
o.AgentPool.EnableLogs = true
|
|
}
|
|
|
|
func WithAgentPoolCollectionDBPath(path string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.CollectionDBPath = path
|
|
}
|
|
}
|
|
|
|
func WithAgentHubURL(url string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.AgentPool.AgentHubURL = url
|
|
}
|
|
}
|
|
|
|
// 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 WithAuthBaseURL(baseURL string) AppOption {
|
|
return func(o *ApplicationConfig) {
|
|
o.Auth.BaseURL = baseURL
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
// 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),
|
|
}
|
|
}
|
|
|
|
// ToRuntimeSettings converts ApplicationConfig to RuntimeSettings for API responses and JSON serialization.
|
|
// This provides a single source of truth - ApplicationConfig holds the live values,
|
|
// and this method creates a RuntimeSettings snapshot for external consumption.
|
|
func (o *ApplicationConfig) ToRuntimeSettings() RuntimeSettings {
|
|
// Create local copies for pointer fields
|
|
watchdogEnabled := o.WatchDog
|
|
watchdogIdle := o.WatchDogIdle
|
|
watchdogBusy := o.WatchDogBusy
|
|
singleBackend := o.SingleBackend
|
|
maxActiveBackends := o.MaxActiveBackends
|
|
memoryReclaimerEnabled := o.MemoryReclaimerEnabled
|
|
memoryReclaimerThreshold := o.MemoryReclaimerThreshold
|
|
forceEvictionWhenBusy := o.ForceEvictionWhenBusy
|
|
lruEvictionMaxRetries := o.LRUEvictionMaxRetries
|
|
threads := o.Threads
|
|
contextSize := o.ContextSize
|
|
f16 := o.F16
|
|
debug := o.Debug
|
|
tracingMaxItems := o.TracingMaxItems
|
|
enableTracing := o.EnableTracing
|
|
enableBackendLogging := o.EnableBackendLogging
|
|
cors := o.CORS
|
|
csrf := o.DisableCSRF
|
|
corsAllowOrigins := o.CORSAllowOrigins
|
|
p2pToken := o.P2PToken
|
|
p2pNetworkID := o.P2PNetworkID
|
|
federated := o.Federated
|
|
galleries := o.Galleries
|
|
backendGalleries := o.BackendGalleries
|
|
autoloadGalleries := o.AutoloadGalleries
|
|
autoloadBackendGalleries := o.AutoloadBackendGalleries
|
|
autoUpgradeBackends := o.AutoUpgradeBackends
|
|
apiKeys := o.ApiKeys
|
|
agentJobRetentionDays := o.AgentJobRetentionDays
|
|
|
|
// Format timeouts as strings
|
|
var idleTimeout, busyTimeout, watchdogInterval string
|
|
if o.WatchDogIdleTimeout > 0 {
|
|
idleTimeout = o.WatchDogIdleTimeout.String()
|
|
} else {
|
|
idleTimeout = "15m" // default
|
|
}
|
|
if o.WatchDogBusyTimeout > 0 {
|
|
busyTimeout = o.WatchDogBusyTimeout.String()
|
|
} else {
|
|
busyTimeout = "5m" // default
|
|
}
|
|
if o.WatchDogInterval > 0 {
|
|
watchdogInterval = o.WatchDogInterval.String()
|
|
} else {
|
|
watchdogInterval = "2s" // default
|
|
}
|
|
var lruEvictionRetryInterval string
|
|
if o.LRUEvictionRetryInterval > 0 {
|
|
lruEvictionRetryInterval = o.LRUEvictionRetryInterval.String()
|
|
} else {
|
|
lruEvictionRetryInterval = "1s" // default
|
|
}
|
|
var openResponsesStoreTTL string
|
|
if o.OpenResponsesStoreTTL > 0 {
|
|
openResponsesStoreTTL = o.OpenResponsesStoreTTL.String()
|
|
} else {
|
|
openResponsesStoreTTL = "0" // default: no expiration
|
|
}
|
|
|
|
// Agent Pool settings
|
|
agentPoolEnabled := o.AgentPool.Enabled
|
|
agentPoolDefaultModel := o.AgentPool.DefaultModel
|
|
agentPoolEmbeddingModel := o.AgentPool.EmbeddingModel
|
|
agentPoolMaxChunkingSize := o.AgentPool.MaxChunkingSize
|
|
agentPoolChunkOverlap := o.AgentPool.ChunkOverlap
|
|
agentPoolEnableLogs := o.AgentPool.EnableLogs
|
|
agentPoolCollectionDBPath := o.AgentPool.CollectionDBPath
|
|
|
|
return RuntimeSettings{
|
|
WatchdogEnabled: &watchdogEnabled,
|
|
WatchdogIdleEnabled: &watchdogIdle,
|
|
WatchdogBusyEnabled: &watchdogBusy,
|
|
WatchdogIdleTimeout: &idleTimeout,
|
|
WatchdogBusyTimeout: &busyTimeout,
|
|
WatchdogInterval: &watchdogInterval,
|
|
SingleBackend: &singleBackend,
|
|
MaxActiveBackends: &maxActiveBackends,
|
|
MemoryReclaimerEnabled: &memoryReclaimerEnabled,
|
|
MemoryReclaimerThreshold: &memoryReclaimerThreshold,
|
|
ForceEvictionWhenBusy: &forceEvictionWhenBusy,
|
|
LRUEvictionMaxRetries: &lruEvictionMaxRetries,
|
|
LRUEvictionRetryInterval: &lruEvictionRetryInterval,
|
|
Threads: &threads,
|
|
ContextSize: &contextSize,
|
|
F16: &f16,
|
|
Debug: &debug,
|
|
TracingMaxItems: &tracingMaxItems,
|
|
EnableTracing: &enableTracing,
|
|
EnableBackendLogging: &enableBackendLogging,
|
|
CORS: &cors,
|
|
CSRF: &csrf,
|
|
CORSAllowOrigins: &corsAllowOrigins,
|
|
P2PToken: &p2pToken,
|
|
P2PNetworkID: &p2pNetworkID,
|
|
Federated: &federated,
|
|
Galleries: &galleries,
|
|
BackendGalleries: &backendGalleries,
|
|
AutoloadGalleries: &autoloadGalleries,
|
|
AutoloadBackendGalleries: &autoloadBackendGalleries,
|
|
AutoUpgradeBackends: &autoUpgradeBackends,
|
|
ApiKeys: &apiKeys,
|
|
AgentJobRetentionDays: &agentJobRetentionDays,
|
|
OpenResponsesStoreTTL: &openResponsesStoreTTL,
|
|
AgentPoolEnabled: &agentPoolEnabled,
|
|
AgentPoolDefaultModel: &agentPoolDefaultModel,
|
|
AgentPoolEmbeddingModel: &agentPoolEmbeddingModel,
|
|
AgentPoolMaxChunkingSize: &agentPoolMaxChunkingSize,
|
|
AgentPoolChunkOverlap: &agentPoolChunkOverlap,
|
|
AgentPoolEnableLogs: &agentPoolEnableLogs,
|
|
AgentPoolCollectionDBPath: &agentPoolCollectionDBPath,
|
|
}
|
|
}
|
|
|
|
// ApplyRuntimeSettings applies RuntimeSettings to ApplicationConfig.
|
|
// Only non-nil fields in RuntimeSettings are applied.
|
|
// Returns true if watchdog-related settings changed (requiring restart).
|
|
func (o *ApplicationConfig) ApplyRuntimeSettings(settings *RuntimeSettings) (requireRestart bool) {
|
|
if settings == nil {
|
|
return false
|
|
}
|
|
|
|
if settings.WatchdogEnabled != nil {
|
|
o.WatchDog = *settings.WatchdogEnabled
|
|
requireRestart = true
|
|
}
|
|
if settings.WatchdogIdleEnabled != nil {
|
|
o.WatchDogIdle = *settings.WatchdogIdleEnabled
|
|
if o.WatchDogIdle {
|
|
o.WatchDog = true
|
|
}
|
|
requireRestart = true
|
|
}
|
|
if settings.WatchdogBusyEnabled != nil {
|
|
o.WatchDogBusy = *settings.WatchdogBusyEnabled
|
|
if o.WatchDogBusy {
|
|
o.WatchDog = true
|
|
}
|
|
requireRestart = true
|
|
}
|
|
if settings.WatchdogIdleTimeout != nil {
|
|
if dur, err := time.ParseDuration(*settings.WatchdogIdleTimeout); err == nil {
|
|
o.WatchDogIdleTimeout = dur
|
|
requireRestart = true
|
|
}
|
|
}
|
|
if settings.WatchdogBusyTimeout != nil {
|
|
if dur, err := time.ParseDuration(*settings.WatchdogBusyTimeout); err == nil {
|
|
o.WatchDogBusyTimeout = dur
|
|
requireRestart = true
|
|
}
|
|
}
|
|
if settings.WatchdogInterval != nil {
|
|
if dur, err := time.ParseDuration(*settings.WatchdogInterval); err == nil {
|
|
o.WatchDogInterval = dur
|
|
requireRestart = true
|
|
}
|
|
}
|
|
if settings.MaxActiveBackends != nil {
|
|
o.MaxActiveBackends = *settings.MaxActiveBackends
|
|
o.SingleBackend = (*settings.MaxActiveBackends == 1)
|
|
requireRestart = true
|
|
} else if settings.SingleBackend != nil {
|
|
o.SingleBackend = *settings.SingleBackend
|
|
if *settings.SingleBackend {
|
|
o.MaxActiveBackends = 1
|
|
} else {
|
|
o.MaxActiveBackends = 0
|
|
}
|
|
requireRestart = true
|
|
}
|
|
if settings.MemoryReclaimerEnabled != nil {
|
|
o.MemoryReclaimerEnabled = *settings.MemoryReclaimerEnabled
|
|
if *settings.MemoryReclaimerEnabled {
|
|
o.WatchDog = true
|
|
}
|
|
requireRestart = true
|
|
}
|
|
if settings.MemoryReclaimerThreshold != nil {
|
|
if *settings.MemoryReclaimerThreshold > 0 && *settings.MemoryReclaimerThreshold <= 1.0 {
|
|
o.MemoryReclaimerThreshold = *settings.MemoryReclaimerThreshold
|
|
requireRestart = true
|
|
}
|
|
}
|
|
if settings.ForceEvictionWhenBusy != nil {
|
|
o.ForceEvictionWhenBusy = *settings.ForceEvictionWhenBusy
|
|
// This setting doesn't require restart, can be updated dynamically
|
|
}
|
|
if settings.LRUEvictionMaxRetries != nil {
|
|
o.LRUEvictionMaxRetries = *settings.LRUEvictionMaxRetries
|
|
// This setting doesn't require restart, can be updated dynamically
|
|
}
|
|
if settings.LRUEvictionRetryInterval != nil {
|
|
if dur, err := time.ParseDuration(*settings.LRUEvictionRetryInterval); err == nil {
|
|
o.LRUEvictionRetryInterval = dur
|
|
// This setting doesn't require restart, can be updated dynamically
|
|
}
|
|
}
|
|
if settings.Threads != nil {
|
|
o.Threads = *settings.Threads
|
|
}
|
|
if settings.ContextSize != nil {
|
|
o.ContextSize = *settings.ContextSize
|
|
}
|
|
if settings.F16 != nil {
|
|
o.F16 = *settings.F16
|
|
}
|
|
if settings.Debug != nil {
|
|
o.Debug = *settings.Debug
|
|
}
|
|
if settings.EnableTracing != nil {
|
|
o.EnableTracing = *settings.EnableTracing
|
|
}
|
|
if settings.TracingMaxItems != nil {
|
|
o.TracingMaxItems = *settings.TracingMaxItems
|
|
}
|
|
if settings.EnableBackendLogging != nil {
|
|
o.EnableBackendLogging = *settings.EnableBackendLogging
|
|
}
|
|
if settings.CORS != nil {
|
|
o.CORS = *settings.CORS
|
|
}
|
|
if settings.CSRF != nil {
|
|
o.DisableCSRF = *settings.CSRF
|
|
}
|
|
if settings.CORSAllowOrigins != nil {
|
|
o.CORSAllowOrigins = *settings.CORSAllowOrigins
|
|
}
|
|
if settings.P2PToken != nil {
|
|
o.P2PToken = *settings.P2PToken
|
|
}
|
|
if settings.P2PNetworkID != nil {
|
|
o.P2PNetworkID = *settings.P2PNetworkID
|
|
}
|
|
if settings.Federated != nil {
|
|
o.Federated = *settings.Federated
|
|
}
|
|
if settings.Galleries != nil {
|
|
o.Galleries = *settings.Galleries
|
|
}
|
|
if settings.BackendGalleries != nil {
|
|
o.BackendGalleries = *settings.BackendGalleries
|
|
}
|
|
if settings.AutoloadGalleries != nil {
|
|
o.AutoloadGalleries = *settings.AutoloadGalleries
|
|
}
|
|
if settings.AutoloadBackendGalleries != nil {
|
|
o.AutoloadBackendGalleries = *settings.AutoloadBackendGalleries
|
|
}
|
|
if settings.AutoUpgradeBackends != nil {
|
|
o.AutoUpgradeBackends = *settings.AutoUpgradeBackends
|
|
}
|
|
if settings.AgentJobRetentionDays != nil {
|
|
o.AgentJobRetentionDays = *settings.AgentJobRetentionDays
|
|
}
|
|
if settings.OpenResponsesStoreTTL != nil {
|
|
if *settings.OpenResponsesStoreTTL == "0" || *settings.OpenResponsesStoreTTL == "" {
|
|
o.OpenResponsesStoreTTL = 0 // No expiration
|
|
} else if dur, err := time.ParseDuration(*settings.OpenResponsesStoreTTL); err == nil {
|
|
o.OpenResponsesStoreTTL = dur
|
|
}
|
|
// This setting doesn't require restart, can be updated dynamically
|
|
}
|
|
// Agent Pool settings
|
|
if settings.AgentPoolEnabled != nil {
|
|
o.AgentPool.Enabled = *settings.AgentPoolEnabled
|
|
requireRestart = true
|
|
}
|
|
if settings.AgentPoolDefaultModel != nil {
|
|
o.AgentPool.DefaultModel = *settings.AgentPoolDefaultModel
|
|
requireRestart = true
|
|
}
|
|
if settings.AgentPoolEmbeddingModel != nil {
|
|
o.AgentPool.EmbeddingModel = *settings.AgentPoolEmbeddingModel
|
|
requireRestart = true
|
|
}
|
|
if settings.AgentPoolMaxChunkingSize != nil {
|
|
o.AgentPool.MaxChunkingSize = *settings.AgentPoolMaxChunkingSize
|
|
requireRestart = true
|
|
}
|
|
if settings.AgentPoolChunkOverlap != nil {
|
|
o.AgentPool.ChunkOverlap = *settings.AgentPoolChunkOverlap
|
|
requireRestart = true
|
|
}
|
|
if settings.AgentPoolEnableLogs != nil {
|
|
o.AgentPool.EnableLogs = *settings.AgentPoolEnableLogs
|
|
requireRestart = true
|
|
}
|
|
if settings.AgentPoolCollectionDBPath != nil {
|
|
o.AgentPool.CollectionDBPath = *settings.AgentPoolCollectionDBPath
|
|
requireRestart = true
|
|
}
|
|
|
|
// Note: ApiKeys requires special handling (merging with startup keys) - handled in caller
|
|
|
|
return requireRestart
|
|
}
|
|
|
|
// func WithMetrics(meter *metrics.Metrics) AppOption {
|
|
// return func(o *StartupOptions) {
|
|
// o.Metrics = meter
|
|
// }
|
|
// }
|