mirror of
https://gh-proxy.org/https://github.com/klinsmaya/new-api.git
synced 2026-11-10 02:17:24 +08:00
Model-name post-processing is rebuilt around an explicit trailing
@key:value modifier syntax (thinking/effort/temperature/topp) that
overrides request fields, survives model mapping, and records
conversion diagnostics on the consume log.
- Legacy naked aliases (-thinking, -nothinking, -thinking-<budget>,
effort tails) now parse only for positively matched families
(gpt-*/o-series, claude-*, gemini-*, incl. vendor/ namespaces);
names like qwen-max stay opaque. EffortTailModelIDs remains the
escape hatch for real in-family IDs such as gpt-5.1-codex-max.
- Billing identity resolves once in ModelPriceHelper via a ladder:
configured request name first (legacy wildcard entries intact), then
canonical billing names rebuilt from parsed intent
(base@effort:E@thinking:S, then base@thinking:S; order, duplicates,
and budget values are irrelevant; temperature/topp never priced),
then base. Routing and token limits fall back through
RoutingMatchModelName; pricing lookups stay wildcard-only.
- Pass-through stays byte-identical: modifiers and aliases are neither
parsed nor validated there and forward verbatim for the upstream
(or a chained gateway) to interpret.
- Unknown modifier keys and invalid known-key values are rejected with
400; models whose real names contain @tag:value are exempted via the
thinking-suffix blacklist, which now supports re:-prefixed Go regex
entries.
- Claude reasoning render coerces unsupported combinations (disable,
adaptive, budgets) with warning diagnostics instead of erroring;
native-protocol requests without host syntax pass through untouched.
BREAKING(openrouter): drop the host-invented "-thinking" model-name
alias (added in 4f6d16e36) that trimmed any *-thinking model on
OpenRouter channels and injected reasoning.enabled. It matched too
broadly and mangled real model IDs such as kimi-k2-thinking.
Migration: use some-model@thinking:on, or keep the old public name via
a channel model mapping {"some-model-thinking": "some-model@thinking:on"}.
Claude/Gemini family aliases (incl. anthropic/claude-*-thinking) keep
working via the family whitelist.
135 lines
4.0 KiB
Go
135 lines
4.0 KiB
Go
package reasoning
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/QuantumNous/new-api/setting/model_setting"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCanonicalBillingModelNames(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "thinking on",
|
|
in: "qwen3-max@thinking:on",
|
|
want: []string{"qwen3-max@thinking:on"},
|
|
},
|
|
{
|
|
name: "shuffled temperature and thinking",
|
|
in: "qwen3-max@temperature:0.2@thinking:on",
|
|
want: []string{"qwen3-max@thinking:on"},
|
|
},
|
|
{
|
|
name: "thinking first then temperature",
|
|
in: "qwen3-max@thinking:on@temperature:0.2",
|
|
want: []string{"qwen3-max@thinking:on"},
|
|
},
|
|
{
|
|
name: "budget normalizes to on",
|
|
in: "qwen3-max@thinking:8192",
|
|
want: []string{"qwen3-max@thinking:on"},
|
|
},
|
|
{
|
|
name: "minus one normalizes to on",
|
|
in: "qwen3-max@thinking:-1",
|
|
want: []string{"qwen3-max@thinking:on"},
|
|
},
|
|
{
|
|
name: "adaptive normalizes to on",
|
|
in: "qwen3-max@thinking:adaptive",
|
|
want: []string{"qwen3-max@thinking:on"},
|
|
},
|
|
{
|
|
name: "thinking off",
|
|
in: "qwen3-max@thinking:off",
|
|
want: []string{"qwen3-max@thinking:off"},
|
|
},
|
|
{
|
|
name: "effort none becomes thinking off",
|
|
in: "qwen3-max@effort:none",
|
|
want: []string{"qwen3-max@thinking:off"},
|
|
},
|
|
{
|
|
name: "effort high implies thinking on",
|
|
in: "qwen3-max@effort:high",
|
|
want: []string{"qwen3-max@effort:high@thinking:on", "qwen3-max@thinking:on"},
|
|
},
|
|
{
|
|
name: "effort and thinking keys sorted",
|
|
in: "qwen3-max@thinking:on@effort:high@temperature:0.2",
|
|
want: []string{"qwen3-max@effort:high@thinking:on", "qwen3-max@thinking:on"},
|
|
},
|
|
{
|
|
name: "duplicate last wins then normalize",
|
|
in: "qwen3-max@thinking:off@thinking:on@effort:low@effort:high",
|
|
want: []string{"qwen3-max@effort:high@thinking:on", "qwen3-max@thinking:on"},
|
|
},
|
|
{
|
|
name: "legacy thinking alias",
|
|
in: "claude-3-7-sonnet-thinking",
|
|
want: []string{"claude-3-7-sonnet@thinking:on"},
|
|
},
|
|
{
|
|
name: "legacy thinking budget matches explicit budget",
|
|
in: "gemini-2.5-flash-thinking-8192",
|
|
want: []string{"gemini-2.5-flash@thinking:on"},
|
|
},
|
|
{
|
|
name: "legacy nothinking",
|
|
in: "claude-3-7-sonnet-nothinking",
|
|
want: []string{"claude-3-7-sonnet@thinking:off"},
|
|
},
|
|
{
|
|
name: "temperature only has no reasoning state",
|
|
in: "qwen3-max@temperature:0.7",
|
|
want: nil,
|
|
},
|
|
}
|
|
|
|
geminiSettings := model_setting.GetGeminiSettings()
|
|
oldGemini := geminiSettings.ThinkingAdapterEnabled
|
|
geminiSettings.ThinkingAdapterEnabled = true
|
|
t.Cleanup(func() { geminiSettings.ThinkingAdapterEnabled = oldGemini })
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.want, CanonicalBillingModelNames(tt.in))
|
|
})
|
|
}
|
|
|
|
assert.Equal(t,
|
|
CanonicalBillingModelNames("gemini-2.5-flash@thinking:8192"),
|
|
CanonicalBillingModelNames("gemini-2.5-flash-thinking-8192"),
|
|
)
|
|
assert.Equal(t, "gpt-5.1-codex-max", BaseModelName("gpt-5.1-codex-max"))
|
|
assert.Empty(t, CanonicalBillingModelNames("gpt-5.1-codex-max"))
|
|
}
|
|
|
|
func TestParseOpenAIReasoningEffortPreservesCodexMax(t *testing.T) {
|
|
effort, base := ParseOpenAIReasoningEffortFromModelSuffix("gpt-5.1-codex-max")
|
|
assert.Empty(t, effort)
|
|
assert.Equal(t, "gpt-5.1-codex-max", base)
|
|
}
|
|
|
|
func TestBaseModelNameStripsModifiers(t *testing.T) {
|
|
require.Equal(t, "qwen3-max", BaseModelName("qwen3-max@thinking:on@temperature:0.2"))
|
|
}
|
|
|
|
func TestExemptAtNameIsOpaqueForBillingIdentity(t *testing.T) {
|
|
settings := model_setting.GetGlobalSettings()
|
|
original := append([]string(nil), settings.ThinkingModelBlacklist...)
|
|
t.Cleanup(func() { settings.ThinkingModelBlacklist = original })
|
|
settings.ThinkingModelBlacklist = append(original, "re:.*@sha256:.*")
|
|
|
|
const model = "opaque@sha256:deadbeef"
|
|
assert.Equal(t, model, BaseModelName(model))
|
|
assert.Empty(t, CanonicalBillingModelNames(model))
|
|
assert.Equal(t, "kimi-k2-thinking", BaseModelName("kimi-k2-thinking"))
|
|
assert.Empty(t, CanonicalBillingModelNames("kimi-k2-thinking"))
|
|
}
|