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
de61745b
Unverified
Commit
de61745b
authored
Feb 26, 2026
by
Wesley Liddick
Committed by
GitHub
Feb 26, 2026
Browse files
Merge pull request #635 from alfadb/fix/count-tokens-fallback-for-proxy
fix: count_tokens 端点不支持时降级返回空值
parents
3fab0fcd
03bcd94a
Changes
2
Hide whitespace changes
Inline
Side-by-side
backend/internal/service/gateway_anthropic_apikey_passthrough_test.go
View file @
de61745b
...
...
@@ -262,6 +262,101 @@ func TestGatewayService_AnthropicAPIKeyPassthrough_ForwardCountTokensPreservesBo
require
.
Empty
(
t
,
rec
.
Header
()
.
Get
(
"Set-Cookie"
))
}
func
TestGatewayService_AnthropicAPIKeyPassthrough_CountTokensFallbackOn404
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
tests
:=
[]
struct
{
name
string
statusCode
int
respBody
string
wantFallback
bool
}{
{
name
:
"404 endpoint not found triggers fallback"
,
statusCode
:
http
.
StatusNotFound
,
respBody
:
`{"error":{"message":"Not found: /v1/messages/count_tokens","type":"not_found_error"}}`
,
wantFallback
:
true
,
},
{
name
:
"404 generic not found triggers fallback"
,
statusCode
:
http
.
StatusNotFound
,
respBody
:
`{"error":{"message":"resource not found","type":"not_found_error"}}`
,
wantFallback
:
true
,
},
{
name
:
"400 Invalid URL does not fallback"
,
statusCode
:
http
.
StatusBadRequest
,
respBody
:
`{"error":{"message":"Invalid URL (POST /v1/messages/count_tokens)","type":"invalid_request_error"}}`
,
wantFallback
:
false
,
},
{
name
:
"400 model error does not fallback"
,
statusCode
:
http
.
StatusBadRequest
,
respBody
:
`{"error":{"message":"model not found: claude-unknown","type":"invalid_request_error"}}`
,
wantFallback
:
false
,
},
{
name
:
"500 internal error does not fallback"
,
statusCode
:
http
.
StatusInternalServerError
,
respBody
:
`{"error":{"message":"internal error","type":"api_error"}}`
,
wantFallback
:
false
,
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
c
.
Request
=
httptest
.
NewRequest
(
http
.
MethodPost
,
"/v1/messages/count_tokens"
,
nil
)
body
:=
[]
byte
(
`{"model":"claude-sonnet-4-5-20250929","messages":[{"role":"user","content":"hi"}]}`
)
parsed
:=
&
ParsedRequest
{
Body
:
body
,
Model
:
"claude-sonnet-4-5-20250929"
}
upstream
:=
&
anthropicHTTPUpstreamRecorder
{
resp
:
&
http
.
Response
{
StatusCode
:
tt
.
statusCode
,
Header
:
http
.
Header
{
"Content-Type"
:
[]
string
{
"application/json"
}},
Body
:
io
.
NopCloser
(
strings
.
NewReader
(
tt
.
respBody
)),
},
}
svc
:=
&
GatewayService
{
cfg
:
&
config
.
Config
{
Gateway
:
config
.
GatewayConfig
{
MaxLineSize
:
defaultMaxLineSize
},
},
httpUpstream
:
upstream
,
rateLimitService
:
nil
,
}
account
:=
&
Account
{
ID
:
200
,
Name
:
"proxy-acc"
,
Platform
:
PlatformAnthropic
,
Type
:
AccountTypeAPIKey
,
Concurrency
:
1
,
Credentials
:
map
[
string
]
any
{
"api_key"
:
"sk-proxy"
,
"base_url"
:
"https://proxy.example.com"
,
},
Extra
:
map
[
string
]
any
{
"anthropic_passthrough"
:
true
},
Status
:
StatusActive
,
Schedulable
:
true
,
}
err
:=
svc
.
ForwardCountTokens
(
context
.
Background
(),
c
,
account
,
parsed
)
if
tt
.
wantFallback
{
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
http
.
StatusOK
,
rec
.
Code
)
require
.
JSONEq
(
t
,
`{"input_tokens":0}`
,
rec
.
Body
.
String
())
}
else
{
require
.
Error
(
t
,
err
)
require
.
Equal
(
t
,
tt
.
statusCode
,
rec
.
Code
)
}
})
}
}
func
TestGatewayService_AnthropicAPIKeyPassthrough_BuildRequestRejectsInvalidBaseURL
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
rec
:=
httptest
.
NewRecorder
()
...
...
backend/internal/service/gateway_service.go
View file @
de61745b
...
...
@@ -6199,6 +6199,16 @@ func (s *GatewayService) forwardCountTokensAnthropicAPIKeyPassthrough(ctx contex
upstreamMsg
:=
strings
.
TrimSpace
(
extractUpstreamErrorMessage
(
respBody
))
upstreamMsg
=
sanitizeUpstreamErrorMessage
(
upstreamMsg
)
// 中转站不支持 count_tokens 端点时(404),降级返回空值,客户端会 fallback 到本地估算。
if
resp
.
StatusCode
==
http
.
StatusNotFound
{
logger
.
LegacyPrintf
(
"service.gateway"
,
"[count_tokens] Upstream does not support count_tokens (404), returning fallback: account=%d name=%s msg=%s"
,
account
.
ID
,
account
.
Name
,
truncateString
(
upstreamMsg
,
512
))
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"input_tokens"
:
0
})
return
nil
}
upstreamDetail
:=
""
if
s
.
cfg
!=
nil
&&
s
.
cfg
.
Gateway
.
LogUpstreamErrorBody
{
maxBytes
:=
s
.
cfg
.
Gateway
.
LogUpstreamErrorBodyMaxBytes
...
...
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