Commit 6b659fd6 by CaIon

fix(relay): preserve reasoning effort without implicit remapping

parent 7c044d7c
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
"github.com/QuantumNous/new-api/relay/constant" "github.com/QuantumNous/new-api/relay/constant"
"github.com/QuantumNous/new-api/relaykit/dto" "github.com/QuantumNous/new-api/relaykit/dto"
"github.com/QuantumNous/new-api/relaykit/types" "github.com/QuantumNous/new-api/relaykit/types"
"github.com/QuantumNous/new-api/setting/model_setting"
"github.com/QuantumNous/new-api/setting/reasoning" "github.com/QuantumNous/new-api/setting/reasoning"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
...@@ -98,6 +99,9 @@ func applyDeepSeekV4OpenAIThinkingSuffix(info *relaycommon.RelayInfo, request *d ...@@ -98,6 +99,9 @@ func applyDeepSeekV4OpenAIThinkingSuffix(info *relaycommon.RelayInfo, request *d
if info != nil && info.ChannelMeta != nil && info.UpstreamModelName != "" { if info != nil && info.ChannelMeta != nil && info.UpstreamModelName != "" {
modelName = info.UpstreamModelName modelName = info.UpstreamModelName
} }
if model_setting.ShouldPreserveThinkingSuffix(modelName) || info != nil && model_setting.ShouldPreserveThinkingSuffix(info.OriginModelName) {
return nil
}
baseModel, thinkingType, effort, ok := reasoning.ParseDeepSeekV4ThinkingSuffix(modelName) baseModel, thinkingType, effort, ok := reasoning.ParseDeepSeekV4ThinkingSuffix(modelName)
if !ok { if !ok {
return nil return nil
...@@ -125,6 +129,9 @@ func applyDeepSeekV4ClaudeThinkingSuffix(info *relaycommon.RelayInfo, request *d ...@@ -125,6 +129,9 @@ func applyDeepSeekV4ClaudeThinkingSuffix(info *relaycommon.RelayInfo, request *d
if info != nil && info.ChannelMeta != nil && info.UpstreamModelName != "" { if info != nil && info.ChannelMeta != nil && info.UpstreamModelName != "" {
modelName = info.UpstreamModelName modelName = info.UpstreamModelName
} }
if model_setting.ShouldPreserveThinkingSuffix(modelName) || info != nil && model_setting.ShouldPreserveThinkingSuffix(info.OriginModelName) {
return nil
}
baseModel, thinkingType, effort, ok := reasoning.ParseDeepSeekV4ThinkingSuffix(modelName) baseModel, thinkingType, effort, ok := reasoning.ParseDeepSeekV4ThinkingSuffix(modelName)
if !ok { if !ok {
return nil return nil
...@@ -170,6 +177,9 @@ func applyDeepSeekV4ResponsesThinkingSuffix(info *relaycommon.RelayInfo, request ...@@ -170,6 +177,9 @@ func applyDeepSeekV4ResponsesThinkingSuffix(info *relaycommon.RelayInfo, request
if info != nil && info.ChannelMeta != nil && info.UpstreamModelName != "" { if info != nil && info.ChannelMeta != nil && info.UpstreamModelName != "" {
modelName = info.UpstreamModelName modelName = info.UpstreamModelName
} }
if model_setting.ShouldPreserveThinkingSuffix(modelName) || info != nil && model_setting.ShouldPreserveThinkingSuffix(info.OriginModelName) {
return
}
baseModel, thinkingType, effort, ok := reasoning.ParseDeepSeekV4ThinkingSuffix(modelName) baseModel, thinkingType, effort, ok := reasoning.ParseDeepSeekV4ThinkingSuffix(modelName)
if ok { if ok {
if thinkingType == "disabled" { if thinkingType == "disabled" {
......
...@@ -249,6 +249,18 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn ...@@ -249,6 +249,18 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
if info.ChannelType != constant.ChannelTypeOpenAI && info.ChannelType != constant.ChannelTypeAzure { if info.ChannelType != constant.ChannelTypeOpenAI && info.ChannelType != constant.ChannelTypeAzure {
request.StreamOptions = nil request.StreamOptions = nil
} }
// Nested reasoning is an OpenRouter-compatible input dialect and needs
// projection even without a protocol conversion hop. Native top-level
// reasoning_effort stays untouched unless a modifier or conversion applies.
// OpenRouter retains its own dialect normalization below.
preserveSuffix := model_setting.ShouldPreserveThinkingSuffix(info.OriginModelName) || model_setting.ShouldPreserveThinkingSuffix(info.UpstreamModelName)
upstreamEffort, _ := reasoning.ParseOpenAIReasoningEffortFromModelSuffix(info.UpstreamModelName)
originEffort, _ := reasoning.ParseOpenAIReasoningEffortFromModelSuffix(info.OriginModelName)
renderReasoning := len(request.Reasoning) > 0 || len(info.RequestConversionChain) > 1 || request.ReasoningConversion != nil || info.ReasoningState() != nil ||
!preserveSuffix && (upstreamEffort != "" || originEffort != "")
if info.ChannelType != constant.ChannelTypeOpenRouter && !renderReasoning {
info.SetReasoningEffort(request.ReasoningEffort)
}
if info.ChannelType == constant.ChannelTypeOpenRouter { if info.ChannelType == constant.ChannelTypeOpenRouter {
initialIntent, err := kitreasoning.FromOpenAIChat(request) initialIntent, err := kitreasoning.FromOpenAIChat(request)
if err != nil { if err != nil {
...@@ -273,7 +285,6 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn ...@@ -273,7 +285,6 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
request.Usage = json.RawMessage(`{"include":true}`) request.Usage = json.RawMessage(`{"include":true}`)
} }
// 合并 effort 尾巴产生的意图 // 合并 effort 尾巴产生的意图
preserveSuffix := model_setting.ShouldPreserveThinkingSuffix(info.OriginModelName) || model_setting.ShouldPreserveThinkingSuffix(info.UpstreamModelName)
mergeEffortSuffix := func(modelName string) error { mergeEffortSuffix := func(modelName string) error {
rawEffort, _ := reasoning.ParseOpenAIReasoningEffortFromModelSuffix(modelName) rawEffort, _ := reasoning.ParseOpenAIReasoningEffortFromModelSuffix(modelName)
if rawEffort == "" { if rawEffort == "" {
...@@ -374,8 +385,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn ...@@ -374,8 +385,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
} }
} }
if info.ChannelType != constant.ChannelTypeOpenRouter { if info.ChannelType != constant.ChannelTypeOpenRouter && renderReasoning {
preserveSuffix := model_setting.ShouldPreserveThinkingSuffix(info.OriginModelName) || model_setting.ShouldPreserveThinkingSuffix(info.UpstreamModelName)
effort, baseModel := reasoning.ParseOpenAIReasoningEffortFromModelSuffix(info.UpstreamModelName) effort, baseModel := reasoning.ParseOpenAIReasoningEffortFromModelSuffix(info.UpstreamModelName)
if preserveSuffix { if preserveSuffix {
effort = "" effort = ""
...@@ -412,7 +422,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn ...@@ -412,7 +422,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
info.UpstreamModelName = baseModel info.UpstreamModelName = baseModel
request.Model = baseModel request.Model = baseModel
} }
if canonicalEffort := kitreasoning.OpenAIEffort(kitreasoning.EffectiveEffort(currentIntent)); canonicalEffort != "" { if canonicalEffort := kitreasoning.EffectiveEffort(currentIntent); canonicalEffort != "" {
request.ReasoningEffort = string(canonicalEffort) request.ReasoningEffort = string(canonicalEffort)
info.SetReasoningEffort(string(canonicalEffort)) info.SetReasoningEffort(string(canonicalEffort))
} }
...@@ -666,6 +676,21 @@ func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommo ...@@ -666,6 +676,21 @@ func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommo
if preserveSuffix { if preserveSuffix {
effort = "" effort = ""
} }
originEffort := ""
if info != nil && !preserveSuffix {
originEffort, _ = reasoning.ParseOpenAIReasoningEffortFromModelSuffix(info.OriginModelName)
}
crossProtocol := info != nil && len(info.RequestConversionChain) > 1
if (info == nil || info.ChannelType != constant.ChannelTypeOpenRouter) && !crossProtocol && effort == "" && originEffort == "" && request.ReasoningConversion == nil && info.ReasoningState() == nil {
if info != nil {
rawEffort := ""
if request.Reasoning != nil {
rawEffort = request.Reasoning.Effort
}
info.SetReasoningEffort(rawEffort)
}
return request, nil
}
currentIntent, err := kitreasoning.FromOpenAIResponses(&request) currentIntent, err := kitreasoning.FromOpenAIResponses(&request)
if err != nil { if err != nil {
return nil, kitreasoning.AsClientError(err) return nil, kitreasoning.AsClientError(err)
...@@ -700,7 +725,7 @@ func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommo ...@@ -700,7 +725,7 @@ func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommo
info.UpstreamModelName = originModel info.UpstreamModelName = originModel
} }
} }
if canonicalEffort := kitreasoning.OpenAIEffort(kitreasoning.EffectiveEffort(currentIntent)); canonicalEffort != "" { if canonicalEffort := kitreasoning.EffectiveEffort(currentIntent); canonicalEffort != "" {
if request.Reasoning == nil { if request.Reasoning == nil {
request.Reasoning = &dto.Reasoning{} request.Reasoning = &dto.Reasoning{}
} }
......
...@@ -308,6 +308,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn ...@@ -308,6 +308,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
} }
if !model_setting.ShouldPreserveThinkingSuffix(info.OriginModelName) && if !model_setting.ShouldPreserveThinkingSuffix(info.OriginModelName) &&
!model_setting.ShouldPreserveThinkingSuffix(info.UpstreamModelName) &&
strings.HasSuffix(info.UpstreamModelName, "-thinking") && strings.HasSuffix(info.UpstreamModelName, "-thinking") &&
strings.HasPrefix(info.UpstreamModelName, "deepseek") { strings.HasPrefix(info.UpstreamModelName, "deepseek") {
info.UpstreamModelName = strings.TrimSuffix(info.UpstreamModelName, "-thinking") info.UpstreamModelName = strings.TrimSuffix(info.UpstreamModelName, "-thinking")
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
relaycommon "github.com/QuantumNous/new-api/relay/common" relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/relaykit/dto" "github.com/QuantumNous/new-api/relaykit/dto"
"github.com/QuantumNous/new-api/relaykit/types" "github.com/QuantumNous/new-api/relaykit/types"
"github.com/QuantumNous/new-api/setting/model_setting"
"github.com/QuantumNous/new-api/relay/constant" "github.com/QuantumNous/new-api/relay/constant"
...@@ -78,10 +79,11 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn ...@@ -78,10 +79,11 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
request.MaxCompletionTokens = request.MaxTokens request.MaxCompletionTokens = request.MaxTokens
request.MaxTokens = nil request.MaxTokens = nil
} }
if strings.HasSuffix(request.Model, "-high") { preserveSuffix := model_setting.ShouldPreserveThinkingSuffix(info.OriginModelName) || model_setting.ShouldPreserveThinkingSuffix(request.Model)
if !preserveSuffix && strings.HasSuffix(request.Model, "-high") {
request.ReasoningEffort = "high" request.ReasoningEffort = "high"
request.Model = strings.TrimSuffix(request.Model, "-high") request.Model = strings.TrimSuffix(request.Model, "-high")
} else if strings.HasSuffix(request.Model, "-low") { } else if !preserveSuffix && strings.HasSuffix(request.Model, "-low") {
request.ReasoningEffort = "low" request.ReasoningEffort = "low"
request.Model = strings.TrimSuffix(request.Model, "-low") request.Model = strings.TrimSuffix(request.Model, "-low")
} }
......
...@@ -496,9 +496,10 @@ func reasoningEffortFromRequest(request dto.Request) string { ...@@ -496,9 +496,10 @@ func reasoningEffortFromRequest(request dto.Request) string {
} }
case *dto.GeminiChatRequest: case *dto.GeminiChatRequest:
if req != nil && req.GenerationConfig.ThinkingConfig != nil { if req != nil && req.GenerationConfig.ThinkingConfig != nil {
intent, err := kitreasoning.FromGemini(req) config := req.GenerationConfig.ThinkingConfig
if err == nil { effort = config.ThinkingLevel
effort = string(kitreasoning.EffectiveEffort(intent)) if effort == "" && config.ThinkingBudget != nil {
effort = string(kitreasoning.EffortFromBudget(*config.ThinkingBudget))
} }
} }
} }
......
...@@ -211,7 +211,7 @@ func applyModelControls(req dto.Request, parsed parsedModelModifiers) error { ...@@ -211,7 +211,7 @@ func applyModelControls(req dto.Request, parsed parsedModelModifiers) error {
delete(reasoningConfig, "max_tokens") delete(reasoningConfig, "max_tokens")
request.ReasoningEffort = "" request.ReasoningEffort = ""
if parsed.intent.Effort != "" { if parsed.intent.Effort != "" {
request.ReasoningEffort = string(reasoning.OpenAIEffort(parsed.intent.Effort)) request.ReasoningEffort = string(parsed.intent.Effort)
} }
} }
if len(reasoningConfig) == 0 { if len(reasoningConfig) == 0 {
...@@ -237,7 +237,7 @@ func applyModelControls(req dto.Request, parsed parsedModelModifiers) error { ...@@ -237,7 +237,7 @@ func applyModelControls(req dto.Request, parsed parsedModelModifiers) error {
if request.Reasoning == nil { if request.Reasoning == nil {
request.Reasoning = &dto.Reasoning{} request.Reasoning = &dto.Reasoning{}
} }
request.Reasoning.Effort = string(reasoning.OpenAIEffort(parsed.intent.Effort)) request.Reasoning.Effort = string(parsed.intent.Effort)
} else if request.Reasoning != nil && parsed.intent.BudgetTokens == nil { } else if request.Reasoning != nil && parsed.intent.BudgetTokens == nil {
request.Reasoning.Effort = "" request.Reasoning.Effort = ""
} }
......
...@@ -106,6 +106,9 @@ func ApplyReasoningModelSuffix(c *gin.Context, info *relaycommon.RelayInfo, outb ...@@ -106,6 +106,9 @@ func ApplyReasoningModelSuffix(c *gin.Context, info *relaycommon.RelayInfo, outb
if info.Request != nil { if info.Request != nil {
info.Request.SetModelName(info.UpstreamModelName) info.Request.SetModelName(info.UpstreamModelName)
} }
if selected.hasThinking {
info.SetReasoningEffort(string(reasoning.EffectiveEffort(selected.intent)))
}
for i := range diagnostics { for i := range diagnostics {
diagnostics[i].From = info.RelayFormat diagnostics[i].From = info.RelayFormat
} }
......
...@@ -35,14 +35,19 @@ func ApplyReasoning(ctx context.Context, req *dto.ClaudeRequest, info convmeta.M ...@@ -35,14 +35,19 @@ func ApplyReasoning(ctx context.Context, req *dto.ClaudeRequest, info convmeta.M
// accounting metadata, but do not run the capability renderer or rewrite // accounting metadata, but do not run the capability renderer or rewrite
// provider-native controls. // provider-native controls.
if !crossProtocol && source.IsEmpty() && suffix.IsEmpty() { if !crossProtocol && source.IsEmpty() && suffix.IsEmpty() {
native, err := reasoning.FromClaude(req)
if err != nil {
return err
}
if info != nil { if info != nil {
if effort := reasoning.EffectiveEffort(native); effort != "" { effort := req.GetEfforts()
info.SetReasoningEffort(string(effort)) if effort == "" && req.Thinking != nil {
} switch {
case req.Thinking.Type == "disabled":
effort = string(reasoning.EffortNone)
case req.Thinking.BudgetTokens != nil:
effort = string(reasoning.EffortFromBudget(*req.Thinking.BudgetTokens))
case req.Thinking.Type == "enabled" || req.Thinking.Type == "adaptive":
effort = string(reasoning.EffortHigh)
}
}
info.SetReasoningEffort(effort)
} }
return nil return nil
} }
......
...@@ -106,14 +106,15 @@ func ApplyThinkingConfig(geminiRequest *dto.GeminiChatRequest, info convmeta.Met ...@@ -106,14 +106,15 @@ func ApplyThinkingConfig(geminiRequest *dto.GeminiChatRequest, info convmeta.Met
// modifier, read portable effort metadata without running the capability // modifier, read portable effort metadata without running the capability
// renderer or rewriting provider-native controls. // renderer or rewriting provider-native controls.
if !crossProtocol && suffix.IsEmpty() { if !crossProtocol && suffix.IsEmpty() {
native, err := reasoning.FromGemini(geminiRequest)
if err != nil {
return err
}
if info != nil { if info != nil {
if effort := reasoning.EffectiveEffort(native); effort != "" { effort := ""
info.SetReasoningEffort(string(effort)) if config := geminiRequest.GenerationConfig.ThinkingConfig; config != nil {
effort = config.ThinkingLevel
if effort == "" && config.ThinkingBudget != nil {
effort = string(reasoning.EffortFromBudget(*config.ThinkingBudget))
}
} }
info.SetReasoningEffort(effort)
} }
return nil return nil
} }
......
...@@ -363,8 +363,8 @@ func FromOpenAIChat(req *dto.GeneralOpenAIRequest) (Intent, error) { ...@@ -363,8 +363,8 @@ func FromOpenAIChat(req *dto.GeneralOpenAIRequest) (Intent, error) {
BudgetSource: SourcePivot, BudgetSource: SourcePivot,
} }
if req.ReasoningEffort != "" { if req.ReasoningEffort != "" {
projectedEffort := OpenAIEffort(EffectiveEffort(pivot)) pivotEffort := EffectiveEffort(pivot)
if Effort(req.ReasoningEffort) == projectedEffort { if Effort(req.ReasoningEffort) == pivotEffort {
intent.Effort = "" intent.Effort = ""
intent.Mode = ModeUnset intent.Mode = ModeUnset
} }
...@@ -384,7 +384,7 @@ func ApplyToOpenAIChat(req *dto.GeneralOpenAIRequest, intent Intent) error { ...@@ -384,7 +384,7 @@ func ApplyToOpenAIChat(req *dto.GeneralOpenAIRequest, intent Intent) error {
return err return err
} }
if effort := OpenAIEffort(EffectiveEffort(intent)); effort != "" { if effort := EffectiveEffort(intent); effort != "" {
req.ReasoningEffort = string(effort) req.ReasoningEffort = string(effort)
} }
...@@ -412,7 +412,7 @@ func ApplyToOpenAIResponses(req *dto.OpenAIResponsesRequest, intent Intent) erro ...@@ -412,7 +412,7 @@ func ApplyToOpenAIResponses(req *dto.OpenAIResponsesRequest, intent Intent) erro
return err return err
} }
if effort := OpenAIEffort(EffectiveEffort(intent)); effort != "" { if effort := EffectiveEffort(intent); effort != "" {
summary := "detailed" summary := "detailed"
if effort == EffortNone || (intent.IncludeThoughts != nil && !*intent.IncludeThoughts) { if effort == EffortNone || (intent.IncludeThoughts != nil && !*intent.IncludeThoughts) {
summary = "" summary = ""
...@@ -436,16 +436,6 @@ func ApplyToOpenAIResponses(req *dto.OpenAIResponsesRequest, intent Intent) erro ...@@ -436,16 +436,6 @@ func ApplyToOpenAIResponses(req *dto.OpenAIResponsesRequest, intent Intent) erro
return nil return nil
} }
// OpenAIEffort maps the canonical cross-provider vocabulary to the public
// OpenAI reasoning_effort vocabulary. Claude/OpenRouter "max" has no direct
// OpenAI equivalent and is represented by xhigh at that wire boundary.
func OpenAIEffort(effort Effort) Effort {
if effort == EffortMax {
return EffortXHigh
}
return effort
}
func FromOpenAIResponses(req *dto.OpenAIResponsesRequest) (Intent, error) { func FromOpenAIResponses(req *dto.OpenAIResponsesRequest) (Intent, error) {
if req == nil { if req == nil {
return Intent{}, nil return Intent{}, nil
...@@ -481,8 +471,8 @@ func FromOpenAIResponses(req *dto.OpenAIResponsesRequest) (Intent, error) { ...@@ -481,8 +471,8 @@ func FromOpenAIResponses(req *dto.OpenAIResponsesRequest) (Intent, error) {
BudgetSource: SourcePivot, BudgetSource: SourcePivot,
} }
if req.Reasoning != nil && req.Reasoning.Effort != "" { if req.Reasoning != nil && req.Reasoning.Effort != "" {
projectedEffort := OpenAIEffort(EffectiveEffort(pivot)) pivotEffort := EffectiveEffort(pivot)
if Effort(req.Reasoning.Effort) == projectedEffort { if Effort(req.Reasoning.Effort) == pivotEffort {
intent.Effort = "" intent.Effort = ""
intent.Mode = ModeUnset intent.Mode = ModeUnset
} }
......
...@@ -3,6 +3,7 @@ package reasoning ...@@ -3,6 +3,7 @@ package reasoning
import ( import (
"testing" "testing"
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -123,3 +124,49 @@ func TestIntentStateRoundTrip(t *testing.T) { ...@@ -123,3 +124,49 @@ func TestIntentStateRoundTrip(t *testing.T) {
func boolPtr(v bool) *bool { func boolPtr(v bool) *bool {
return &v return &v
} }
func TestOpenAIPivotRetainsExactStrengthAndBudget(t *testing.T) {
budget, include := 16384, false
for _, effort := range []Effort{EffortMax, EffortXHigh} {
t.Run(string(effort), func(t *testing.T) {
intent := Intent{Mode: ModeEnabled, Effort: effort, BudgetTokens: &budget, IncludeThoughts: &include}
chat := &dto.GeneralOpenAIRequest{}
require.NoError(t, ApplyToOpenAIChat(chat, intent))
assert.Equal(t, string(effort), chat.ReasoningEffort)
restored, err := FromOpenAIChat(chat)
require.NoError(t, err)
assert.Equal(t, effort, restored.Effort)
require.NotNil(t, restored.BudgetTokens)
assert.Equal(t, budget, *restored.BudgetTokens)
require.NotNil(t, restored.IncludeThoughts)
assert.False(t, *restored.IncludeThoughts)
responses := &dto.OpenAIResponsesRequest{}
require.NoError(t, ApplyToOpenAIResponses(responses, restored))
require.NotNil(t, responses.Reasoning)
assert.Equal(t, string(effort), responses.Reasoning.Effort)
restored, err = FromOpenAIResponses(responses)
require.NoError(t, err)
assert.Equal(t, effort, restored.Effort)
require.NotNil(t, restored.BudgetTokens)
assert.Equal(t, budget, *restored.BudgetTokens)
require.NotNil(t, restored.IncludeThoughts)
assert.False(t, *restored.IncludeThoughts)
})
}
}
func TestOpenAIPivotDoesNotTreatMaxAndXHighAsEquivalent(t *testing.T) {
intent := Intent{Mode: ModeEnabled, Effort: EffortMax}
chat := &dto.GeneralOpenAIRequest{}
require.NoError(t, ApplyToOpenAIChat(chat, intent))
chat.ReasoningEffort = "xhigh"
_, err := FromOpenAIChat(chat)
require.ErrorIs(t, err, ErrEffortConflict)
responses := &dto.OpenAIResponsesRequest{}
require.NoError(t, ApplyToOpenAIResponses(responses, intent))
responses.Reasoning.Effort = "xhigh"
_, err = FromOpenAIResponses(responses)
require.ErrorIs(t, err, ErrEffortConflict)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment