fix(bedrock): strip context_management when beta is removed

This commit is contained in:
wucm667 2026-05-25 14:15:39 +08:00
parent 63b0631a58
commit a9c7a3a095
2 changed files with 83 additions and 0 deletions

View File

@ -185,6 +185,7 @@ func BuildBedrockURL(region, modelID string, stream bool) string {
// 5. 清理 cache_control 中 Bedrock 不支持的字段scope, ttl
// 6. 修复 thinking 字段兼容性Opus 4.7 仅支持 adaptiveenabled 需要 budget_tokens
// 7. 清理 tool_use.id / tool_use_id 中 Bedrock 不接受的字符
// 8. 根据最终 Bedrock beta tokens 剥离不再支持的 beta 字段
func PrepareBedrockRequestBody(body []byte, modelID string, betaHeader string) ([]byte, error) {
betaTokens := ResolveBedrockBetaTokens(betaHeader, body, modelID)
return PrepareBedrockRequestBodyWithTokens(body, modelID, betaTokens, false)
@ -195,6 +196,9 @@ func PrepareBedrockRequestBody(body []byte, modelID string, betaHeader string) (
func PrepareBedrockRequestBodyWithTokens(body []byte, modelID string, betaTokens []string, ccCompat bool) ([]byte, error) {
var err error
betaTokens = filterBedrockBetaTokens(betaTokens)
body = sanitizeBedrockFieldsForBetaTokens(body, betaTokens)
// 注入 anthropic_versionBedrock 要求)
body, err = sjson.SetBytes(body, "anthropic_version", "bedrock-2023-05-31")
if err != nil {
@ -471,6 +475,8 @@ var bedrockSupportedBetaTokens = map[string]bool{
"tool-examples-2025-10-29": true,
}
const bedrockContextManagementBetaToken = "context-management-2025-06-27"
// bedrockBetaTokenTransforms 定义 Bedrock Invoke 特有的 beta 头转换规则
// Anthropic 直接 API 使用通用头Bedrock Invoke 需要特定的替代头
var bedrockBetaTokenTransforms = map[string]string{
@ -617,6 +623,22 @@ func filterBedrockBetaTokens(tokens []string) []string {
return result
}
func sanitizeBedrockFieldsForBetaTokens(body []byte, betaTokens []string) []byte {
if !containsBedrockBetaToken(betaTokens, bedrockContextManagementBetaToken) && gjson.GetBytes(body, "context_management").Exists() {
body, _ = sjson.DeleteBytes(body, "context_management")
}
return body
}
func containsBedrockBetaToken(tokens []string, target string) bool {
for _, token := range tokens {
if token == target {
return true
}
}
return false
}
// bedrockToolUseIDRe 匹配 Bedrock 允许的 tool_use ID 字符(字母、数字、下划线、连字符)
var bedrockToolUseIDRe = regexp.MustCompile(`[^a-zA-Z0-9_-]`)

View File

@ -378,6 +378,67 @@ func TestPrepareBedrockRequestBody_BetaFiltering(t *testing.T) {
})
}
func TestPrepareBedrockRequestBodyWithTokens_ContextManagementRequiresSupportedBeta(t *testing.T) {
modelID := "us.anthropic.claude-opus-4-6-v1"
t.Run("strips context_management when final tokens omit context-management beta", func(t *testing.T) {
input := `{
"messages":[{"role":"user","content":"hi"}],
"max_tokens":100,
"context_management":{"edits":[{"type":"clear_thinking_20251015","keep":"all"}]}
}`
betaTokens := []string{"context-1m-2025-08-07"}
originalTokens := append([]string(nil), betaTokens...)
result, err := PrepareBedrockRequestBodyWithTokens([]byte(input), modelID, betaTokens, false)
require.NoError(t, err)
assert.False(t, gjson.GetBytes(result, "context_management").Exists())
assert.Equal(t, originalTokens, betaTokens)
assert.Equal(t, originalTokens, bedrockAnthropicBetaNames(result))
})
t.Run("leaves body without context_management otherwise intact", func(t *testing.T) {
input := `{"messages":[{"role":"user","content":"hi"}],"max_tokens":100}`
result, err := PrepareBedrockRequestBodyWithTokens([]byte(input), modelID, nil, false)
require.NoError(t, err)
assert.False(t, gjson.GetBytes(result, "context_management").Exists())
assert.False(t, gjson.GetBytes(result, "anthropic_beta").Exists())
assert.Equal(t, "hi", gjson.GetBytes(result, "messages.0.content").String())
assert.Equal(t, int64(100), gjson.GetBytes(result, "max_tokens").Int())
})
t.Run("filters explicit unsupported context-management beta and strips field", func(t *testing.T) {
input := `{
"messages":[{"role":"user","content":"hi"}],
"max_tokens":100,
"context_management":{"edits":[{"type":"clear_thinking_20251015","keep":"all"}]}
}`
result, err := PrepareBedrockRequestBodyWithTokens(
[]byte(input),
modelID,
[]string{bedrockContextManagementBetaToken, "context-1m-2025-08-07"},
false,
)
require.NoError(t, err)
assert.False(t, gjson.GetBytes(result, "context_management").Exists())
assert.Equal(t, []string{"context-1m-2025-08-07"}, bedrockAnthropicBetaNames(result))
})
}
func bedrockAnthropicBetaNames(body []byte) []string {
arr := gjson.GetBytes(body, "anthropic_beta").Array()
names := make([]string, len(arr))
for i, token := range arr {
names[i] = token.String()
}
return names
}
func TestBedrockCrossRegionPrefix(t *testing.T) {
tests := []struct {
region string