Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
edb09370
Commit
edb09370
authored
Feb 07, 2026
by
erio
Browse files
fix: restore non-failover error passthrough from
7b156489
parent
43a4840d
Changes
8
Hide whitespace changes
Inline
Side-by-side
backend/internal/handler/gateway_handler.go
View file @
edb09370
...
@@ -137,6 +137,11 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
...
@@ -137,6 +137,11 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
// Track if we've started streaming (for error handling)
// Track if we've started streaming (for error handling)
streamStarted
:=
false
streamStarted
:=
false
// 绑定错误透传服务,允许 service 层在非 failover 错误场景复用规则。
if
h
.
errorPassthroughService
!=
nil
{
service
.
BindErrorPassthroughService
(
c
,
h
.
errorPassthroughService
)
}
// 获取订阅信息(可能为nil)- 提前获取用于后续检查
// 获取订阅信息(可能为nil)- 提前获取用于后续检查
subscription
,
_
:=
middleware2
.
GetSubscriptionFromContext
(
c
)
subscription
,
_
:=
middleware2
.
GetSubscriptionFromContext
(
c
)
...
...
backend/internal/handler/gemini_v1beta_handler.go
View file @
edb09370
...
@@ -209,6 +209,9 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
...
@@ -209,6 +209,9 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
// 1) user concurrency slot
// 1) user concurrency slot
streamStarted
:=
false
streamStarted
:=
false
if
h
.
errorPassthroughService
!=
nil
{
service
.
BindErrorPassthroughService
(
c
,
h
.
errorPassthroughService
)
}
userReleaseFunc
,
err
:=
geminiConcurrency
.
AcquireUserSlotWithWait
(
c
,
authSubject
.
UserID
,
authSubject
.
Concurrency
,
stream
,
&
streamStarted
)
userReleaseFunc
,
err
:=
geminiConcurrency
.
AcquireUserSlotWithWait
(
c
,
authSubject
.
UserID
,
authSubject
.
Concurrency
,
stream
,
&
streamStarted
)
if
err
!=
nil
{
if
err
!=
nil
{
googleError
(
c
,
http
.
StatusTooManyRequests
,
err
.
Error
())
googleError
(
c
,
http
.
StatusTooManyRequests
,
err
.
Error
())
...
...
backend/internal/handler/openai_gateway_handler.go
View file @
edb09370
...
@@ -149,6 +149,11 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
...
@@ -149,6 +149,11 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
// Track if we've started streaming (for error handling)
// Track if we've started streaming (for error handling)
streamStarted
:=
false
streamStarted
:=
false
// 绑定错误透传服务,允许 service 层在非 failover 错误场景复用规则。
if
h
.
errorPassthroughService
!=
nil
{
service
.
BindErrorPassthroughService
(
c
,
h
.
errorPassthroughService
)
}
// Get subscription info (may be nil)
// Get subscription info (may be nil)
subscription
,
_
:=
middleware2
.
GetSubscriptionFromContext
(
c
)
subscription
,
_
:=
middleware2
.
GetSubscriptionFromContext
(
c
)
...
...
backend/internal/service/error_passthrough_runtime.go
0 → 100644
View file @
edb09370
package
service
import
"github.com/gin-gonic/gin"
const
errorPassthroughServiceContextKey
=
"error_passthrough_service"
// BindErrorPassthroughService 将错误透传服务绑定到请求上下文,供 service 层在非 failover 场景下复用规则。
func
BindErrorPassthroughService
(
c
*
gin
.
Context
,
svc
*
ErrorPassthroughService
)
{
if
c
==
nil
||
svc
==
nil
{
return
}
c
.
Set
(
errorPassthroughServiceContextKey
,
svc
)
}
func
getBoundErrorPassthroughService
(
c
*
gin
.
Context
)
*
ErrorPassthroughService
{
if
c
==
nil
{
return
nil
}
v
,
ok
:=
c
.
Get
(
errorPassthroughServiceContextKey
)
if
!
ok
{
return
nil
}
svc
,
ok
:=
v
.
(
*
ErrorPassthroughService
)
if
!
ok
{
return
nil
}
return
svc
}
// applyErrorPassthroughRule 按规则改写错误响应;未命中时返回默认响应参数。
func
applyErrorPassthroughRule
(
c
*
gin
.
Context
,
platform
string
,
upstreamStatus
int
,
responseBody
[]
byte
,
defaultStatus
int
,
defaultErrType
string
,
defaultErrMsg
string
,
)
(
status
int
,
errType
string
,
errMsg
string
,
matched
bool
)
{
status
=
defaultStatus
errType
=
defaultErrType
errMsg
=
defaultErrMsg
svc
:=
getBoundErrorPassthroughService
(
c
)
if
svc
==
nil
{
return
status
,
errType
,
errMsg
,
false
}
rule
:=
svc
.
MatchRule
(
platform
,
upstreamStatus
,
responseBody
)
if
rule
==
nil
{
return
status
,
errType
,
errMsg
,
false
}
status
=
upstreamStatus
if
!
rule
.
PassthroughCode
&&
rule
.
ResponseCode
!=
nil
{
status
=
*
rule
.
ResponseCode
}
errMsg
=
ExtractUpstreamErrorMessage
(
responseBody
)
if
!
rule
.
PassthroughBody
&&
rule
.
CustomMessage
!=
nil
{
errMsg
=
*
rule
.
CustomMessage
}
// 与现有 failover 场景保持一致:命中规则时统一返回 upstream_error。
errType
=
"upstream_error"
return
status
,
errType
,
errMsg
,
true
}
backend/internal/service/error_passthrough_runtime_test.go
0 → 100644
View file @
edb09370
package
service
import
(
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func
TestApplyErrorPassthroughRule_NoBoundService
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
status
,
errType
,
errMsg
,
matched
:=
applyErrorPassthroughRule
(
c
,
PlatformAnthropic
,
http
.
StatusUnprocessableEntity
,
[]
byte
(
`{"error":{"message":"invalid schema"}}`
),
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream request failed"
,
)
assert
.
False
(
t
,
matched
)
assert
.
Equal
(
t
,
http
.
StatusBadGateway
,
status
)
assert
.
Equal
(
t
,
"upstream_error"
,
errType
)
assert
.
Equal
(
t
,
"Upstream request failed"
,
errMsg
)
}
func
TestGatewayHandleErrorResponse_NoRuleKeepsDefault
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
svc
:=
&
GatewayService
{}
respBody
:=
[]
byte
(
`{"error":{"message":"Invalid schema for field messages"}}`
)
resp
:=
&
http
.
Response
{
StatusCode
:
http
.
StatusUnprocessableEntity
,
Body
:
io
.
NopCloser
(
bytes
.
NewReader
(
respBody
)),
Header
:
http
.
Header
{},
}
account
:=
&
Account
{
ID
:
11
,
Platform
:
PlatformAnthropic
,
Type
:
AccountTypeAPIKey
}
_
,
err
:=
svc
.
handleErrorResponse
(
context
.
Background
(),
resp
,
c
,
account
)
require
.
Error
(
t
,
err
)
assert
.
Equal
(
t
,
http
.
StatusBadGateway
,
rec
.
Code
)
var
payload
map
[
string
]
any
require
.
NoError
(
t
,
json
.
Unmarshal
(
rec
.
Body
.
Bytes
(),
&
payload
))
errField
,
ok
:=
payload
[
"error"
]
.
(
map
[
string
]
any
)
require
.
True
(
t
,
ok
)
assert
.
Equal
(
t
,
"upstream_error"
,
errField
[
"type"
])
assert
.
Equal
(
t
,
"Upstream request failed"
,
errField
[
"message"
])
}
func
TestOpenAIHandleErrorResponse_NoRuleKeepsDefault
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
svc
:=
&
OpenAIGatewayService
{}
respBody
:=
[]
byte
(
`{"error":{"message":"Invalid schema for field messages"}}`
)
resp
:=
&
http
.
Response
{
StatusCode
:
http
.
StatusUnprocessableEntity
,
Body
:
io
.
NopCloser
(
bytes
.
NewReader
(
respBody
)),
Header
:
http
.
Header
{},
}
account
:=
&
Account
{
ID
:
12
,
Platform
:
PlatformOpenAI
,
Type
:
AccountTypeAPIKey
}
_
,
err
:=
svc
.
handleErrorResponse
(
context
.
Background
(),
resp
,
c
,
account
)
require
.
Error
(
t
,
err
)
assert
.
Equal
(
t
,
http
.
StatusBadGateway
,
rec
.
Code
)
var
payload
map
[
string
]
any
require
.
NoError
(
t
,
json
.
Unmarshal
(
rec
.
Body
.
Bytes
(),
&
payload
))
errField
,
ok
:=
payload
[
"error"
]
.
(
map
[
string
]
any
)
require
.
True
(
t
,
ok
)
assert
.
Equal
(
t
,
"upstream_error"
,
errField
[
"type"
])
assert
.
Equal
(
t
,
"Upstream request failed"
,
errField
[
"message"
])
}
func
TestGeminiWriteGeminiMappedError_NoRuleKeepsDefault
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
svc
:=
&
GeminiMessagesCompatService
{}
respBody
:=
[]
byte
(
`{"error":{"code":422,"message":"Invalid schema for field messages","status":"INVALID_ARGUMENT"}}`
)
account
:=
&
Account
{
ID
:
13
,
Platform
:
PlatformGemini
,
Type
:
AccountTypeAPIKey
}
err
:=
svc
.
writeGeminiMappedError
(
c
,
account
,
http
.
StatusUnprocessableEntity
,
"req-2"
,
respBody
)
require
.
Error
(
t
,
err
)
assert
.
Equal
(
t
,
http
.
StatusBadRequest
,
rec
.
Code
)
var
payload
map
[
string
]
any
require
.
NoError
(
t
,
json
.
Unmarshal
(
rec
.
Body
.
Bytes
(),
&
payload
))
errField
,
ok
:=
payload
[
"error"
]
.
(
map
[
string
]
any
)
require
.
True
(
t
,
ok
)
assert
.
Equal
(
t
,
"invalid_request_error"
,
errField
[
"type"
])
assert
.
Equal
(
t
,
"Upstream request failed"
,
errField
[
"message"
])
}
func
TestGatewayHandleErrorResponse_AppliesRuleFor422
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
ruleSvc
:=
&
ErrorPassthroughService
{}
ruleSvc
.
setLocalCache
([]
*
model
.
ErrorPassthroughRule
{
newNonFailoverPassthroughRule
(
http
.
StatusUnprocessableEntity
,
"invalid schema"
,
http
.
StatusTeapot
,
"上游请求失败"
)})
BindErrorPassthroughService
(
c
,
ruleSvc
)
svc
:=
&
GatewayService
{}
respBody
:=
[]
byte
(
`{"error":{"message":"Invalid schema for field messages"}}`
)
resp
:=
&
http
.
Response
{
StatusCode
:
http
.
StatusUnprocessableEntity
,
Body
:
io
.
NopCloser
(
bytes
.
NewReader
(
respBody
)),
Header
:
http
.
Header
{},
}
account
:=
&
Account
{
ID
:
1
,
Platform
:
PlatformAnthropic
,
Type
:
AccountTypeAPIKey
}
_
,
err
:=
svc
.
handleErrorResponse
(
context
.
Background
(),
resp
,
c
,
account
)
require
.
Error
(
t
,
err
)
assert
.
Equal
(
t
,
http
.
StatusTeapot
,
rec
.
Code
)
var
payload
map
[
string
]
any
require
.
NoError
(
t
,
json
.
Unmarshal
(
rec
.
Body
.
Bytes
(),
&
payload
))
errField
,
ok
:=
payload
[
"error"
]
.
(
map
[
string
]
any
)
require
.
True
(
t
,
ok
)
assert
.
Equal
(
t
,
"upstream_error"
,
errField
[
"type"
])
assert
.
Equal
(
t
,
"上游请求失败"
,
errField
[
"message"
])
}
func
TestOpenAIHandleErrorResponse_AppliesRuleFor422
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
ruleSvc
:=
&
ErrorPassthroughService
{}
ruleSvc
.
setLocalCache
([]
*
model
.
ErrorPassthroughRule
{
newNonFailoverPassthroughRule
(
http
.
StatusUnprocessableEntity
,
"invalid schema"
,
http
.
StatusTeapot
,
"OpenAI上游失败"
)})
BindErrorPassthroughService
(
c
,
ruleSvc
)
svc
:=
&
OpenAIGatewayService
{}
respBody
:=
[]
byte
(
`{"error":{"message":"Invalid schema for field messages"}}`
)
resp
:=
&
http
.
Response
{
StatusCode
:
http
.
StatusUnprocessableEntity
,
Body
:
io
.
NopCloser
(
bytes
.
NewReader
(
respBody
)),
Header
:
http
.
Header
{},
}
account
:=
&
Account
{
ID
:
2
,
Platform
:
PlatformOpenAI
,
Type
:
AccountTypeAPIKey
}
_
,
err
:=
svc
.
handleErrorResponse
(
context
.
Background
(),
resp
,
c
,
account
)
require
.
Error
(
t
,
err
)
assert
.
Equal
(
t
,
http
.
StatusTeapot
,
rec
.
Code
)
var
payload
map
[
string
]
any
require
.
NoError
(
t
,
json
.
Unmarshal
(
rec
.
Body
.
Bytes
(),
&
payload
))
errField
,
ok
:=
payload
[
"error"
]
.
(
map
[
string
]
any
)
require
.
True
(
t
,
ok
)
assert
.
Equal
(
t
,
"upstream_error"
,
errField
[
"type"
])
assert
.
Equal
(
t
,
"OpenAI上游失败"
,
errField
[
"message"
])
}
func
TestGeminiWriteGeminiMappedError_AppliesRuleFor422
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
ruleSvc
:=
&
ErrorPassthroughService
{}
ruleSvc
.
setLocalCache
([]
*
model
.
ErrorPassthroughRule
{
newNonFailoverPassthroughRule
(
http
.
StatusUnprocessableEntity
,
"invalid schema"
,
http
.
StatusTeapot
,
"Gemini上游失败"
)})
BindErrorPassthroughService
(
c
,
ruleSvc
)
svc
:=
&
GeminiMessagesCompatService
{}
respBody
:=
[]
byte
(
`{"error":{"code":422,"message":"Invalid schema for field messages","status":"INVALID_ARGUMENT"}}`
)
account
:=
&
Account
{
ID
:
3
,
Platform
:
PlatformGemini
,
Type
:
AccountTypeAPIKey
}
err
:=
svc
.
writeGeminiMappedError
(
c
,
account
,
http
.
StatusUnprocessableEntity
,
"req-1"
,
respBody
)
require
.
Error
(
t
,
err
)
assert
.
Equal
(
t
,
http
.
StatusTeapot
,
rec
.
Code
)
var
payload
map
[
string
]
any
require
.
NoError
(
t
,
json
.
Unmarshal
(
rec
.
Body
.
Bytes
(),
&
payload
))
errField
,
ok
:=
payload
[
"error"
]
.
(
map
[
string
]
any
)
require
.
True
(
t
,
ok
)
assert
.
Equal
(
t
,
"upstream_error"
,
errField
[
"type"
])
assert
.
Equal
(
t
,
"Gemini上游失败"
,
errField
[
"message"
])
}
func
newNonFailoverPassthroughRule
(
statusCode
int
,
keyword
string
,
respCode
int
,
customMessage
string
)
*
model
.
ErrorPassthroughRule
{
return
&
model
.
ErrorPassthroughRule
{
ID
:
1
,
Name
:
"non-failover-rule"
,
Enabled
:
true
,
Priority
:
1
,
ErrorCodes
:
[]
int
{
statusCode
},
Keywords
:
[]
string
{
keyword
},
MatchMode
:
model
.
MatchModeAll
,
PassthroughCode
:
false
,
ResponseCode
:
&
respCode
,
PassthroughBody
:
false
,
CustomMessage
:
&
customMessage
,
}
}
backend/internal/service/gateway_service.go
View file @
edb09370
...
@@ -2576,24 +2576,20 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
...
@@ -2576,24 +2576,20 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
// 对于 Antigravity 平台,会先获取映射后的最终模型名(包括 thinking 后缀)再检查支持
// 对于 Antigravity 平台,会先获取映射后的最终模型名(包括 thinking 后缀)再检查支持
func
(
s
*
GatewayService
)
isModelSupportedByAccountWithContext
(
ctx
context
.
Context
,
account
*
Account
,
requestedModel
string
)
bool
{
func
(
s
*
GatewayService
)
isModelSupportedByAccountWithContext
(
ctx
context
.
Context
,
account
*
Account
,
requestedModel
string
)
bool
{
if
account
.
Platform
==
PlatformAntigravity
{
if
account
.
Platform
==
PlatformAntigravity
{
// Antigravity 平台使用专门的模型支持检查
if
strings
.
TrimSpace
(
requestedModel
)
==
""
{
if
strings
.
TrimSpace
(
requestedModel
)
==
""
{
return
true
return
true
}
}
if
!
IsAntigravityModelSupported
(
requestedModel
)
{
// 使用与转发阶段一致的映射逻辑:自定义映射优先 → 默认映射兜底
mapped
:=
mapAntigravityModel
(
account
,
requestedModel
)
if
mapped
==
""
{
return
false
return
false
}
}
// 先用默认映射获取基础模型名,再应用 thinking 后缀
// 应用 thinking 后缀后检查最终模型是否在账号映射中
defaultMapped
,
exists
:=
domain
.
DefaultAntigravityModelMapping
[
requestedModel
]
if
!
exists
||
defaultMapped
==
""
{
return
false
}
finalModel
:=
defaultMapped
if
enabled
,
ok
:=
ctx
.
Value
(
ctxkey
.
ThinkingEnabled
)
.
(
bool
);
ok
{
if
enabled
,
ok
:=
ctx
.
Value
(
ctxkey
.
ThinkingEnabled
)
.
(
bool
);
ok
{
finalModel
=
applyThinkingModelSuffix
(
finalModel
,
enabled
)
finalModel
:=
applyThinkingModelSuffix
(
mapped
,
enabled
)
return
account
.
IsModelSupported
(
finalModel
)
}
}
// 使用最终模型名检查 model_mapping 支持
return
true
return
account
.
IsModelSupported
(
finalModel
)
}
}
return
s
.
isModelSupportedByAccount
(
account
,
requestedModel
)
return
s
.
isModelSupportedByAccount
(
account
,
requestedModel
)
}
}
...
@@ -2601,15 +2597,10 @@ func (s *GatewayService) isModelSupportedByAccountWithContext(ctx context.Contex
...
@@ -2601,15 +2597,10 @@ func (s *GatewayService) isModelSupportedByAccountWithContext(ctx context.Contex
// isModelSupportedByAccount 根据账户平台检查模型支持(无 context,用于非 Antigravity 平台)
// isModelSupportedByAccount 根据账户平台检查模型支持(无 context,用于非 Antigravity 平台)
func
(
s
*
GatewayService
)
isModelSupportedByAccount
(
account
*
Account
,
requestedModel
string
)
bool
{
func
(
s
*
GatewayService
)
isModelSupportedByAccount
(
account
*
Account
,
requestedModel
string
)
bool
{
if
account
.
Platform
==
PlatformAntigravity
{
if
account
.
Platform
==
PlatformAntigravity
{
// Antigravity 应使用 isModelSupportedByAccountWithContext
// 这里作为兼容保留,使用原始模型名检查
if
strings
.
TrimSpace
(
requestedModel
)
==
""
{
if
strings
.
TrimSpace
(
requestedModel
)
==
""
{
return
true
return
true
}
}
if
!
IsAntigravityModelSupported
(
requestedModel
)
{
return
mapAntigravityModel
(
account
,
requestedModel
)
!=
""
return
false
}
return
account
.
IsModelSupported
(
requestedModel
)
}
}
// OAuth/SetupToken 账号使用 Anthropic 标准映射(短ID → 长ID)
// OAuth/SetupToken 账号使用 Anthropic 标准映射(短ID → 长ID)
if
account
.
Platform
==
PlatformAnthropic
&&
account
.
Type
!=
AccountTypeAPIKey
{
if
account
.
Platform
==
PlatformAnthropic
&&
account
.
Type
!=
AccountTypeAPIKey
{
...
@@ -3919,6 +3910,34 @@ func (s *GatewayService) handleErrorResponse(ctx context.Context, resp *http.Res
...
@@ -3919,6 +3910,34 @@ func (s *GatewayService) handleErrorResponse(ctx context.Context, resp *http.Res
)
)
}
}
// 非 failover 错误也支持错误透传规则匹配。
if
status
,
errType
,
errMsg
,
matched
:=
applyErrorPassthroughRule
(
c
,
account
.
Platform
,
resp
.
StatusCode
,
body
,
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream request failed"
,
);
matched
{
c
.
JSON
(
status
,
gin
.
H
{
"type"
:
"error"
,
"error"
:
gin
.
H
{
"type"
:
errType
,
"message"
:
errMsg
,
},
})
summary
:=
upstreamMsg
if
summary
==
""
{
summary
=
errMsg
}
if
summary
==
""
{
return
nil
,
fmt
.
Errorf
(
"upstream error: %d (passthrough rule matched)"
,
resp
.
StatusCode
)
}
return
nil
,
fmt
.
Errorf
(
"upstream error: %d (passthrough rule matched) message=%s"
,
resp
.
StatusCode
,
summary
)
}
// 根据状态码返回适当的自定义错误响应(不透传上游详细信息)
// 根据状态码返回适当的自定义错误响应(不透传上游详细信息)
var
errType
,
errMsg
string
var
errType
,
errMsg
string
var
statusCode
int
var
statusCode
int
...
@@ -4050,6 +4069,33 @@ func (s *GatewayService) handleRetryExhaustedError(ctx context.Context, resp *ht
...
@@ -4050,6 +4069,33 @@ func (s *GatewayService) handleRetryExhaustedError(ctx context.Context, resp *ht
)
)
}
}
if
status
,
errType
,
errMsg
,
matched
:=
applyErrorPassthroughRule
(
c
,
account
.
Platform
,
resp
.
StatusCode
,
respBody
,
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream request failed after retries"
,
);
matched
{
c
.
JSON
(
status
,
gin
.
H
{
"type"
:
"error"
,
"error"
:
gin
.
H
{
"type"
:
errType
,
"message"
:
errMsg
,
},
})
summary
:=
upstreamMsg
if
summary
==
""
{
summary
=
errMsg
}
if
summary
==
""
{
return
nil
,
fmt
.
Errorf
(
"upstream error: %d (retries exhausted, passthrough rule matched)"
,
resp
.
StatusCode
)
}
return
nil
,
fmt
.
Errorf
(
"upstream error: %d (retries exhausted, passthrough rule matched) message=%s"
,
resp
.
StatusCode
,
summary
)
}
// 返回统一的重试耗尽错误响应
// 返回统一的重试耗尽错误响应
c
.
JSON
(
http
.
StatusBadGateway
,
gin
.
H
{
c
.
JSON
(
http
.
StatusBadGateway
,
gin
.
H
{
"type"
:
"error"
,
"type"
:
"error"
,
...
...
backend/internal/service/gemini_messages_compat_service.go
View file @
edb09370
...
@@ -362,7 +362,10 @@ func (s *GeminiMessagesCompatService) isBetterGeminiAccount(candidate, current *
...
@@ -362,7 +362,10 @@ func (s *GeminiMessagesCompatService) isBetterGeminiAccount(candidate, current *
// isModelSupportedByAccount 根据账户平台检查模型支持
// isModelSupportedByAccount 根据账户平台检查模型支持
func
(
s
*
GeminiMessagesCompatService
)
isModelSupportedByAccount
(
account
*
Account
,
requestedModel
string
)
bool
{
func
(
s
*
GeminiMessagesCompatService
)
isModelSupportedByAccount
(
account
*
Account
,
requestedModel
string
)
bool
{
if
account
.
Platform
==
PlatformAntigravity
{
if
account
.
Platform
==
PlatformAntigravity
{
return
IsAntigravityModelSupported
(
requestedModel
)
if
strings
.
TrimSpace
(
requestedModel
)
==
""
{
return
true
}
return
mapAntigravityModel
(
account
,
requestedModel
)
!=
""
}
}
return
account
.
IsModelSupported
(
requestedModel
)
return
account
.
IsModelSupported
(
requestedModel
)
}
}
...
@@ -1498,6 +1501,28 @@ func (s *GeminiMessagesCompatService) writeGeminiMappedError(c *gin.Context, acc
...
@@ -1498,6 +1501,28 @@ func (s *GeminiMessagesCompatService) writeGeminiMappedError(c *gin.Context, acc
log
.
Printf
(
"[Gemini] upstream error %d: %s"
,
upstreamStatus
,
truncateForLog
(
body
,
s
.
cfg
.
Gateway
.
LogUpstreamErrorBodyMaxBytes
))
log
.
Printf
(
"[Gemini] upstream error %d: %s"
,
upstreamStatus
,
truncateForLog
(
body
,
s
.
cfg
.
Gateway
.
LogUpstreamErrorBodyMaxBytes
))
}
}
if
status
,
errType
,
errMsg
,
matched
:=
applyErrorPassthroughRule
(
c
,
PlatformGemini
,
upstreamStatus
,
body
,
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream request failed"
,
);
matched
{
c
.
JSON
(
status
,
gin
.
H
{
"type"
:
"error"
,
"error"
:
gin
.
H
{
"type"
:
errType
,
"message"
:
errMsg
},
})
if
upstreamMsg
==
""
{
upstreamMsg
=
errMsg
}
if
upstreamMsg
==
""
{
return
fmt
.
Errorf
(
"upstream error: %d (passthrough rule matched)"
,
upstreamStatus
)
}
return
fmt
.
Errorf
(
"upstream error: %d (passthrough rule matched) message=%s"
,
upstreamStatus
,
upstreamMsg
)
}
var
statusCode
int
var
statusCode
int
var
errType
,
errMsg
string
var
errType
,
errMsg
string
...
...
backend/internal/service/openai_gateway_service.go
View file @
edb09370
...
@@ -1087,6 +1087,30 @@ func (s *OpenAIGatewayService) handleErrorResponse(ctx context.Context, resp *ht
...
@@ -1087,6 +1087,30 @@ func (s *OpenAIGatewayService) handleErrorResponse(ctx context.Context, resp *ht
)
)
}
}
if
status
,
errType
,
errMsg
,
matched
:=
applyErrorPassthroughRule
(
c
,
PlatformOpenAI
,
resp
.
StatusCode
,
body
,
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream request failed"
,
);
matched
{
c
.
JSON
(
status
,
gin
.
H
{
"error"
:
gin
.
H
{
"type"
:
errType
,
"message"
:
errMsg
,
},
})
if
upstreamMsg
==
""
{
upstreamMsg
=
errMsg
}
if
upstreamMsg
==
""
{
return
nil
,
fmt
.
Errorf
(
"upstream error: %d (passthrough rule matched)"
,
resp
.
StatusCode
)
}
return
nil
,
fmt
.
Errorf
(
"upstream error: %d (passthrough rule matched) message=%s"
,
resp
.
StatusCode
,
upstreamMsg
)
}
// Check custom error codes
// Check custom error codes
if
!
account
.
ShouldHandleErrorCode
(
resp
.
StatusCode
)
{
if
!
account
.
ShouldHandleErrorCode
(
resp
.
StatusCode
)
{
appendOpsUpstreamError
(
c
,
OpsUpstreamErrorEvent
{
appendOpsUpstreamError
(
c
,
OpsUpstreamErrorEvent
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment