mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-18 22:04:45 +08:00
Merge pull request #864 from StarryKira/fix/clear-thinking-context-management
[Fix] Fix issue #851
This commit is contained in:
@@ -259,6 +259,7 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
|
|||||||
if !hasEmptyContent && !containsThinkingBlocks {
|
if !hasEmptyContent && !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)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
return body
|
return body
|
||||||
@@ -396,6 +397,10 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
|
|||||||
} else {
|
} else {
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
// Removing "thinking" makes any context_management strategy that requires it invalid
|
||||||
|
// (e.g. clear_thinking_20251015). Strip those entries so the retry request does not
|
||||||
|
// receive a 400 "strategy requires thinking to be enabled or adaptive".
|
||||||
|
out = removeThinkingDependentContextStrategies(out)
|
||||||
}
|
}
|
||||||
if modified {
|
if modified {
|
||||||
msgsBytes, err := json.Marshal(messages)
|
msgsBytes, err := json.Marshal(messages)
|
||||||
@@ -410,6 +415,49 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// removeThinkingDependentContextStrategies 从 context_management.edits 中移除
|
||||||
|
// 需要 thinking 启用的策略(如 clear_thinking_20251015)。
|
||||||
|
// 当顶层 "thinking" 字段被禁用时必须调用,否则上游会返回
|
||||||
|
// "strategy requires thinking to be enabled or adaptive"。
|
||||||
|
func removeThinkingDependentContextStrategies(body []byte) []byte {
|
||||||
|
jsonStr := *(*string)(unsafe.Pointer(&body))
|
||||||
|
editsRes := gjson.Get(jsonStr, "context_management.edits")
|
||||||
|
if !editsRes.Exists() || !editsRes.IsArray() {
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
var filtered []json.RawMessage
|
||||||
|
hasRemoved := false
|
||||||
|
editsRes.ForEach(func(_, v gjson.Result) bool {
|
||||||
|
if v.Get("type").String() == "clear_thinking_20251015" {
|
||||||
|
hasRemoved = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
filtered = append(filtered, json.RawMessage(v.Raw))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if !hasRemoved {
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filtered) == 0 {
|
||||||
|
if b, err := sjson.DeleteBytes(body, "context_management.edits"); err == nil {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredBytes, err := json.Marshal(filtered)
|
||||||
|
if err != nil {
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
if b, err := sjson.SetRawBytes(body, "context_management.edits", filteredBytes); err == nil {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
// FilterSignatureSensitiveBlocksForRetry is a stronger retry filter for cases where upstream errors indicate
|
// FilterSignatureSensitiveBlocksForRetry is a stronger retry filter for cases where upstream errors indicate
|
||||||
// signature/thought_signature validation issues involving tool blocks.
|
// signature/thought_signature validation issues involving tool blocks.
|
||||||
//
|
//
|
||||||
@@ -445,6 +493,28 @@ func FilterSignatureSensitiveBlocksForRetry(body []byte) []byte {
|
|||||||
if _, exists := req["thinking"]; exists {
|
if _, exists := req["thinking"]; exists {
|
||||||
delete(req, "thinking")
|
delete(req, "thinking")
|
||||||
modified = true
|
modified = true
|
||||||
|
// Remove context_management strategies that require thinking to be enabled
|
||||||
|
// (e.g. clear_thinking_20251015), otherwise upstream returns 400.
|
||||||
|
if cm, ok := req["context_management"].(map[string]any); ok {
|
||||||
|
if edits, ok := cm["edits"].([]any); ok {
|
||||||
|
filtered := make([]any, 0, len(edits))
|
||||||
|
for _, edit := range edits {
|
||||||
|
if editMap, ok := edit.(map[string]any); ok {
|
||||||
|
if editMap["type"] == "clear_thinking_20251015" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filtered = append(filtered, edit)
|
||||||
|
}
|
||||||
|
if len(filtered) != len(edits) {
|
||||||
|
if len(filtered) == 0 {
|
||||||
|
delete(cm, "edits")
|
||||||
|
} else {
|
||||||
|
cm["edits"] = filtered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messages, ok := req["messages"].([]any)
|
messages, ok := req["messages"].([]any)
|
||||||
|
|||||||
@@ -439,6 +439,210 @@ func TestFilterSignatureSensitiveBlocksForRetry_DowngradesTools(t *testing.T) {
|
|||||||
require.Contains(t, content1["text"], "tool_result")
|
require.Contains(t, content1["text"], "tool_result")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============ Group 6b: context_management.edits 清理测试 ============
|
||||||
|
|
||||||
|
// removeThinkingDependentContextStrategies — 边界用例
|
||||||
|
|
||||||
|
func TestRemoveThinkingDependentContextStrategies_NoContextManagement(t *testing.T) {
|
||||||
|
input := []byte(`{"thinking":{"type":"enabled"},"messages":[]}`)
|
||||||
|
out := removeThinkingDependentContextStrategies(input)
|
||||||
|
require.Equal(t, input, out, "无 context_management 字段时应原样返回")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveThinkingDependentContextStrategies_EmptyEdits(t *testing.T) {
|
||||||
|
input := []byte(`{"context_management":{"edits":[]},"messages":[]}`)
|
||||||
|
out := removeThinkingDependentContextStrategies(input)
|
||||||
|
require.Equal(t, input, out, "edits 为空数组时应原样返回")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveThinkingDependentContextStrategies_NoClearThinkingEntry(t *testing.T) {
|
||||||
|
input := []byte(`{"context_management":{"edits":[{"type":"other_strategy"}]},"messages":[]}`)
|
||||||
|
out := removeThinkingDependentContextStrategies(input)
|
||||||
|
require.Equal(t, input, out, "edits 中无 clear_thinking_20251015 时应原样返回")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveThinkingDependentContextStrategies_RemovesSingleEntry(t *testing.T) {
|
||||||
|
input := []byte(`{"context_management":{"edits":[{"type":"clear_thinking_20251015"}]},"messages":[]}`)
|
||||||
|
out := removeThinkingDependentContextStrategies(input)
|
||||||
|
|
||||||
|
var req map[string]any
|
||||||
|
require.NoError(t, json.Unmarshal(out, &req))
|
||||||
|
cm, ok := req["context_management"].(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
_, hasEdits := cm["edits"]
|
||||||
|
require.False(t, hasEdits, "所有 edits 均为 clear_thinking_20251015 时应删除 edits 键")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveThinkingDependentContextStrategies_MixedEntries(t *testing.T) {
|
||||||
|
input := []byte(`{"context_management":{"edits":[{"type":"clear_thinking_20251015"},{"type":"other_strategy","param":1}]},"messages":[]}`)
|
||||||
|
out := removeThinkingDependentContextStrategies(input)
|
||||||
|
|
||||||
|
var req map[string]any
|
||||||
|
require.NoError(t, json.Unmarshal(out, &req))
|
||||||
|
cm, ok := req["context_management"].(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
edits, ok := cm["edits"].([]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Len(t, edits, 1, "仅移除 clear_thinking_20251015,保留其他条目")
|
||||||
|
edit0, ok := edits[0].(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, "other_strategy", edit0["type"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterThinkingBlocksForRetry — 包含 context_management 的场景
|
||||||
|
|
||||||
|
func TestFilterThinkingBlocksForRetry_RemovesClearThinkingStrategy_FastPath(t *testing.T) {
|
||||||
|
// 快速路径:messages 中无 thinking 块,仅有顶层 thinking 字段
|
||||||
|
// 这条路径曾因提前 return 跳过 removeThinkingDependentContextStrategies 而存在 bug
|
||||||
|
input := []byte(`{
|
||||||
|
"thinking":{"type":"enabled","budget_tokens":1024},
|
||||||
|
"context_management":{"edits":[{"type":"clear_thinking_20251015"}]},
|
||||||
|
"messages":[
|
||||||
|
{"role":"user","content":[{"type":"text","text":"Hello"}]}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
out := FilterThinkingBlocksForRetry(input)
|
||||||
|
|
||||||
|
var req map[string]any
|
||||||
|
require.NoError(t, json.Unmarshal(out, &req))
|
||||||
|
_, hasThinking := req["thinking"]
|
||||||
|
require.False(t, hasThinking, "顶层 thinking 应被移除")
|
||||||
|
|
||||||
|
cm, ok := req["context_management"].(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
_, hasEdits := cm["edits"]
|
||||||
|
require.False(t, hasEdits, "fast path 下 clear_thinking_20251015 应被移除,edits 键应被删除")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterThinkingBlocksForRetry_RemovesClearThinkingStrategy_WithThinkingBlocks(t *testing.T) {
|
||||||
|
// 完整路径:messages 中有 thinking 块(非 fast path)
|
||||||
|
input := []byte(`{
|
||||||
|
"thinking":{"type":"enabled","budget_tokens":1024},
|
||||||
|
"context_management":{"edits":[{"type":"clear_thinking_20251015"},{"type":"keep_this"}]},
|
||||||
|
"messages":[
|
||||||
|
{"role":"assistant","content":[
|
||||||
|
{"type":"thinking","thinking":"some thought","signature":"sig"},
|
||||||
|
{"type":"text","text":"Answer"}
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
out := FilterThinkingBlocksForRetry(input)
|
||||||
|
|
||||||
|
var req map[string]any
|
||||||
|
require.NoError(t, json.Unmarshal(out, &req))
|
||||||
|
_, hasThinking := req["thinking"]
|
||||||
|
require.False(t, hasThinking, "顶层 thinking 应被移除")
|
||||||
|
|
||||||
|
cm, ok := req["context_management"].(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
edits, ok := cm["edits"].([]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Len(t, edits, 1, "仅移除 clear_thinking_20251015,保留 keep_this")
|
||||||
|
edit0, ok := edits[0].(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, "keep_this", edit0["type"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterThinkingBlocksForRetry_NoContextManagement_Unaffected(t *testing.T) {
|
||||||
|
// 无 context_management 时不应报错,且 thinking 正常被移除
|
||||||
|
input := []byte(`{
|
||||||
|
"thinking":{"type":"enabled"},
|
||||||
|
"messages":[{"role":"user","content":[{"type":"text","text":"Hi"}]}]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
out := FilterThinkingBlocksForRetry(input)
|
||||||
|
|
||||||
|
var req map[string]any
|
||||||
|
require.NoError(t, json.Unmarshal(out, &req))
|
||||||
|
_, hasThinking := req["thinking"]
|
||||||
|
require.False(t, hasThinking)
|
||||||
|
_, hasCM := req["context_management"]
|
||||||
|
require.False(t, hasCM)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterSignatureSensitiveBlocksForRetry — 包含 context_management 的场景
|
||||||
|
|
||||||
|
func TestFilterSignatureSensitiveBlocksForRetry_RemovesClearThinkingStrategy(t *testing.T) {
|
||||||
|
input := []byte(`{
|
||||||
|
"thinking":{"type":"enabled","budget_tokens":1024},
|
||||||
|
"context_management":{"edits":[{"type":"clear_thinking_20251015"}]},
|
||||||
|
"messages":[
|
||||||
|
{"role":"assistant","content":[
|
||||||
|
{"type":"thinking","thinking":"thought","signature":"sig"}
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
out := FilterSignatureSensitiveBlocksForRetry(input)
|
||||||
|
|
||||||
|
var req map[string]any
|
||||||
|
require.NoError(t, json.Unmarshal(out, &req))
|
||||||
|
_, hasThinking := req["thinking"]
|
||||||
|
require.False(t, hasThinking, "顶层 thinking 应被移除")
|
||||||
|
|
||||||
|
cm, ok := req["context_management"].(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
if rawEdits, hasEdits := cm["edits"]; hasEdits {
|
||||||
|
edits, ok := rawEdits.([]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
for _, e := range edits {
|
||||||
|
em, ok := e.(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.NotEqual(t, "clear_thinking_20251015", em["type"], "clear_thinking_20251015 应被移除")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterSignatureSensitiveBlocksForRetry_PreservesNonThinkingStrategies(t *testing.T) {
|
||||||
|
input := []byte(`{
|
||||||
|
"thinking":{"type":"enabled"},
|
||||||
|
"context_management":{"edits":[{"type":"clear_thinking_20251015"},{"type":"other_edit"}]},
|
||||||
|
"messages":[
|
||||||
|
{"role":"assistant","content":[
|
||||||
|
{"type":"thinking","thinking":"t","signature":"s"}
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
out := FilterSignatureSensitiveBlocksForRetry(input)
|
||||||
|
|
||||||
|
var req map[string]any
|
||||||
|
require.NoError(t, json.Unmarshal(out, &req))
|
||||||
|
|
||||||
|
cm, ok := req["context_management"].(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
edits, ok := cm["edits"].([]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Len(t, edits, 1, "仅移除 clear_thinking_20251015,保留 other_edit")
|
||||||
|
edit0, ok := edits[0].(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, "other_edit", edit0["type"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterSignatureSensitiveBlocksForRetry_NoThinkingField_ContextManagementUntouched(t *testing.T) {
|
||||||
|
// 没有顶层 thinking 字段时,context_management 不应被修改
|
||||||
|
input := []byte(`{
|
||||||
|
"context_management":{"edits":[{"type":"clear_thinking_20251015"}]},
|
||||||
|
"messages":[
|
||||||
|
{"role":"assistant","content":[
|
||||||
|
{"type":"thinking","thinking":"t","signature":"s"}
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
out := FilterSignatureSensitiveBlocksForRetry(input)
|
||||||
|
|
||||||
|
var req map[string]any
|
||||||
|
require.NoError(t, json.Unmarshal(out, &req))
|
||||||
|
cm, ok := req["context_management"].(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
edits, ok := cm["edits"].([]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Len(t, edits, 1, "无顶层 thinking 时 context_management 不应被修改")
|
||||||
|
}
|
||||||
|
|
||||||
// ============ Group 7: ParseGatewayRequest 补充单元测试 ============
|
// ============ Group 7: ParseGatewayRequest 补充单元测试 ============
|
||||||
|
|
||||||
// Task 7.1 — 类型校验边界测试
|
// Task 7.1 — 类型校验边界测试
|
||||||
|
|||||||
Reference in New Issue
Block a user