mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
Merge pull request #7568 from SPHINX998/fix/apicompat-gpt6-reasoning-models
fix(apicompat): treat every GPT generation from 5 on as a reasoning model
This commit is contained in:
@@ -469,10 +469,38 @@ func boolPtr(v bool) *bool {
|
||||
|
||||
// isReasoningModel reports whether model is a reasoning model that does not
|
||||
// support sampling parameters (temperature, top_p) via the Responses API.
|
||||
// All gpt-5.x models are reasoning-only; the Responses API returns
|
||||
// "Unsupported parameter: temperature" if these fields are present.
|
||||
// GPT-5 and every later generation are reasoning-only; the Responses API
|
||||
// returns "Unsupported parameter: temperature" if these fields are present.
|
||||
//
|
||||
// Keyed on the generation number instead of a "gpt-5" prefix: pinning the
|
||||
// prefix meant each new family (gpt-6-astra and whatever follows) silently
|
||||
// fell through to the sampling branch and failed upstream on every compat
|
||||
// request until someone edited this line.
|
||||
func isReasoningModel(model string) bool {
|
||||
return strings.HasPrefix(model, "gpt-5") || openai.IsGPT6SolOrLunaModelSpelling(model)
|
||||
major, ok := openAIModelGeneration(model)
|
||||
return (ok && major >= 5) || openai.IsGPT6SolOrLunaModelSpelling(model)
|
||||
}
|
||||
|
||||
// openAIModelGeneration extracts N from a "gpt-N[.M][-suffix]" model id.
|
||||
// ok is false for non-GPT ids and for GPT families that carry no numeric
|
||||
// generation (gpt-image-1, gpt-audio, ...).
|
||||
func openAIModelGeneration(model string) (int, bool) {
|
||||
rest, ok := strings.CutPrefix(strings.ToLower(strings.TrimSpace(model)), "gpt-")
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
major, digits := 0, 0
|
||||
for _, r := range rest {
|
||||
if r < '0' || r > '9' {
|
||||
break
|
||||
}
|
||||
major = major*10 + int(r-'0')
|
||||
digits++
|
||||
}
|
||||
if digits == 0 {
|
||||
return 0, false
|
||||
}
|
||||
return major, true
|
||||
}
|
||||
|
||||
// normalizeToolParameters ensures the tool parameter schema is valid for
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package apicompat
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// GPT-5 and every later generation are reasoning-only: forwarding temperature or
|
||||
// top_p makes the Responses API reject the request with "Unsupported parameter".
|
||||
// The check is keyed on the generation number so a new family (gpt-6-astra and
|
||||
// whatever follows) does not silently fall into the sampling branch.
|
||||
func TestIsReasoningModelCoversLaterGenerations(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
model string
|
||||
want bool
|
||||
}{
|
||||
{"gpt-6-astra", true},
|
||||
{"gpt-6", true},
|
||||
{"gpt-7-whatever", true},
|
||||
{"gpt-6-sol", true},
|
||||
{"gpt-5.5", true},
|
||||
{"gpt-5.2", true},
|
||||
{"gpt-5", true},
|
||||
{"GPT-6-Astra", true},
|
||||
{" gpt-6-astra ", true},
|
||||
{"gpt-4o", false},
|
||||
{"gpt-4.1", false},
|
||||
{"gpt-image-1", false},
|
||||
{"gpt-audio", false},
|
||||
{"claude-opus-4-6", false},
|
||||
{"gemini-3.1-pro", false},
|
||||
{"", false},
|
||||
} {
|
||||
if got := isReasoningModel(tc.model); got != tc.want {
|
||||
t.Errorf("isReasoningModel(%q) = %v, want %v", tc.model, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenAIModelGeneration(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
model string
|
||||
wantMajor int
|
||||
wantOK bool
|
||||
}{
|
||||
{"gpt-6-astra", 6, true},
|
||||
{"gpt-5.5", 5, true},
|
||||
{"gpt-4o", 4, true},
|
||||
{"gpt-10-future", 10, true},
|
||||
{"gpt-image-1", 0, false},
|
||||
{"claude-opus-4-6", 0, false},
|
||||
{"", 0, false},
|
||||
} {
|
||||
major, ok := openAIModelGeneration(tc.model)
|
||||
if major != tc.wantMajor || ok != tc.wantOK {
|
||||
t.Errorf("openAIModelGeneration(%q) = (%d, %v), want (%d, %v)",
|
||||
tc.model, major, ok, tc.wantMajor, tc.wantOK)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnthropicToResponses_TemperatureStrippedForGPT6Astra(t *testing.T) {
|
||||
temp := 0.7
|
||||
req := &AnthropicRequest{
|
||||
Model: "gpt-6-astra",
|
||||
MaxTokens: 1024,
|
||||
Messages: []AnthropicMessage{{Role: "user", Content: json.RawMessage(`"Hello"`)}},
|
||||
Temperature: &temp,
|
||||
TopP: &temp,
|
||||
}
|
||||
|
||||
resp, err := AnthropicToResponses(req)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp.Temperature, "gpt-6-astra is reasoning-only: temperature must be stripped")
|
||||
require.Nil(t, resp.TopP, "gpt-6-astra is reasoning-only: top_p must be stripped")
|
||||
}
|
||||
Reference in New Issue
Block a user