Files
amcs/internal/config/validate.go
Hein 14e218d784
Some checks failed
CI / build-and-test (push) Failing after -32m22s
test(config): add migration tests for litellm provider
* Implement tests for migrating configuration from v1 to v2 for the litellm provider.
* Validate the structure and values of the migrated configuration.
* Ensure migration rejects newer versions of the configuration.
fix(validate): enhance AI provider validation logic
* Consolidate provider validation into a dedicated method.
* Ensure at least one provider is specified and validate its type.
* Check for required fields based on provider type.
fix(mcpserver): update tool set to use new enrichment tool
* Replace RetryMetadataTool with RetryEnrichmentTool in the ToolSet.
fix(tools): refactor tools to use embedding and metadata runners
* Update tools to utilize EmbeddingRunner and MetadataRunner instead of Provider.
* Adjust method calls to align with the new runner interfaces.
2026-04-21 21:14:28 +02:00

139 lines
4.4 KiB
Go

package config
import (
"fmt"
"strings"
)
func (c Config) Validate() error {
if strings.TrimSpace(c.Database.URL) == "" {
return fmt.Errorf("invalid config: database.url is required")
}
if len(c.Auth.Keys) == 0 && len(c.Auth.OAuth.Clients) == 0 {
return fmt.Errorf("invalid config: at least one of auth.keys or auth.oauth.clients must be configured")
}
for i, key := range c.Auth.Keys {
if strings.TrimSpace(key.ID) == "" {
return fmt.Errorf("invalid config: auth.keys[%d].id is required", i)
}
if strings.TrimSpace(key.Value) == "" {
return fmt.Errorf("invalid config: auth.keys[%d].value is required", i)
}
}
for i, client := range c.Auth.OAuth.Clients {
if strings.TrimSpace(client.ClientID) == "" {
return fmt.Errorf("invalid config: auth.oauth.clients[%d].client_id is required", i)
}
if strings.TrimSpace(client.ClientSecret) == "" {
return fmt.Errorf("invalid config: auth.oauth.clients[%d].client_secret is required", i)
}
}
if strings.TrimSpace(c.MCP.Path) == "" {
return fmt.Errorf("invalid config: mcp.path is required")
}
if c.MCP.SSEPath != "" {
if strings.TrimSpace(c.MCP.SSEPath) == "" {
return fmt.Errorf("invalid config: mcp.sse_path must not be blank whitespace")
}
if c.MCP.SSEPath == c.MCP.Path {
return fmt.Errorf("invalid config: mcp.sse_path %q must differ from mcp.path", c.MCP.SSEPath)
}
}
if c.MCP.SessionTimeout <= 0 {
return fmt.Errorf("invalid config: mcp.session_timeout must be greater than zero")
}
if err := c.AI.validate(); err != nil {
return err
}
if c.Server.Port <= 0 {
return fmt.Errorf("invalid config: server.port must be greater than zero")
}
if c.Search.DefaultLimit <= 0 {
return fmt.Errorf("invalid config: search.default_limit must be greater than zero")
}
if c.Search.MaxLimit < c.Search.DefaultLimit {
return fmt.Errorf("invalid config: search.max_limit must be greater than or equal to search.default_limit")
}
if strings.TrimSpace(c.Logging.Level) == "" {
return fmt.Errorf("invalid config: logging.level is required")
}
if c.Backfill.Enabled {
if c.Backfill.BatchSize <= 0 {
return fmt.Errorf("invalid config: backfill.batch_size must be greater than zero when backfill is enabled")
}
if c.Backfill.MaxPerRun < c.Backfill.BatchSize {
return fmt.Errorf("invalid config: backfill.max_per_run must be >= backfill.batch_size")
}
}
if c.MetadataRetry.Enabled {
if c.MetadataRetry.MaxPerRun <= 0 {
return fmt.Errorf("invalid config: metadata_retry.max_per_run must be greater than zero when metadata_retry is enabled")
}
}
return nil
}
func (a AIConfig) validate() error {
if len(a.Providers) == 0 {
return fmt.Errorf("invalid config: ai.providers must contain at least one entry")
}
for name, p := range a.Providers {
if strings.TrimSpace(name) == "" {
return fmt.Errorf("invalid config: ai.providers contains an entry with an empty name")
}
switch p.Type {
case "litellm", "ollama", "openrouter":
default:
return fmt.Errorf("invalid config: ai.providers.%s.type %q is not supported", name, p.Type)
}
if strings.TrimSpace(p.BaseURL) == "" {
return fmt.Errorf("invalid config: ai.providers.%s.base_url is required", name)
}
if strings.TrimSpace(p.APIKey) == "" {
return fmt.Errorf("invalid config: ai.providers.%s.api_key is required", name)
}
}
if a.Embeddings.Dimensions <= 0 {
return fmt.Errorf("invalid config: ai.embeddings.dimensions must be greater than zero")
}
if err := a.validateChain("ai.embeddings", a.Embeddings.Chain()); err != nil {
return err
}
if err := a.validateChain("ai.metadata", a.Metadata.Chain()); err != nil {
return err
}
if a.Background != nil {
if a.Background.Embeddings != nil {
if err := a.validateChain("ai.background.embeddings", a.Background.Embeddings.AsTargets()); err != nil {
return err
}
}
if a.Background.Metadata != nil {
if err := a.validateChain("ai.background.metadata", a.Background.Metadata.AsTargets()); err != nil {
return err
}
}
}
return nil
}
func (a AIConfig) validateChain(prefix string, chain []RoleTarget) error {
if len(chain) == 0 {
return fmt.Errorf("invalid config: %s.primary must reference a configured provider and model", prefix)
}
for i, target := range chain {
if _, ok := a.Providers[target.Provider]; !ok {
return fmt.Errorf("invalid config: %s[%d] references unknown provider %q", prefix, i, target.Provider)
}
}
return nil
}