mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
4f1043a50a
Adds `coder exp scaletest chat`, a harness for creating Coder Agents chat load. Start the mock LLM separately, prepare the scaletest workspaces you want to target, then run the chat scaletest against the existing `scaletest-*` fleet selected by the shared workspace targeting flags: ```sh coder exp scaletest llm-mock --address 127.0.0.1:18080 coder exp scaletest chat --llm-mock-url http://127.0.0.1:18080/v1 --chats-per-workspace 10 --turns 1 coder exp scaletest chat --llm-mock-url http://127.0.0.1:18080/v1 --template docker --target-workspaces 0:10 --chats-per-workspace 1 --turns 10 --turn-start-delay 30s ``` This is the same pattern used by the `workspace-traffic` load generator. Keeping the fake LLM as a separate process is intentional so it can be scaled independently from the Coder deployment, which will likely be necessary as we scale up and up. This PR is the starting point: it provides the command, mock provider/model bootstrap, existing workspace selection, chat streaming, follow-up turns, metrics, and cleanup. Follow-up PRs will add multi-step turns via tool calls. I'm still a bit iffy on the mechanism I have for that. It'll likely involve having the runner send some magic strings that the mock will recognise. Relates to CODAGT-307 Relates to GRU-48 Relates to https://github.com/coder/scaletest/issues/124 Generated by Mux, but reviewed by a human
149 lines
5.0 KiB
Go
149 lines
5.0 KiB
Go
package chat
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/xerrors"
|
|
|
|
"cdr.dev/slog/v3"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
)
|
|
|
|
const (
|
|
scaletestProviderType = "openai-compat"
|
|
scaletestProviderDisplayName = "Scaletest LLM Mock"
|
|
scaletestModelName = "scaletest-model"
|
|
scaletestModelDisplayName = "Scaletest Model"
|
|
)
|
|
|
|
type scaletestProviderAction string
|
|
|
|
const (
|
|
scaletestProviderActionCreated scaletestProviderAction = "created"
|
|
scaletestProviderActionUpdated scaletestProviderAction = "updated"
|
|
scaletestProviderActionReused scaletestProviderAction = "reused"
|
|
)
|
|
|
|
// EnsureScaletestModelConfig bootstraps the shared chat provider and model
|
|
// config used by chat scaletests.
|
|
func EnsureScaletestModelConfig(ctx context.Context, client *codersdk.ExperimentalClient, logger slog.Logger, llmMockURL string) (uuid.UUID, error) {
|
|
logger.Info(ctx, "bootstrapping mock LLM provider", slog.F("llm_mock_url", llmMockURL))
|
|
|
|
provider, providerAction, err := ensureScaletestProvider(ctx, client, llmMockURL)
|
|
if err != nil {
|
|
return uuid.Nil, err
|
|
}
|
|
|
|
switch providerAction {
|
|
case scaletestProviderActionCreated:
|
|
logger.Info(ctx, "created mock LLM provider",
|
|
slog.F("provider_type", scaletestProviderType),
|
|
slog.F("llm_mock_url", llmMockURL),
|
|
)
|
|
case scaletestProviderActionUpdated:
|
|
logger.Info(ctx, "updated mock LLM provider",
|
|
slog.F("provider_type", scaletestProviderType),
|
|
slog.F("provider_id", provider.ID),
|
|
slog.F("llm_mock_url", llmMockURL),
|
|
)
|
|
case scaletestProviderActionReused:
|
|
logger.Info(ctx, "reusing mock LLM provider",
|
|
slog.F("provider_type", scaletestProviderType),
|
|
slog.F("provider_id", provider.ID),
|
|
)
|
|
}
|
|
|
|
modelConfigs, err := client.ListChatModelConfigs(ctx)
|
|
if err != nil {
|
|
return uuid.Nil, xerrors.Errorf("list chat model configs: %w", err)
|
|
}
|
|
|
|
for i := range modelConfigs {
|
|
if modelConfigs[i].Provider != provider.Provider || modelConfigs[i].Model != scaletestModelName {
|
|
continue
|
|
}
|
|
if !modelConfigs[i].Enabled {
|
|
return uuid.Nil, xerrors.Errorf("existing scaletest chat model config %s is disabled; re-enable or delete it before running scaletests", modelConfigs[i].ID)
|
|
}
|
|
modelConfigID := modelConfigs[i].ID
|
|
logger.Info(ctx, "reusing scaletest model config", slog.F("model_config_id", modelConfigID))
|
|
return modelConfigID, nil
|
|
}
|
|
|
|
enabled := true
|
|
isDefault := false
|
|
contextLimit := int64(4096)
|
|
created, err := client.CreateChatModelConfig(ctx, codersdk.CreateChatModelConfigRequest{
|
|
Provider: provider.Provider,
|
|
Model: scaletestModelName,
|
|
DisplayName: scaletestModelDisplayName,
|
|
Enabled: &enabled,
|
|
IsDefault: &isDefault,
|
|
ContextLimit: &contextLimit,
|
|
})
|
|
if err != nil {
|
|
return uuid.Nil, xerrors.Errorf("create scaletest chat model config: %w", err)
|
|
}
|
|
logger.Info(ctx, "created scaletest model config", slog.F("model_config_id", created.ID))
|
|
return created.ID, nil
|
|
}
|
|
|
|
func ensureScaletestProvider(ctx context.Context, client *codersdk.ExperimentalClient, llmMockURL string) (codersdk.ChatProviderConfig, scaletestProviderAction, error) {
|
|
enabled := true
|
|
mockProviderToken := uuid.NewString()
|
|
created, err := client.CreateChatProvider(ctx, codersdk.CreateChatProviderConfigRequest{
|
|
Provider: scaletestProviderType,
|
|
DisplayName: scaletestProviderDisplayName,
|
|
APIKey: mockProviderToken,
|
|
BaseURL: llmMockURL,
|
|
Enabled: &enabled,
|
|
})
|
|
if err == nil {
|
|
return created, scaletestProviderActionCreated, nil
|
|
}
|
|
|
|
var sdkErr *codersdk.Error
|
|
if !xerrors.As(err, &sdkErr) || sdkErr.StatusCode() != http.StatusConflict {
|
|
return codersdk.ChatProviderConfig{}, "", xerrors.Errorf("create scaletest chat provider: %w", err)
|
|
}
|
|
|
|
providers, err := client.ListChatProviders(ctx)
|
|
if err != nil {
|
|
return codersdk.ChatProviderConfig{}, "", xerrors.Errorf("list chat providers: %w", err)
|
|
}
|
|
|
|
var existing *codersdk.ChatProviderConfig
|
|
for i := range providers {
|
|
if providers[i].Provider == scaletestProviderType {
|
|
existing = &providers[i]
|
|
break
|
|
}
|
|
}
|
|
if existing == nil {
|
|
return codersdk.ChatProviderConfig{}, "", xerrors.Errorf("find existing %s provider after conflict: not found", scaletestProviderType)
|
|
}
|
|
if existing.DisplayName != scaletestProviderDisplayName {
|
|
return codersdk.ChatProviderConfig{}, "", xerrors.Errorf("refusing to overwrite existing %s provider %s with display name %q", scaletestProviderType, existing.ID, existing.DisplayName)
|
|
}
|
|
|
|
if !existing.Enabled {
|
|
return codersdk.ChatProviderConfig{}, "", xerrors.Errorf("existing scaletest chat provider %s is disabled; re-enable or delete it before running scaletests", existing.ID)
|
|
}
|
|
if existing.BaseURL == llmMockURL {
|
|
return *existing, scaletestProviderActionReused, nil
|
|
}
|
|
|
|
updated, err := client.UpdateChatProvider(ctx, existing.ID, codersdk.UpdateChatProviderConfigRequest{
|
|
DisplayName: scaletestProviderDisplayName,
|
|
APIKey: &mockProviderToken,
|
|
BaseURL: &llmMockURL,
|
|
Enabled: &enabled,
|
|
})
|
|
if err != nil {
|
|
return codersdk.ChatProviderConfig{}, "", xerrors.Errorf("update scaletest chat provider: %w", err)
|
|
}
|
|
return updated, scaletestProviderActionUpdated, nil
|
|
}
|