Skip to content

Commit c206962

Browse files
authored
Merge pull request #2038 from dgageot/board/you-are-the-master-decoupler-you-know-ev-e75a9547
refactor: remove duplication in model resolution, thinking budget, and message construction
2 parents f4d9d0f + 6930817 commit c206962

13 files changed

Lines changed: 115 additions & 176 deletions

File tree

pkg/config/latest/model_ref.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package latest
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
// ParseModelRef parses an inline "provider/model" reference into a
9+
// ModelConfig. It returns an error when the string does not contain
10+
// exactly one "/" separator or when either part is empty.
11+
//
12+
// cfg, err := ParseModelRef("openai/gpt-4o")
13+
// // cfg.Provider == "openai", cfg.Model == "gpt-4o"
14+
func ParseModelRef(ref string) (ModelConfig, error) {
15+
providerName, model, ok := strings.Cut(ref, "/")
16+
if !ok || providerName == "" || model == "" {
17+
return ModelConfig{}, fmt.Errorf("invalid model reference %q: expected 'provider/model' format", ref)
18+
}
19+
return ModelConfig{Provider: providerName, Model: model}, nil
20+
}

pkg/config/latest/types.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -439,12 +439,12 @@ func (f *FlexibleModelConfig) UnmarshalYAML(unmarshal func(any) error) error {
439439
// Try string shorthand first
440440
var shorthand string
441441
if err := unmarshal(&shorthand); err == nil && shorthand != "" {
442-
provider, model, ok := strings.Cut(shorthand, "/")
443-
if !ok || provider == "" || model == "" {
442+
parsed, parseErr := ParseModelRef(shorthand)
443+
if parseErr != nil {
444444
return fmt.Errorf("invalid model shorthand %q: expected format 'provider/model'", shorthand)
445445
}
446-
f.Provider = provider
447-
f.Model = model
446+
f.Provider = parsed.Provider
447+
f.Model = parsed.Model
448448
return nil
449449
}
450450

@@ -707,6 +707,26 @@ func (t ThinkingBudget) MarshalYAML() (any, error) {
707707
return t.Tokens, nil
708708
}
709709

710+
// IsDisabled returns true if the thinking budget is explicitly disabled.
711+
// A nil receiver is treated as "not configured" (not disabled).
712+
//
713+
// Disabled when:
714+
// - Tokens == 0 with no Effort (thinking_budget: 0)
715+
// - Effort == "none" (thinking_budget: none)
716+
//
717+
// NOT disabled when:
718+
// - Tokens > 0 or Tokens == -1 (explicit token budget)
719+
// - Effort is a real level like "medium" or "high"
720+
func (t *ThinkingBudget) IsDisabled() bool {
721+
if t == nil {
722+
return false
723+
}
724+
if t.Tokens == 0 && t.Effort == "" {
725+
return true
726+
}
727+
return t.Effort == "none"
728+
}
729+
710730
// MarshalJSON implements custom marshaling to output simple string or int format
711731
// This ensures JSON and YAML have the same flattened format for consistency
712732
func (t ThinkingBudget) MarshalJSON() ([]byte, error) {

pkg/config/overrides.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,15 +186,12 @@ func ensureSingleModelExists(cfg *latest.Config, modelName, context string) erro
186186
return nil
187187
}
188188

189-
providerName, model, ok := strings.Cut(modelName, "/")
190-
if !ok || providerName == "" || model == "" {
189+
parsed, err := latest.ParseModelRef(modelName)
190+
if err != nil {
191191
return fmt.Errorf("%s references non-existent model '%s'", context, modelName)
192192
}
193193

194-
cfg.Models[modelName] = latest.ModelConfig{
195-
Provider: providerName,
196-
Model: model,
197-
}
194+
cfg.Models[modelName] = parsed
198195

199196
return nil
200197
}

pkg/evaluation/eval.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -597,22 +597,17 @@ func createJudgeModel(ctx context.Context, judgeModel string, runConfig *config.
597597
return nil, nil
598598
}
599599

600-
providerName, model, ok := strings.Cut(judgeModel, "/")
601-
if !ok {
600+
cfg, err := latest.ParseModelRef(judgeModel)
601+
if err != nil {
602602
return nil, fmt.Errorf("invalid judge model format %q: expected 'provider/model'", judgeModel)
603603
}
604604

605-
cfg := &latest.ModelConfig{
606-
Provider: providerName,
607-
Model: model,
608-
}
609-
610605
var opts []options.Opt
611606
if runConfig.ModelsGateway != "" {
612607
opts = append(opts, options.WithGateway(runConfig.ModelsGateway))
613608
}
614609

615-
judge, err := provider.New(ctx, cfg, runConfig.EnvProvider(), opts...)
610+
judge, err := provider.New(ctx, &cfg, runConfig.EnvProvider(), opts...)
616611
if err != nil {
617612
return nil, fmt.Errorf("creating judge model: %w", err)
618613
}

pkg/model/provider/override_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ func TestIsThinkingBudgetDisabled(t *testing.T) {
501501
for _, tt := range tests {
502502
t.Run(tt.name, func(t *testing.T) {
503503
t.Parallel()
504-
assert.Equal(t, tt.expected, isThinkingBudgetDisabled(tt.budget))
504+
assert.Equal(t, tt.expected, tt.budget.IsDisabled())
505505
})
506506
}
507507
}

pkg/model/provider/provider.go

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -205,16 +205,11 @@ func createRuleBasedRouter(ctx context.Context, cfg *latest.ModelConfig, models
205205
}
206206

207207
// Otherwise, treat as an inline model spec (e.g., "openai/gpt-4o")
208-
providerName, model, ok := strings.Cut(modelSpec, "/")
209-
if !ok {
208+
inlineCfg, parseErr := latest.ParseModelRef(modelSpec)
209+
if parseErr != nil {
210210
return nil, fmt.Errorf("invalid model spec %q: expected 'provider/model' format or a model reference", modelSpec)
211211
}
212-
213-
inlineCfg := &latest.ModelConfig{
214-
Provider: providerName,
215-
Model: model,
216-
}
217-
p, err := createDirectProvider(ctx, inlineCfg, env, factoryOpts...)
212+
p, err := createDirectProvider(ctx, &inlineCfg, env, factoryOpts...)
218213
if err != nil {
219214
return nil, err
220215
}
@@ -394,7 +389,7 @@ func applyOverrides(cfg *latest.ModelConfig, opts *options.ModelOptions) *latest
394389
// 1. ThinkingBudget is nil (not configured) - apply defaults to enable thinking
395390
// 2. ThinkingBudget is explicitly disabled (Tokens == 0 or Effort == "none") - clear and re-apply defaults
396391
// This allows /think to enable thinking with provider defaults even when config had thinking_budget: 0
397-
if enhancedCfg.ThinkingBudget == nil || isThinkingBudgetDisabled(enhancedCfg.ThinkingBudget) {
392+
if enhancedCfg.ThinkingBudget == nil || enhancedCfg.ThinkingBudget.IsDisabled() {
398393
enhancedCfg.ThinkingBudget = nil
399394
applyModelDefaults(&enhancedCfg)
400395
slog.Debug("Override: thinking enabled - applied default thinking configuration",
@@ -407,22 +402,6 @@ func applyOverrides(cfg *latest.ModelConfig, opts *options.ModelOptions) *latest
407402
return &enhancedCfg
408403
}
409404

410-
// isThinkingBudgetDisabled returns true if the thinking budget is explicitly disabled.
411-
// NOT disabled when:
412-
// - Tokens > 0 or Tokens == -1 (explicit token budget)
413-
// - Effort is set to something other than "none" (e.g., "medium", "high")
414-
func isThinkingBudgetDisabled(tb *latest.ThinkingBudget) bool {
415-
if tb == nil {
416-
return false
417-
}
418-
if tb.Effort == "none" {
419-
return true
420-
}
421-
// Tokens == 0 with no Effort means explicitly disabled (thinking_budget: 0)
422-
// Tokens == 0 with Effort set (e.g., "medium") means Effort-based config, not disabled
423-
return tb.Tokens == 0 && tb.Effort == ""
424-
}
425-
426405
// applyModelDefaults applies provider-specific default values for model configuration.
427406
// These defaults are applied only if the user hasn't explicitly set the values.
428407
//
@@ -441,7 +420,7 @@ func applyModelDefaults(cfg *latest.ModelConfig) {
441420
// If thinking is explicitly disabled (thinking_budget: 0 or thinking_budget: none),
442421
// set ThinkingBudget to nil to completely disable thinking.
443422
// This ensures no thinking config is sent to the provider.
444-
if isThinkingBudgetDisabled(cfg.ThinkingBudget) {
423+
if cfg.ThinkingBudget.IsDisabled() {
445424
cfg.ThinkingBudget = nil
446425
slog.Debug("Thinking explicitly disabled via thinking_budget: 0 or none",
447426
"provider", cfg.Provider,

pkg/runtime/model_switcher.go

Lines changed: 22 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func (r *LocalRuntime) SetAgentModel(ctx context.Context, agentName, modelRef st
8888
modelConfig.Name = modelRef
8989
// Check if this is an alloy model (no provider, comma-separated models)
9090
if isAlloyModelConfig(modelConfig) {
91-
providers, err := r.createProvidersFromAlloyConfig(ctx, modelConfig)
91+
providers, err := r.resolveModelRefs(ctx, modelConfig.Model)
9292
if err != nil {
9393
return fmt.Errorf("failed to create alloy model from config: %w", err)
9494
}
@@ -109,7 +109,7 @@ func (r *LocalRuntime) SetAgentModel(ctx context.Context, agentName, modelRef st
109109
// Check if this is an inline alloy spec (comma-separated provider/model specs)
110110
// e.g., "openai/gpt-4o,anthropic/claude-sonnet-4-0"
111111
if isInlineAlloySpec(modelRef) {
112-
providers, err := r.createProvidersFromInlineAlloy(ctx, modelRef)
112+
providers, err := r.resolveModelRefs(ctx, modelRef)
113113
if err != nil {
114114
return fmt.Errorf("failed to create inline alloy model: %w", err)
115115
}
@@ -146,17 +146,12 @@ func (r *LocalRuntime) resolveModelRef(ctx context.Context, modelRef string) (pr
146146
}
147147

148148
// Try inline "provider/model" format.
149-
providerName, modelName, ok := strings.Cut(modelRef, "/")
150-
if !ok || providerName == "" || modelName == "" {
149+
inlineCfg, err := latest.ParseModelRef(modelRef)
150+
if err != nil {
151151
return nil, fmt.Errorf("invalid model reference %q: expected a model name from config or 'provider/model' format", modelRef)
152152
}
153153

154-
inlineCfg := &latest.ModelConfig{
155-
Provider: providerName,
156-
Model: modelName,
157-
}
158-
159-
return r.createProviderFromConfig(ctx, inlineCfg)
154+
return r.createProviderFromConfig(ctx, &inlineCfg)
160155
}
161156

162157
// isAlloyModelConfig checks if a model config is an alloy model (multiple models).
@@ -186,92 +181,44 @@ func isInlineAlloySpec(modelRef string) bool {
186181
return validParts >= 2
187182
}
188183

189-
// createProvidersFromInlineAlloy creates providers from an inline alloy spec.
190-
// An inline alloy is comma-separated provider/model specs like "openai/gpt-4o,anthropic/claude-sonnet-4-0".
191-
func (r *LocalRuntime) createProvidersFromInlineAlloy(ctx context.Context, modelRef string) ([]provider.Provider, error) {
184+
// resolveModelRefs resolves a comma-separated list of model references into
185+
// providers. Each reference is first looked up in the config by name; if not
186+
// found it is parsed as an inline "provider/model" spec.
187+
func (r *LocalRuntime) resolveModelRefs(ctx context.Context, commaSeparatedRefs string) ([]provider.Provider, error) {
192188
var providers []provider.Provider
193189

194-
for part := range strings.SplitSeq(modelRef, ",") {
195-
part = strings.TrimSpace(part)
196-
if part == "" {
190+
for ref := range strings.SplitSeq(commaSeparatedRefs, ",") {
191+
ref = strings.TrimSpace(ref)
192+
if ref == "" {
197193
continue
198194
}
199195

200-
// Check if this part exists as a named model in config
201-
if modelCfg, exists := r.modelSwitcherCfg.Models[part]; exists {
202-
modelCfg.Name = part
196+
// Check if this ref exists as a named model in config
197+
if modelCfg, exists := r.modelSwitcherCfg.Models[ref]; exists {
198+
modelCfg.Name = ref
203199
prov, err := r.createProviderFromConfig(ctx, &modelCfg)
204200
if err != nil {
205-
return nil, fmt.Errorf("failed to create provider for %q: %w", part, err)
201+
return nil, fmt.Errorf("failed to create provider for %q: %w", ref, err)
206202
}
207203
providers = append(providers, prov)
208204
continue
209205
}
210206

211207
// Parse as provider/model
212-
providerName, modelName, ok := strings.Cut(part, "/")
213-
if !ok {
214-
return nil, fmt.Errorf("invalid model reference %q in inline alloy: expected 'provider/model' format", part)
215-
}
216-
217-
inlineCfg := &latest.ModelConfig{
218-
Provider: providerName,
219-
Model: modelName,
220-
}
221-
prov, err := r.createProviderFromConfig(ctx, inlineCfg)
222-
if err != nil {
223-
return nil, fmt.Errorf("failed to create provider for %q: %w", part, err)
224-
}
225-
providers = append(providers, prov)
226-
}
227-
228-
if len(providers) == 0 {
229-
return nil, errors.New("inline alloy spec has no valid models")
230-
}
231-
232-
return providers, nil
233-
}
234-
235-
// createProvidersFromAlloyConfig creates providers for each model in an alloy configuration.
236-
func (r *LocalRuntime) createProvidersFromAlloyConfig(ctx context.Context, alloyCfg latest.ModelConfig) ([]provider.Provider, error) {
237-
var providers []provider.Provider
238-
239-
for modelRef := range strings.SplitSeq(alloyCfg.Model, ",") {
240-
modelRef = strings.TrimSpace(modelRef)
241-
if modelRef == "" {
242-
continue
208+
inlineCfg, parseErr := latest.ParseModelRef(ref)
209+
if parseErr != nil {
210+
return nil, fmt.Errorf("invalid model reference %q: expected 'provider/model' format or a named model from config", ref)
243211
}
244212

245-
// Check if this model reference exists in the config
246-
if modelCfg, exists := r.modelSwitcherCfg.Models[modelRef]; exists {
247-
modelCfg.Name = modelRef
248-
prov, err := r.createProviderFromConfig(ctx, &modelCfg)
249-
if err != nil {
250-
return nil, fmt.Errorf("failed to create provider for %q: %w", modelRef, err)
251-
}
252-
providers = append(providers, prov)
253-
continue
254-
}
255-
256-
// Try parsing as inline spec (provider/model)
257-
providerName, modelName, ok := strings.Cut(modelRef, "/")
258-
if !ok {
259-
return nil, fmt.Errorf("invalid model reference %q in alloy config: expected 'provider/model' format", modelRef)
260-
}
261-
262-
inlineCfg := &latest.ModelConfig{
263-
Provider: providerName,
264-
Model: modelName,
265-
}
266-
prov, err := r.createProviderFromConfig(ctx, inlineCfg)
213+
prov, err := r.createProviderFromConfig(ctx, &inlineCfg)
267214
if err != nil {
268-
return nil, fmt.Errorf("failed to create provider for %q: %w", modelRef, err)
215+
return nil, fmt.Errorf("failed to create provider for %q: %w", ref, err)
269216
}
270217
providers = append(providers, prov)
271218
}
272219

273220
if len(providers) == 0 {
274-
return nil, errors.New("alloy model config has no valid models")
221+
return nil, errors.New("no valid models found in model reference list")
275222
}
276223

277224
return providers, nil

pkg/runtime/tool_dispatch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ func (r *LocalRuntime) runAgentTool(ctx context.Context, handler ToolHandlerFunc
424424
}
425425

426426
func addAgentMessage(sess *session.Session, a *agent.Agent, msg *chat.Message, events chan Event) {
427-
agentMsg := session.NewAgentMessage(a, msg)
427+
agentMsg := session.NewAgentMessage(a.Name(), msg)
428428
sess.AddMessage(agentMsg)
429429
events <- MessageAdded(sess.ID, agentMsg, a.Name())
430430
}

pkg/session/session.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,9 @@ func UserMessage(content string, multiContent ...chat.MessagePart) *Message {
190190
}
191191
}
192192

193-
func NewAgentMessage(a *agent.Agent, message *chat.Message) *Message {
193+
func NewAgentMessage(agentName string, message *chat.Message) *Message {
194194
return &Message{
195-
AgentName: a.Name(),
195+
AgentName: agentName,
196196
Message: *message,
197197
}
198198
}

0 commit comments

Comments
 (0)