Files

234 lines
7.5 KiB
Go

// Package reasoning re-exports the pure model-name effort-suffix helpers,
// which moved to the conversion kit (relaykit/relayconvert/reasoning) as part
// of the relaykit extraction. Host code keeps importing this path unchanged.
package reasoning
import (
"strings"
kitreasoning "github.com/QuantumNous/new-api/relaykit/relayconvert/reasoning"
"github.com/QuantumNous/new-api/setting/model_setting"
)
var (
EffortSuffixes = kitreasoning.EffortSuffixes
OpenAIEffortSuffixes = kitreasoning.OpenAIEffortSuffixes
DeepSeekV4EffortSuffixes = kitreasoning.DeepSeekV4EffortSuffixes
)
var (
TrimEffortSuffixWithSuffixes = kitreasoning.TrimEffortSuffixWithSuffixes
ParseDeepSeekV4ThinkingSuffix = kitreasoning.ParseDeepSeekV4ThinkingSuffix
TrimGeminiThinkingSuffix = kitreasoning.TrimGeminiThinkingSuffix
)
// ParseOpenAIReasoningEffortFromModelSuffix applies RelayKit's positive family
// whitelist and the host EffortTailModelIDs escape hatch so real model IDs
// such as gpt-5.1-codex-max remain opaque.
func ParseOpenAIReasoningEffortFromModelSuffix(modelName string) (string, string) {
return kitreasoning.ParseOpenAIReasoningEffortFromModelSuffix(modelName, model_setting.ShouldPreserveEffortTail)
}
// ParseLegacyModelSuffix parses the old naked aliases only for positively
// matched GPT/o-series, Claude, and Gemini model families. The provider prefix
// before the final path segment is kept opaque.
func ParseLegacyModelSuffix(modelName string, allowClaudeThinkingAlias bool, allowGeminiThinkingAlias bool) (string, kitreasoning.Intent, bool, error) {
prefix, bare := splitModelNamespace(modelName)
var (
base string
intent kitreasoning.Intent
found bool
err error
)
switch {
case strings.HasPrefix(bare, "claude-"):
base, intent, found, err = kitreasoning.ParseClaudeModelSuffix(bare, allowClaudeThinkingAlias)
case strings.HasPrefix(bare, "gemini-"):
base, intent, found, err = kitreasoning.ParseGeminiModelSuffix(bare, allowGeminiThinkingAlias)
default:
effort, openAIBase := ParseOpenAIReasoningEffortFromModelSuffix(bare)
if effort == "" {
return modelName, kitreasoning.Intent{}, false, nil
}
parsedEffort, parseErr := kitreasoning.ParseEffort(effort)
if parseErr != nil {
return modelName, kitreasoning.Intent{}, false, parseErr
}
mode := kitreasoning.ModeEnabled
if parsedEffort == kitreasoning.EffortNone {
mode = kitreasoning.ModeDisabled
}
base = openAIBase
intent = kitreasoning.Intent{Mode: mode, Effort: parsedEffort, Source: kitreasoning.SourceSuffix}
found = true
}
if err != nil || !found {
return modelName, kitreasoning.Intent{}, false, err
}
return prefix + base, intent, true, nil
}
// BaseModelName strips explicit model modifiers and any enabled legacy alias.
// Names on the thinking-suffix blacklist stay verbatim, including @ tails.
// Malformed legacy aliases stay intact so request conversion can report the
// precise validation error later.
func BaseModelName(modelName string) string {
if model_setting.ShouldPreserveThinkingSuffix(modelName) {
return modelName
}
base := kitreasoning.ParseModelModifiers(modelName).Base
if model_setting.ShouldPreserveThinkingSuffix(base) {
return base
}
legacyBase, _, found, err := ParseLegacyModelSuffix(
base,
model_setting.GetClaudeSettings().ThinkingAdapterEnabled,
model_setting.GetGeminiSettings().ThinkingAdapterEnabled,
)
if err != nil {
return base
}
if found {
return legacyBase
}
return base
}
func splitModelNamespace(modelName string) (string, string) {
if slash := strings.LastIndex(modelName, "/"); slash >= 0 {
return modelName[:slash+1], modelName[slash+1:]
}
return "", modelName
}
// CanonicalBillingModelNames returns specificity-descending canonical billing
// name candidates (without the raw request name or the bare base). Explicit
// @ modifiers and legacy aliases normalize through the same Intent, so order,
// duplicates, and case do not matter. Temperature and topp never appear.
func CanonicalBillingModelNames(modelName string) []string {
if model_setting.ShouldPreserveThinkingSuffix(modelName) {
return nil
}
spec := kitreasoning.ParseModelModifiers(modelName)
base := spec.Base
intent, hasThinking := billingIntentFromModifiers(spec)
if !model_setting.ShouldPreserveThinkingSuffix(base) {
legacyBase, legacyIntent, found, err := ParseLegacyModelSuffix(
base,
model_setting.GetClaudeSettings().ThinkingAdapterEnabled,
model_setting.GetGeminiSettings().ThinkingAdapterEnabled,
)
if err == nil && found {
base = legacyBase
if !hasThinking {
intent = legacyIntent
hasThinking = true
}
}
}
if !hasThinking {
return nil
}
return canonicalNamesFromIntent(base, intent)
}
func billingIntentFromModifiers(spec kitreasoning.ModelModifierSpec) (kitreasoning.Intent, bool) {
last := make(map[string]int, len(spec.Modifiers))
for index, modifier := range spec.Modifiers {
last[modifier.Key] = index
}
var (
intent kitreasoning.Intent
hasThinking bool
)
for index, modifier := range spec.Modifiers {
if last[modifier.Key] != index {
continue
}
switch modifier.Key {
case "thinking":
parsed, ok := kitreasoning.ParseThinkingModifier(modifier.Value)
if !ok {
continue
}
if hasThinking && parsed.Mode != kitreasoning.ModeDisabled && parsed.BudgetTokens == nil {
intent.Mode = parsed.Mode
intent.Source = kitreasoning.SourceSuffix
} else if hasThinking && parsed.Mode != kitreasoning.ModeDisabled {
intent.Mode = parsed.Mode
intent.BudgetTokens = parsed.BudgetTokens
intent.BudgetSource = parsed.BudgetSource
intent.Source = kitreasoning.SourceSuffix
} else {
intent = parsed
}
hasThinking = true
case "effort":
effort, err := kitreasoning.ParseEffort(modifier.Value)
if err != nil || effort == "" {
continue
}
if effort == kitreasoning.EffortNone {
intent = kitreasoning.Intent{Mode: kitreasoning.ModeDisabled, Effort: kitreasoning.EffortNone, Source: kitreasoning.SourceSuffix}
} else {
if intent.Mode == kitreasoning.ModeUnset || intent.Mode == kitreasoning.ModeDisabled {
intent.Mode = kitreasoning.ModeEnabled
}
intent.Effort = effort
intent.Source = kitreasoning.SourceSuffix
}
hasThinking = true
}
}
return intent, hasThinking
}
func canonicalNamesFromIntent(base string, intent kitreasoning.Intent) []string {
thinking, effort, ok := normalizeBillingThinking(intent)
if !ok || base == "" {
return nil
}
var names []string
if effort != "" {
names = append(names, base+"@effort:"+effort+"@thinking:"+thinking)
}
thinkingForm := base + "@thinking:" + thinking
if thinkingForm != base {
names = append(names, thinkingForm)
}
seen := make(map[string]struct{}, len(names))
out := make([]string, 0, len(names))
for _, name := range names {
if name == "" || name == base {
continue
}
if _, exists := seen[name]; exists {
continue
}
seen[name] = struct{}{}
out = append(out, name)
}
return out
}
func normalizeBillingThinking(intent kitreasoning.Intent) (thinking string, effort string, ok bool) {
if intent.Effort == kitreasoning.EffortNone || intent.Mode == kitreasoning.ModeDisabled {
return "off", "", true
}
if intent.BudgetTokens != nil && *intent.BudgetTokens == 0 {
return "off", "", true
}
if intent.Effort != "" && intent.Effort != kitreasoning.EffortNone {
return "on", strings.ToLower(string(intent.Effort)), true
}
if intent.Mode == kitreasoning.ModeEnabled || intent.Mode == kitreasoning.ModeAdaptive || intent.BudgetTokens != nil {
return "on", "", true
}
return "", "", false
}