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
e7780cd8
Unverified
Commit
e7780cd8
authored
Mar 18, 2026
by
Wesley Liddick
Committed by
GitHub
Mar 18, 2026
Browse files
Merge pull request #1117 from alfadb/fix/empty-text-block-retry
fix: 修复空 text block 导致上游 400 错误未被重试捕获的问题
parents
62a566ef
7d26b810
Changes
3
Hide whitespace changes
Inline
Side-by-side
backend/internal/service/gateway_request.go
View file @
e7780cd8
...
@@ -28,6 +28,12 @@ var (
...
@@ -28,6 +28,12 @@ var (
patternEmptyContentSpaced
=
[]
byte
(
`"content": []`
)
patternEmptyContentSpaced
=
[]
byte
(
`"content": []`
)
patternEmptyContentSp1
=
[]
byte
(
`"content" : []`
)
patternEmptyContentSp1
=
[]
byte
(
`"content" : []`
)
patternEmptyContentSp2
=
[]
byte
(
`"content" :[]`
)
patternEmptyContentSp2
=
[]
byte
(
`"content" :[]`
)
// Fast-path patterns for empty text blocks: {"type":"text","text":""}
patternEmptyText
=
[]
byte
(
`"text":""`
)
patternEmptyTextSpaced
=
[]
byte
(
`"text": ""`
)
patternEmptyTextSp1
=
[]
byte
(
`"text" : ""`
)
patternEmptyTextSp2
=
[]
byte
(
`"text" :""`
)
)
)
// SessionContext 粘性会话上下文,用于区分不同来源的请求。
// SessionContext 粘性会话上下文,用于区分不同来源的请求。
...
@@ -233,15 +239,22 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
...
@@ -233,15 +239,22 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
bytes
.
Contains
(
body
,
patternThinkingField
)
||
bytes
.
Contains
(
body
,
patternThinkingField
)
||
bytes
.
Contains
(
body
,
patternThinkingFieldSpaced
)
bytes
.
Contains
(
body
,
patternThinkingFieldSpaced
)
// Also check for empty content arrays that need fixing.
// Also check for empty content arrays
and empty text blocks
that need fixing.
// Note: This is a heuristic check; the actual empty content handling is done below.
// Note: This is a heuristic check; the actual empty content handling is done below.
hasEmptyContent
:=
bytes
.
Contains
(
body
,
patternEmptyContent
)
||
hasEmptyContent
:=
bytes
.
Contains
(
body
,
patternEmptyContent
)
||
bytes
.
Contains
(
body
,
patternEmptyContentSpaced
)
||
bytes
.
Contains
(
body
,
patternEmptyContentSpaced
)
||
bytes
.
Contains
(
body
,
patternEmptyContentSp1
)
||
bytes
.
Contains
(
body
,
patternEmptyContentSp1
)
||
bytes
.
Contains
(
body
,
patternEmptyContentSp2
)
bytes
.
Contains
(
body
,
patternEmptyContentSp2
)
// Check for empty text blocks: {"type":"text","text":""}
// These cause upstream 400: "text content blocks must be non-empty"
hasEmptyTextBlock
:=
bytes
.
Contains
(
body
,
patternEmptyText
)
||
bytes
.
Contains
(
body
,
patternEmptyTextSpaced
)
||
bytes
.
Contains
(
body
,
patternEmptyTextSp1
)
||
bytes
.
Contains
(
body
,
patternEmptyTextSp2
)
// Fast path: nothing to process
// Fast path: nothing to process
if
!
hasThinkingContent
&&
!
hasEmptyContent
{
if
!
hasThinkingContent
&&
!
hasEmptyContent
&&
!
hasEmptyTextBlock
{
return
body
return
body
}
}
...
@@ -260,7 +273,7 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
...
@@ -260,7 +273,7 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
bytes
.
Contains
(
body
,
patternTypeRedactedThinking
)
||
bytes
.
Contains
(
body
,
patternTypeRedactedThinking
)
||
bytes
.
Contains
(
body
,
patternTypeRedactedSpaced
)
||
bytes
.
Contains
(
body
,
patternTypeRedactedSpaced
)
||
bytes
.
Contains
(
body
,
patternThinkingFieldSpaced
)
bytes
.
Contains
(
body
,
patternThinkingFieldSpaced
)
if
!
hasEmptyContent
&&
!
containsThinkingBlocks
{
if
!
hasEmptyContent
&&
!
hasEmptyTextBlock
&&
!
containsThinkingBlocks
{
if
topThinking
:=
gjson
.
Get
(
jsonStr
,
"thinking"
);
topThinking
.
Exists
()
{
if
topThinking
:=
gjson
.
Get
(
jsonStr
,
"thinking"
);
topThinking
.
Exists
()
{
if
out
,
err
:=
sjson
.
DeleteBytes
(
body
,
"thinking"
);
err
==
nil
{
if
out
,
err
:=
sjson
.
DeleteBytes
(
body
,
"thinking"
);
err
==
nil
{
out
=
removeThinkingDependentContextStrategies
(
out
)
out
=
removeThinkingDependentContextStrategies
(
out
)
...
@@ -320,6 +333,16 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
...
@@ -320,6 +333,16 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
blockType
,
_
:=
blockMap
[
"type"
]
.
(
string
)
blockType
,
_
:=
blockMap
[
"type"
]
.
(
string
)
// Strip empty text blocks: {"type":"text","text":""}
// Upstream rejects these with 400: "text content blocks must be non-empty"
if
blockType
==
"text"
{
if
txt
,
_
:=
blockMap
[
"text"
]
.
(
string
);
txt
==
""
{
modifiedThisMsg
=
true
ensureNewContent
(
bi
)
continue
}
}
// Convert thinking blocks to text (preserve content) and drop redacted_thinking.
// Convert thinking blocks to text (preserve content) and drop redacted_thinking.
switch
blockType
{
switch
blockType
{
case
"thinking"
:
case
"thinking"
:
...
...
backend/internal/service/gateway_request_test.go
View file @
e7780cd8
...
@@ -404,6 +404,51 @@ func TestFilterThinkingBlocksForRetry_EmptyContentGetsPlaceholder(t *testing.T)
...
@@ -404,6 +404,51 @@ func TestFilterThinkingBlocksForRetry_EmptyContentGetsPlaceholder(t *testing.T)
require
.
NotEmpty
(
t
,
content0
[
"text"
])
require
.
NotEmpty
(
t
,
content0
[
"text"
])
}
}
func
TestFilterThinkingBlocksForRetry_StripsEmptyTextBlocks
(
t
*
testing
.
T
)
{
// Empty text blocks cause upstream 400: "text content blocks must be non-empty"
input
:=
[]
byte
(
`{
"messages":[
{"role":"user","content":[{"type":"text","text":"hello"},{"type":"text","text":""}]},
{"role":"assistant","content":[{"type":"text","text":""}]}
]
}`
)
out
:=
FilterThinkingBlocksForRetry
(
input
)
var
req
map
[
string
]
any
require
.
NoError
(
t
,
json
.
Unmarshal
(
out
,
&
req
))
msgs
,
ok
:=
req
[
"messages"
]
.
([]
any
)
require
.
True
(
t
,
ok
)
// First message: empty text block stripped, "hello" preserved
msg0
:=
msgs
[
0
]
.
(
map
[
string
]
any
)
content0
:=
msg0
[
"content"
]
.
([]
any
)
require
.
Len
(
t
,
content0
,
1
)
require
.
Equal
(
t
,
"hello"
,
content0
[
0
]
.
(
map
[
string
]
any
)[
"text"
])
// Second message: only had empty text block → gets placeholder
msg1
:=
msgs
[
1
]
.
(
map
[
string
]
any
)
content1
:=
msg1
[
"content"
]
.
([]
any
)
require
.
Len
(
t
,
content1
,
1
)
block1
:=
content1
[
0
]
.
(
map
[
string
]
any
)
require
.
Equal
(
t
,
"text"
,
block1
[
"type"
])
require
.
NotEmpty
(
t
,
block1
[
"text"
])
}
func
TestFilterThinkingBlocksForRetry_PreservesNonEmptyTextBlocks
(
t
*
testing
.
T
)
{
// Non-empty text blocks should pass through unchanged
input
:=
[]
byte
(
`{
"messages":[
{"role":"user","content":[{"type":"text","text":"hello"},{"type":"text","text":"world"}]}
]
}`
)
out
:=
FilterThinkingBlocksForRetry
(
input
)
// Fast path: no thinking content, no empty content, no empty text blocks → unchanged
require
.
Equal
(
t
,
input
,
out
)
}
func
TestFilterSignatureSensitiveBlocksForRetry_DowngradesTools
(
t
*
testing
.
T
)
{
func
TestFilterSignatureSensitiveBlocksForRetry_DowngradesTools
(
t
*
testing
.
T
)
{
input
:=
[]
byte
(
`{
input
:=
[]
byte
(
`{
"thinking":{"type":"enabled","budget_tokens":1024},
"thinking":{"type":"enabled","budget_tokens":1024},
...
...
backend/internal/service/gateway_service.go
View file @
e7780cd8
...
@@ -6067,9 +6067,11 @@ func (s *GatewayService) isThinkingBlockSignatureError(respBody []byte) bool {
...
@@ -6067,9 +6067,11 @@ func (s *GatewayService) isThinkingBlockSignatureError(respBody []byte) bool {
return
true
return
true
}
}
// 检测空消息内容错误(可能是过滤 thinking blocks 后导致的)
// 检测空消息内容错误(可能是过滤 thinking blocks 后导致的
,或客户端发送了空 text block
)
// 例如: "all messages must have non-empty content"
// 例如: "all messages must have non-empty content"
if
strings
.
Contains
(
msg
,
"non-empty content"
)
||
strings
.
Contains
(
msg
,
"empty content"
)
{
// "messages: text content blocks must be non-empty"
if
strings
.
Contains
(
msg
,
"non-empty content"
)
||
strings
.
Contains
(
msg
,
"empty content"
)
||
strings
.
Contains
(
msg
,
"content blocks must be non-empty"
)
{
logger
.
LegacyPrintf
(
"service.gateway"
,
"[SignatureCheck] Detected empty content error"
)
logger
.
LegacyPrintf
(
"service.gateway"
,
"[SignatureCheck] Detected empty content error"
)
return
true
return
true
}
}
...
...
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