test(config): add migration tests for litellm provider
Some checks failed
CI / build-and-test (push) Failing after -32m22s

* 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.
This commit is contained in:
2026-04-21 21:14:28 +02:00
parent 532d1560a3
commit 14e218d784
39 changed files with 2062 additions and 901 deletions

View File

@@ -8,6 +8,7 @@ const (
)
type Config struct {
Version int `yaml:"version"`
Server ServerConfig `yaml:"server"`
MCP MCPConfig `yaml:"mcp"`
Auth AuthConfig `yaml:"auth"`
@@ -37,11 +38,8 @@ type MCPConfig struct {
Version string `yaml:"version"`
Transport string `yaml:"transport"`
SessionTimeout time.Duration `yaml:"session_timeout"`
// PublicURL is the externally reachable base URL of this server (e.g. https://amcs.example.com).
// When set, it is used to build absolute icon URLs in the MCP server identity.
PublicURL string `yaml:"public_url"`
// Instructions is set at startup from the embedded memory.md and sent to MCP clients on initialise.
Instructions string `yaml:"-"`
PublicURL string `yaml:"public_url"`
Instructions string `yaml:"-"`
}
type AuthConfig struct {
@@ -77,52 +75,82 @@ type DatabaseConfig struct {
MaxConnIdleTime time.Duration `yaml:"max_conn_idle_time"`
}
// AIConfig (v2): named providers + per-role chains.
type AIConfig struct {
Provider string `yaml:"provider"`
Embeddings AIEmbeddingConfig `yaml:"embeddings"`
Metadata AIMetadataConfig `yaml:"metadata"`
LiteLLM LiteLLMConfig `yaml:"litellm"`
Ollama OllamaConfig `yaml:"ollama"`
OpenRouter OpenRouterAIConfig `yaml:"openrouter"`
Providers map[string]ProviderConfig `yaml:"providers"`
Embeddings EmbeddingsRoleConfig `yaml:"embeddings"`
Metadata MetadataRoleConfig `yaml:"metadata"`
Background *BackgroundRolesConfig `yaml:"background,omitempty"`
}
type AIEmbeddingConfig struct {
Model string `yaml:"model"`
Dimensions int `yaml:"dimensions"`
type ProviderConfig struct {
Type string `yaml:"type"`
BaseURL string `yaml:"base_url"`
APIKey string `yaml:"api_key"`
RequestHeaders map[string]string `yaml:"request_headers,omitempty"`
AppName string `yaml:"app_name,omitempty"`
SiteURL string `yaml:"site_url,omitempty"`
}
type AIMetadataConfig struct {
Model string `yaml:"model"`
FallbackModels []string `yaml:"fallback_models"`
FallbackModel string `yaml:"fallback_model"` // legacy single fallback
type RoleTarget struct {
Provider string `yaml:"provider"`
Model string `yaml:"model"`
}
type RoleChain struct {
Primary RoleTarget `yaml:"primary"`
Fallbacks []RoleTarget `yaml:"fallbacks,omitempty"`
}
type EmbeddingsRoleConfig struct {
Dimensions int `yaml:"dimensions"`
Primary RoleTarget `yaml:"primary"`
Fallbacks []RoleTarget `yaml:"fallbacks,omitempty"`
}
type MetadataRoleConfig struct {
Temperature float64 `yaml:"temperature"`
LogConversations bool `yaml:"log_conversations"`
Timeout time.Duration `yaml:"timeout"`
Primary RoleTarget `yaml:"primary"`
Fallbacks []RoleTarget `yaml:"fallbacks,omitempty"`
}
type LiteLLMConfig struct {
BaseURL string `yaml:"base_url"`
APIKey string `yaml:"api_key"`
UseResponsesAPI bool `yaml:"use_responses_api"`
RequestHeaders map[string]string `yaml:"request_headers"`
EmbeddingModel string `yaml:"embedding_model"`
MetadataModel string `yaml:"metadata_model"`
FallbackMetadataModels []string `yaml:"fallback_metadata_models"`
FallbackMetadataModel string `yaml:"fallback_metadata_model"` // legacy single fallback
// BackgroundRolesConfig overrides the foreground chains for background workers
// (backfill_embeddings, metadata_retry, reparse_metadata). Either field may be
// nil to inherit the foreground role unchanged.
type BackgroundRolesConfig struct {
Embeddings *RoleChain `yaml:"embeddings,omitempty"`
Metadata *RoleChain `yaml:"metadata,omitempty"`
}
type OllamaConfig struct {
BaseURL string `yaml:"base_url"`
APIKey string `yaml:"api_key"`
RequestHeaders map[string]string `yaml:"request_headers"`
// Chain returns primary followed by fallbacks (deduped, blanks dropped).
func (e EmbeddingsRoleConfig) Chain() []RoleTarget {
return dedupeTargets(append([]RoleTarget{e.Primary}, e.Fallbacks...))
}
type OpenRouterAIConfig struct {
BaseURL string `yaml:"base_url"`
APIKey string `yaml:"api_key"`
AppName string `yaml:"app_name"`
SiteURL string `yaml:"site_url"`
ExtraHeaders map[string]string `yaml:"extra_headers"`
func (m MetadataRoleConfig) Chain() []RoleTarget {
return dedupeTargets(append([]RoleTarget{m.Primary}, m.Fallbacks...))
}
func (c RoleChain) AsTargets() []RoleTarget {
return dedupeTargets(append([]RoleTarget{c.Primary}, c.Fallbacks...))
}
func dedupeTargets(in []RoleTarget) []RoleTarget {
out := make([]RoleTarget, 0, len(in))
seen := make(map[RoleTarget]struct{}, len(in))
for _, t := range in {
if t.Provider == "" || t.Model == "" {
continue
}
if _, ok := seen[t]; ok {
continue
}
seen[t] = struct{}{}
out = append(out, t)
}
return out
}
type CaptureConfig struct {
@@ -167,45 +195,3 @@ type MetadataRetryConfig struct {
MaxPerRun int `yaml:"max_per_run"`
IncludeArchived bool `yaml:"include_archived"`
}
func (c AIMetadataConfig) EffectiveFallbackModels() []string {
models := make([]string, 0, len(c.FallbackModels)+1)
for _, model := range c.FallbackModels {
if model != "" {
models = append(models, model)
}
}
if c.FallbackModel != "" {
models = append(models, c.FallbackModel)
}
return dedupeNonEmpty(models)
}
func (c LiteLLMConfig) EffectiveFallbackMetadataModels() []string {
models := make([]string, 0, len(c.FallbackMetadataModels)+1)
for _, model := range c.FallbackMetadataModels {
if model != "" {
models = append(models, model)
}
}
if c.FallbackMetadataModel != "" {
models = append(models, c.FallbackMetadataModel)
}
return dedupeNonEmpty(models)
}
func dedupeNonEmpty(values []string) []string {
seen := make(map[string]struct{}, len(values))
out := make([]string, 0, len(values))
for _, value := range values {
if value == "" {
continue
}
if _, ok := seen[value]; ok {
continue
}
seen[value] = struct{}{}
out = append(out, value)
}
return out
}