This commit is contained in:
@@ -45,13 +45,21 @@ type Config struct {
|
||||
OllamaMaxConcurrent int
|
||||
OllamaJSONRetries int
|
||||
|
||||
KnowledgeDir string
|
||||
RAGEnabled bool
|
||||
KnowledgeTopK int
|
||||
CategoryPromptLimit int
|
||||
KnowledgeAllowedSources []string
|
||||
KnowledgeAutoReplySources []string
|
||||
KnowledgeWebEditEnabled bool
|
||||
KnowledgeDir string
|
||||
RAGEnabled bool
|
||||
KnowledgeTopK int
|
||||
CategoryPromptLimit int
|
||||
KnowledgeAllowedSources []string
|
||||
KnowledgeAutoReplySources []string
|
||||
KnowledgeWebEditEnabled bool
|
||||
GLPIKBEnabled bool
|
||||
GLPIKBPath string
|
||||
GLPIKBFilter string
|
||||
GLPIKBLimit int
|
||||
GLPIKBSyncInterval time.Duration
|
||||
GLPIKBSource string
|
||||
GLPIKBAutoReply bool
|
||||
GLPIKBAutoReplyCategoryIDs []int64
|
||||
|
||||
LearningEnabled bool
|
||||
LearningMaxExamples int
|
||||
@@ -139,6 +147,14 @@ func Load() (Config, error) {
|
||||
KnowledgeAllowedSources: envStringList("KNOWLEDGE_ALLOWED_SOURCES", "internal-kb"),
|
||||
KnowledgeAutoReplySources: envStringList("KNOWLEDGE_AUTO_REPLY_SOURCES", "internal-kb"),
|
||||
KnowledgeWebEditEnabled: envBool("KNOWLEDGE_WEB_EDIT_ENABLED", false),
|
||||
GLPIKBEnabled: envBool("GLPI_KB_ENABLED", false),
|
||||
GLPIKBPath: env("GLPI_KB_PATH", "auto"),
|
||||
GLPIKBFilter: strings.TrimSpace(os.Getenv("GLPI_KB_FILTER")),
|
||||
GLPIKBLimit: envInt("GLPI_KB_LIMIT", 500),
|
||||
GLPIKBSyncInterval: envDuration("GLPI_KB_SYNC_INTERVAL", 10*time.Minute),
|
||||
GLPIKBSource: strings.ToLower(env("GLPI_KB_SOURCE", "glpi-kb")),
|
||||
GLPIKBAutoReply: envBool("GLPI_KB_AUTO_REPLY", false),
|
||||
GLPIKBAutoReplyCategoryIDs: envInt64ListAllowEmpty("GLPI_KB_AUTO_REPLY_CATEGORY_IDS"),
|
||||
LearningEnabled: envBool("LEARNING_ENABLED", true),
|
||||
LearningMaxExamples: envInt("LEARNING_MAX_EXAMPLES", 500),
|
||||
LearningExamplesPerCategory: envInt("LEARNING_EXAMPLES_PER_CATEGORY", 5),
|
||||
@@ -292,6 +308,38 @@ func (c Config) Validate() error {
|
||||
return fmt.Errorf("KNOWLEDGE_AUTO_REPLY_SOURCES source %q is not present in KNOWLEDGE_ALLOWED_SOURCES", source)
|
||||
}
|
||||
}
|
||||
if c.GLPIKBEnabled {
|
||||
if c.GLPIKBLimit < 1 || c.GLPIKBLimit > 5000 {
|
||||
return errors.New("GLPI_KB_LIMIT must be between 1 and 5000")
|
||||
}
|
||||
if c.GLPIKBSyncInterval < time.Minute {
|
||||
return errors.New("GLPI_KB_SYNC_INTERVAL must be at least 1m")
|
||||
}
|
||||
if c.GLPIKBPath != "auto" && !validAPIPath(c.GLPIKBPath) {
|
||||
return errors.New("GLPI_KB_PATH must be 'auto' or an absolute API path")
|
||||
}
|
||||
if strings.TrimSpace(c.GLPIKBSource) == "" {
|
||||
return errors.New("GLPI_KB_SOURCE must not be empty")
|
||||
}
|
||||
if _, ok := allowedSources[c.GLPIKBSource]; !ok {
|
||||
return fmt.Errorf("GLPI_KB_SOURCE %q must be present in KNOWLEDGE_ALLOWED_SOURCES", c.GLPIKBSource)
|
||||
}
|
||||
if c.GLPIKBAutoReply {
|
||||
if len(c.GLPIKBAutoReplyCategoryIDs) == 0 {
|
||||
return errors.New("GLPI_KB_AUTO_REPLY_CATEGORY_IDS must contain at least one GLPI KB category when GLPI_KB_AUTO_REPLY=true")
|
||||
}
|
||||
found := false
|
||||
for _, source := range c.KnowledgeAutoReplySources {
|
||||
if strings.EqualFold(source, c.GLPIKBSource) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("GLPI_KB_SOURCE %q must be present in KNOWLEDGE_AUTO_REPLY_SOURCES when GLPI_KB_AUTO_REPLY=true", c.GLPIKBSource)
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.AutoReply && len(c.KnowledgeAutoReplySources) == 0 {
|
||||
return errors.New("KNOWLEDGE_AUTO_REPLY_SOURCES must contain at least one source when AUTO_REPLY=true")
|
||||
}
|
||||
@@ -402,6 +450,28 @@ func envInt64List(key, def string) []int64 {
|
||||
return out
|
||||
}
|
||||
|
||||
func envInt64ListAllowEmpty(key string) []int64 {
|
||||
raw := strings.TrimSpace(os.Getenv(key))
|
||||
if raw == "" || strings.EqualFold(raw, "none") {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(raw, ",")
|
||||
out := make([]int64, 0, len(parts))
|
||||
seen := map[int64]struct{}{}
|
||||
for _, part := range parts {
|
||||
n, err := strconv.ParseInt(strings.TrimSpace(part), 10, 64)
|
||||
if err != nil || n <= 0 {
|
||||
return nil
|
||||
}
|
||||
if _, ok := seen[n]; ok {
|
||||
continue
|
||||
}
|
||||
seen[n] = struct{}{}
|
||||
out = append(out, n)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func envStringList(key, def string) []string {
|
||||
raw, ok := os.LookupEnv(key)
|
||||
if !ok {
|
||||
|
||||
@@ -124,3 +124,38 @@ func TestKnowledgeWebEditRequiresAuthentication(t *testing.T) {
|
||||
t.Fatal("expected anonymous KB editing to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateGLPIKBRequiresAllowedSource(t *testing.T) {
|
||||
c := validConfig()
|
||||
c.GLPIKBEnabled = true
|
||||
c.GLPIKBPath = "auto"
|
||||
c.GLPIKBLimit = 100
|
||||
c.GLPIKBSyncInterval = 10 * time.Minute
|
||||
c.GLPIKBSource = "glpi-kb"
|
||||
if err := c.Validate(); err == nil {
|
||||
t.Fatal("expected GLPI KB source outside allowlist to be rejected")
|
||||
}
|
||||
c.KnowledgeAllowedSources = []string{"internal-kb", "glpi-kb"}
|
||||
if err := c.Validate(); err != nil {
|
||||
t.Fatalf("expected GLPI KB config to validate: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateGLPIKBAutoReplyRequiresCategoryWhitelist(t *testing.T) {
|
||||
c := validConfig()
|
||||
c.GLPIKBEnabled = true
|
||||
c.GLPIKBPath = "auto"
|
||||
c.GLPIKBLimit = 100
|
||||
c.GLPIKBSyncInterval = 10 * time.Minute
|
||||
c.GLPIKBSource = "glpi-kb"
|
||||
c.KnowledgeAllowedSources = []string{"internal-kb", "glpi-kb"}
|
||||
c.KnowledgeAutoReplySources = []string{"internal-kb", "glpi-kb"}
|
||||
c.GLPIKBAutoReply = true
|
||||
if err := c.Validate(); err == nil {
|
||||
t.Fatal("expected auto reply category whitelist to be required")
|
||||
}
|
||||
c.GLPIKBAutoReplyCategoryIDs = []int64{3}
|
||||
if err := c.Validate(); err != nil {
|
||||
t.Fatalf("expected explicit category whitelist to validate: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user