270 lines
7.1 KiB
Go
270 lines
7.1 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
)
|
|
|
|
const (
|
|
DefaultConfigPath = "/etc/audita/config.yml"
|
|
DefaultConfigPathUsrLocal = "/usr/local/etc/audita/config.yml"
|
|
)
|
|
|
|
var DefaultConfigSearchPaths = []string{
|
|
DefaultConfigPathUsrLocal,
|
|
DefaultConfigPath,
|
|
}
|
|
|
|
func LoadFromEnv() (Config, error) {
|
|
cfg := Default()
|
|
if err := cfg.applyEnvOverrides(os.LookupEnv); err != nil {
|
|
return Config{}, err
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func loadFromLookup(lookup func(string) (string, bool)) (Config, error) {
|
|
cfg := Default()
|
|
if err := cfg.applyEnvOverrides(lookup); err != nil {
|
|
return Config{}, err
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func (c *Config) ApplyEnvOverrides() error {
|
|
return c.applyEnvOverrides(os.LookupEnv)
|
|
}
|
|
|
|
func (c *Config) applyEnvOverrides(lookup func(string) (string, bool)) error {
|
|
if c == nil {
|
|
return fmt.Errorf("config must not be nil")
|
|
}
|
|
|
|
cfg := c
|
|
if raw, ok := lookup("AUDITA_MODULES"); ok {
|
|
modules, err := ParseModulesCSV(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_MODULES: %w", err)
|
|
}
|
|
cfg.Modules = modules
|
|
}
|
|
|
|
primaryLLM := llmTargetPatch{}
|
|
if raw, ok := lookup("AUDITA_LLM_API_KEY"); ok {
|
|
primaryLLM.apiKey = &raw
|
|
} else if raw, ok := lookup("OPENROUTER_API_KEY"); ok {
|
|
primaryLLM.apiKey = &raw
|
|
}
|
|
if raw, ok := lookup("AUDITA_MODEL"); ok {
|
|
primaryLLM.model = &raw
|
|
}
|
|
if raw, ok := lookup("AUDITA_BASE_URL"); ok {
|
|
primaryLLM.baseURL = &raw
|
|
}
|
|
if raw, ok := lookup("AUDITA_LLM_TIMEOUT_SECONDS"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_LLM_TIMEOUT_SECONDS: %w", err)
|
|
}
|
|
primaryLLM.timeoutSeconds = &value
|
|
}
|
|
if raw, ok := lookup("AUDITA_MAX_RETRIES"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_MAX_RETRIES: %w", err)
|
|
}
|
|
primaryLLM.maxRetries = &value
|
|
}
|
|
cfg.applyPrimaryLLMTargetPatch(primaryLLM)
|
|
|
|
validationLLM := llmTargetPatch{}
|
|
if raw, ok := lookup("AUDITA_VALIDATION_LLM_API_KEY"); ok {
|
|
validationLLM.apiKey = &raw
|
|
}
|
|
if raw, ok := lookup("AUDITA_VALIDATION_MODEL"); ok {
|
|
validationLLM.model = &raw
|
|
}
|
|
if raw, ok := lookup("AUDITA_VALIDATION_BASE_URL"); ok {
|
|
validationLLM.baseURL = &raw
|
|
}
|
|
if raw, ok := lookup("AUDITA_VALIDATION_LLM_TIMEOUT_SECONDS"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_VALIDATION_LLM_TIMEOUT_SECONDS: %w", err)
|
|
}
|
|
validationLLM.timeoutSeconds = &value
|
|
}
|
|
if raw, ok := lookup("AUDITA_VALIDATION_MAX_RETRIES"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_VALIDATION_MAX_RETRIES: %w", err)
|
|
}
|
|
validationLLM.maxRetries = &value
|
|
}
|
|
cfg.applyValidationLLMTargetPatch(validationLLM)
|
|
|
|
concurrency := concurrencyPatch{
|
|
inheritProposal: true,
|
|
allowLegacyAlias: true,
|
|
}
|
|
if raw, ok := lookup("AUDITA_TOTAL_LLM_CONCURRENCY"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_TOTAL_LLM_CONCURRENCY: %w", err)
|
|
}
|
|
concurrency.totalLLM = &value
|
|
}
|
|
if raw, ok := lookup("AUDITA_LLM_CONCURRENCY"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_LLM_CONCURRENCY: %w", err)
|
|
}
|
|
concurrency.legacyTotalLLM = &value
|
|
}
|
|
if raw, ok := lookup("AUDITA_PROPOSAL_LLM_CONCURRENCY"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_PROPOSAL_LLM_CONCURRENCY: %w", err)
|
|
}
|
|
concurrency.proposalLLM = &value
|
|
}
|
|
if raw, ok := lookup("AUDITA_VALIDATION_LLM_CONCURRENCY"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_VALIDATION_LLM_CONCURRENCY: %w", err)
|
|
}
|
|
concurrency.validationLLM = &value
|
|
}
|
|
cfg.applyConcurrencyPatch(concurrency)
|
|
|
|
if raw, ok := lookup("AUDITA_VALIDATION_MAX_PROMPT_TOKENS"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_VALIDATION_MAX_PROMPT_TOKENS: %w", err)
|
|
}
|
|
cfg.ValidationMaxPromptTokens = value
|
|
}
|
|
|
|
chunking := chunkingPatch{}
|
|
if raw, ok := lookup("AUDITA_MAX_SECTION_TOKENS"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_MAX_SECTION_TOKENS: %w", err)
|
|
}
|
|
chunking.maxSectionTokens = &value
|
|
}
|
|
|
|
if raw, ok := lookup("AUDITA_MIN_SECTION_TOKENS"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_MIN_SECTION_TOKENS: %w", err)
|
|
}
|
|
chunking.minSectionTokens = &value
|
|
}
|
|
|
|
if raw, ok := lookup("AUDITA_TARGET_SECTIONS"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_TARGET_SECTIONS: %w", err)
|
|
}
|
|
chunking.targetSections = &value
|
|
}
|
|
cfg.applyChunkingPatch(chunking)
|
|
|
|
thresholds := thresholdsPatch{}
|
|
if raw, ok := lookup("AUDITA_GLOSSARY_CONFIDENCE_THRESHOLD"); ok {
|
|
value, err := parseFloat(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_GLOSSARY_CONFIDENCE_THRESHOLD: %w", err)
|
|
}
|
|
thresholds.glossary = &value
|
|
}
|
|
if raw, ok := lookup("AUDITA_GRAMMAR_CONFIDENCE_THRESHOLD"); ok {
|
|
value, err := parseFloat(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_GRAMMAR_CONFIDENCE_THRESHOLD: %w", err)
|
|
}
|
|
thresholds.grammar = &value
|
|
}
|
|
if raw, ok := lookup("AUDITA_HOMOPHONES_CONFIDENCE_THRESHOLD"); ok {
|
|
value, err := parseFloat(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_HOMOPHONES_CONFIDENCE_THRESHOLD: %w", err)
|
|
}
|
|
thresholds.homophones = &value
|
|
}
|
|
if raw, ok := lookup("AUDITA_SPOKEN_WORD_CONFIDENCE_THRESHOLD"); ok {
|
|
value, err := parseFloat(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_SPOKEN_WORD_CONFIDENCE_THRESHOLD: %w", err)
|
|
}
|
|
thresholds.spokenWord = &value
|
|
}
|
|
cfg.applyThresholdsPatch(thresholds)
|
|
|
|
normalization := normalizationPatch{}
|
|
if raw, ok := lookup("AUDITA_NORMALIZE_MAX_SEGMENT_GAP"); ok {
|
|
value, err := parseFloat(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_NORMALIZE_MAX_SEGMENT_GAP: %w", err)
|
|
}
|
|
normalization.maxSegmentGap = &value
|
|
}
|
|
if raw, ok := lookup("AUDITA_NORMALIZE_ELLIPSIS_GAP"); ok {
|
|
value, err := parseFloat(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_NORMALIZE_ELLIPSIS_GAP: %w", err)
|
|
}
|
|
normalization.ellipsisGap = &value
|
|
}
|
|
if raw, ok := lookup("AUDITA_NORMALIZE_MAX_SEGMENT_DURATION"); ok {
|
|
value, err := parseFloat(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_NORMALIZE_MAX_SEGMENT_DURATION: %w", err)
|
|
}
|
|
normalization.maxSegmentDuration = &value
|
|
}
|
|
if raw, ok := lookup("AUDITA_NORMALIZE_MAX_SEGMENT_TOKENS"); ok {
|
|
value, err := parseInt(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("AUDITA_NORMALIZE_MAX_SEGMENT_TOKENS: %w", err)
|
|
}
|
|
normalization.maxSegmentTokens = &value
|
|
}
|
|
cfg.applyNormalizationPatch(normalization)
|
|
|
|
diagnostics := diagnosticsPatch{}
|
|
if raw, ok := lookup("AUDITA_WORK_DIR"); ok {
|
|
diagnostics.workDir = &raw
|
|
}
|
|
if raw, ok := lookup("AUDITA_WORK_DIR_RETENTION"); ok {
|
|
diagnostics.workDirRetention = &raw
|
|
}
|
|
cfg.applyDiagnosticsPatch(diagnostics)
|
|
|
|
cfg.syncLegacyConcurrencyAliases()
|
|
|
|
if err := cfg.Validate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return 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
|
|
}
|