mirror of
https://github.com/mudler/LocalAI.git
synced 2026-09-12 14:22:11 -04:00
[model-config] feat: add environment variables support for backends (#10721)
* feat: add environment variables support for backends in model configurations - Add field to model configuration to pass environment variables to backend processes - Update backend options and model configuration handling - Add documentation for environment variables configuration with examples including CUDA_VISIBLE_DEVICES Assisted-by: qwen-agentworld-35b-a3b Signed-off-by: nold <nold42@pm.me> * fix(test): Test environment variables configuration parsing from YAML Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: nold <Nold360@users.noreply.github.com> --------- Signed-off-by: nold <nold42@pm.me> Signed-off-by: nold <Nold360@users.noreply.github.com> Co-authored-by: nold <nold42@pm.me> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: localai-org-maint-bot <306269227+localai-org-maint-bot@users.noreply.github.com>
This commit is contained in:
11 files changed
+71
-6
No files matched your search
@@ -500,6 +500,9 @@ message ModelOptions {
|
||||
// Unknown keys produce an error at LoadModel time.
|
||||
string EngineArgs = 73;
|
||||
|
||||
// EnvVars carries environment variables to be passed to the backend process.
|
||||
map<string, string> EnvVars = 76;
|
||||
|
||||
// Proxy carries the cloud-proxy backend's per-model configuration.
|
||||
// Empty for non-proxy backends.
|
||||
ProxyOptions Proxy = 74;
|
||||
|
||||
@@ -244,6 +244,10 @@ func ModelOptions(c config.ModelConfig, so *config.ApplicationConfig, opts ...mo
|
||||
defOpts = append(defOpts, model.WithModelSizeBytes(sizeBytes))
|
||||
}
|
||||
|
||||
if c.Environment != nil && len(c.Environment) > 0 {
|
||||
defOpts = append(defOpts, model.WithEnvVars(c.Environment))
|
||||
}
|
||||
|
||||
return append(defOpts, opts...)
|
||||
}
|
||||
|
||||
@@ -525,6 +529,14 @@ func grpcModelOpts(c config.ModelConfig, modelPath string) *pb.ModelOptions {
|
||||
opts.DraftModel = filepath.Join(modelPath, c.DraftModel)
|
||||
}
|
||||
|
||||
// Add environment variables from model configuration
|
||||
if c.Environment != nil && len(c.Environment) > 0 {
|
||||
opts.EnvVars = make(map[string]string)
|
||||
for k, v := range c.Environment {
|
||||
opts.EnvVars[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,13 @@ func DefaultRegistry() map[string]FieldMetaOverride {
|
||||
Advanced: true,
|
||||
Order: 9,
|
||||
},
|
||||
"env": {
|
||||
Section: "general",
|
||||
Label: "Environment Variables",
|
||||
Description: "Environment variables to be applied to the backend process",
|
||||
Component: "map-editor",
|
||||
Order: 10,
|
||||
},
|
||||
|
||||
// --- LLM ---
|
||||
"context_size": {
|
||||
|
||||
@@ -160,6 +160,9 @@ type ModelConfig struct {
|
||||
Proxy ProxyConfig `yaml:"proxy,omitempty" json:"proxy,omitempty"`
|
||||
MITM MITMModelConfig `yaml:"mitm,omitempty" json:"mitm,omitempty"`
|
||||
Limits LimitsConfig `yaml:"limits,omitempty" json:"limits,omitempty"`
|
||||
|
||||
// Environment variables to set when starting the backend process
|
||||
Environment map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
|
||||
}
|
||||
|
||||
// CompressionConfig controls opt-in compression of chat history before inference.
|
||||
|
||||
@@ -199,6 +199,12 @@ parameters:
|
||||
Expect(valid).To(BeTrue())
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Test environment variables configuration parsing from YAML
|
||||
envYAML := "name: env-test-model\nbackend: test-backend\nenv:\n TEST_ENV_VAR: test_value\n ANOTHER_VAR: another_value\n"
|
||||
var envConfig ModelConfig
|
||||
Expect(yaml.Unmarshal([]byte(envYAML), &envConfig)).To(Succeed())
|
||||
Expect(envConfig.Environment).To(HaveKeyWithValue("TEST_ENV_VAR", "test_value"))
|
||||
Expect(envConfig.Environment).To(HaveKeyWithValue("ANOTHER_VAR", "another_value"))
|
||||
tcAndChat := FLAG_TOKEN_CLASSIFY | FLAG_CHAT
|
||||
tcCombined := ModelConfig{
|
||||
Name: "ner-and-chat",
|
||||
|
||||
@@ -465,7 +465,7 @@ func (s *backendSupervisor) startBackend(backend, backendName, backendPath strin
|
||||
bindAddr := fmt.Sprintf("0.0.0.0:%d", port)
|
||||
clientAddr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||
|
||||
proc, err := s.ml.StartProcess(backendPath, backend, bindAddr)
|
||||
proc, err := s.ml.StartProcess(backendPath, backend, bindAddr, nil)
|
||||
if err != nil {
|
||||
s.releasePortForKey(backend, port)
|
||||
s.mu.Unlock()
|
||||
|
||||
@@ -1059,6 +1059,24 @@ Multiple detectors union their detections; overlapping spans resolve to the stro
|
||||
|
||||
> The earlier regex pattern tier (`pii.patterns`, the global pattern catalogue, `--pii-config`, and the `/api/pii/patterns` admin endpoints) has been removed, along with response/streaming-side redaction. Those keys now no-op with a startup warning; migrate to `pii.detectors` + a detector's `pii_detection` block.
|
||||
|
||||
## Environment Variables Configuration
|
||||
|
||||
Model configurations can specify environment variables passed to the backend process:
|
||||
|
||||
```yaml
|
||||
name: vllm-model
|
||||
backend: vllm
|
||||
parameters:
|
||||
model: my-vllm-model
|
||||
|
||||
env:
|
||||
VLLM_WORKER_MULTIPROC_METHOD: "spawn"
|
||||
VLLM_CACHE_DIR: "/tmp/vllm_cache"
|
||||
CUDA_VISIBLE_DEVICES: "0,1"
|
||||
```
|
||||
|
||||
Environment variables are appended to the system environment variables and will override any conflicting system variables with the same name.
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a comprehensive example combining many options:
|
||||
|
||||
@@ -133,7 +133,7 @@ func (ml *ModelLoader) spawnGRPCModel(backend, uri string, o *Options, modelID,
|
||||
return nil, fmt.Errorf("failed allocating free ports: %s", err.Error())
|
||||
}
|
||||
// Make sure the process is executable
|
||||
process, err := ml.startProcess(uri, modelID, serverAddress)
|
||||
process, err := ml.StartProcess(uri, modelID, serverAddress, o.envVars)
|
||||
if err != nil {
|
||||
xlog.Error("failed to launch", "error", err, "path", uri)
|
||||
return nil, err
|
||||
|
||||
@@ -26,6 +26,9 @@ type Options struct {
|
||||
// by the caller using the vram estimation scaffolding. When non-zero it is
|
||||
// registered with the watchdog so size-aware eviction can rank models.
|
||||
modelSizeBytes int64
|
||||
|
||||
// envVars contains model-specific environment variables to pass to the backend process
|
||||
envVars map[string]string
|
||||
}
|
||||
|
||||
// WithConfigRevision binds a load to the semantic revision of the resolved
|
||||
@@ -112,6 +115,12 @@ func WithModelSizeBytes(bytes int64) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithEnvVars(envVars map[string]string) Option {
|
||||
return func(o *Options) {
|
||||
o.envVars = envVars
|
||||
}
|
||||
}
|
||||
|
||||
func NewOptions(opts ...Option) *Options {
|
||||
o := &Options{
|
||||
gRPCOptions: &pb.ModelOptions{},
|
||||
|
||||
+10
-3
@@ -229,11 +229,11 @@ func (ml *ModelLoader) GetGRPCPID(id string) (int, error) {
|
||||
// StartProcess starts a gRPC backend process and returns its process handle.
|
||||
// This is the public wrapper for the internal startProcess method, used by
|
||||
// the serve-backend CLI subcommand to start a backend on a specified address.
|
||||
func (ml *ModelLoader) StartProcess(grpcProcess, id string, serverAddress string, args ...string) (*process.Process, error) {
|
||||
return ml.startProcess(grpcProcess, id, serverAddress, args...)
|
||||
func (ml *ModelLoader) StartProcess(grpcProcess, id string, serverAddress string, envVars map[string]string, args ...string) (*process.Process, error) {
|
||||
return ml.startProcess(grpcProcess, id, serverAddress, envVars, args...)
|
||||
}
|
||||
|
||||
func (ml *ModelLoader) startProcess(grpcProcess, id string, serverAddress string, args ...string) (*process.Process, error) {
|
||||
func (ml *ModelLoader) startProcess(grpcProcess, id string, serverAddress string, envVars map[string]string, args ...string) (*process.Process, error) {
|
||||
// Make sure the process is executable
|
||||
// Check first if it has executable permissions
|
||||
if fi, err := os.Stat(grpcProcess); err == nil {
|
||||
@@ -277,6 +277,13 @@ func (ml *ModelLoader) startProcess(grpcProcess, id string, serverAddress string
|
||||
// exit cannot strand request files directly in the host's shared /tmp.
|
||||
stateDir := runtime.dir
|
||||
|
||||
// Add model-specific environment variables
|
||||
if envVars != nil {
|
||||
for key, value := range envVars {
|
||||
env = append(env, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
}
|
||||
|
||||
grpcControlProcess := process.New(
|
||||
process.WithStateDir(stateDir),
|
||||
process.WithName(filepath.Base(grpcProcess)),
|
||||
|
||||
@@ -28,7 +28,7 @@ var _ = Describe("backend process exit diagnostics", func() {
|
||||
})
|
||||
|
||||
loader := NewModelLoader(&system.SystemState{Model: system.Model{ModelsPath: tmpDir}})
|
||||
process, err := loader.startProcess(backendPath, "test-model", "127.0.0.1:65535")
|
||||
process, err := loader.startProcess(backendPath, "test-model", "127.0.0.1:65535", nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Eventually(process.Done()).Should(BeClosed())
|
||||
backendTemp, err := os.ReadFile(backendPath + ".tmpdir")
|
||||
|
||||
Reference in new issue
Block a user