Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/exp_scaletest_chat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func chatMessageText(messages []codersdk.ChatMessage, role codersdk.ChatMessageR
func scaletestModelConfigsForProvider(configs []codersdk.ChatModelConfig, providerID uuid.UUID) []codersdk.ChatModelConfig {
matches := make([]codersdk.ChatModelConfig, 0, 1)
for _, config := range configs {
if config.AIProviderID == nil || *config.AIProviderID != providerID {
if config.AIProviderID != providerID {
continue
}
if config.Model != "scaletest-model" {
Expand Down
3 changes: 0 additions & 3 deletions cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1117,9 +1117,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
}
// Must run after newAPI so options.Database is dbcrypt-wrapped.
coderd.BackfillBedrockProviderType(aibridgeInitCtx, options.Database, logger.Named("aibridge.backfill"))
// Must run after BackfillBedrockProviderType; shares aibridgeInitCtx so
// a timeout on the first backfill will skip this one until next startup.
coderd.BackfillChatModelConfigProviderStrings(aibridgeInitCtx, options.Database, logger.Named("aibridge.backfill"))
Comment on lines -1120 to -1122

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewer note: added in v2.34.0

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No change needed. BackfillChatModelConfigProviderStrings (v2.34.0) only repaired the chat_model_configs.provider text column that this PR drops. Migration 000534 just DROP COLUMN provider and never reads it; ai_provider_id is populated independently. So an upgrade that skips v2.34 is harmless: the data the backfill repaired is being removed.

🤖 Posted using /amend-review skill via Coder Agents.


// In-memory aibridge daemon. Registered on coderd so chatd can
// dispatch LLM requests via the in-process transport without
Expand Down
26 changes: 0 additions & 26 deletions coderd/ai_providers_backfill.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,12 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/db2sdk"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/codersdk"
)

// BackfillBedrockProviderType promotes legacy ai_providers rows stored as
// type=anthropic with Bedrock settings to type=bedrock. Must run after newAPI
// so options.Database is dbcrypt-wrapped. Idempotent; errors are logged and
// startup continues.
//
// BackfillChatModelConfigProviderStrings must run after this function so
// provider types are correct when its JOIN executes.
func BackfillBedrockProviderType(ctx context.Context, db database.Store, logger slog.Logger) {
//nolint:gocritic // Startup-only backfill; no user actor is present.
sysCtx := dbauthz.AsSystemRestricted(ctx)
Expand Down Expand Up @@ -70,25 +66,3 @@ func BackfillBedrockProviderType(ctx context.Context, db database.Store, logger
logger.Info(ctx, "backfilled bedrock provider types", slog.F("count", promoted))
}
}

// BackfillChatModelConfigProviderStrings fixes stale chat_model_configs.provider
// strings left as "anthropic" when the linked provider was promoted from
// type=anthropic to type=bedrock by BackfillBedrockProviderType. Errors are
// logged and startup continues.
func BackfillChatModelConfigProviderStrings(ctx context.Context, db database.Store, logger slog.Logger) {
//nolint:gocritic // Startup-only backfill; no user actor is present.
sysCtx := dbauthz.AsSystemRestricted(ctx)
result, err := db.BackfillChatModelConfigProvider(sysCtx, database.BackfillChatModelConfigProviderParams{
OldProvider: string(codersdk.AIProviderTypeAnthropic),
NewProvider: string(codersdk.AIProviderTypeBedrock),
})
if err != nil {
logger.Error(ctx, "backfill chat model config provider strings", slog.Error(err))
return
}
if result != nil {
if n, _ := result.RowsAffected(); n > 0 {
logger.Info(ctx, "backfilled chat model config provider strings", slog.F("count", n))
}
}
}
120 changes: 0 additions & 120 deletions coderd/ai_providers_backfill_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"database/sql"
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"

Expand Down Expand Up @@ -193,112 +192,6 @@ func TestBackfillBedrockProviderType(t *testing.T) {
require.NoError(t, err)
require.Equal(t, database.AIProviderTypeBedrock, goodRow.Type, "valid row alongside unparsable one must still be promoted")
})

// --- chat_model_configs.provider backfill ---
// These subtests rely on the DB already having type=bedrock providers
// from the provider backfill subtests above.

t.Run("FixesStaleModelConfigProvider", func(t *testing.T) {
// Simulate a model config created when the linked provider was still
// type=anthropic. The stored provider string is "anthropic" but the
// linked provider row now has type=bedrock.
bedrockProvider := dbgen.AIProvider(t, db, database.AIProvider{
Type: database.AIProviderTypeBedrock,
Settings: bedrockSettings,
})
staleConfig := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
Provider: "anthropic",
AIProviderID: uuid.NullUUID{UUID: bedrockProvider.ID, Valid: true},
})

coderd.BackfillChatModelConfigProviderStrings(ctx, db, logger)

updated, err := db.GetChatModelConfigByID(ctx, staleConfig.ID)
require.NoError(t, err)
require.Equal(t, "bedrock", updated.Provider, "stale anthropic provider string must be fixed to bedrock")

// Second run must be a no-op: the same config must still be "bedrock".
coderd.BackfillChatModelConfigProviderStrings(ctx, db, logger)

updated, err = db.GetChatModelConfigByID(ctx, staleConfig.ID)
require.NoError(t, err)
require.Equal(t, "bedrock", updated.Provider, "provider must remain bedrock after second run")
})

t.Run("ModelConfigIdempotent", func(t *testing.T) {
before, err := db.GetChatModelConfigs(ctx)
require.NoError(t, err)

coderd.BackfillChatModelConfigProviderStrings(ctx, db, logger)

after, err := db.GetChatModelConfigs(ctx)
require.NoError(t, err)
require.Equal(t, len(before), len(after), "second run must not create or delete rows")
})

t.Run("PreservesNonAnthropicModelConfig", func(t *testing.T) {
// A model config with provider="openai" linked to a Bedrock provider
// must not be touched. Only "anthropic" → "bedrock" is in scope.
bedrockProvider := dbgen.AIProvider(t, db, database.AIProvider{
Type: database.AIProviderTypeBedrock,
Settings: bedrockSettings,
})
openAIConfig := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
Provider: "openai",
AIProviderID: uuid.NullUUID{UUID: bedrockProvider.ID, Valid: true},
})

coderd.BackfillChatModelConfigProviderStrings(ctx, db, logger)

row, err := db.GetChatModelConfigByID(ctx, openAIConfig.ID)
require.NoError(t, err)
require.Equal(t, "openai", row.Provider, "non-anthropic provider string must not be changed")
})

t.Run("SkipsModelConfigWithDeletedProvider", func(t *testing.T) {
// Verifies the EXISTS subquery excludes soft-deleted providers.
// The model config provider string must stay "anthropic" because
// the linked provider is deleted and therefore excluded by the
// AND deleted = FALSE condition in the query.
deletedProvider := dbgen.AIProvider(t, db, database.AIProvider{
Type: database.AIProviderTypeBedrock,
Settings: bedrockSettings,
})
staleConfig := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
Provider: "anthropic",
AIProviderID: uuid.NullUUID{UUID: deletedProvider.ID, Valid: true},
})
require.NoError(t, db.DeleteAIProviderByID(ctx, deletedProvider.ID))

coderd.BackfillChatModelConfigProviderStrings(ctx, db, logger)

row, err := db.GetChatModelConfigByID(ctx, staleConfig.ID)
require.NoError(t, err)
require.Equal(t, "anthropic", row.Provider, "config linked to deleted provider must not be updated")
})

t.Run("SkipsDeletedModelConfig", func(t *testing.T) {
// The SQL query guards on deleted = FALSE. Capture the config ID
// before deletion so we delete the right row regardless of ordering.
bedrockProvider := dbgen.AIProvider(t, db, database.AIProvider{
Type: database.AIProviderTypeBedrock,
Settings: bedrockSettings,
})
cfg := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
Provider: "anthropic",
AIProviderID: uuid.NullUUID{UUID: bedrockProvider.ID, Valid: true},
})

before, err := db.GetChatModelConfigs(ctx)
require.NoError(t, err)
require.NoError(t, db.DeleteChatModelConfigByID(ctx, cfg.ID))

coderd.BackfillChatModelConfigProviderStrings(ctx, db, logger)

after, err := db.GetChatModelConfigs(ctx)
require.NoError(t, err)
require.Equal(t, len(before)-1, len(after), "deleted config must not reappear after backfill")
})
})

t.Run("ListFailure", func(t *testing.T) {
Expand Down Expand Up @@ -352,17 +245,4 @@ func TestBackfillBedrockProviderType(t *testing.T) {
// ErrNoRows is benign: provider was deleted between list and update.
coderd.BackfillBedrockProviderType(ctx, db, testLogger(t))
})

t.Run("ModelConfigQueryFailure", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitShort)
ctrl := gomock.NewController(t)
db := dbmock.NewMockStore(ctrl)

db.EXPECT().
BackfillChatModelConfigProvider(gomock.Any(), gomock.Any()).
Return(nil, sql.ErrConnDone)

coderd.BackfillChatModelConfigProviderStrings(ctx, db, testLogger(t))
})
}
1 change: 0 additions & 1 deletion coderd/coderdtest/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ func CreateOpenAICompatChatModelConfig(
contextLimit := int64(4096)
isDefault := true
modelConfig, err := client.CreateChatModelConfig(ctx, codersdk.CreateChatModelConfigRequest{
Provider: TestChatProviderOpenAICompat,
AIProviderID: &provider.ID,
Model: TestChatModelOpenAICompat,
ContextLimit: &contextLimit,
Expand Down
16 changes: 1 addition & 15 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -1724,13 +1724,6 @@ func (q *querier) AutoArchiveInactiveChats(ctx context.Context, arg database.Aut
return q.db.AutoArchiveInactiveChats(ctx, arg)
}

func (q *querier) BackfillChatModelConfigProvider(ctx context.Context, arg database.BackfillChatModelConfigProviderParams) (sql.Result, error) {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
return nil, err
}
return q.db.BackfillChatModelConfigProvider(ctx, arg)
}

func (q *querier) BackoffChatDiffStatus(ctx context.Context, arg database.BackoffChatDiffStatusParams) error {
// This is a system-level operation used by the gitsync
// background worker to reschedule failed refreshes. Same
Expand Down Expand Up @@ -2113,13 +2106,6 @@ func (q *querier) DeleteChatModelConfigsByAIProviderID(ctx context.Context, aiPr
return q.db.DeleteChatModelConfigsByAIProviderID(ctx, aiProviderID)
}

func (q *querier) DeleteChatModelConfigsByProvider(ctx context.Context, provider string) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
return err
}
return q.db.DeleteChatModelConfigsByProvider(ctx, provider)
}

func (q *querier) DeleteChatQueuedMessage(ctx context.Context, arg database.DeleteChatQueuedMessageParams) error {
chat, err := q.db.GetChatByID(ctx, arg.ChatID)
if err != nil {
Expand Down Expand Up @@ -3677,7 +3663,7 @@ func (q *querier) GetEnabledChatModelConfigByID(ctx context.Context, id uuid.UUI
return q.db.GetEnabledChatModelConfigByID(ctx, id)
}

func (q *querier) GetEnabledChatModelConfigs(ctx context.Context) ([]database.ChatModelConfig, error) {
func (q *querier) GetEnabledChatModelConfigs(ctx context.Context) ([]database.GetEnabledChatModelConfigsRow, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
return nil, err
}
Expand Down
25 changes: 5 additions & 20 deletions coderd/database/dbauthz/dbauthz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -657,11 +657,6 @@ func (s *MethodTestSuite) TestChats() {
dbm.EXPECT().DeleteChatModelConfigByID(gomock.Any(), id).Return(nil).AnyTimes()
check.Args(id).Asserts(rbac.ResourceDeploymentConfig, policy.ActionUpdate)
}))
s.Run("DeleteChatModelConfigsByProvider", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
providerName := "test-provider"
dbm.EXPECT().DeleteChatModelConfigsByProvider(gomock.Any(), providerName).Return(nil).AnyTimes()
check.Args(providerName).Asserts(rbac.ResourceDeploymentConfig, policy.ActionUpdate)
}))
s.Run("DeleteChatModelConfigsByAIProviderID", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
providerID := uuid.New()
dbm.EXPECT().DeleteChatModelConfigsByAIProviderID(gomock.Any(), providerID).Return(nil).AnyTimes()
Expand Down Expand Up @@ -1212,10 +1207,10 @@ func (s *MethodTestSuite) TestChats() {
check.Args(config.ID).Asserts(rbac.ResourceDeploymentConfig, policy.ActionRead).Returns(config)
}))
s.Run("GetEnabledChatModelConfigs", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
configA := testutil.Fake(s.T(), faker, database.ChatModelConfig{})
configB := testutil.Fake(s.T(), faker, database.ChatModelConfig{})
dbm.EXPECT().GetEnabledChatModelConfigs(gomock.Any()).Return([]database.ChatModelConfig{configA, configB}, nil).AnyTimes()
check.Args().Asserts(rbac.ResourceDeploymentConfig, policy.ActionRead).Returns([]database.ChatModelConfig{configA, configB})
rowA := testutil.Fake(s.T(), faker, database.GetEnabledChatModelConfigsRow{})
rowB := testutil.Fake(s.T(), faker, database.GetEnabledChatModelConfigsRow{})
Comment on lines +1210 to +1211

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Why the naming change from config[AB] to row[AB]?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept rowA/rowB: the return type changed to GetEnabledChatModelConfigsRow (the embedded config plus the derived provider), so row is accurate and config would mislead.

🤖 Posted using /amend-review skill via Coder Agents.

dbm.EXPECT().GetEnabledChatModelConfigs(gomock.Any()).Return([]database.GetEnabledChatModelConfigsRow{rowA, rowB}, nil).AnyTimes()
check.Args().Asserts(rbac.ResourceDeploymentConfig, policy.ActionRead).Returns([]database.GetEnabledChatModelConfigsRow{rowA, rowB})
}))

s.Run("GetStaleChats", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
Expand Down Expand Up @@ -1257,12 +1252,11 @@ func (s *MethodTestSuite) TestChats() {
}))
s.Run("InsertChatModelConfig", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
arg := database.InsertChatModelConfigParams{
Provider: "test-provider",
Model: "test-model",
DisplayName: "Test Model",
Enabled: true,
}
config := testutil.Fake(s.T(), faker, database.ChatModelConfig{Provider: arg.Provider, Model: arg.Model, DisplayName: arg.DisplayName, Enabled: arg.Enabled})
config := testutil.Fake(s.T(), faker, database.ChatModelConfig{Model: arg.Model, DisplayName: arg.DisplayName, Enabled: arg.Enabled})
dbm.EXPECT().InsertChatModelConfig(gomock.Any(), arg).Return(config, nil).AnyTimes()
check.Args(arg).Asserts(rbac.ResourceDeploymentConfig, policy.ActionUpdate).Returns(config)
}))
Expand Down Expand Up @@ -1515,7 +1509,6 @@ func (s *MethodTestSuite) TestChats() {
config := testutil.Fake(s.T(), faker, database.ChatModelConfig{})
arg := database.UpdateChatModelConfigParams{
ID: config.ID,
Provider: "updated-provider",
Model: "updated-model",
DisplayName: "Updated Model",
Enabled: true,
Expand Down Expand Up @@ -6859,14 +6852,6 @@ func (s *MethodTestSuite) TestAIBridge() {
dbm.EXPECT().DeleteAIProviderByID(gomock.Any(), provider.ID).Return(nil).AnyTimes()
check.Args(provider.ID).Asserts(rbac.ResourceAIProvider, policy.ActionDelete).Returns()
}))
s.Run("BackfillChatModelConfigProvider", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
arg := database.BackfillChatModelConfigProviderParams{
OldProvider: "anthropic",
NewProvider: "bedrock",
}
dbm.EXPECT().BackfillChatModelConfigProvider(gomock.Any(), arg).Return(nil, nil).AnyTimes()
check.Args(arg).Asserts(rbac.ResourceDeploymentConfig, policy.ActionUpdate)
}))
s.Run("UpdateEncryptedAIProviderSettings", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
provider := testutil.Fake(s.T(), faker, database.AIProvider{})
arg := database.UpdateEncryptedAIProviderSettingsParams{
Expand Down
8 changes: 4 additions & 4 deletions coderd/database/dbgen/dbgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,15 @@ const (

func ChatModelConfig(t testing.TB, db database.Store, seed database.ChatModelConfig, munge ...func(*database.InsertChatModelConfigParams)) database.ChatModelConfig {
t.Helper()
providerName := takeFirst(seed.Provider, "openai")
aiProviderID := seed.AIProviderID
if !aiProviderID.Valid {
// No AIProviderID supplied: reuse or create a default openai provider.
// Tests needing a specific provider type should pass seed.AIProviderID.
providers, err := db.GetAIProviders(genCtx, database.GetAIProvidersParams{IncludeDisabled: true})
require.NoError(t, err, "get ai providers")
var provider database.AIProvider
for _, candidate := range providers {
if candidate.Type != database.AIProviderType(providerName) {
if candidate.Type != database.AIProviderTypeOpenai {
continue
}
if provider.ID == uuid.Nil || candidate.CreatedAt.After(provider.CreatedAt) {
Expand All @@ -176,13 +177,12 @@ func ChatModelConfig(t testing.TB, db database.Store, seed database.ChatModelCon
}
if provider.ID == uuid.Nil {
provider = AIProvider(t, db, database.AIProvider{
Type: database.AIProviderType(providerName),
Type: database.AIProviderTypeOpenai,
})
}
aiProviderID = uuid.NullUUID{UUID: provider.ID, Valid: true}
}
params := database.InsertChatModelConfigParams{
Provider: providerName,
Model: takeFirst(seed.Model, "gpt-4o-mini"),
DisplayName: takeFirst(seed.DisplayName, "Test Model"),
CreatedBy: seed.CreatedBy,
Expand Down
16 changes: 10 additions & 6 deletions coderd/database/dbgen/dbgen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,21 +296,25 @@ func TestGenerator(t *testing.T) {
// Defaults.
cfg := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{})
require.NotEqual(t, uuid.Nil, cfg.ID)
require.Equal(t, "openai", cfg.Provider)
prov, err := db.GetAIProviderByID(context.Background(), cfg.AIProviderID.UUID)
require.NoError(t, err)
require.Equal(t, "openai", string(prov.Type))
require.Equal(t, "gpt-4o-mini", cfg.Model)
require.Equal(t, "Test Model", cfg.DisplayName)
require.True(t, cfg.Enabled)
require.Equal(t, int64(128000), cfg.ContextLimit)
require.Equal(t, int32(70), cfg.CompressionThreshold)

// Overrides.
_ = dbgen.ChatProvider(t, db, database.ChatProvider{Provider: "anthropic"})
anthropicProvider := dbgen.ChatProvider(t, db, database.ChatProvider{Provider: "anthropic"})
cfg2 := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
Provider: "anthropic",
AIProviderID: uuid.NullUUID{UUID: anthropicProvider.ID, Valid: true},
Model: "claude-4",
ContextLimit: 200000,
})
require.Equal(t, "anthropic", cfg2.Provider)
prov2, err := db.GetAIProviderByID(context.Background(), cfg2.AIProviderID.UUID)
require.NoError(t, err)
require.Equal(t, "anthropic", string(prov2.Type))
require.Equal(t, "claude-4", cfg2.Model)
require.Equal(t, int64(200000), cfg2.ContextLimit)
})
Expand All @@ -325,7 +329,7 @@ func TestGenerator(t *testing.T) {
OrganizationID: o.ID,
})
p := dbgen.ChatProvider(t, db, database.ChatProvider{})
m := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{Provider: p.Provider})
m := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{AIProviderID: uuid.NullUUID{UUID: p.ID, Valid: true}})

// Defaults.
chat := dbgen.Chat(t, db, database.Chat{
Expand Down Expand Up @@ -360,7 +364,7 @@ func TestGenerator(t *testing.T) {
OrganizationID: o.ID,
})
p := dbgen.ChatProvider(t, db, database.ChatProvider{})
m := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{Provider: p.Provider})
m := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{AIProviderID: uuid.NullUUID{UUID: p.ID, Valid: true}})
chat := dbgen.Chat(t, db, database.Chat{
OwnerID: u.ID,
OrganizationID: o.ID,
Expand Down
Loading
Loading