From d9c79ff3285ed9cc09dd02a645abd645c356e185 Mon Sep 17 00:00:00 2001 From: Samyabrata Maji <116789799+sammaji@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:58:07 +0530 Subject: [PATCH] fix: force reasoning effort to none when reasoning with tools is unsupported --- core/schemas/modelcapabilities.go | 1 + .../datasheet/capabilities_test.go | 17 +++++ framework/modelcatalog/datasheet/types.go | 3 + plugins/compat/dropparams.go | 17 ++++- plugins/compat/dropparams_test.go | 72 ++++++++++++++++++- 5 files changed, 105 insertions(+), 5 deletions(-) diff --git a/core/schemas/modelcapabilities.go b/core/schemas/modelcapabilities.go index de24607afcd..5c9bfe74d6f 100644 --- a/core/schemas/modelcapabilities.go +++ b/core/schemas/modelcapabilities.go @@ -70,6 +70,7 @@ type ModelCapabilities struct { SupportsReasoning *bool `json:"supports_reasoning,omitempty"` SupportsResponseSchema *bool `json:"supports_response_schema,omitempty"` SupportsReasoningWithToolCalls *bool `json:"supports_reasoning_with_tool_calls,omitempty"` + SupportsNoneReasoningEffort *bool `json:"supports_none_reasoning_effort,omitempty"` SupportsServiceTier *bool `json:"supports_service_tier,omitempty"` SupportsPromptCaching *bool `json:"supports_prompt_caching,omitempty"` SupportsWebSearch *bool `json:"supports_web_search,omitempty"` diff --git a/framework/modelcatalog/datasheet/capabilities_test.go b/framework/modelcatalog/datasheet/capabilities_test.go index 47552bef8f2..c4ea6a24660 100644 --- a/framework/modelcatalog/datasheet/capabilities_test.go +++ b/framework/modelcatalog/datasheet/capabilities_test.go @@ -298,3 +298,20 @@ func TestExtractSupportedParams_WebSearchAbsent(t *testing.T) { } } } + +// TestExtractSupportedParams_NoneReasoningEffort guards the +// supports_none_reasoning_effort flag: compat's dropUnsupportedParams uses it +// to decide whether to force reasoning.effort to "none" (vs. dropping +// reasoning entirely) for models that reason by default even when +// reasoning_effort is omitted. +func TestExtractSupportedParams_NoneReasoningEffort(t *testing.T) { + if got := extractSupportedParams(&schemas.ModelCapabilities{SupportsNoneReasoningEffort: capabilityBoolPtr(true)}); !slices.Contains(got, "supports_none_reasoning_effort") { + t.Errorf("expected supported params to contain \"supports_none_reasoning_effort\", got %v", got) + } + if got := extractSupportedParams(&schemas.ModelCapabilities{SupportsNoneReasoningEffort: capabilityBoolPtr(false)}); slices.Contains(got, "supports_none_reasoning_effort") { + t.Errorf("expected supported params to omit \"supports_none_reasoning_effort\", got %v", got) + } + if got := extractSupportedParams(&schemas.ModelCapabilities{}); slices.Contains(got, "supports_none_reasoning_effort") { + t.Errorf("expected supported params to omit \"supports_none_reasoning_effort\" when unset, got %v", got) + } +} diff --git a/framework/modelcatalog/datasheet/types.go b/framework/modelcatalog/datasheet/types.go index 4e838c1e3df..cc8a2b1fd31 100644 --- a/framework/modelcatalog/datasheet/types.go +++ b/framework/modelcatalog/datasheet/types.go @@ -518,6 +518,9 @@ func extractSupportedParams(parsed *schemas.ModelCapabilities) []string { if parsed.SupportsReasoningWithToolCalls == nil || *parsed.SupportsReasoningWithToolCalls { addParam("reasoning_with_tool_calls") } + if parsed.SupportsNoneReasoningEffort != nil && *parsed.SupportsNoneReasoningEffort { + addParam("supports_none_reasoning_effort") + } if parsed.SupportsResponseSchema != nil && *parsed.SupportsResponseSchema { addParam("response_format") addParam("text") diff --git a/plugins/compat/dropparams.go b/plugins/compat/dropparams.go index 411faa6c1ec..de8027b3bd5 100644 --- a/plugins/compat/dropparams.go +++ b/plugins/compat/dropparams.go @@ -72,10 +72,23 @@ func dropUnsupportedParams(ctx *schemas.BifrostContext, req *schemas.BifrostRequ if params.Reasoning != nil { // for chat completions, some models do not support reasoning_effort // with tools - if !isSupported["reasoning"] || (hasSupportedTools && !isSupported["reasoning_with_tool_calls"]) { + if !isSupported["reasoning"] { params.Reasoning = nil dropped = append(dropped, "reasoning") + } else if hasSupportedTools && !isSupported["reasoning_with_tool_calls"] { + // models like gpt-5.6 series models defaults to reasoning, even when + // reasoning_effort is not set. + if isSupported["supports_none_reasoning_effort"] { + params.Reasoning = &schemas.ChatReasoning{Effort: new("none")} + dropped = append(dropped, "reasoning") + } else { + params.Reasoning = nil + dropped = append(dropped, "reasoning") + } } + } else if isSupported["reasoning"] && isSupported["supports_none_reasoning_effort"] && hasSupportedTools && !isSupported["reasoning_with_tool_calls"] { + params.Reasoning = &schemas.ChatReasoning{Effort: new("none")} + dropped = append(dropped, "reasoning") } if params.ResponseFormat != nil && !isSupported["response_format"] { params.ResponseFormat = nil @@ -364,4 +377,4 @@ func applyBedrockResponsesCompatibility(req *schemas.BifrostResponsesRequest) [] msg.Content.ContentBlocks = kept } return dropped -} \ No newline at end of file +} diff --git a/plugins/compat/dropparams_test.go b/plugins/compat/dropparams_test.go index 8a6752a8ec2..2b864c9acb2 100644 --- a/plugins/compat/dropparams_test.go +++ b/plugins/compat/dropparams_test.go @@ -289,9 +289,12 @@ func TestDropUnsupportedParams_ChatReasoningWithUnsupportedTools(t *testing.T) { } dropReasoning := newChat() - dropped = dropUnsupportedParams(newTestContext(), dropReasoning, []string{"reasoning", "tools"}) - if dropReasoning.ChatRequest.Params.Reasoning != nil { - t.Fatalf("reasoning = preserved, want dropped when tools survive without reasoning_with_tool_calls") + dropped = dropUnsupportedParams(newTestContext(), dropReasoning, []string{"reasoning", "tools", "supports_none_reasoning_effort"}) + if dropReasoning.ChatRequest.Params.Reasoning == nil { + t.Fatalf("reasoning = nil, want forced to effort=none when tools survive without reasoning_with_tool_calls") + } + if got := dropReasoning.ChatRequest.Params.Reasoning.Effort; got == nil || *got != "none" { + t.Fatalf("reasoning.effort = %v, want \"none\"", got) } if dropReasoning.ChatRequest.Params.Tools == nil { t.Fatalf("tools = dropped, want preserved") @@ -299,4 +302,67 @@ func TestDropUnsupportedParams_ChatReasoningWithUnsupportedTools(t *testing.T) { if !slices.Contains(dropped, "reasoning") { t.Errorf("reasoning not reported in dropped=%v, want present", dropped) } + + dropReasoningNoNoneSupport := newChat() + dropped = dropUnsupportedParams(newTestContext(), dropReasoningNoNoneSupport, []string{"reasoning", "tools"}) + if dropReasoningNoNoneSupport.ChatRequest.Params.Reasoning != nil { + t.Fatalf("reasoning = %v, want dropped to nil when model doesn't support effort=none", dropReasoningNoNoneSupport.ChatRequest.Params.Reasoning) + } + if !slices.Contains(dropped, "reasoning") { + t.Errorf("reasoning not reported in dropped=%v, want present", dropped) + } +} + +func TestDropUnsupportedParams_ChatReasoningNilForcedToNoneWithUnsupportedTools(t *testing.T) { + newChatNoReasoning := func() *schemas.BifrostRequest { + return &schemas.BifrostRequest{ + RequestType: schemas.ChatCompletionRequest, + ChatRequest: &schemas.BifrostChatRequest{ + Provider: schemas.OpenAI, + Model: "reasoning-no-tools-model", + Params: &schemas.ChatParameters{ + Tools: []schemas.ChatTool{{ + Type: schemas.ChatToolTypeFunction, + Function: &schemas.ChatToolFunction{ + Name: "get_weather", + Description: schemas.Ptr("Returns weather"), + }, + }}, + }, + }, + } + } + + forceNone := newChatNoReasoning() + dropped := dropUnsupportedParams(newTestContext(), forceNone, []string{"reasoning", "tools", "supports_none_reasoning_effort"}) + if forceNone.ChatRequest.Params.Reasoning == nil { + t.Fatalf("reasoning = nil, want forced to effort=none when model reasons by default and doesn't support reasoning with tools") + } + if got := forceNone.ChatRequest.Params.Reasoning.Effort; got == nil || *got != "none" { + t.Fatalf("reasoning.effort = %v, want \"none\"", got) + } + if forceNone.ChatRequest.Params.Tools == nil { + t.Fatalf("tools = dropped, want preserved") + } + if !slices.Contains(dropped, "reasoning") { + t.Errorf("reasoning not reported in dropped=%v, want present", dropped) + } + + noNoneSupport := newChatNoReasoning() + dropped = dropUnsupportedParams(newTestContext(), noNoneSupport, []string{"reasoning", "tools"}) + if noNoneSupport.ChatRequest.Params.Reasoning != nil { + t.Fatalf("reasoning = %v, want left nil when model doesn't support effort=none", noNoneSupport.ChatRequest.Params.Reasoning) + } + if slices.Contains(dropped, "reasoning") { + t.Errorf("reasoning reported in dropped=%v, want absent since it was never set", dropped) + } + + reasoningWithToolsSupported := newChatNoReasoning() + dropped = dropUnsupportedParams(newTestContext(), reasoningWithToolsSupported, []string{"reasoning", "tools", "reasoning_with_tool_calls", "supports_none_reasoning_effort"}) + if reasoningWithToolsSupported.ChatRequest.Params.Reasoning != nil { + t.Fatalf("reasoning = %v, want left nil when reasoning_with_tool_calls is supported", reasoningWithToolsSupported.ChatRequest.Params.Reasoning) + } + if slices.Contains(dropped, "reasoning") { + t.Errorf("reasoning reported in dropped=%v, want absent since it was never set", dropped) + } }