mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
refactor: load AI providers from the database at startup (#25672)
Replace the env-based `BuildProviders` with a DB-backed loader. The database is now the single source of truth for runtime provider configuration; env config arrives via `SeedAIProvidersFromEnv` (run at boot) and `BuildProviders` reads it back as `aibridge.Provider` instances. `cli/server.go` and `enterprise/cli/server.go` both call the same path, so aibridged and aibridgeproxyd see the same provider set. Per-provider `DumpDir` is replaced by a top-level `CODER_AI_GATEWAY_DUMP_DIR` base; each provider's effective dump path is `<base>/<provider name>`.
This commit is contained in:
@@ -6,11 +6,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/aibridge"
|
||||
agplcli "github.com/coder/coder/v2/cli"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
func TestDomainsFromProviders(t *testing.T) {
|
||||
@@ -19,14 +16,11 @@ func TestDomainsFromProviders(t *testing.T) {
|
||||
t.Run("ExtractsHostnames", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
providers, err := agplcli.BuildProviders(codersdk.AIBridgeConfig{
|
||||
Providers: []codersdk.AIProviderConfig{
|
||||
{Type: aibridge.ProviderOpenAI, Name: "openai", Keys: []string{"k"}},
|
||||
{Type: aibridge.ProviderAnthropic, Name: "anthropic", Keys: []string{"k"}},
|
||||
{Type: aibridge.ProviderOpenAI, Name: "custom", Keys: []string{"k"}, BaseURL: "https://custom-llm.example.com:8443/api"},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
providers := []aibridge.Provider{
|
||||
aibridge.NewOpenAIProvider(aibridge.OpenAIConfig{Name: "openai", BaseURL: "https://api.openai.com/v1/"}),
|
||||
aibridge.NewAnthropicProvider(aibridge.AnthropicConfig{Name: "anthropic", BaseURL: "https://api.anthropic.com/"}, nil),
|
||||
aibridge.NewOpenAIProvider(aibridge.OpenAIConfig{Name: "custom", BaseURL: "https://custom-llm.example.com:8443/api"}),
|
||||
}
|
||||
|
||||
domains, mapping := domainsFromProviders(providers)
|
||||
|
||||
@@ -43,13 +37,10 @@ func TestDomainsFromProviders(t *testing.T) {
|
||||
t.Run("DeduplicatesSameHost", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
providers, err := agplcli.BuildProviders(codersdk.AIBridgeConfig{
|
||||
Providers: []codersdk.AIProviderConfig{
|
||||
{Type: aibridge.ProviderOpenAI, Name: "first", Keys: []string{"k"}, BaseURL: "https://api.example.com/v1"},
|
||||
{Type: aibridge.ProviderOpenAI, Name: "second", Keys: []string{"k"}, BaseURL: "https://api.example.com/v2"},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
providers := []aibridge.Provider{
|
||||
aibridge.NewOpenAIProvider(aibridge.OpenAIConfig{Name: "first", BaseURL: "https://api.example.com/v1"}),
|
||||
aibridge.NewOpenAIProvider(aibridge.OpenAIConfig{Name: "second", BaseURL: "https://api.example.com/v2"}),
|
||||
}
|
||||
|
||||
domains, mapping := domainsFromProviders(providers)
|
||||
|
||||
@@ -68,12 +59,9 @@ func TestDomainsFromProviders(t *testing.T) {
|
||||
t.Run("CaseInsensitive", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
providers, err := agplcli.BuildProviders(codersdk.AIBridgeConfig{
|
||||
Providers: []codersdk.AIProviderConfig{
|
||||
{Type: aibridge.ProviderOpenAI, Name: "provider", Keys: []string{"k"}, BaseURL: "https://API.Example.COM/v1"},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
providers := []aibridge.Provider{
|
||||
aibridge.NewOpenAIProvider(aibridge.OpenAIConfig{Name: "provider", BaseURL: "https://API.Example.COM/v1"}),
|
||||
}
|
||||
|
||||
domains, mapping := domainsFromProviders(providers)
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ func (r *RootCmd) Server(_ func()) *serpent.Command {
|
||||
// in-memory roundtripper regardless of license); only the proxy
|
||||
// daemon remains enterprise-gated by config.
|
||||
if options.DeploymentValues.AI.BridgeProxyConfig.Enabled.Value() {
|
||||
providers, err := agplcli.BuildProviders(options.DeploymentValues.AI.BridgeConfig)
|
||||
providers, err := agplcli.BuildProviders(ctx, options.Database, options.DeploymentValues.AI.BridgeConfig, options.Logger.Named("aibridge.providers"))
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("build AI providers: %w", err)
|
||||
}
|
||||
|
||||
@@ -114,6 +114,12 @@ AI GATEWAY OPTIONS:
|
||||
with AI budgets. "highest" selects the group with the largest spend
|
||||
limit, and is currently the only supported value.
|
||||
|
||||
--ai-gateway-dump-dir string, $CODER_AI_GATEWAY_DUMP_DIR
|
||||
Base directory for dumping AI Bridge request/response pairs to disk
|
||||
for debugging. When set, each provider writes under a subdirectory
|
||||
named after the provider. Sensitive headers are redacted. Leave empty
|
||||
to disable.
|
||||
|
||||
--ai-gateway-allow-byok bool, $CODER_AI_GATEWAY_ALLOW_BYOK (default: true)
|
||||
Allow users to provide their own LLM API keys or subscriptions. When
|
||||
disabled, only centralized key authentication is permitted.
|
||||
|
||||
Reference in New Issue
Block a user