package config import ( "fmt" "os" "strconv" ) func LoadFromEnv() (Config, error) { return loadFromLookup(os.LookupEnv) } func loadFromLookup(lookup func(string) (string, bool)) (Config, error) { cfg := Default() if raw, ok := lookup("AUDITA_MODULES"); ok { modules, err := ParseModulesCSV(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_MODULES: %w", err) } cfg.Modules = modules } if raw, ok := lookup("AUDITA_LLM_API_KEY"); ok { cfg.PrimaryLLM.APIKey = raw } else if raw, ok := lookup("OPENROUTER_API_KEY"); ok { cfg.PrimaryLLM.APIKey = raw } if raw, ok := lookup("AUDITA_VALIDATION_LLM_API_KEY"); ok { cfg.ValidationLLM.APIKey = raw } if raw, ok := lookup("AUDITA_MODEL"); ok { cfg.PrimaryLLM.Model = raw } if raw, ok := lookup("AUDITA_VALIDATION_MODEL"); ok { cfg.ValidationLLM.Model = raw } if raw, ok := lookup("AUDITA_BASE_URL"); ok { cfg.PrimaryLLM.BaseURL = raw } if raw, ok := lookup("AUDITA_VALIDATION_BASE_URL"); ok { cfg.ValidationLLM.BaseURL = raw } if raw, ok := lookup("AUDITA_LLM_TIMEOUT_SECONDS"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_LLM_TIMEOUT_SECONDS: %w", err) } cfg.PrimaryLLM.TimeoutSeconds = value } if raw, ok := lookup("AUDITA_VALIDATION_LLM_TIMEOUT_SECONDS"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_VALIDATION_LLM_TIMEOUT_SECONDS: %w", err) } cfg.ValidationLLM.TimeoutSeconds = &value } if raw, ok := lookup("AUDITA_MAX_RETRIES"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_MAX_RETRIES: %w", err) } cfg.PrimaryLLM.MaxRetries = value } totalConcurrencySet := false if raw, ok := lookup("AUDITA_TOTAL_LLM_CONCURRENCY"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_TOTAL_LLM_CONCURRENCY: %w", err) } cfg.TotalLLMConcurrency = value totalConcurrencySet = true } if raw, ok := lookup("AUDITA_LLM_CONCURRENCY"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_LLM_CONCURRENCY: %w", err) } if !totalConcurrencySet { cfg.TotalLLMConcurrency = value totalConcurrencySet = true } } proposalConcurrencySet := false if raw, ok := lookup("AUDITA_PROPOSAL_LLM_CONCURRENCY"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_PROPOSAL_LLM_CONCURRENCY: %w", err) } cfg.ProposalLLMConcurrency = value proposalConcurrencySet = true } if totalConcurrencySet && !proposalConcurrencySet { cfg.ProposalLLMConcurrency = cfg.TotalLLMConcurrency } if raw, ok := lookup("AUDITA_VALIDATION_MAX_RETRIES"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_VALIDATION_MAX_RETRIES: %w", err) } cfg.ValidationLLM.MaxRetries = &value } if raw, ok := lookup("AUDITA_VALIDATION_LLM_CONCURRENCY"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_VALIDATION_LLM_CONCURRENCY: %w", err) } cfg.ValidationLLMConcurrency = &value } if raw, ok := lookup("AUDITA_VALIDATION_MAX_PROMPT_TOKENS"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_VALIDATION_MAX_PROMPT_TOKENS: %w", err) } cfg.ValidationMaxPromptTokens = value } if raw, ok := lookup("AUDITA_MAX_SECTION_TOKENS"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_MAX_SECTION_TOKENS: %w", err) } cfg.MaxSectionTokens = value } if raw, ok := lookup("AUDITA_MIN_SECTION_TOKENS"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_MIN_SECTION_TOKENS: %w", err) } cfg.MinSectionTokens = value } if raw, ok := lookup("AUDITA_TARGET_SECTIONS"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_TARGET_SECTIONS: %w", err) } cfg.TargetSections = &value } if raw, ok := lookup("AUDITA_GLOSSARY_CONFIDENCE_THRESHOLD"); ok { value, err := parseFloat(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_GLOSSARY_CONFIDENCE_THRESHOLD: %w", err) } cfg.Thresholds.Glossary = value } if raw, ok := lookup("AUDITA_GRAMMAR_CONFIDENCE_THRESHOLD"); ok { value, err := parseFloat(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_GRAMMAR_CONFIDENCE_THRESHOLD: %w", err) } cfg.Thresholds.Grammar = value } if raw, ok := lookup("AUDITA_HOMOPHONES_CONFIDENCE_THRESHOLD"); ok { value, err := parseFloat(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_HOMOPHONES_CONFIDENCE_THRESHOLD: %w", err) } cfg.Thresholds.Homophones = value } if raw, ok := lookup("AUDITA_SPOKEN_WORD_CONFIDENCE_THRESHOLD"); ok { value, err := parseFloat(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_SPOKEN_WORD_CONFIDENCE_THRESHOLD: %w", err) } cfg.Thresholds.SpokenWord = value } if raw, ok := lookup("AUDITA_NORMALIZE_MAX_SEGMENT_GAP"); ok { value, err := parseFloat(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_NORMALIZE_MAX_SEGMENT_GAP: %w", err) } cfg.Normalization.MaxSegmentGap = value } if raw, ok := lookup("AUDITA_NORMALIZE_ELLIPSIS_GAP"); ok { value, err := parseFloat(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_NORMALIZE_ELLIPSIS_GAP: %w", err) } cfg.Normalization.EllipsisGap = value } if raw, ok := lookup("AUDITA_NORMALIZE_MAX_SEGMENT_DURATION"); ok { value, err := parseFloat(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_NORMALIZE_MAX_SEGMENT_DURATION: %w", err) } cfg.Normalization.MaxSegmentDuration = value } if raw, ok := lookup("AUDITA_NORMALIZE_MAX_SEGMENT_TOKENS"); ok { value, err := parseInt(raw) if err != nil { return Config{}, fmt.Errorf("AUDITA_NORMALIZE_MAX_SEGMENT_TOKENS: %w", err) } cfg.Normalization.MaxSegmentTokens = value } if raw, ok := lookup("AUDITA_WORK_DIR"); ok { cfg.WorkDir = raw } if raw, ok := lookup("AUDITA_WORK_DIR_RETENTION"); ok { cfg.WorkDirRetention = WorkDirRetention(raw) } cfg.syncLegacyConcurrencyAliases() if err := cfg.Validate(); err != nil { return Config{}, err } return cfg, nil } func parseInt(raw string) (int, error) { value, err := strconv.Atoi(raw) if err != nil { return 0, fmt.Errorf("must be an integer") } return value, nil } func parseFloat(raw string) (float64, error) { value, err := strconv.ParseFloat(raw, 64) if err != nil { return 0, fmt.Errorf("must be a number") } return value, nil }