feat: publish v1.0.0-rc.36-custom.1
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 9m20s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 55m7s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 9m20s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 55m7s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
This commit is contained in:
commit
bb7968b612
2381 files changed
+551494
No files matched your search
@@ -0,0 +1,366 @@
|
||||
package reasoning
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/QuantumNous/new-api/relaykit/dto"
|
||||
"github.com/QuantumNous/new-api/relaykit/types"
|
||||
)
|
||||
|
||||
type ClaudeRender struct {
|
||||
Thinking *dto.Thinking
|
||||
OutputEffort Effort
|
||||
EffectiveEffort Effort
|
||||
ClearSampling bool
|
||||
ConstrainThinkingSampling bool
|
||||
Diagnostics []types.ConversionDiagnostic
|
||||
}
|
||||
|
||||
type claudeCapabilities struct {
|
||||
adaptive bool
|
||||
supportsManual bool
|
||||
defaultThinking bool
|
||||
supportsDisable bool
|
||||
supportsEffort bool
|
||||
supportsXHigh bool
|
||||
supportsMax bool
|
||||
strictSampling bool
|
||||
}
|
||||
|
||||
func claudeCapabilitiesFor(model string) claudeCapabilities {
|
||||
model = strings.ToLower(model)
|
||||
capabilities := claudeCapabilities{supportsManual: true, supportsDisable: true}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(model, "claude-fable-5"),
|
||||
strings.HasPrefix(model, "claude-mythos-5"):
|
||||
capabilities.adaptive = true
|
||||
capabilities.supportsManual = false
|
||||
capabilities.defaultThinking = true
|
||||
capabilities.supportsDisable = false
|
||||
capabilities.supportsXHigh = true
|
||||
capabilities.supportsMax = true
|
||||
capabilities.strictSampling = true
|
||||
case strings.HasPrefix(model, "claude-mythos-preview"):
|
||||
capabilities.adaptive = true
|
||||
capabilities.defaultThinking = true
|
||||
capabilities.supportsDisable = false
|
||||
capabilities.supportsMax = true
|
||||
capabilities.strictSampling = true
|
||||
case strings.HasPrefix(model, "claude-opus-5"),
|
||||
strings.HasPrefix(model, "claude-sonnet-5"),
|
||||
strings.HasPrefix(model, "claude-opus-4-8"),
|
||||
strings.HasPrefix(model, "claude-opus-4-7"):
|
||||
capabilities.adaptive = true
|
||||
capabilities.supportsManual = false
|
||||
if strings.HasPrefix(model, "claude-opus-5") || strings.HasPrefix(model, "claude-sonnet-5") {
|
||||
capabilities.defaultThinking = true
|
||||
}
|
||||
capabilities.supportsEffort = true
|
||||
capabilities.supportsXHigh = true
|
||||
capabilities.supportsMax = true
|
||||
capabilities.strictSampling = true
|
||||
case strings.HasPrefix(model, "claude-opus-4-6"),
|
||||
strings.HasPrefix(model, "claude-sonnet-4-6"):
|
||||
capabilities.adaptive = true
|
||||
capabilities.supportsEffort = true
|
||||
capabilities.supportsMax = true
|
||||
case strings.HasPrefix(model, "claude-opus-4-5"):
|
||||
capabilities.supportsEffort = true
|
||||
}
|
||||
|
||||
return capabilities
|
||||
}
|
||||
|
||||
func RenderClaude(model string, intent Intent, maxTokens *uint, adapterBudgetPercentage float64) (ClaudeRender, error) {
|
||||
disabledWithEffort := intent.Mode == ModeDisabled && intent.Effort != "" && intent.Effort != EffortNone
|
||||
if disabledWithEffort {
|
||||
effort, err := ParseEffort(string(intent.Effort))
|
||||
if err != nil {
|
||||
return ClaudeRender{}, err
|
||||
}
|
||||
intent.Effort = effort
|
||||
} else {
|
||||
var err error
|
||||
intent, err = normalizeIntent(intent)
|
||||
if err != nil {
|
||||
return ClaudeRender{}, err
|
||||
}
|
||||
}
|
||||
capabilities := claudeCapabilitiesFor(model)
|
||||
diagnostics := make([]types.ConversionDiagnostic, 0, 1)
|
||||
if disabledWithEffort {
|
||||
diagnostics = append(diagnostics, claudeReasoningDiagnostic(
|
||||
"claude_disabled_effort_ignored",
|
||||
fmt.Sprintf("model %q cannot apply effort %q while thinking is disabled; the effort was ignored", model, intent.Effort),
|
||||
))
|
||||
}
|
||||
if !intent.HasStrength() {
|
||||
if intent.IncludeThoughts != nil && capabilities.adaptive && capabilities.defaultThinking {
|
||||
thinking := &dto.Thinking{Type: "adaptive"}
|
||||
if *intent.IncludeThoughts {
|
||||
thinking.Display = "summarized"
|
||||
} else {
|
||||
thinking.Display = "omitted"
|
||||
}
|
||||
return ClaudeRender{
|
||||
Thinking: thinking,
|
||||
EffectiveEffort: EffortHigh,
|
||||
ClearSampling: capabilities.strictSampling,
|
||||
}, nil
|
||||
}
|
||||
if capabilities.defaultThinking {
|
||||
return ClaudeRender{EffectiveEffort: EffortHigh, ClearSampling: capabilities.strictSampling}, nil
|
||||
}
|
||||
return ClaudeRender{ClearSampling: capabilities.strictSampling}, nil
|
||||
}
|
||||
|
||||
if intent.Mode == ModeDisabled || intent.Effort == EffortNone {
|
||||
if !capabilities.supportsDisable {
|
||||
diagnostics = append(diagnostics, claudeReasoningDiagnostic(
|
||||
"claude_thinking_disable_unsupported",
|
||||
fmt.Sprintf("model %q cannot disable thinking; using the lowest representable thinking mode", model),
|
||||
))
|
||||
if capabilities.adaptive {
|
||||
thinking := &dto.Thinking{Type: "adaptive"}
|
||||
if intent.IncludeThoughts != nil {
|
||||
if *intent.IncludeThoughts {
|
||||
thinking.Display = "summarized"
|
||||
} else {
|
||||
thinking.Display = "omitted"
|
||||
}
|
||||
}
|
||||
outputEffort := Effort("")
|
||||
effectiveEffort := EffortHigh
|
||||
if capabilities.supportsEffort {
|
||||
outputEffort = EffortLow
|
||||
effectiveEffort = EffortLow
|
||||
}
|
||||
return ClaudeRender{
|
||||
Thinking: thinking,
|
||||
OutputEffort: outputEffort,
|
||||
EffectiveEffort: effectiveEffort,
|
||||
ClearSampling: capabilities.strictSampling,
|
||||
Diagnostics: diagnostics,
|
||||
}, nil
|
||||
}
|
||||
return ClaudeRender{Diagnostics: diagnostics}, nil
|
||||
}
|
||||
return ClaudeRender{
|
||||
Thinking: &dto.Thinking{Type: "disabled"},
|
||||
EffectiveEffort: EffortNone,
|
||||
ClearSampling: capabilities.strictSampling,
|
||||
Diagnostics: diagnostics,
|
||||
}, nil
|
||||
}
|
||||
|
||||
preferManual := capabilities.supportsManual && intent.BudgetTokens != nil && intent.Mode != ModeAdaptive
|
||||
if !capabilities.supportsManual && intent.BudgetTokens != nil && intent.Mode == ModeEnabled {
|
||||
diagnostics = append(diagnostics, claudeReasoningDiagnostic(
|
||||
"claude_budget_to_adaptive",
|
||||
fmt.Sprintf("model %q uses adaptive thinking; budget_tokens was converted to an effort level", model),
|
||||
))
|
||||
}
|
||||
if capabilities.adaptive && !preferManual {
|
||||
effort := intent.Effort
|
||||
if effort == "" && intent.BudgetTokens != nil {
|
||||
effort = EffortFromBudget(*intent.BudgetTokens)
|
||||
}
|
||||
if effort == "" && intent.Mode == ModeEnabled {
|
||||
effort = EffortHigh
|
||||
}
|
||||
normalizedEffort := normalizeClaudeEffort(effort, capabilities)
|
||||
if effort != "" && normalizedEffort != effort {
|
||||
diagnostics = append(diagnostics, claudeReasoningDiagnostic(
|
||||
"claude_effort_adjusted",
|
||||
fmt.Sprintf("model %q does not support effort %q; using %q", model, effort, normalizedEffort),
|
||||
))
|
||||
}
|
||||
effort = normalizedEffort
|
||||
effectiveEffort := effort
|
||||
if effectiveEffort == "" && intent.Mode == ModeAdaptive {
|
||||
effectiveEffort = EffortHigh
|
||||
}
|
||||
|
||||
// Claude effort can be used without enabling thinking. Preserve that
|
||||
// distinction for native Claude requests; OpenAI extractors explicitly
|
||||
// mark reasoning efforts as ModeEnabled.
|
||||
if intent.Mode == ModeUnset {
|
||||
return ClaudeRender{
|
||||
OutputEffort: effort,
|
||||
EffectiveEffort: effectiveEffort,
|
||||
ClearSampling: capabilities.strictSampling,
|
||||
Diagnostics: diagnostics,
|
||||
}, nil
|
||||
}
|
||||
|
||||
thinking := &dto.Thinking{Type: "adaptive"}
|
||||
if intent.IncludeThoughts != nil {
|
||||
if *intent.IncludeThoughts {
|
||||
thinking.Display = "summarized"
|
||||
} else {
|
||||
thinking.Display = "omitted"
|
||||
}
|
||||
}
|
||||
return ClaudeRender{
|
||||
Thinking: thinking,
|
||||
OutputEffort: effort,
|
||||
EffectiveEffort: effectiveEffort,
|
||||
ClearSampling: capabilities.strictSampling,
|
||||
ConstrainThinkingSampling: !capabilities.strictSampling,
|
||||
Diagnostics: diagnostics,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if intent.Mode == ModeAdaptive {
|
||||
diagnostics = append(diagnostics, claudeReasoningDiagnostic(
|
||||
"claude_adaptive_to_manual",
|
||||
fmt.Sprintf("model %q does not support adaptive thinking; using manual thinking", model),
|
||||
))
|
||||
intent.Mode = ModeEnabled
|
||||
if intent.Effort == "" {
|
||||
intent.Effort = EffortHigh
|
||||
}
|
||||
}
|
||||
if intent.Mode == ModeUnset {
|
||||
return ClaudeRender{OutputEffort: intent.Effort, EffectiveEffort: intent.Effort}, nil
|
||||
}
|
||||
if maxTokens == nil {
|
||||
return ClaudeRender{}, fmt.Errorf("max_tokens is required for manual Claude thinking")
|
||||
}
|
||||
if *maxTokens <= 1024 {
|
||||
return ClaudeRender{}, fmt.Errorf("max_tokens must be greater than 1024 for manual Claude thinking")
|
||||
}
|
||||
if uint64(*maxTokens) > uint64(math.MaxInt) {
|
||||
return ClaudeRender{}, fmt.Errorf("max_tokens is too large for a thinking budget")
|
||||
}
|
||||
|
||||
budget := 0
|
||||
if intent.BudgetTokens != nil && *intent.BudgetTokens >= 0 {
|
||||
requestedBudget := *intent.BudgetTokens
|
||||
budget = max(requestedBudget, 1024)
|
||||
if uint(budget) >= *maxTokens {
|
||||
budget = int(*maxTokens) - 1
|
||||
}
|
||||
if budget != requestedBudget {
|
||||
diagnostics = append(diagnostics, claudeReasoningDiagnostic(
|
||||
"claude_budget_adjusted",
|
||||
fmt.Sprintf("model %q requires 1024 <= budget_tokens < max_tokens; adjusted %d to %d", model, requestedBudget, budget),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
if intent.BudgetTokens != nil {
|
||||
diagnostics = append(diagnostics, claudeReasoningDiagnostic(
|
||||
"claude_dynamic_budget_converted",
|
||||
fmt.Sprintf("model %q does not support a dynamic budget; derived a manual budget from reasoning effort", model),
|
||||
))
|
||||
}
|
||||
percentage := effortPercentage(intent.Effort, adapterBudgetPercentage)
|
||||
budget = max(int(*maxTokens)*percentage/100, 1024)
|
||||
if uint(budget) >= *maxTokens {
|
||||
budget = int(*maxTokens) - 1
|
||||
}
|
||||
}
|
||||
|
||||
effectiveEffort := intent.Effort
|
||||
if intent.BudgetTokens != nil && !capabilities.supportsEffort {
|
||||
effectiveEffort = EffortFromBudget(budget)
|
||||
} else if effectiveEffort == "" {
|
||||
effectiveEffort = EffortFromBudget(budget)
|
||||
}
|
||||
outputEffort := Effort("")
|
||||
if capabilities.supportsEffort && intent.Effort != "" {
|
||||
outputEffort = normalizeClaudeEffort(intent.Effort, capabilities)
|
||||
effectiveEffort = outputEffort
|
||||
}
|
||||
thinking := &dto.Thinking{Type: "enabled", BudgetTokens: &budget}
|
||||
if intent.IncludeThoughts != nil {
|
||||
if *intent.IncludeThoughts {
|
||||
thinking.Display = "summarized"
|
||||
} else {
|
||||
thinking.Display = "omitted"
|
||||
}
|
||||
}
|
||||
return ClaudeRender{
|
||||
Thinking: thinking,
|
||||
OutputEffort: outputEffort,
|
||||
EffectiveEffort: effectiveEffort,
|
||||
ConstrainThinkingSampling: true,
|
||||
Diagnostics: diagnostics,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func claudeReasoningDiagnostic(code string, message string) types.ConversionDiagnostic {
|
||||
return types.ConversionDiagnostic{
|
||||
Code: code,
|
||||
Path: "thinking",
|
||||
Message: message,
|
||||
Severity: types.ConversionDiagnosticWarning,
|
||||
To: types.RelayFormatClaude,
|
||||
}
|
||||
}
|
||||
|
||||
// ClaudeUsesManualThinking reports whether an exact numeric budget is rendered
|
||||
// as legacy extended thinking rather than being reduced to adaptive effort.
|
||||
func ClaudeUsesManualThinking(model string, intent Intent) bool {
|
||||
capabilities := claudeCapabilitiesFor(model)
|
||||
return capabilities.supportsManual && intent.BudgetTokens != nil && intent.Mode != ModeAdaptive
|
||||
}
|
||||
|
||||
func IsKnownClaudeModel(model string) bool {
|
||||
return isKnownClaudeModel(model)
|
||||
}
|
||||
|
||||
func ResolveClaudeDefault(model string, intent Intent) Intent {
|
||||
if intent.HasStrength() || !claudeCapabilitiesFor(model).defaultThinking {
|
||||
return intent
|
||||
}
|
||||
intent.Mode = ModeAdaptive
|
||||
intent.Effort = EffortHigh
|
||||
return intent
|
||||
}
|
||||
|
||||
func normalizeClaudeEffort(effort Effort, capabilities claudeCapabilities) Effort {
|
||||
switch effort {
|
||||
case EffortMinimal:
|
||||
return EffortLow
|
||||
case EffortXHigh:
|
||||
if capabilities.supportsXHigh {
|
||||
return effort
|
||||
}
|
||||
if capabilities.supportsMax {
|
||||
return EffortMax
|
||||
}
|
||||
return EffortHigh
|
||||
case EffortMax:
|
||||
if !capabilities.supportsMax {
|
||||
return EffortHigh
|
||||
}
|
||||
}
|
||||
return effort
|
||||
}
|
||||
|
||||
func effortPercentage(effort Effort, adapterBudgetPercentage float64) int {
|
||||
switch effort {
|
||||
case EffortMinimal:
|
||||
return 5
|
||||
case EffortLow:
|
||||
return 20
|
||||
case EffortMedium:
|
||||
return 50
|
||||
case EffortHigh:
|
||||
return 80
|
||||
case EffortXHigh, EffortMax:
|
||||
return 95
|
||||
}
|
||||
percentage := int(math.Round(adapterBudgetPercentage * 100))
|
||||
if percentage <= 0 {
|
||||
return 80
|
||||
}
|
||||
if percentage >= 100 {
|
||||
return 99
|
||||
}
|
||||
return percentage
|
||||
}
|
||||
Reference in new issue
Block a user