feat: 适配 sonnet5

This commit is contained in:
shaw
2026-07-01 11:32:22 +08:00
parent e2d21c35af
commit db0414233c
6 changed files with 127 additions and 13 deletions
+1
View File
@@ -136,6 +136,7 @@ var DefaultBedrockModelMapping = map[string]string{
"claude-opus-4-1": "us.anthropic.claude-opus-4-1-20250805-v1:0",
"claude-opus-4-20250514": "us.anthropic.claude-opus-4-20250514-v1:0",
// Claude Sonnet
"claude-sonnet-5": "us.anthropic.claude-sonnet-5-v1",
"claude-sonnet-4-6-thinking": "us.anthropic.claude-sonnet-4-6",
"claude-sonnet-4-6": "us.anthropic.claude-sonnet-4-6",
"claude-sonnet-4-5": "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
+12 -12
View File
@@ -28,8 +28,8 @@ import (
// user-authored prose like "Today is foo." or "His date is 2026-06-30." from
// being touched.
var (
datelineRegexHyphen = regexp.MustCompile("Today(['’ʼʹ])s date is (\\d{4})-(\\d{2})-(\\d{2})\\.")
datelineRegexSlash = regexp.MustCompile("Today(['’ʼʹ])s date is (\\d{4})/(\\d{2})/(\\d{2})\\.")
datelineRegexHyphen = regexp.MustCompile(`Today(['’ʼʹ])s date is (\d{4})-(\d{2})-(\d{2})\.`)
datelineRegexSlash = regexp.MustCompile(`Today(['’ʼʹ])s date is (\d{4})/(\d{2})/(\d{2})\.`)
)
// systemReminderRegex matches a <system-reminder> block. The dateline lives in
@@ -66,10 +66,10 @@ func apostropheVariant(r rune) string {
}
type datelineMatch struct {
start, end int
apoRune rune
sep string
year, month, day string
start, end int
apoRune rune
sep string
year, month, day string
}
func collectMatches(text string, re *regexp.Regexp, sep string) []datelineMatch {
@@ -124,8 +124,8 @@ func NormalizeText(text string) (string, []DatelineHit) {
// Already canonical: no rewrite, no hit.
continue
}
b.WriteString(text[prev:m.start])
b.WriteString(canonical)
_, _ = b.WriteString(text[prev:m.start])
_, _ = b.WriteString(canonical)
prev = m.end
changed = true
hits = append(hits, DatelineHit{
@@ -136,7 +136,7 @@ func NormalizeText(text string) (string, []DatelineHit) {
if !changed {
return text, nil
}
b.WriteString(text[prev:])
_, _ = b.WriteString(text[prev:])
return b.String(), hits
}
@@ -159,20 +159,20 @@ func normalizeSystemReminderScopedText(text string) (string, []DatelineHit) {
var hits []DatelineHit
changed := false
for _, loc := range locs {
b.WriteString(text[prev:loc[0]])
_, _ = b.WriteString(text[prev:loc[0]])
block := text[loc[0]:loc[1]]
normalized, blockHits := NormalizeText(block)
if normalized != block {
changed = true
}
b.WriteString(normalized)
_, _ = b.WriteString(normalized)
hits = append(hits, blockHits...)
prev = loc[1]
}
if !changed {
return text, nil
}
b.WriteString(text[prev:])
_, _ = b.WriteString(text[prev:])
return b.String(), hits
}
+6
View File
@@ -146,6 +146,12 @@ var DefaultModels = []Model{
DisplayName: "Claude Opus 4.8",
CreatedAt: "2026-05-29T00:00:00Z",
},
{
ID: "claude-sonnet-5",
Type: "model",
DisplayName: "Claude Sonnet 5",
CreatedAt: "2026-07-01T00:00:00Z",
},
{
ID: "claude-sonnet-4-6",
Type: "model",
@@ -242,3 +242,72 @@ func TestIsCountTokensUnsupported404(t *testing.T) {
})
}
}
// TestDefaultBetaPolicy_Context1M_Sonnet5Whitelist 验证默认策略下 context-1m-2025-08-07 的分模型行为:
// - claude-sonnet-5 及后续版本:pass(放行),保留 1M 上下文能力
// - 其他 sonnet 版本(4.x 及以下)、opus、haiku:filter(过滤),因为上游不支持
func TestDefaultBetaPolicy_Context1M_Sonnet5Whitelist(t *testing.T) {
settings := DefaultBetaPolicySettings()
// 找到 context-1m-2025-08-07 规则
var rule *BetaPolicyRule
for i := range settings.Rules {
if settings.Rules[i].BetaToken == "context-1m-2025-08-07" {
rule = &settings.Rules[i]
break
}
}
require.NotNil(t, rule, "default policy must include context-1m-2025-08-07 rule")
require.Equal(t, BetaPolicyActionPass, rule.Action, "primary action for whitelisted models is pass")
require.Equal(t, BetaPolicyActionFilter, rule.FallbackAction, "non-whitelisted models must be filtered")
require.NotEmpty(t, rule.ModelWhitelist, "context-1m must be scoped to sonnet-5+ via whitelist")
// 表驱动:模型 → 期望 action
// 覆盖每种上游路径下的模型 ID 变形:直连 Anthropic API、Vertex AI("@YYYYMMDD" 后缀)、
// AWS Bedrock 跨区域推理(us./eu./apac./jp./au./us-gov./global./anthropic. 前缀)。
cases := []struct {
model string
wantAction string
desc string
}{
// —— 直连 Anthropic API —— sonnet-5 系列应放行
{"claude-sonnet-5", BetaPolicyActionPass, "sonnet-5 canonical"},
{"claude-sonnet-5-20260701", BetaPolicyActionPass, "sonnet-5 dated variant matches wildcard"},
{"claude-sonnet-5-thinking", BetaPolicyActionPass, "sonnet-5 thinking variant matches wildcard"},
// —— Vertex AI 归一化后的 sonnet-5 —— 也应放行
{"claude-sonnet-5@20260701", BetaPolicyActionPass, "sonnet-5 Vertex-normalized dated form"},
// —— AWS Bedrock 各跨区域前缀 sonnet-5 —— 也应放行
{"us.anthropic.claude-sonnet-5-v1", BetaPolicyActionPass, "bedrock us. sonnet-5"},
{"eu.anthropic.claude-sonnet-5-20260701-v1:0", BetaPolicyActionPass, "bedrock eu. sonnet-5 dated"},
{"apac.anthropic.claude-sonnet-5-v1", BetaPolicyActionPass, "bedrock apac. sonnet-5"},
{"jp.anthropic.claude-sonnet-5-v1", BetaPolicyActionPass, "bedrock jp. sonnet-5"},
{"au.anthropic.claude-sonnet-5-v1", BetaPolicyActionPass, "bedrock au. sonnet-5"},
{"us-gov.anthropic.claude-sonnet-5-v1", BetaPolicyActionPass, "bedrock us-gov. sonnet-5"},
{"global.anthropic.claude-sonnet-5-v1", BetaPolicyActionPass, "bedrock global. sonnet-5"},
{"anthropic.claude-sonnet-5-v1", BetaPolicyActionPass, "bedrock no-region sonnet-5"},
// —— sonnet-4.x 及以下必须过滤 ——
{"claude-sonnet-4-6", BetaPolicyActionFilter, "sonnet-4.6 must be filtered"},
{"claude-sonnet-4-5-20250929", BetaPolicyActionFilter, "sonnet-4.5 dated must be filtered"},
{"claude-sonnet-4", BetaPolicyActionFilter, "sonnet-4 must be filtered"},
{"claude-sonnet-4-5@20250929", BetaPolicyActionFilter, "sonnet-4.5 Vertex format must be filtered"},
{"us.anthropic.claude-sonnet-4-6", BetaPolicyActionFilter, "bedrock us. sonnet-4.6 must be filtered"},
{"us.anthropic.claude-sonnet-4-5-20250929-v1:0", BetaPolicyActionFilter, "bedrock us. sonnet-4.5 must be filtered"},
// —— Opus / Haiku 必须过滤(无 1M) ——
{"claude-opus-4-8", BetaPolicyActionFilter, "opus must be filtered"},
{"claude-opus-4-7", BetaPolicyActionFilter, "opus 4.7 must be filtered"},
{"us.anthropic.claude-opus-4-8-v1", BetaPolicyActionFilter, "bedrock opus 4.8 must be filtered"},
{"claude-haiku-4-5", BetaPolicyActionFilter, "haiku must be filtered"},
{"us.anthropic.claude-haiku-4-5-20251001-v1:0", BetaPolicyActionFilter, "bedrock haiku must be filtered"},
{"claude-3-5-sonnet-20241022", BetaPolicyActionFilter, "legacy sonnet 3.5 must be filtered"},
// —— 特殊边界:不应把 "claude-sonnet-50" / "claude-sonnet-5.1" 之类意外命名误放行 ——
{"claude-sonnet-50", BetaPolicyActionFilter, "must not over-match a hypothetical sonnet-50"},
}
for _, tc := range cases {
t.Run(tc.model, func(t *testing.T) {
action, _ := resolveRuleAction(*rule, tc.model)
require.Equal(t, tc.wantAction, action, tc.desc)
})
}
}
+36 -1
View File
@@ -489,6 +489,23 @@ func DefaultRateLimit429CooldownSettings() *RateLimit429CooldownSettings {
}
// DefaultBetaPolicySettings 返回默认的 Beta 策略配置
//
// context-1m-2025-08-07 的默认策略:
// - 仅 claude-sonnet-5 及后续版本(如 claude-sonnet-5-*)在上游默认支持 1M 上下文。
// - Sonnet 4.x 及以下、Opus、Haiku 上游都不支持该 beta,透传上去会被上游 400 或降级。
// - 因此默认对 sonnet-5* 放行、其余全部过滤,与上游能力保持一致。
//
// 白名单需要覆盖每个上游路径的模型 ID 变形:
// - 直连 Anthropic API(OAuth mimic / API Key / SetupToken):模型保持客户端原样
// (如 "claude-sonnet-5"、"claude-sonnet-5-YYYYMMDD"、"claude-sonnet-5-thinking")。
// - Vertex AI:normalizeVertexAnthropicModelID 会把 "-YYYYMMDD" 后缀转成 "@YYYYMMDD"
// (如 "claude-sonnet-5@YYYYMMDD")。
// - AWS Bedrock:ResolveBedrockModelID 会输出带跨区域前缀的模型 ID
// (us./eu./apac./jp./au./us-gov./global. 或无前缀的 "anthropic." 形式)。
//
// 白名单只用后缀通配符(matchModelPattern 语义),因此每个路径都需要显式列出前缀。
// 精确匹配 "claude-sonnet-5" + 后缀 "-*" 与 "@*",可覆盖直连/Vertex 场景,同时避免误伤
// 未来可能出现的 "claude-sonnet-50" 或 "claude-sonnet-5.x" 之类的意外命名。
func DefaultBetaPolicySettings() *BetaPolicySettings {
return &BetaPolicySettings{
Rules: []BetaPolicyRule{
@@ -499,8 +516,26 @@ func DefaultBetaPolicySettings() *BetaPolicySettings {
},
{
BetaToken: "context-1m-2025-08-07",
Action: BetaPolicyActionFilter,
Action: BetaPolicyActionPass,
Scope: BetaPolicyScopeAll,
ModelWhitelist: []string{
// 直连 Anthropic API(客户端请求 model 原样)
"claude-sonnet-5",
"claude-sonnet-5-*",
// Vertex AI 走 normalizeVertexAnthropicModelID 后 "@YYYYMMDD" 格式
"claude-sonnet-5@*",
// AWS Bedrock cross-region inference profile
"us.anthropic.claude-sonnet-5*",
"eu.anthropic.claude-sonnet-5*",
"apac.anthropic.claude-sonnet-5*",
"jp.anthropic.claude-sonnet-5*",
"au.anthropic.claude-sonnet-5*",
"us-gov.anthropic.claude-sonnet-5*",
"global.anthropic.claude-sonnet-5*",
// AWS Bedrock 无 cross-region 前缀
"anthropic.claude-sonnet-5*",
},
FallbackAction: BetaPolicyActionFilter,
},
},
}
@@ -31,6 +31,7 @@ export const claudeModels = [
'claude-opus-4-7',
'claude-opus-4-8',
'claude-sonnet-4-6',
'claude-sonnet-5',
'claude-fable-5'
]
@@ -243,6 +244,7 @@ export const allModels = allModelsList.map(m => ({ value: m, label: m }))
const anthropicPresetMappings = [
{ label: 'Fable 5', from: 'claude-fable-5', to: 'claude-fable-5', color: 'bg-rose-100 text-rose-700 hover:bg-rose-200 dark:bg-rose-900/30 dark:text-rose-400' },
{ label: 'Sonnet 5', from: 'claude-sonnet-5', to: 'claude-sonnet-5', color: 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400' },
{ label: 'Sonnet 4', from: 'claude-sonnet-4-20250514', to: 'claude-sonnet-4-20250514', color: 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400' },
{ label: 'Sonnet 4.5', from: 'claude-sonnet-4-5-20250929', to: 'claude-sonnet-4-5-20250929', color: 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400' },
{ label: 'Sonnet 4.6', from: 'claude-sonnet-4-6', to: 'claude-sonnet-4-6', color: 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400' },
@@ -328,6 +330,7 @@ const bedrockPresetMappings = [
{ label: 'Opus 4.6', from: 'claude-opus-4-6', to: 'us.anthropic.claude-opus-4-6-v1', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' },
{ label: 'Opus 4.7', from: 'claude-opus-4-7', to: 'us.anthropic.claude-opus-4-7-v1', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' },
{ label: 'Opus 4.8', from: 'claude-opus-4-8', to: 'us.anthropic.claude-opus-4-8-v1', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' },
{ label: 'Sonnet 5', from: 'claude-sonnet-5', to: 'us.anthropic.claude-sonnet-5-v1', color: 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400' },
{ label: 'Sonnet 4.6', from: 'claude-sonnet-4-6', to: 'us.anthropic.claude-sonnet-4-6', color: 'bg-cyan-100 text-cyan-700 hover:bg-cyan-200 dark:bg-cyan-900/30 dark:text-cyan-400' },
{ label: 'Opus 4.5', from: 'claude-opus-4-5-thinking', to: 'us.anthropic.claude-opus-4-5-20251101-v1:0', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' },
{ label: 'Sonnet 4.5', from: 'claude-sonnet-4-5', to: 'us.anthropic.claude-sonnet-4-5-20250929-v1:0', color: 'bg-cyan-100 text-cyan-700 hover:bg-cyan-200 dark:bg-cyan-900/30 dark:text-cyan-400' },