Skip to content

Commit 2f3df49

Browse files
sirozhaclaude
andcommitted
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 <noreply@anthropic.com>
1 parent 9e8ee58 commit 2f3df49

1 file changed

Lines changed: 84 additions & 0 deletions

File tree

backend/pkg/providers/pconfig/config_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package pconfig
22

33
import (
4+
"context"
45
"encoding/json"
56
"os"
67
"path/filepath"
@@ -13,6 +14,89 @@ import (
1314
"gopkg.in/yaml.v3"
1415
)
1516

17+
func TestProviderConfig_UsesAdaptiveThinking(t *testing.T) {
18+
adaptiveOnly := ModelsConfig{{Name: "opus-4-8", Reasoning: &ModelReasoningInfo{Mode: ModelReasoningAdaptiveOnly}}}
19+
adaptiveCapable := ModelsConfig{{Name: "opus-4-6", Reasoning: &ModelReasoningInfo{Mode: ModelReasoningAdaptive}}}
20+
budgetOnly := ModelsConfig{{Name: "sonnet-4-5", Reasoning: &ModelReasoningInfo{Mode: ModelReasoningBudget}}}
21+
22+
tests := []struct {
23+
name string
24+
models ModelsConfig
25+
agent *AgentConfig
26+
want bool
27+
}{
28+
{"adaptive-only model forces adaptive with no agent reasoning",
29+
adaptiveOnly, &AgentConfig{Model: "opus-4-8"}, true},
30+
{"adaptive-only model overrides an agent budget choice",
31+
adaptiveOnly, &AgentConfig{Model: "opus-4-8", Reasoning: ReasoningConfig{Mode: ReasoningModeBudget, MaxTokens: 4096}}, true},
32+
{"agent selects adaptive on an adaptive-capable model",
33+
adaptiveCapable, &AgentConfig{Model: "opus-4-6", Reasoning: ReasoningConfig{Mode: ReasoningModeAdaptive}}, true},
34+
{"agent selects budget on an adaptive-capable model",
35+
adaptiveCapable, &AgentConfig{Model: "opus-4-6", Reasoning: ReasoningConfig{Mode: ReasoningModeBudget, MaxTokens: 4096}}, false},
36+
{"budget-only model with a budget agent",
37+
budgetOnly, &AgentConfig{Model: "sonnet-4-5", Reasoning: ReasoningConfig{Mode: ReasoningModeBudget, MaxTokens: 4096}}, false},
38+
{"no reasoning anywhere",
39+
budgetOnly, &AgentConfig{Model: "sonnet-4-5"}, false},
40+
}
41+
for _, tt := range tests {
42+
t.Run(tt.name, func(t *testing.T) {
43+
pc := &ProviderConfig{Simple: tt.agent}
44+
assert.Equal(t, tt.want, pc.UsesAdaptiveThinking(tt.models, OptionsTypeSimple))
45+
})
46+
}
47+
}
48+
49+
func TestProviderConfig_PrepareAdaptiveCallOptions(t *testing.T) {
50+
adaptiveOnly := ModelsConfig{{Name: "opus-4-8", Reasoning: &ModelReasoningInfo{Mode: ModelReasoningAdaptiveOnly}}}
51+
52+
// adaptiveReasoning applies the returned options to a CallOptions and reports
53+
// the reasoning the provider would emit (nil if no adaptive option was added).
54+
adaptiveReasoning := func(options []llms.CallOption) *llms.ReasoningConfig {
55+
var co llms.CallOptions
56+
for _, o := range options {
57+
o(&co)
58+
}
59+
return co.Reasoning
60+
}
61+
62+
t.Run("overrides an agent budget choice on an adaptive-only model", func(t *testing.T) {
63+
pc := &ProviderConfig{Simple: &AgentConfig{
64+
Model: "opus-4-8",
65+
Reasoning: ReasoningConfig{Mode: ReasoningModeBudget, Effort: llms.ReasoningHigh, MaxTokens: 4096},
66+
}}
67+
_, options := pc.PrepareAdaptiveCallOptions(context.Background(), adaptiveOnly, OptionsTypeSimple, nil)
68+
require.Len(t, options, 1, "the adaptive call option must be appended")
69+
70+
r := adaptiveReasoning(options)
71+
require.NotNil(t, r)
72+
assert.True(t, r.Adaptive, "a budget choice must be overridden to adaptive for an adaptive-only model")
73+
assert.Equal(t, llms.ReasoningHigh, r.Effort)
74+
})
75+
76+
t.Run("forces adaptive on an adaptive-only model with no agent reasoning block", func(t *testing.T) {
77+
pc := &ProviderConfig{Simple: &AgentConfig{Model: "opus-4-8"}}
78+
_, options := pc.PrepareAdaptiveCallOptions(context.Background(), adaptiveOnly, OptionsTypeSimple, nil)
79+
require.Len(t, options, 1)
80+
81+
r := adaptiveReasoning(options)
82+
require.NotNil(t, r)
83+
assert.True(t, r.Adaptive)
84+
assert.Equal(t, llms.ReasoningNone, r.Effort, "empty effort is left for the provider to default to high")
85+
})
86+
87+
t.Run("no-op for a budget-only model, preserving existing options", func(t *testing.T) {
88+
budgetOnly := ModelsConfig{{Name: "sonnet-4-5", Reasoning: &ModelReasoningInfo{Mode: ModelReasoningBudget}}}
89+
pc := &ProviderConfig{Simple: &AgentConfig{
90+
Model: "sonnet-4-5",
91+
Reasoning: ReasoningConfig{Mode: ReasoningModeBudget, MaxTokens: 4096},
92+
}}
93+
existing := []llms.CallOption{llms.WithTemperature(1)}
94+
_, options := pc.PrepareAdaptiveCallOptions(context.Background(), budgetOnly, OptionsTypeSimple, existing)
95+
require.Len(t, options, 1, "a non-adaptive call must not append the adaptive option")
96+
assert.Nil(t, adaptiveReasoning(options), "no adaptive reasoning option should be present")
97+
})
98+
}
99+
16100
func TestReasoningConfig_UnmarshalJSON(t *testing.T) {
17101
tests := []struct {
18102
name string

0 commit comments

Comments
 (0)