Merge pull request #864 from StarryKira/fix/clear-thinking-context-management

[Fix] Fix issue #851
This commit is contained in:
Wesley Liddick
2026-03-09 09:02:58 +08:00
committed by GitHub
2 changed files with 274 additions and 0 deletions

View File

@@ -259,6 +259,7 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
if !hasEmptyContent && !containsThinkingBlocks {
if topThinking := gjson.Get(jsonStr, "thinking"); topThinking.Exists() {
if out, err := sjson.DeleteBytes(body, "thinking"); err == nil {
out = removeThinkingDependentContextStrategies(out)
return out
}
return body
@@ -396,6 +397,10 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
} else {
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 {
msgsBytes, err := json.Marshal(messages)
@@ -410,6 +415,49 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
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
// signature/thought_signature validation issues involving tool blocks.
//
@@ -445,6 +493,28 @@ func FilterSignatureSensitiveBlocksForRetry(body []byte) []byte {
if _, exists := req["thinking"]; exists {
delete(req, "thinking")
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)

View File

@@ -439,6 +439,210 @@ func TestFilterSignatureSensitiveBlocksForRetry_DowngradesTools(t *testing.T) {
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 补充单元测试 ============
// Task 7.1 — 类型校验边界测试