Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 75fced579e |
+3
-2
@@ -386,7 +386,8 @@ peers:
|
|||||||
# - optional, default: ""
|
# - optional, default: ""
|
||||||
# - if blank, no key will be added to the request
|
# - if blank, no key will be added to the request
|
||||||
# - key will be injected into headers: Authorization: Bearer <key> and x-api-key: <key>
|
# - key will be injected into headers: Authorization: Bearer <key> and x-api-key: <key>
|
||||||
apiKey: sk-your-openrouter-key
|
# - can be a string or a macro
|
||||||
|
apiKey: ${env.OPENROUTER_API_KEY}
|
||||||
models:
|
models:
|
||||||
- meta-llama/llama-3.1-8b-instruct
|
- meta-llama/llama-3.1-8b-instruct
|
||||||
- qwen/qwen3-235b-a22b-2507
|
- qwen/qwen3-235b-a22b-2507
|
||||||
@@ -413,4 +414,4 @@ peers:
|
|||||||
# Example: enforce zero-data-retention for OpenRouter
|
# Example: enforce zero-data-retention for OpenRouter
|
||||||
provider:
|
provider:
|
||||||
data_collection: "deny"
|
data_collection: "deny"
|
||||||
allow_fallbacks: false
|
zdr: true
|
||||||
|
|||||||
+63
-7
@@ -427,7 +427,7 @@ func LoadConfigFromReader(r io.Reader) (Config, error) {
|
|||||||
|
|
||||||
// Check for unknown macros in metadata
|
// Check for unknown macros in metadata
|
||||||
if len(modelConfig.Metadata) > 0 {
|
if len(modelConfig.Metadata) > 0 {
|
||||||
if err := validateMetadataForUnknownMacros(modelConfig.Metadata, modelId); err != nil {
|
if err := validateNestedForUnknownMacros(modelConfig.Metadata, fmt.Sprintf("model %s metadata", modelId)); err != nil {
|
||||||
return Config{}, err
|
return Config{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,6 +502,62 @@ func LoadConfigFromReader(r io.Reader) (Config, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// substitute macros and env macros in peer fields
|
||||||
|
for peerName, peerConfig := range config.Peers {
|
||||||
|
// Substitute global macros first (LIFO order like models)
|
||||||
|
for i := len(config.Macros) - 1; i >= 0; i-- {
|
||||||
|
entry := config.Macros[i]
|
||||||
|
macroSlug := fmt.Sprintf("${%s}", entry.Name)
|
||||||
|
macroStr := fmt.Sprintf("%v", entry.Value)
|
||||||
|
|
||||||
|
peerConfig.ApiKey = strings.ReplaceAll(peerConfig.ApiKey, macroSlug, macroStr)
|
||||||
|
peerConfig.Filters.StripParams = strings.ReplaceAll(peerConfig.Filters.StripParams, macroSlug, macroStr)
|
||||||
|
|
||||||
|
// Substitute in setParams
|
||||||
|
if len(peerConfig.Filters.SetParams) > 0 {
|
||||||
|
result, err := substituteMacroInValue(peerConfig.Filters.SetParams, entry.Name, entry.Value)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, fmt.Errorf("peers.%s.filters.setParams: %w", peerName, err)
|
||||||
|
}
|
||||||
|
peerConfig.Filters.SetParams = result.(map[string]any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Substitute env macros
|
||||||
|
peerConfig.ApiKey, err = substituteEnvMacros(peerConfig.ApiKey)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, fmt.Errorf("peers.%s.apiKey: %w", peerName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peerConfig.Filters.StripParams, err = substituteEnvMacros(peerConfig.Filters.StripParams)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, fmt.Errorf("peers.%s.filters.stripParams: %w", peerName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(peerConfig.Filters.SetParams) > 0 {
|
||||||
|
result, err := substituteEnvMacrosInValue(peerConfig.Filters.SetParams)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, fmt.Errorf("peers.%s.filters.setParams: %w", peerName, err)
|
||||||
|
}
|
||||||
|
peerConfig.Filters.SetParams = result.(map[string]any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate no unknown macros remain
|
||||||
|
if matches := macroPatternRegex.FindAllStringSubmatch(peerConfig.ApiKey, -1); len(matches) > 0 {
|
||||||
|
return Config{}, fmt.Errorf("peers.%s.apiKey: unknown macro '${%s}'", peerName, matches[0][1])
|
||||||
|
}
|
||||||
|
if matches := macroPatternRegex.FindAllStringSubmatch(peerConfig.Filters.StripParams, -1); len(matches) > 0 {
|
||||||
|
return Config{}, fmt.Errorf("peers.%s.filters.stripParams: unknown macro '${%s}'", peerName, matches[0][1])
|
||||||
|
}
|
||||||
|
if len(peerConfig.Filters.SetParams) > 0 {
|
||||||
|
if err := validateNestedForUnknownMacros(peerConfig.Filters.SetParams, fmt.Sprintf("peers.%s.filters.setParams", peerName)); err != nil {
|
||||||
|
return Config{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Peers[peerName] = peerConfig
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,26 +688,26 @@ func validateMacro(name string, value any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateMetadataForUnknownMacros recursively checks for any remaining macro references in metadata
|
// validateNestedForUnknownMacros recursively checks for any remaining macro references in nested structures
|
||||||
func validateMetadataForUnknownMacros(value any, modelId string) error {
|
func validateNestedForUnknownMacros(value any, context string) error {
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
matches := macroPatternRegex.FindAllStringSubmatch(v, -1)
|
matches := macroPatternRegex.FindAllStringSubmatch(v, -1)
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
macroName := match[1]
|
macroName := match[1]
|
||||||
return fmt.Errorf("model %s metadata: unknown macro '${%s}'", modelId, macroName)
|
return fmt.Errorf("%s: unknown macro '${%s}'", context, macroName)
|
||||||
}
|
}
|
||||||
// Check for unsubstituted env macros
|
// Check for unsubstituted env macros
|
||||||
envMatches := envMacroRegex.FindAllStringSubmatch(v, -1)
|
envMatches := envMacroRegex.FindAllStringSubmatch(v, -1)
|
||||||
for _, match := range envMatches {
|
for _, match := range envMatches {
|
||||||
varName := match[1]
|
varName := match[1]
|
||||||
return fmt.Errorf("model %s metadata: environment variable '%s' not set", modelId, varName)
|
return fmt.Errorf("%s: environment variable '%s' not set", context, varName)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
for _, val := range v {
|
for _, val := range v {
|
||||||
if err := validateMetadataForUnknownMacros(val, modelId); err != nil {
|
if err := validateNestedForUnknownMacros(val, context); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -659,7 +715,7 @@ func validateMetadataForUnknownMacros(value any, modelId string) error {
|
|||||||
|
|
||||||
case []any:
|
case []any:
|
||||||
for _, val := range v {
|
for _, val := range v {
|
||||||
if err := validateMetadataForUnknownMacros(val, modelId); err != nil {
|
if err := validateNestedForUnknownMacros(val, context); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1057,3 +1057,191 @@ models:
|
|||||||
assert.Equal(t, "server --auth admin:secret", config.Models["test"].Cmd)
|
assert.Equal(t, "server --auth admin:secret", config.Models["test"].Cmd)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfig_PeerApiKey_EnvMacros(t *testing.T) {
|
||||||
|
t.Run("env substitution in peer apiKey", func(t *testing.T) {
|
||||||
|
t.Setenv("TEST_PEER_API_KEY", "sk-peer-secret-123")
|
||||||
|
|
||||||
|
content := `
|
||||||
|
peers:
|
||||||
|
openrouter:
|
||||||
|
proxy: https://openrouter.ai/api
|
||||||
|
apiKey: "${env.TEST_PEER_API_KEY}"
|
||||||
|
models:
|
||||||
|
- llama-3.1-8b
|
||||||
|
`
|
||||||
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "sk-peer-secret-123", config.Peers["openrouter"].ApiKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missing env var in peer apiKey", func(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
peers:
|
||||||
|
openrouter:
|
||||||
|
proxy: https://openrouter.ai/api
|
||||||
|
apiKey: "${env.NONEXISTENT_PEER_KEY}"
|
||||||
|
models:
|
||||||
|
- llama-3.1-8b
|
||||||
|
`
|
||||||
|
_, err := LoadConfigFromReader(strings.NewReader(content))
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "peers.openrouter.apiKey")
|
||||||
|
assert.Contains(t, err.Error(), "NONEXISTENT_PEER_KEY")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("static apiKey unchanged", func(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
peers:
|
||||||
|
openrouter:
|
||||||
|
proxy: https://openrouter.ai/api
|
||||||
|
apiKey: sk-static-key
|
||||||
|
models:
|
||||||
|
- llama-3.1-8b
|
||||||
|
`
|
||||||
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "sk-static-key", config.Peers["openrouter"].ApiKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple peers with env apiKeys", func(t *testing.T) {
|
||||||
|
t.Setenv("TEST_PEER_KEY_1", "key-one")
|
||||||
|
t.Setenv("TEST_PEER_KEY_2", "key-two")
|
||||||
|
|
||||||
|
content := `
|
||||||
|
peers:
|
||||||
|
peer1:
|
||||||
|
proxy: https://peer1.example.com
|
||||||
|
apiKey: "${env.TEST_PEER_KEY_1}"
|
||||||
|
models:
|
||||||
|
- model-a
|
||||||
|
peer2:
|
||||||
|
proxy: https://peer2.example.com
|
||||||
|
apiKey: "${env.TEST_PEER_KEY_2}"
|
||||||
|
models:
|
||||||
|
- model-b
|
||||||
|
`
|
||||||
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "key-one", config.Peers["peer1"].ApiKey)
|
||||||
|
assert.Equal(t, "key-two", config.Peers["peer2"].ApiKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("global macro substitution in peer apiKey", func(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
macros:
|
||||||
|
API_KEY: sk-from-global-macro
|
||||||
|
peers:
|
||||||
|
openrouter:
|
||||||
|
proxy: https://openrouter.ai/api
|
||||||
|
apiKey: "${API_KEY}"
|
||||||
|
models:
|
||||||
|
- llama-3.1-8b
|
||||||
|
`
|
||||||
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "sk-from-global-macro", config.Peers["openrouter"].ApiKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("global macro in peer filters.stripParams", func(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
macros:
|
||||||
|
STRIP_LIST: "temperature, top_p"
|
||||||
|
peers:
|
||||||
|
openrouter:
|
||||||
|
proxy: https://openrouter.ai/api
|
||||||
|
models:
|
||||||
|
- llama-3.1-8b
|
||||||
|
filters:
|
||||||
|
stripParams: "${STRIP_LIST}"
|
||||||
|
`
|
||||||
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "temperature, top_p", config.Peers["openrouter"].Filters.StripParams)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("global macro in peer filters.setParams", func(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
macros:
|
||||||
|
MAX_TOKENS: 4096
|
||||||
|
peers:
|
||||||
|
openrouter:
|
||||||
|
proxy: https://openrouter.ai/api
|
||||||
|
models:
|
||||||
|
- llama-3.1-8b
|
||||||
|
filters:
|
||||||
|
setParams:
|
||||||
|
max_tokens: "${MAX_TOKENS}"
|
||||||
|
`
|
||||||
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 4096, config.Peers["openrouter"].Filters.SetParams["max_tokens"])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("env macro in peer filters.setParams", func(t *testing.T) {
|
||||||
|
t.Setenv("TEST_RETENTION_POLICY", "deny")
|
||||||
|
|
||||||
|
content := `
|
||||||
|
peers:
|
||||||
|
openrouter:
|
||||||
|
proxy: https://openrouter.ai/api
|
||||||
|
models:
|
||||||
|
- llama-3.1-8b
|
||||||
|
filters:
|
||||||
|
setParams:
|
||||||
|
data_collection: "${env.TEST_RETENTION_POLICY}"
|
||||||
|
`
|
||||||
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "deny", config.Peers["openrouter"].Filters.SetParams["data_collection"])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("env macro in peer filters.stripParams", func(t *testing.T) {
|
||||||
|
t.Setenv("TEST_STRIP_PARAMS", "frequency_penalty, presence_penalty")
|
||||||
|
|
||||||
|
content := `
|
||||||
|
peers:
|
||||||
|
openrouter:
|
||||||
|
proxy: https://openrouter.ai/api
|
||||||
|
models:
|
||||||
|
- llama-3.1-8b
|
||||||
|
filters:
|
||||||
|
stripParams: "${env.TEST_STRIP_PARAMS}"
|
||||||
|
`
|
||||||
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "frequency_penalty, presence_penalty", config.Peers["openrouter"].Filters.StripParams)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown macro in peer apiKey fails", func(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
peers:
|
||||||
|
openrouter:
|
||||||
|
proxy: https://openrouter.ai/api
|
||||||
|
apiKey: "${UNDEFINED_MACRO}"
|
||||||
|
models:
|
||||||
|
- llama-3.1-8b
|
||||||
|
`
|
||||||
|
_, err := LoadConfigFromReader(strings.NewReader(content))
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "peers.openrouter.apiKey")
|
||||||
|
assert.Contains(t, err.Error(), "unknown macro")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown macro in peer filters.setParams fails", func(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
peers:
|
||||||
|
openrouter:
|
||||||
|
proxy: https://openrouter.ai/api
|
||||||
|
models:
|
||||||
|
- llama-3.1-8b
|
||||||
|
filters:
|
||||||
|
setParams:
|
||||||
|
value: "${UNDEFINED_MACRO}"
|
||||||
|
`
|
||||||
|
_, err := LoadConfigFromReader(strings.NewReader(content))
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "peers.openrouter.filters.setParams")
|
||||||
|
assert.Contains(t, err.Error(), "unknown macro")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user