feat: 添加 Anthropic 缓存 TTL 注入开关

This commit is contained in:
shaw
2026-04-30 13:38:22 +08:00
parent 094e1171ef
commit 73b872998e
12 changed files with 394 additions and 54 deletions
@@ -1,13 +1,91 @@
package service
import (
"context"
"errors"
"strings"
"testing"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/claude"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
)
type gatewayTTLSettingRepo struct {
data map[string]string
}
func (r *gatewayTTLSettingRepo) Get(context.Context, string) (*Setting, error) {
return nil, ErrSettingNotFound
}
func (r *gatewayTTLSettingRepo) GetValue(_ context.Context, key string) (string, error) {
if r == nil {
return "", ErrSettingNotFound
}
v, ok := r.data[key]
if !ok {
return "", ErrSettingNotFound
}
return v, nil
}
func (r *gatewayTTLSettingRepo) Set(_ context.Context, key, value string) error {
if r == nil {
return errors.New("setting repo is nil")
}
if r.data == nil {
r.data = map[string]string{}
}
r.data[key] = value
return nil
}
func (r *gatewayTTLSettingRepo) GetMultiple(_ context.Context, keys []string) (map[string]string, error) {
result := make(map[string]string)
if r == nil {
return result, nil
}
for _, key := range keys {
if v, ok := r.data[key]; ok {
result[key] = v
}
}
return result, nil
}
func (r *gatewayTTLSettingRepo) SetMultiple(_ context.Context, settings map[string]string) error {
if r == nil {
return errors.New("setting repo is nil")
}
if r.data == nil {
r.data = map[string]string{}
}
for key, value := range settings {
r.data[key] = value
}
return nil
}
func (r *gatewayTTLSettingRepo) GetAll(context.Context) (map[string]string, error) {
result := make(map[string]string)
if r == nil {
return result, nil
}
for key, value := range r.data {
result[key] = value
}
return result, nil
}
func (r *gatewayTTLSettingRepo) Delete(_ context.Context, key string) error {
if r != nil {
delete(r.data, key)
}
return nil
}
func assertJSONTokenOrder(t *testing.T, body string, tokens ...string) {
t.Helper()
@@ -71,3 +149,60 @@ func TestEnforceCacheControlLimit_PreservesTopLevelFieldOrder(t *testing.T) {
assertJSONTokenOrder(t, resultStr, `"alpha"`, `"system"`, `"messages"`, `"omega"`)
require.Equal(t, 4, strings.Count(resultStr, `"cache_control"`))
}
func TestInjectAnthropicCacheControlTTL1h_OnlyUpdatesExistingEphemeralCacheControl(t *testing.T) {
body := []byte(`{"alpha":1,"cache_control":{"type":"ephemeral"},"system":[{"type":"text","text":"sys","cache_control":{"type":"ephemeral","ttl":"5m"}},{"type":"text","text":"plain"}],"messages":[{"role":"user","content":[{"type":"text","text":"hi","cache_control":{"type":"ephemeral"}},{"type":"text","text":"non","cache_control":{"type":"persistent","ttl":"5m"}}]}],"tools":[{"name":"a","input_schema":{},"cache_control":{"type":"ephemeral"}}],"omega":2}`)
result := injectAnthropicCacheControlTTL1h(body)
resultStr := string(result)
assertJSONTokenOrder(t, resultStr, `"alpha"`, `"cache_control"`, `"system"`, `"messages"`, `"tools"`, `"omega"`)
require.Equal(t, "1h", gjson.GetBytes(result, "cache_control.ttl").String())
require.Equal(t, "1h", gjson.GetBytes(result, "system.0.cache_control.ttl").String())
require.False(t, gjson.GetBytes(result, "system.1.cache_control").Exists())
require.Equal(t, "1h", gjson.GetBytes(result, "messages.0.content.0.cache_control.ttl").String())
require.Equal(t, "5m", gjson.GetBytes(result, "messages.0.content.1.cache_control.ttl").String())
require.Equal(t, "1h", gjson.GetBytes(result, "tools.0.cache_control.ttl").String())
}
func TestGatewayCacheTTLGlobalSetting_TargetResolution(t *testing.T) {
repo := &gatewayTTLSettingRepo{data: map[string]string{
SettingKeyEnableAnthropicCacheTTL1hInjection: "true",
}}
gatewayForwardingCache.Store(&cachedGatewayForwardingSettings{})
svc := &GatewayService{
settingService: NewSettingService(repo, &config.Config{}),
}
account := &Account{Platform: PlatformAnthropic, Type: AccountTypeOAuth}
target, ok := svc.resolveCacheTTLUsageOverrideTarget(context.Background(), account)
require.True(t, ok)
require.Equal(t, cacheTTLTarget5m, target)
account.Extra = map[string]any{
"cache_ttl_override_enabled": true,
"cache_ttl_override_target": "1h",
}
target, ok = svc.resolveCacheTTLUsageOverrideTarget(context.Background(), account)
require.True(t, ok)
require.Equal(t, cacheTTLTarget1h, target)
}
func TestGatewayCacheTTLGlobalSetting_RequestInjectionScope(t *testing.T) {
repo := &gatewayTTLSettingRepo{data: map[string]string{
SettingKeyEnableAnthropicCacheTTL1hInjection: "true",
}}
gatewayForwardingCache.Store(&cachedGatewayForwardingSettings{})
svc := &GatewayService{
settingService: NewSettingService(repo, &config.Config{}),
}
require.True(t, svc.shouldInjectAnthropicCacheTTL1h(context.Background(), &Account{Platform: PlatformAnthropic, Type: AccountTypeOAuth}))
require.True(t, svc.shouldInjectAnthropicCacheTTL1h(context.Background(), &Account{Platform: PlatformAnthropic, Type: AccountTypeSetupToken}))
require.False(t, svc.shouldInjectAnthropicCacheTTL1h(context.Background(), &Account{Platform: PlatformAnthropic, Type: AccountTypeAPIKey}))
require.False(t, svc.shouldInjectAnthropicCacheTTL1h(context.Background(), &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth}))
repo.data[SettingKeyEnableAnthropicCacheTTL1hInjection] = "false"
gatewayForwardingCache.Store(&cachedGatewayForwardingSettings{})
require.False(t, svc.shouldInjectAnthropicCacheTTL1h(context.Background(), &Account{Platform: PlatformAnthropic, Type: AccountTypeOAuth}))
}