Skip to content

Commit c546b56

Browse files
committed
feat: 升级项目依赖到最新稳定版本并优化动态权重精度
依赖升级: - 升级 Go 依赖:sonic v1.15.0, mimetype v1.4.13, redis v9.17.3 - 升级 Vue 依赖:axios v1.13.4, vue-router v5.0.2, @types/node v25.2.0, prettier v3.8.1 - 升级前端开发依赖:@vitejs/plugin-vue v6.0.4, @vueuse/core v14.2.0, vue-tsc v3.2.4 功能优化: - 优化动态权重计算精度,从整数改为保留1位小数的浮点数 - 更新有效权重最小值从1调整为0.1,提供更精细的权重控制 - 改进权重显示格式,统一使用1位小数展示 代码改进: - 优化子组权重编辑模态框的数值处理逻辑 - 改进组表单模态框的权重验证和显示 - 增强子组表格的权重格式化显示功能 - 更新类型定义以支持浮点数权重值 - 完善动态权重管理器的测试用例覆盖 脚本优化: - 改进 PGO 性能分析脚本的错误处理和输出格式 国际化: - 补充日语翻译文本 测试增强: - 扩展动态权重测试用例,覆盖浮点数精度场景 - 增加边界值和精度验证测试
1 parent 74ec3a7 commit c546b56

25 files changed

+2651
-180
lines changed

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ require (
1515
github.com/joho/godotenv v1.5.1
1616
github.com/klauspost/compress v1.18.3
1717
github.com/nicksnyder/go-i18n/v2 v2.6.1
18-
github.com/redis/go-redis/v9 v9.17.2
18+
github.com/redis/go-redis/v9 v9.17.3
1919
github.com/refraction-networking/utls v1.8.2
2020
github.com/sirupsen/logrus v1.9.4
2121
github.com/stretchr/testify v1.11.1
@@ -33,14 +33,14 @@ require (
3333
require (
3434
filippo.io/edwards25519 v1.1.0 // indirect
3535
github.com/bytedance/gopkg v0.1.3 // indirect
36-
github.com/bytedance/sonic v1.14.2 // indirect
37-
github.com/bytedance/sonic/loader v0.4.0 // indirect
36+
github.com/bytedance/sonic v1.15.0 // indirect
37+
github.com/bytedance/sonic/loader v0.5.0 // indirect
3838
github.com/cespare/xxhash/v2 v2.3.0 // indirect
3939
github.com/cloudwego/base64x v0.1.6 // indirect
4040
github.com/davecgh/go-spew v1.1.1 // indirect
4141
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
4242
github.com/dustin/go-humanize v1.0.1 // indirect
43-
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
43+
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
4444
github.com/gin-contrib/sse v1.1.0 // indirect
4545
github.com/glebarez/go-sqlite v1.22.0 // indirect
4646
github.com/go-playground/locales v0.14.1 // indirect

go.sum

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
1212
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
1313
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
1414
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
15-
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
16-
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
17-
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
18-
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
15+
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
16+
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
17+
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
18+
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
1919
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
2020
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
2121
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
@@ -28,8 +28,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
2828
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
2929
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
3030
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
31-
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
32-
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
31+
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
32+
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
3333
github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI=
3434
github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw=
3535
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
@@ -119,8 +119,8 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
119119
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
120120
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
121121
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
122-
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
123-
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
122+
github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4=
123+
github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
124124
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
125125
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
126126
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=

internal/centralizedmgmt/hub_service.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ type ModelSource struct {
6363
Sort int `json:"sort"`
6464
Weight int `json:"weight"`
6565
HealthScore float64 `json:"health_score"`
66-
EffectiveWeight int `json:"effective_weight"`
66+
EffectiveWeight float64 `json:"effective_weight"` // Effective weight (1 decimal place, min 0.1)
6767
Enabled bool `json:"enabled"`
6868
}
6969

@@ -224,11 +224,13 @@ func (s *HubService) buildModelPool(ctx context.Context) ([]ModelPoolEntry, erro
224224
healthScore := s.calculateGroupHealthScore(&group)
225225
baseWeight := 100 // Default weight for standard groups
226226

227-
// Calculate effective weight with minimum of 1 to allow recovery
227+
// Calculate effective weight with 1 decimal place precision, minimum 0.1
228228
// Even unhealthy groups get minimum weight to receive occasional requests
229-
effectiveWeight := int(float64(baseWeight) * healthScore)
230-
if effectiveWeight < 1 {
231-
effectiveWeight = 1
229+
effectiveWeight := float64(baseWeight) * healthScore
230+
// Round to 1 decimal place
231+
effectiveWeight = math.Round(effectiveWeight*10) / 10
232+
if effectiveWeight < 0.1 {
233+
effectiveWeight = 0.1
232234
}
233235

234236
source := ModelSource{
@@ -833,7 +835,8 @@ func (s *HubService) selectFromSources(sources []ModelSource) (*models.Group, er
833835
// Multiple sources with same priority - use weighted random selection
834836
weights := make([]int, len(topSources))
835837
for i, source := range topSources {
836-
weights[i] = source.EffectiveWeight
838+
// Convert float effective weight to int for weighted selection
839+
weights[i] = services.GetEffectiveWeightForSelection(source.EffectiveWeight)
837840
}
838841

839842
idx := utils.WeightedRandomSelect(weights)
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package i18n
2+
3+
import (
4+
"testing"
5+
)
6+
7+
// TestMessagesEnUSNotEmpty verifies that English translations are not empty
8+
func TestMessagesEnUSNotEmpty(t *testing.T) {
9+
if len(MessagesEnUS) == 0 {
10+
t.Fatal("MessagesEnUS should not be empty")
11+
}
12+
}
13+
14+
// TestMessagesZhCNNotEmpty verifies that Chinese translations are not empty
15+
func TestMessagesZhCNNotEmpty(t *testing.T) {
16+
if len(MessagesZhCN) == 0 {
17+
t.Fatal("MessagesZhCN should not be empty")
18+
}
19+
}
20+
21+
// TestMessagesJaJPNotEmpty verifies that Japanese translations are not empty
22+
func TestMessagesJaJPNotEmpty(t *testing.T) {
23+
if len(MessagesJaJP) == 0 {
24+
t.Fatal("MessagesJaJP should not be empty")
25+
}
26+
}
27+
28+
// TestTranslationKeysConsistency verifies all languages have the same keys
29+
func TestTranslationKeysConsistency(t *testing.T) {
30+
// Check if all languages have the same number of keys
31+
enCount := len(MessagesEnUS)
32+
zhCount := len(MessagesZhCN)
33+
jaCount := len(MessagesJaJP)
34+
35+
if enCount != zhCount || enCount != jaCount {
36+
t.Errorf("Translation key count mismatch: EN=%d, ZH=%d, JA=%d", enCount, zhCount, jaCount)
37+
}
38+
39+
// Check if all keys in English exist in other languages
40+
for key := range MessagesEnUS {
41+
if _, exists := MessagesZhCN[key]; !exists {
42+
t.Errorf("Key %q exists in English but missing in Chinese", key)
43+
}
44+
if _, exists := MessagesJaJP[key]; !exists {
45+
t.Errorf("Key %q exists in English but missing in Japanese", key)
46+
}
47+
}
48+
49+
// Check if all keys in Chinese exist in other languages
50+
for key := range MessagesZhCN {
51+
if _, exists := MessagesEnUS[key]; !exists {
52+
t.Errorf("Key %q exists in Chinese but missing in English", key)
53+
}
54+
if _, exists := MessagesJaJP[key]; !exists {
55+
t.Errorf("Key %q exists in Chinese but missing in Japanese", key)
56+
}
57+
}
58+
59+
// Check if all keys in Japanese exist in other languages
60+
for key := range MessagesJaJP {
61+
if _, exists := MessagesEnUS[key]; !exists {
62+
t.Errorf("Key %q exists in Japanese but missing in English", key)
63+
}
64+
if _, exists := MessagesZhCN[key]; !exists {
65+
t.Errorf("Key %q exists in Japanese but missing in Chinese", key)
66+
}
67+
}
68+
}
69+
70+
// TestTranslationValuesNotEmpty verifies that translation values are not empty
71+
func TestTranslationValuesNotEmpty(t *testing.T) {
72+
tests := []struct {
73+
name string
74+
messages map[string]string
75+
}{
76+
{"English", MessagesEnUS},
77+
{"Chinese", MessagesZhCN},
78+
{"Japanese", MessagesJaJP},
79+
}
80+
81+
for _, tt := range tests {
82+
t.Run(tt.name, func(t *testing.T) {
83+
for key, value := range tt.messages {
84+
if value == "" {
85+
t.Errorf("Translation value for key %q is empty in %s", key, tt.name)
86+
}
87+
}
88+
})
89+
}
90+
}
91+
92+
// TestSpecificTranslationKeys verifies that specific important keys exist
93+
func TestSpecificTranslationKeys(t *testing.T) {
94+
requiredKeys := []string{
95+
"hub.access_key.created",
96+
"hub.access_key.updated",
97+
"hub.access_key.deleted",
98+
"hub.model_pool.updated",
99+
"hub.routing.model_required",
100+
"channel.type.openai",
101+
"relay_format.openai_chat",
102+
}
103+
104+
for _, key := range requiredKeys {
105+
if _, exists := MessagesEnUS[key]; !exists {
106+
t.Errorf("Required key %q missing in English translations", key)
107+
}
108+
if _, exists := MessagesZhCN[key]; !exists {
109+
t.Errorf("Required key %q missing in Chinese translations", key)
110+
}
111+
if _, exists := MessagesJaJP[key]; !exists {
112+
t.Errorf("Required key %q missing in Japanese translations", key)
113+
}
114+
}
115+
}
116+
117+
// TestChannelTypeTranslations verifies channel type translations exist
118+
func TestChannelTypeTranslations(t *testing.T) {
119+
channelTypes := []string{
120+
"channel.type.openai",
121+
"channel.type.anthropic",
122+
"channel.type.gemini",
123+
"channel.type.codex",
124+
"channel.type.azure",
125+
"channel.type.custom",
126+
}
127+
128+
for _, key := range channelTypes {
129+
if _, exists := MessagesEnUS[key]; !exists {
130+
t.Errorf("Channel type key %q missing in English", key)
131+
}
132+
if _, exists := MessagesZhCN[key]; !exists {
133+
t.Errorf("Channel type key %q missing in Chinese", key)
134+
}
135+
if _, exists := MessagesJaJP[key]; !exists {
136+
t.Errorf("Channel type key %q missing in Japanese", key)
137+
}
138+
}
139+
}
140+
141+
// TestRelayFormatTranslations verifies relay format translations exist
142+
func TestRelayFormatTranslations(t *testing.T) {
143+
relayFormats := []string{
144+
"relay_format.openai_chat",
145+
"relay_format.openai_completion",
146+
"relay_format.claude",
147+
"relay_format.codex",
148+
"relay_format.gemini",
149+
"relay_format.unknown",
150+
}
151+
152+
for _, key := range relayFormats {
153+
if _, exists := MessagesEnUS[key]; !exists {
154+
t.Errorf("Relay format key %q missing in English", key)
155+
}
156+
if _, exists := MessagesZhCN[key]; !exists {
157+
t.Errorf("Relay format key %q missing in Chinese", key)
158+
}
159+
if _, exists := MessagesJaJP[key]; !exists {
160+
t.Errorf("Relay format key %q missing in Japanese", key)
161+
}
162+
}
163+
}

internal/handler/group_handler.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,7 @@ type ModelRedirectDynamicWeightResponse struct {
760760
type ModelRedirectTargetWeight struct {
761761
Model string `json:"model"`
762762
BaseWeight int `json:"base_weight"`
763-
EffectiveWeight int `json:"effective_weight"`
763+
EffectiveWeight float64 `json:"effective_weight"` // Effective weight (1 decimal place, min 0.1)
764764
HealthScore float64 `json:"health_score"`
765765
SuccessRate float64 `json:"success_rate"`
766766
RequestCount int64 `json:"request_count"`
@@ -841,9 +841,9 @@ func (s *Server) GetModelRedirectDynamicWeights(c *gin.Context) {
841841
// No dynamic weight data, use base weight for effective weight
842842
// For disabled targets, effective weight should be 0
843843
if target.IsEnabled() {
844-
targets[i].EffectiveWeight = target.GetWeight()
844+
targets[i].EffectiveWeight = float64(target.GetWeight())
845845
} else {
846-
targets[i].EffectiveWeight = 0
846+
targets[i].EffectiveWeight = 0.0
847847
}
848848
targets[i].HealthScore = 1.0
849849
}

internal/i18n/locales/ja-JP.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,28 @@ var MessagesJaJP = map[string]string{
254254
"binding.group_already_bound": "このグループは既に別のサイトにバインドされています",
255255
"binding.must_unbind_before_delete_group": "グループを削除する前にサイトとのバインドを解除してください",
256256
"binding.must_unbind_before_delete_site": "サイトを削除する前にすべてのグループ({{.count}}個)のバインドを解除してください",
257+
258+
// Channel types
259+
"channel.openai": "OpenAI",
260+
"channel.anthropic": "Anthropic",
261+
"channel.gemini": "Gemini",
262+
"channel.codex": "Codex",
263+
"channel.azure": "Azure",
264+
"channel.custom": "カスタム",
265+
266+
// Relay formats
267+
"relay_format.openai_chat": "OpenAI チャット補完",
268+
"relay_format.openai_completion": "OpenAI テキスト補完",
269+
"relay_format.claude": "Claude メッセージ",
270+
"relay_format.codex": "Codex レスポンス",
271+
"relay_format.openai_image": "OpenAI 画像生成",
272+
"relay_format.openai_image_edit": "OpenAI 画像編集",
273+
"relay_format.openai_audio_transcription": "OpenAI 音声文字起こし",
274+
"relay_format.openai_audio_translation": "OpenAI 音声翻訳",
275+
"relay_format.openai_audio_speech": "OpenAI 音声合成",
276+
"relay_format.openai_embedding": "OpenAI 埋め込み",
277+
"relay_format.openai_moderation": "OpenAI モデレーション",
278+
"relay_format.gemini": "Gemini",
257279
}
258280

259281
func init() {

0 commit comments

Comments
 (0)