mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-19 16:17:56 +00:00
fix(gateway): /v1/responses 转 Anthropic 时统一以去空白的请求模型兜底映射
forwardResponsesViaNativeAnthropic 的 defaultMappedModel 是账号 model_mapping 未命中时的兜底模型。此前按模型分流的账号(OpenCode)传空串、其余账号传 reqModel,源自两处分支各自实现。映射未命中且请求模型带首尾空白时,前者的 计费模型名保留空白(上游模型名因 normalizeOpenAIModelForUpstream 已去空白, 两者不一致,定价查找可能落空),后者为去空白的名字。 统一传 reqModel,并移除入口里最后一处 routesByModel 分支。仅影响按模型 分流账号收到带首尾空白模型名的请求:计费模型名改为去空白后的名字;发往 上游的请求不变。新增测试覆盖 OpenCode 与 Kimi 两类账号。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8ac2936a3d
commit
105c955c88
@@ -187,12 +187,8 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
|
||||
case APIProtocolAnthropic:
|
||||
// Responses 客户端 × Anthropic 上游:转成 Anthropic 请求走原生端点。不能落到
|
||||
// raw-CC 分支——其 URL 构造会把 anthropic base 当 CC base 用。
|
||||
// 按模型分流的账号不以请求模型兜底模型映射。
|
||||
defaultMappedModel := reqModel
|
||||
if account.routesByModel() {
|
||||
defaultMappedModel = ""
|
||||
}
|
||||
return s.forwardResponsesViaNativeAnthropic(ctx, c, account, body, defaultMappedModel)
|
||||
// 账号映射未命中时以去除首尾空白的请求模型兜底,计费名与上游模型名一致。
|
||||
return s.forwardResponsesViaNativeAnthropic(ctx, c, account, body, reqModel)
|
||||
case APIProtocolChatCompletions:
|
||||
return s.forwardResponsesViaRawChatCompletions(ctx, c, account, body)
|
||||
}
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/openai_compat"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func routingTestAccount(platform, accountType string, credentials, extra map[string]any) *Account {
|
||||
@@ -117,3 +121,44 @@ func TestUpstreamRoutingModelOnlyForModelRoutedAccounts(t *testing.T) {
|
||||
require.Equal(t, "glm-5.3", upstreamRoutingModel(openCode, body, ""))
|
||||
require.Empty(t, upstreamRoutingModel(routingTestAccount(PlatformKimi, AccountTypeAPIKey, nil, nil), body, ""))
|
||||
}
|
||||
|
||||
// /v1/responses 转 Anthropic 上游时,计费模型名与上游模型名都应是去除首尾空白的
|
||||
// 请求模型;按模型分流(OpenCode)与显式 anthropic 协议(Kimi)账号行为一致。
|
||||
func TestResponsesToNativeAnthropicTrimsBillingModel(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
openCode := &Account{
|
||||
ID: 703,
|
||||
Name: "opencode-go",
|
||||
Platform: PlatformOpenCodeGo,
|
||||
Type: AccountTypeAPIKey,
|
||||
Concurrency: 1,
|
||||
Credentials: map[string]any{
|
||||
"api_key": "sk-test",
|
||||
"account_mode": AccountModeGo,
|
||||
"api_base_urls": map[string]any{
|
||||
APIProtocolAnthropic: "http://anthropic.example",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
account *Account
|
||||
model string
|
||||
}{
|
||||
{"opencode by model", openCode, "minimax-m3"},
|
||||
{"kimi pinned anthropic", nativeAnthropicTestAccount(), "kimi-k2"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
body := []byte(`{"model":" ` + tc.model + ` ","input":"hello","stream":false}`)
|
||||
upstream := &httpUpstreamRecorder{resp: nativeAnthropicStreamResponse()}
|
||||
svc := &OpenAIGatewayService{cfg: rawChatCompletionsTestConfig(), httpUpstream: upstream}
|
||||
|
||||
result, err := svc.Forward(context.Background(), adaptiveProtocolTestContext("/v1/responses", body), tc.account, body)
|
||||
require.NoError(t, err)
|
||||
require.True(t, strings.HasSuffix(upstream.lastReq.URL.Path, "/v1/messages"), upstream.lastReq.URL.String())
|
||||
require.Equal(t, tc.model, gjson.GetBytes(upstream.lastBody, "model").String())
|
||||
require.Equal(t, tc.model, result.BillingModel)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user