From 70560d53718f9e8ed2c70d15bc26b043c9b562b0 Mon Sep 17 00:00:00 2001 From: Clansty Date: Sun, 29 Mar 2026 02:22:24 +0800 Subject: [PATCH] feat: add IncludeModelName option to channel affinity rules for per-model affinity tracking --- service/channel_affinity.go | 21 +++++++++++++++---- service/channel_affinity_template_test.go | 2 +- .../channel_affinity_setting.go | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/service/channel_affinity.go b/service/channel_affinity.go index 9f89585f..e09cb01f 100644 --- a/service/channel_affinity.go +++ b/service/channel_affinity.go @@ -166,12 +166,22 @@ func GetChannelAffinityCacheStats() ChannelAffinityCacheStats { unknown++ continue } - if rule.IncludeUsingGroup { + if rule.IncludeModelName { if len(parts) < 3 { unknown++ continue } } + if rule.IncludeUsingGroup { + minParts := 3 + if rule.IncludeModelName { + minParts = 4 + } + if len(parts) < minParts { + unknown++ + continue + } + } byRuleName[ruleName]++ } @@ -319,11 +329,14 @@ func extractChannelAffinityValue(c *gin.Context, src operation_setting.ChannelAf } } -func buildChannelAffinityCacheKeySuffix(rule operation_setting.ChannelAffinityRule, usingGroup string, affinityValue string) string { - parts := make([]string, 0, 3) +func buildChannelAffinityCacheKeySuffix(rule operation_setting.ChannelAffinityRule, modelName string, usingGroup string, affinityValue string) string { + parts := make([]string, 0, 4) if rule.IncludeRuleName && rule.Name != "" { parts = append(parts, rule.Name) } + if rule.IncludeModelName && modelName != "" { + parts = append(parts, modelName) + } if rule.IncludeUsingGroup && usingGroup != "" { parts = append(parts, usingGroup) } @@ -573,7 +586,7 @@ func GetPreferredChannelByAffinity(c *gin.Context, modelName string, usingGroup if ttlSeconds <= 0 { ttlSeconds = setting.DefaultTTLSeconds } - cacheKeySuffix := buildChannelAffinityCacheKeySuffix(rule, usingGroup, affinityValue) + cacheKeySuffix := buildChannelAffinityCacheKeySuffix(rule, modelName, usingGroup, affinityValue) cacheKeyFull := channelAffinityCacheNamespace + ":" + cacheKeySuffix setChannelAffinityContext(c, channelAffinityMeta{ CacheKey: cacheKeyFull, diff --git a/service/channel_affinity_template_test.go b/service/channel_affinity_template_test.go index 264f9122..033cbd83 100644 --- a/service/channel_affinity_template_test.go +++ b/service/channel_affinity_template_test.go @@ -193,7 +193,7 @@ func TestChannelAffinityHitCodexTemplatePassHeadersEffective(t *testing.T) { require.NotNil(t, codexRule) affinityValue := fmt.Sprintf("pc-hit-%d", time.Now().UnixNano()) - cacheKeySuffix := buildChannelAffinityCacheKeySuffix(*codexRule, "default", affinityValue) + cacheKeySuffix := buildChannelAffinityCacheKeySuffix(*codexRule, "gpt-5", "default", affinityValue) cache := getChannelAffinityCache() require.NoError(t, cache.SetWithTTL(cacheKeySuffix, 9527, time.Minute)) diff --git a/setting/operation_setting/channel_affinity_setting.go b/setting/operation_setting/channel_affinity_setting.go index 74213e99..ebe00f44 100644 --- a/setting/operation_setting/channel_affinity_setting.go +++ b/setting/operation_setting/channel_affinity_setting.go @@ -23,6 +23,7 @@ type ChannelAffinityRule struct { SkipRetryOnFailure bool `json:"skip_retry_on_failure,omitempty"` IncludeUsingGroup bool `json:"include_using_group"` + IncludeModelName bool `json:"include_model_name"` IncludeRuleName bool `json:"include_rule_name"` }