From 2f3df495fac7e968ec85c6c496d2aa2c6ec894ce Mon Sep 17 00:00:00 2001 From: Sergey Kozyrenko Date: Wed, 24 Jun 2026 09:49:00 +0700 Subject: [PATCH] test(pconfig): cover the adaptive-thinking backstop (refs #288) UsesAdaptiveThinking/PrepareAdaptiveCallOptions had no test coverage after the native-langchaingo migration removed the old transport-layer tests. Add table tests: an adaptive-only model forces adaptive over an agent's budget choice or an absent reasoning block, while budget-only models stay untouched. Co-Authored-By: Claude Opus 4.8 --- backend/pkg/providers/pconfig/config_test.go | 84 ++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/backend/pkg/providers/pconfig/config_test.go b/backend/pkg/providers/pconfig/config_test.go index d70881f0..94d1f4c0 100644 --- a/backend/pkg/providers/pconfig/config_test.go +++ b/backend/pkg/providers/pconfig/config_test.go @@ -1,6 +1,7 @@ package pconfig import ( + "context" "encoding/json" "os" "path/filepath" @@ -13,6 +14,89 @@ import ( "gopkg.in/yaml.v3" ) +func TestProviderConfig_UsesAdaptiveThinking(t *testing.T) { + adaptiveOnly := ModelsConfig{{Name: "opus-4-8", Reasoning: &ModelReasoningInfo{Mode: ModelReasoningAdaptiveOnly}}} + adaptiveCapable := ModelsConfig{{Name: "opus-4-6", Reasoning: &ModelReasoningInfo{Mode: ModelReasoningAdaptive}}} + budgetOnly := ModelsConfig{{Name: "sonnet-4-5", Reasoning: &ModelReasoningInfo{Mode: ModelReasoningBudget}}} + + tests := []struct { + name string + models ModelsConfig + agent *AgentConfig + want bool + }{ + {"adaptive-only model forces adaptive with no agent reasoning", + adaptiveOnly, &AgentConfig{Model: "opus-4-8"}, true}, + {"adaptive-only model overrides an agent budget choice", + adaptiveOnly, &AgentConfig{Model: "opus-4-8", Reasoning: ReasoningConfig{Mode: ReasoningModeBudget, MaxTokens: 4096}}, true}, + {"agent selects adaptive on an adaptive-capable model", + adaptiveCapable, &AgentConfig{Model: "opus-4-6", Reasoning: ReasoningConfig{Mode: ReasoningModeAdaptive}}, true}, + {"agent selects budget on an adaptive-capable model", + adaptiveCapable, &AgentConfig{Model: "opus-4-6", Reasoning: ReasoningConfig{Mode: ReasoningModeBudget, MaxTokens: 4096}}, false}, + {"budget-only model with a budget agent", + budgetOnly, &AgentConfig{Model: "sonnet-4-5", Reasoning: ReasoningConfig{Mode: ReasoningModeBudget, MaxTokens: 4096}}, false}, + {"no reasoning anywhere", + budgetOnly, &AgentConfig{Model: "sonnet-4-5"}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pc := &ProviderConfig{Simple: tt.agent} + assert.Equal(t, tt.want, pc.UsesAdaptiveThinking(tt.models, OptionsTypeSimple)) + }) + } +} + +func TestProviderConfig_PrepareAdaptiveCallOptions(t *testing.T) { + adaptiveOnly := ModelsConfig{{Name: "opus-4-8", Reasoning: &ModelReasoningInfo{Mode: ModelReasoningAdaptiveOnly}}} + + // adaptiveReasoning applies the returned options to a CallOptions and reports + // the reasoning the provider would emit (nil if no adaptive option was added). + adaptiveReasoning := func(options []llms.CallOption) *llms.ReasoningConfig { + var co llms.CallOptions + for _, o := range options { + o(&co) + } + return co.Reasoning + } + + t.Run("overrides an agent budget choice on an adaptive-only model", func(t *testing.T) { + pc := &ProviderConfig{Simple: &AgentConfig{ + Model: "opus-4-8", + Reasoning: ReasoningConfig{Mode: ReasoningModeBudget, Effort: llms.ReasoningHigh, MaxTokens: 4096}, + }} + _, options := pc.PrepareAdaptiveCallOptions(context.Background(), adaptiveOnly, OptionsTypeSimple, nil) + require.Len(t, options, 1, "the adaptive call option must be appended") + + r := adaptiveReasoning(options) + require.NotNil(t, r) + assert.True(t, r.Adaptive, "a budget choice must be overridden to adaptive for an adaptive-only model") + assert.Equal(t, llms.ReasoningHigh, r.Effort) + }) + + t.Run("forces adaptive on an adaptive-only model with no agent reasoning block", func(t *testing.T) { + pc := &ProviderConfig{Simple: &AgentConfig{Model: "opus-4-8"}} + _, options := pc.PrepareAdaptiveCallOptions(context.Background(), adaptiveOnly, OptionsTypeSimple, nil) + require.Len(t, options, 1) + + r := adaptiveReasoning(options) + require.NotNil(t, r) + assert.True(t, r.Adaptive) + assert.Equal(t, llms.ReasoningNone, r.Effort, "empty effort is left for the provider to default to high") + }) + + t.Run("no-op for a budget-only model, preserving existing options", func(t *testing.T) { + budgetOnly := ModelsConfig{{Name: "sonnet-4-5", Reasoning: &ModelReasoningInfo{Mode: ModelReasoningBudget}}} + pc := &ProviderConfig{Simple: &AgentConfig{ + Model: "sonnet-4-5", + Reasoning: ReasoningConfig{Mode: ReasoningModeBudget, MaxTokens: 4096}, + }} + existing := []llms.CallOption{llms.WithTemperature(1)} + _, options := pc.PrepareAdaptiveCallOptions(context.Background(), budgetOnly, OptionsTypeSimple, existing) + require.Len(t, options, 1, "a non-adaptive call must not append the adaptive option") + assert.Nil(t, adaptiveReasoning(options), "no adaptive reasoning option should be present") + }) +} + func TestReasoningConfig_UnmarshalJSON(t *testing.T) { tests := []struct { name string