feat(P1/P2): 完成TDD开发及P1/P2设计文档
## 设计文档 - multi_role_permission_design: 多角色权限设计 (CONDITIONAL GO) - audit_log_enhancement_design: 审计日志增强 (CONDITIONAL GO) - routing_strategy_template_design: 路由策略模板 (CONDITIONAL GO) - sso_saml_technical_research: SSO/SAML调研 (CONDITIONAL GO) - compliance_capability_package_design: 合规能力包设计 (CONDITIONAL GO) ## TDD开发成果 - IAM模块: supply-api/internal/iam/ (111个测试) - 审计日志模块: supply-api/internal/audit/ (40+测试) - 路由策略模块: gateway/internal/router/ (33+测试) - 合规能力包: gateway/internal/compliance/ + scripts/ci/compliance/ ## 规范文档 - parallel_agent_output_quality_standards: 并行Agent产出质量规范 - project_experience_summary: 项目经验总结 (v2) - 2026-04-02-p1-p2-tdd-execution-plan: TDD执行计划 ## 评审报告 - 5个CONDITIONAL GO设计文档评审报告 - fix_verification_report: 修复验证报告 - full_verification_report: 全面质量验证报告 - tdd_module_quality_verification: TDD模块质量验证 - tdd_execution_summary: TDD执行总结 依据: Superpowers执行框架 + TDD规范
This commit is contained in:
74
gateway/internal/router/scoring/scoring_model.go
Normal file
74
gateway/internal/router/scoring/scoring_model.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package scoring
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// ProviderMetrics Provider评分指标
|
||||
type ProviderMetrics struct {
|
||||
Name string
|
||||
LatencyMs int64
|
||||
Availability float64
|
||||
CostPer1KTokens float64
|
||||
QualityScore float64
|
||||
}
|
||||
|
||||
// ScoringModel 评分模型
|
||||
type ScoringModel struct {
|
||||
weights ScoreWeights
|
||||
}
|
||||
|
||||
// NewScoringModel 创建评分模型
|
||||
func NewScoringModel(weights ScoreWeights) *ScoringModel {
|
||||
return &ScoringModel{
|
||||
weights: weights,
|
||||
}
|
||||
}
|
||||
|
||||
// CalculateScore 计算单个Provider的综合评分
|
||||
// 评分范围: 0.0 - 1.0, 越高越好
|
||||
func (m *ScoringModel) CalculateScore(provider ProviderMetrics) float64 {
|
||||
// 计算各维度得分
|
||||
|
||||
// 延迟得分: 使用指数衰减,越低越好
|
||||
// 基准延迟100ms,得分0.5;延迟0ms得分1.0
|
||||
latencyScore := math.Exp(-float64(provider.LatencyMs) / 200.0)
|
||||
|
||||
// 可用性得分: 直接使用可用性值
|
||||
availabilityScore := provider.Availability
|
||||
|
||||
// 成本得分: 使用指数衰减,越低越好
|
||||
// 基准成本$1/1K tokens,得分0.5;成本0得分1.0
|
||||
costScore := math.Exp(-provider.CostPer1KTokens)
|
||||
|
||||
// 质量得分: 直接使用质量分数
|
||||
qualityScore := provider.QualityScore
|
||||
|
||||
// 综合评分 = 延迟权重*延迟得分 + 可用性权重*可用性得分 + 成本权重*成本得分 + 质量权重*质量得分
|
||||
totalScore := m.weights.LatencyWeight*latencyScore +
|
||||
m.weights.AvailabilityWeight*availabilityScore +
|
||||
m.weights.CostWeight*costScore +
|
||||
m.weights.QualityWeight*qualityScore
|
||||
|
||||
return math.Max(0, math.Min(1, totalScore))
|
||||
}
|
||||
|
||||
// SelectBestProvider 从候选列表中选择最佳Provider
|
||||
func (m *ScoringModel) SelectBestProvider(providers []ProviderMetrics) *ProviderMetrics {
|
||||
if len(providers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
best := &providers[0]
|
||||
bestScore := m.CalculateScore(*best)
|
||||
|
||||
for i := 1; i < len(providers); i++ {
|
||||
score := m.CalculateScore(providers[i])
|
||||
if score > bestScore {
|
||||
best = &providers[i]
|
||||
bestScore = score
|
||||
}
|
||||
}
|
||||
|
||||
return best
|
||||
}
|
||||
149
gateway/internal/router/scoring/scoring_model_test.go
Normal file
149
gateway/internal/router/scoring/scoring_model_test.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package scoring
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestScoringModel_CalculateScore_Latency(t *testing.T) {
|
||||
// 低延迟应该得高分
|
||||
model := NewScoringModel(DefaultWeights)
|
||||
|
||||
// Provider A: 延迟100ms
|
||||
providerA := ProviderMetrics{
|
||||
Name: "ProviderA",
|
||||
LatencyMs: 100,
|
||||
}
|
||||
|
||||
// Provider B: 延迟200ms
|
||||
providerB := ProviderMetrics{
|
||||
Name: "ProviderB",
|
||||
LatencyMs: 200,
|
||||
}
|
||||
|
||||
scoreA := model.CalculateScore(providerA)
|
||||
scoreB := model.CalculateScore(providerB)
|
||||
|
||||
// 延迟低的应该分数高
|
||||
assert.Greater(t, scoreA, scoreB, "Lower latency should result in higher score")
|
||||
}
|
||||
|
||||
func TestScoringModel_CalculateScore_Availability(t *testing.T) {
|
||||
// 高可用应该得高分
|
||||
model := NewScoringModel(DefaultWeights)
|
||||
|
||||
// Provider A: 可用性 99%
|
||||
providerA := ProviderMetrics{
|
||||
Name: "ProviderA",
|
||||
Availability: 0.99,
|
||||
}
|
||||
|
||||
// Provider B: 可用性 90%
|
||||
providerB := ProviderMetrics{
|
||||
Name: "ProviderB",
|
||||
Availability: 0.90,
|
||||
}
|
||||
|
||||
scoreA := model.CalculateScore(providerA)
|
||||
scoreB := model.CalculateScore(providerB)
|
||||
|
||||
// 可用性高的应该分数高
|
||||
assert.Greater(t, scoreA, scoreB, "Higher availability should result in higher score")
|
||||
}
|
||||
|
||||
func TestScoringModel_CalculateScore_Cost(t *testing.T) {
|
||||
// 低成本应该得高分
|
||||
model := NewScoringModel(DefaultWeights)
|
||||
|
||||
// Provider A: 成本 $0.5/1K tokens
|
||||
providerA := ProviderMetrics{
|
||||
Name: "ProviderA",
|
||||
CostPer1KTokens: 0.5,
|
||||
}
|
||||
|
||||
// Provider B: 成本 $1.0/1K tokens
|
||||
providerB := ProviderMetrics{
|
||||
Name: "ProviderB",
|
||||
CostPer1KTokens: 1.0,
|
||||
}
|
||||
|
||||
scoreA := model.CalculateScore(providerA)
|
||||
scoreB := model.CalculateScore(providerB)
|
||||
|
||||
// 成本低的应该分数高
|
||||
assert.Greater(t, scoreA, scoreB, "Lower cost should result in higher score")
|
||||
}
|
||||
|
||||
func TestScoringModel_CalculateScore_Quality(t *testing.T) {
|
||||
// 高质量应该得高分
|
||||
model := NewScoringModel(DefaultWeights)
|
||||
|
||||
// Provider A: 质量 0.95
|
||||
providerA := ProviderMetrics{
|
||||
Name: "ProviderA",
|
||||
QualityScore: 0.95,
|
||||
}
|
||||
|
||||
// Provider B: 质量 0.80
|
||||
providerB := ProviderMetrics{
|
||||
Name: "ProviderB",
|
||||
QualityScore: 0.80,
|
||||
}
|
||||
|
||||
scoreA := model.CalculateScore(providerA)
|
||||
scoreB := model.CalculateScore(providerB)
|
||||
|
||||
// 质量高的应该分数高
|
||||
assert.Greater(t, scoreA, scoreB, "Higher quality should result in higher score")
|
||||
}
|
||||
|
||||
func TestScoringModel_CalculateScore_Combined(t *testing.T) {
|
||||
// 综合评分正确
|
||||
model := NewScoringModel(DefaultWeights)
|
||||
|
||||
// 完美provider: 延迟0ms, 可用性100%, 成本0$/1K, 质量1.0
|
||||
perfect := ProviderMetrics{
|
||||
Name: "Perfect",
|
||||
LatencyMs: 0,
|
||||
Availability: 1.0,
|
||||
CostPer1KTokens: 0,
|
||||
QualityScore: 1.0,
|
||||
}
|
||||
|
||||
// 最差provider: 延迟1000ms, 可用性0%, 成本10$/1K, 质量0
|
||||
worst := ProviderMetrics{
|
||||
Name: "Worst",
|
||||
LatencyMs: 1000,
|
||||
Availability: 0.0,
|
||||
CostPer1KTokens: 10.0,
|
||||
QualityScore: 0.0,
|
||||
}
|
||||
|
||||
scorePerfect := model.CalculateScore(perfect)
|
||||
scoreWorst := model.CalculateScore(worst)
|
||||
|
||||
// 完美的应该分数高
|
||||
assert.Greater(t, scorePerfect, scoreWorst, "Perfect provider should score higher than worst")
|
||||
|
||||
// 完美分数应该在合理范围内 (接近1.0)
|
||||
assert.LessOrEqual(t, scorePerfect, 1.0, "Perfect score should be <= 1.0")
|
||||
assert.Greater(t, scorePerfect, 0.9, "Perfect score should be > 0.9")
|
||||
}
|
||||
|
||||
func TestScoringModel_SelectBestProvider(t *testing.T) {
|
||||
// 选择最佳provider
|
||||
model := NewScoringModel(DefaultWeights)
|
||||
|
||||
providers := []ProviderMetrics{
|
||||
{Name: "ProviderA", LatencyMs: 100, Availability: 0.99, CostPer1KTokens: 0.5, QualityScore: 0.9},
|
||||
{Name: "ProviderB", LatencyMs: 50, Availability: 0.95, CostPer1KTokens: 0.8, QualityScore: 0.85},
|
||||
{Name: "ProviderC", LatencyMs: 200, Availability: 0.99, CostPer1KTokens: 0.3, QualityScore: 0.8},
|
||||
}
|
||||
|
||||
best := model.SelectBestProvider(providers)
|
||||
|
||||
// 验证返回了provider
|
||||
assert.NotNil(t, best, "Should return a provider")
|
||||
assert.Equal(t, "ProviderB", best.Name, "ProviderB should be selected (low latency with good balance)")
|
||||
}
|
||||
25
gateway/internal/router/scoring/weights.go
Normal file
25
gateway/internal/router/scoring/weights.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package scoring
|
||||
|
||||
// ScoreWeights 评分权重配置
|
||||
type ScoreWeights struct {
|
||||
// LatencyWeight 延迟权重 (40%)
|
||||
LatencyWeight float64
|
||||
// AvailabilityWeight 可用性权重 (30%)
|
||||
AvailabilityWeight float64
|
||||
// CostWeight 成本权重 (20%)
|
||||
CostWeight float64
|
||||
// QualityWeight 质量权重 (10%)
|
||||
QualityWeight float64
|
||||
}
|
||||
|
||||
// DefaultWeights 默认权重配置
|
||||
// LatencyWeight = 0.4 (40%)
|
||||
// AvailabilityWeight = 0.3 (30%)
|
||||
// CostWeight = 0.2 (20%)
|
||||
// QualityWeight = 0.1 (10%)
|
||||
var DefaultWeights = ScoreWeights{
|
||||
LatencyWeight: 0.4,
|
||||
AvailabilityWeight: 0.3,
|
||||
CostWeight: 0.2,
|
||||
QualityWeight: 0.1,
|
||||
}
|
||||
30
gateway/internal/router/scoring/weights_test.go
Normal file
30
gateway/internal/router/scoring/weights_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package scoring
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestScoreWeights_DefaultValues(t *testing.T) {
|
||||
// 验证默认权重
|
||||
// LatencyWeight = 0.4 (40%)
|
||||
// AvailabilityWeight = 0.3 (30%)
|
||||
// CostWeight = 0.2 (20%)
|
||||
// QualityWeight = 0.1 (10%)
|
||||
|
||||
assert.Equal(t, 0.4, DefaultWeights.LatencyWeight, "LatencyWeight should be 0.4 (40%%)")
|
||||
assert.Equal(t, 0.3, DefaultWeights.AvailabilityWeight, "AvailabilityWeight should be 0.3 (30%%)")
|
||||
assert.Equal(t, 0.2, DefaultWeights.CostWeight, "CostWeight should be 0.2 (20%%)")
|
||||
assert.Equal(t, 0.1, DefaultWeights.QualityWeight, "QualityWeight should be 0.1 (10%%)")
|
||||
}
|
||||
|
||||
func TestScoreWeights_Sum(t *testing.T) {
|
||||
// 验证权重总和为1.0
|
||||
total := DefaultWeights.LatencyWeight +
|
||||
DefaultWeights.AvailabilityWeight +
|
||||
DefaultWeights.CostWeight +
|
||||
DefaultWeights.QualityWeight
|
||||
|
||||
assert.InDelta(t, 1.0, total, 0.001, "Weights sum should be 1.0")
|
||||
}
|
||||
Reference in New Issue
Block a user