Merge pull request #2751 from wucm667/fix/bedrock-strip-context-management-when-beta-removed
fix(bedrock): v0.1.130 回归 — beta token 被移除时同步剥离 context_management 字段
This commit is contained in:
commit
b9f421d647
@ -185,6 +185,7 @@ func BuildBedrockURL(region, modelID string, stream bool) string {
|
|||||||
// 5. 清理 cache_control 中 Bedrock 不支持的字段(scope, ttl)
|
// 5. 清理 cache_control 中 Bedrock 不支持的字段(scope, ttl)
|
||||||
// 6. 修复 thinking 字段兼容性(Opus 4.7 仅支持 adaptive,enabled 需要 budget_tokens)
|
// 6. 修复 thinking 字段兼容性(Opus 4.7 仅支持 adaptive,enabled 需要 budget_tokens)
|
||||||
// 7. 清理 tool_use.id / tool_use_id 中 Bedrock 不接受的字符
|
// 7. 清理 tool_use.id / tool_use_id 中 Bedrock 不接受的字符
|
||||||
|
// 8. 根据最终 Bedrock beta tokens 剥离不再支持的 beta 字段
|
||||||
func PrepareBedrockRequestBody(body []byte, modelID string, betaHeader string) ([]byte, error) {
|
func PrepareBedrockRequestBody(body []byte, modelID string, betaHeader string) ([]byte, error) {
|
||||||
betaTokens := ResolveBedrockBetaTokens(betaHeader, body, modelID)
|
betaTokens := ResolveBedrockBetaTokens(betaHeader, body, modelID)
|
||||||
return PrepareBedrockRequestBodyWithTokens(body, modelID, betaTokens, false)
|
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) {
|
func PrepareBedrockRequestBodyWithTokens(body []byte, modelID string, betaTokens []string, ccCompat bool) ([]byte, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
betaTokens = filterBedrockBetaTokens(betaTokens)
|
||||||
|
body = sanitizeBedrockFieldsForBetaTokens(body, betaTokens)
|
||||||
|
|
||||||
// 注入 anthropic_version(Bedrock 要求)
|
// 注入 anthropic_version(Bedrock 要求)
|
||||||
body, err = sjson.SetBytes(body, "anthropic_version", "bedrock-2023-05-31")
|
body, err = sjson.SetBytes(body, "anthropic_version", "bedrock-2023-05-31")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -471,6 +475,8 @@ var bedrockSupportedBetaTokens = map[string]bool{
|
|||||||
"tool-examples-2025-10-29": true,
|
"tool-examples-2025-10-29": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bedrockContextManagementBetaToken = "context-management-2025-06-27"
|
||||||
|
|
||||||
// bedrockBetaTokenTransforms 定义 Bedrock Invoke 特有的 beta 头转换规则
|
// bedrockBetaTokenTransforms 定义 Bedrock Invoke 特有的 beta 头转换规则
|
||||||
// Anthropic 直接 API 使用通用头,Bedrock Invoke 需要特定的替代头
|
// Anthropic 直接 API 使用通用头,Bedrock Invoke 需要特定的替代头
|
||||||
var bedrockBetaTokenTransforms = map[string]string{
|
var bedrockBetaTokenTransforms = map[string]string{
|
||||||
@ -617,6 +623,22 @@ func filterBedrockBetaTokens(tokens []string) []string {
|
|||||||
return result
|
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 字符(字母、数字、下划线、连字符)
|
// bedrockToolUseIDRe 匹配 Bedrock 允许的 tool_use ID 字符(字母、数字、下划线、连字符)
|
||||||
var bedrockToolUseIDRe = regexp.MustCompile(`[^a-zA-Z0-9_-]`)
|
var bedrockToolUseIDRe = regexp.MustCompile(`[^a-zA-Z0-9_-]`)
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
func TestBedrockCrossRegionPrefix(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
region string
|
region string
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user