mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add configurable reasoning effort to Coder agents (#26974)
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
DROP VIEW IF EXISTS chats_expanded;
|
||||
|
||||
-- The up migration left the legacy per-provider effort keys in
|
||||
-- place, so removing the reasoning_effort key restores the previous
|
||||
-- state exactly.
|
||||
UPDATE chat_model_configs
|
||||
SET options = options - 'reasoning_effort'
|
||||
WHERE options ? 'reasoning_effort';
|
||||
|
||||
ALTER TABLE chats DROP COLUMN last_reasoning_effort;
|
||||
ALTER TABLE chat_messages DROP COLUMN reasoning_effort;
|
||||
ALTER TABLE chat_queued_messages DROP COLUMN reasoning_effort;
|
||||
DROP TYPE chat_reasoning_effort;
|
||||
|
||||
CREATE VIEW chats_expanded AS
|
||||
SELECT c.id,
|
||||
c.owner_id,
|
||||
c.workspace_id,
|
||||
c.title,
|
||||
c.status,
|
||||
c.worker_id,
|
||||
c.started_at,
|
||||
c.heartbeat_at,
|
||||
c.created_at,
|
||||
c.updated_at,
|
||||
c.parent_chat_id,
|
||||
c.root_chat_id,
|
||||
c.last_model_config_id,
|
||||
c.archived,
|
||||
c.last_error,
|
||||
c.mode,
|
||||
c.mcp_server_ids,
|
||||
c.labels,
|
||||
c.build_id,
|
||||
c.agent_id,
|
||||
c.pin_order,
|
||||
c.last_read_message_id,
|
||||
c.dynamic_tools,
|
||||
c.organization_id,
|
||||
c.plan_mode,
|
||||
c.client_type,
|
||||
c.last_turn_summary,
|
||||
c.snapshot_version,
|
||||
c.history_version,
|
||||
c.queue_version,
|
||||
c.generation_attempt,
|
||||
c.retry_state,
|
||||
c.retry_state_version,
|
||||
c.runner_id,
|
||||
c.requires_action_deadline_at,
|
||||
COALESCE(root.user_acl, c.user_acl) AS user_acl,
|
||||
COALESCE(root.group_acl, c.group_acl) AS group_acl,
|
||||
owner.username AS owner_username,
|
||||
owner.name AS owner_name,
|
||||
c.context_aggregate_hash,
|
||||
c.context_dirty_since,
|
||||
c.context_dirty_resources,
|
||||
c.context_error
|
||||
FROM ((chats c
|
||||
LEFT JOIN chats root ON ((root.id = COALESCE(c.root_chat_id, c.parent_chat_id))))
|
||||
JOIN visible_users owner ON ((owner.id = c.owner_id)));
|
||||
@@ -0,0 +1,88 @@
|
||||
-- Per-turn reasoning effort. The chats_expanded view must be dropped
|
||||
-- and recreated so the new chats column can appear in its column list.
|
||||
DROP VIEW IF EXISTS chats_expanded;
|
||||
|
||||
CREATE TYPE chat_reasoning_effort AS ENUM ('none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max');
|
||||
|
||||
ALTER TABLE chats ADD COLUMN last_reasoning_effort chat_reasoning_effort;
|
||||
ALTER TABLE chat_messages ADD COLUMN reasoning_effort chat_reasoning_effort;
|
||||
ALTER TABLE chat_queued_messages ADD COLUMN reasoning_effort chat_reasoning_effort;
|
||||
|
||||
COMMENT ON COLUMN chats.last_reasoning_effort IS 'Stores the most recent message effort once per-turn selection is wired.';
|
||||
COMMENT ON COLUMN chat_messages.reasoning_effort IS 'Stores the selected effort for the turn triggered by this message.';
|
||||
COMMENT ON COLUMN chat_queued_messages.reasoning_effort IS 'Stores the selected effort until the queued row is promoted.';
|
||||
|
||||
CREATE VIEW chats_expanded AS
|
||||
SELECT c.id,
|
||||
c.owner_id,
|
||||
c.workspace_id,
|
||||
c.title,
|
||||
c.status,
|
||||
c.worker_id,
|
||||
c.started_at,
|
||||
c.heartbeat_at,
|
||||
c.created_at,
|
||||
c.updated_at,
|
||||
c.parent_chat_id,
|
||||
c.root_chat_id,
|
||||
c.last_model_config_id,
|
||||
c.last_reasoning_effort,
|
||||
c.archived,
|
||||
c.last_error,
|
||||
c.mode,
|
||||
c.mcp_server_ids,
|
||||
c.labels,
|
||||
c.build_id,
|
||||
c.agent_id,
|
||||
c.pin_order,
|
||||
c.last_read_message_id,
|
||||
c.dynamic_tools,
|
||||
c.organization_id,
|
||||
c.plan_mode,
|
||||
c.client_type,
|
||||
c.last_turn_summary,
|
||||
c.snapshot_version,
|
||||
c.history_version,
|
||||
c.queue_version,
|
||||
c.generation_attempt,
|
||||
c.retry_state,
|
||||
c.retry_state_version,
|
||||
c.runner_id,
|
||||
c.requires_action_deadline_at,
|
||||
COALESCE(root.user_acl, c.user_acl) AS user_acl,
|
||||
COALESCE(root.group_acl, c.group_acl) AS group_acl,
|
||||
owner.username AS owner_username,
|
||||
owner.name AS owner_name,
|
||||
c.context_aggregate_hash,
|
||||
c.context_dirty_since,
|
||||
c.context_dirty_resources,
|
||||
c.context_error
|
||||
FROM ((chats c
|
||||
LEFT JOIN chats root ON ((root.id = COALESCE(c.root_chat_id, c.parent_chat_id))))
|
||||
JOIN visible_users owner ON ((owner.id = c.owner_id)));
|
||||
|
||||
-- Copy legacy per-provider effort values to top-level
|
||||
-- reasoning_effort. Preserve legacy keys so the down migration can
|
||||
-- restore the original options shape.
|
||||
UPDATE chat_model_configs
|
||||
SET options = options || jsonb_build_object(
|
||||
'reasoning_effort',
|
||||
jsonb_build_object('default', legacy.effort, 'max', legacy.effort)
|
||||
)
|
||||
FROM (
|
||||
SELECT
|
||||
id,
|
||||
COALESCE(
|
||||
NULLIF(lower(trim(options #>> '{provider_options,openai,reasoning_effort}')), ''),
|
||||
NULLIF(lower(trim(options #>> '{provider_options,azure,reasoning_effort}')), ''),
|
||||
NULLIF(lower(trim(options #>> '{provider_options,anthropic,effort}')), ''),
|
||||
NULLIF(lower(trim(options #>> '{provider_options,bedrock,effort}')), ''),
|
||||
NULLIF(lower(trim(options #>> '{provider_options,openaicompat,reasoning_effort}')), ''),
|
||||
NULLIF(lower(trim(options #>> '{provider_options,openrouter,reasoning,effort}')), ''),
|
||||
NULLIF(lower(trim(options #>> '{provider_options,vercel,reasoning,effort}')), '')
|
||||
) AS effort
|
||||
FROM chat_model_configs
|
||||
) legacy
|
||||
WHERE chat_model_configs.id = legacy.id
|
||||
AND legacy.effort IS NOT NULL
|
||||
AND legacy.effort IN ('none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max');
|
||||
@@ -1582,6 +1582,80 @@ func applyMigrationsInTxn(ctx context.Context, t *testing.T, sqlDB *sql.DB, from
|
||||
require.NoError(t, tx.Commit())
|
||||
}
|
||||
|
||||
func TestMigration000542ChatReasoningEffortBackfill(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const priorMigrationVersion = 539
|
||||
|
||||
sqlDB := testSQLDB(t)
|
||||
|
||||
next, err := migrations.Stepper(sqlDB)
|
||||
require.NoError(t, err)
|
||||
for {
|
||||
version, more, err := next()
|
||||
require.NoError(t, err)
|
||||
if !more || version == priorMigrationVersion {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitSuperLong)
|
||||
now := time.Now().UTC().Truncate(time.Microsecond)
|
||||
|
||||
tx, err := sqlDB.BeginTx(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
defer tx.Rollback()
|
||||
azureID := uuid.New()
|
||||
bedrockID := uuid.New()
|
||||
emptyID := uuid.New()
|
||||
invalidID := uuid.New()
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO ai_providers (id, type, name, enabled, base_url, created_at, updated_at)
|
||||
VALUES
|
||||
($1, 'azure', 'test-azure-reasoning', TRUE, '', $3, $3),
|
||||
($2, 'bedrock', 'test-bedrock-reasoning', TRUE, '', $3, $3)
|
||||
`, azureID, bedrockID, now)
|
||||
require.NoError(t, err)
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO chat_model_configs (id, ai_provider_id, model, display_name, enabled, context_limit, compression_threshold, options, created_at, updated_at)
|
||||
VALUES
|
||||
($3, $1, 'gpt-5.1-azure', 'Azure GPT-5.1', TRUE, 200000, 70, '{"provider_options": {"azure": {"reasoning_effort": " LOW "}}}', $5, $5),
|
||||
($4, $2, 'anthropic.claude-opus-4-6', 'Bedrock Claude Opus', TRUE, 200000, 70, '{"provider_options": {"bedrock": {"effort": "minimal"}}}', $5, $5),
|
||||
(gen_random_uuid(), $1, 'gpt-5.1-empty-effort', 'Azure Empty Effort', TRUE, 200000, 70, '{"provider_options": {"azure": {"reasoning_effort": ""}}}', $5, $5),
|
||||
(gen_random_uuid(), $2, 'anthropic.invalid-effort', 'Bedrock Invalid Effort', TRUE, 200000, 70, '{"provider_options": {"bedrock": {"effort": "extreme"}}}', $5, $5)
|
||||
`, azureID, bedrockID, emptyID, invalidID, now)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, tx.Commit())
|
||||
|
||||
migrationSQL, err := os.ReadFile("000542_chat_reasoning_effort.up.sql")
|
||||
require.NoError(t, err)
|
||||
_, err = sqlDB.ExecContext(ctx, string(migrationSQL))
|
||||
require.NoError(t, err)
|
||||
|
||||
rows, err := sqlDB.QueryContext(ctx, `
|
||||
SELECT ap.type::text, cmc.model, cmc.options->'reasoning_effort'->>'default'
|
||||
FROM chat_model_configs cmc
|
||||
JOIN ai_providers ap ON ap.id = cmc.ai_provider_id
|
||||
WHERE cmc.ai_provider_id IN ($1, $2)
|
||||
ORDER BY cmc.model
|
||||
`, azureID, bedrockID)
|
||||
require.NoError(t, err)
|
||||
defer rows.Close()
|
||||
|
||||
got := map[string]sql.NullString{}
|
||||
for rows.Next() {
|
||||
var provider, model string
|
||||
var effort sql.NullString
|
||||
require.NoError(t, rows.Scan(&provider, &model, &effort))
|
||||
got[provider+":"+model] = effort
|
||||
}
|
||||
require.NoError(t, rows.Err())
|
||||
require.Equal(t, sql.NullString{String: "low", Valid: true}, got["azure:gpt-5.1-azure"])
|
||||
require.Equal(t, sql.NullString{}, got["azure:gpt-5.1-empty-effort"])
|
||||
require.Equal(t, sql.NullString{String: "minimal", Valid: true}, got["bedrock:anthropic.claude-opus-4-6"])
|
||||
require.Equal(t, sql.NullString{}, got["bedrock:anthropic.invalid-effort"])
|
||||
}
|
||||
|
||||
func TestMigration000498SoftDeleteStaleWorkspaceAgents(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
+127
-21
@@ -7,16 +7,47 @@ INSERT INTO chat_providers (
|
||||
enabled,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
'0a8b2f84-b5a8-4c44-8c9f-e58c44a534a7',
|
||||
'openai',
|
||||
'OpenAI',
|
||||
'',
|
||||
NULL,
|
||||
TRUE,
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
);
|
||||
) VALUES
|
||||
(
|
||||
'0a8b2f84-b5a8-4c44-8c9f-e58c44a534a7',
|
||||
'openai',
|
||||
'OpenAI',
|
||||
'',
|
||||
NULL,
|
||||
TRUE,
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
),
|
||||
(
|
||||
'4f0a9c2e-1d3b-4a5c-8e7f-6a9b8c7d6e02',
|
||||
'anthropic',
|
||||
'Anthropic (Reasoning Effort Fixture)',
|
||||
'',
|
||||
NULL,
|
||||
TRUE,
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
),
|
||||
(
|
||||
'4f0a9c2e-1d3b-4a5c-8e7f-6a9b8c7d6e03',
|
||||
'azure',
|
||||
'Azure OpenAI (Reasoning Effort Fixture)',
|
||||
'',
|
||||
NULL,
|
||||
TRUE,
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
),
|
||||
(
|
||||
'4f0a9c2e-1d3b-4a5c-8e7f-6a9b8c7d6e04',
|
||||
'bedrock',
|
||||
'Bedrock (Reasoning Effort Fixture)',
|
||||
'',
|
||||
NULL,
|
||||
TRUE,
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
);
|
||||
|
||||
INSERT INTO chat_model_configs (
|
||||
id,
|
||||
@@ -26,19 +57,94 @@ INSERT INTO chat_model_configs (
|
||||
enabled,
|
||||
context_limit,
|
||||
compression_threshold,
|
||||
options,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
'9af5f8d5-6a57-4505-8a69-3d6c787b95fd',
|
||||
'openai',
|
||||
'gpt-5.2',
|
||||
'GPT 5.2',
|
||||
TRUE,
|
||||
200000,
|
||||
70,
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
);
|
||||
) VALUES
|
||||
(
|
||||
'9af5f8d5-6a57-4505-8a69-3d6c787b95fd',
|
||||
'openai',
|
||||
'gpt-5.2',
|
||||
'GPT 5.2',
|
||||
TRUE,
|
||||
200000,
|
||||
70,
|
||||
'{}'::jsonb,
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
),
|
||||
(
|
||||
'4f0a9c2e-1d3b-4a5c-8e7f-6a9b8c7d6f01',
|
||||
'openai',
|
||||
'gpt-5.1',
|
||||
'GPT-5.1 (Legacy Effort)',
|
||||
TRUE,
|
||||
200000,
|
||||
70,
|
||||
'{"provider_options": {"openai": {"reasoning_effort": " HIGH ", "reasoning_summary": "auto"}}}',
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
),
|
||||
(
|
||||
'4f0a9c2e-1d3b-4a5c-8e7f-6a9b8c7d6f02',
|
||||
'anthropic',
|
||||
'claude-opus-4-6',
|
||||
'Claude Opus (Legacy Effort)',
|
||||
TRUE,
|
||||
200000,
|
||||
70,
|
||||
'{"provider_options": {"anthropic": {"effort": "max", "send_reasoning": true}}}',
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
),
|
||||
(
|
||||
'4f0a9c2e-1d3b-4a5c-8e7f-6a9b8c7d6f05',
|
||||
'azure',
|
||||
'gpt-5.1-azure',
|
||||
'Azure GPT-5.1 (Legacy Effort)',
|
||||
TRUE,
|
||||
200000,
|
||||
70,
|
||||
'{"provider_options": {"azure": {"reasoning_effort": "low"}}}',
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
),
|
||||
(
|
||||
'4f0a9c2e-1d3b-4a5c-8e7f-6a9b8c7d6f06',
|
||||
'bedrock',
|
||||
'anthropic.claude-opus-4-6',
|
||||
'Bedrock Claude Opus (Legacy Effort)',
|
||||
TRUE,
|
||||
200000,
|
||||
70,
|
||||
'{"provider_options": {"bedrock": {"effort": "minimal"}}}',
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
),
|
||||
(
|
||||
'4f0a9c2e-1d3b-4a5c-8e7f-6a9b8c7d6f03',
|
||||
'openai',
|
||||
'gpt-5.1-empty-effort',
|
||||
'GPT-5.1 (Empty Legacy Effort)',
|
||||
TRUE,
|
||||
200000,
|
||||
70,
|
||||
'{"provider_options": {"openai": {"reasoning_effort": ""}}}',
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
),
|
||||
(
|
||||
'4f0a9c2e-1d3b-4a5c-8e7f-6a9b8c7d6f04',
|
||||
'openai',
|
||||
'gpt-5.1-invalid-effort',
|
||||
'GPT-5.1 (Invalid Legacy Effort)',
|
||||
TRUE,
|
||||
200000,
|
||||
70,
|
||||
'{"provider_options": {"openai": {"reasoning_effort": "extreme"}}}',
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
);
|
||||
|
||||
INSERT INTO chats (
|
||||
id,
|
||||
|
||||
Reference in New Issue
Block a user