feat(service): 扩展 CRS 同步和定价服务支持 Gemini

- CRS 同步服务新增 Gemini 账号同步逻辑(+273行)
- 定价服务扩展 Gemini 模型定价计算(+99行)
- 更新 Token 刷新服务集成 Gemini
- 更新相关单元测试
This commit is contained in:
ianshaw
2025-12-25 06:44:40 -08:00
parent dc109827b7
commit 55258bf099
4 changed files with 360 additions and 18 deletions

View File

@@ -393,27 +393,32 @@ func (s *PricingService) GetModelPricing(modelName string) *LiteLLMModelPricing
return nil
}
// 标准化模型名称
modelLower := strings.ToLower(modelName)
// 标准化模型名称(同时兼容 "models/xxx"、VertexAI 资源名等前缀)
modelLower := strings.ToLower(strings.TrimSpace(modelName))
lookupCandidates := s.buildModelLookupCandidates(modelLower)
// 1. 精确匹配
if pricing, ok := s.pricingData[modelLower]; ok {
return pricing
}
if pricing, ok := s.pricingData[modelName]; ok {
return pricing
for _, candidate := range lookupCandidates {
if candidate == "" {
continue
}
if pricing, ok := s.pricingData[candidate]; ok {
return pricing
}
}
// 2. 处理常见的模型名称变体
// claude-opus-4-5-20251101 -> claude-opus-4.5-20251101
normalized := strings.ReplaceAll(modelLower, "-4-5-", "-4.5-")
if pricing, ok := s.pricingData[normalized]; ok {
return pricing
for _, candidate := range lookupCandidates {
normalized := strings.ReplaceAll(candidate, "-4-5-", "-4.5-")
if pricing, ok := s.pricingData[normalized]; ok {
return pricing
}
}
// 3. 尝试模糊匹配(去掉版本号后缀)
// claude-opus-4-5-20251101 -> claude-opus-4.5
baseName := s.extractBaseName(modelLower)
baseName := s.extractBaseName(lookupCandidates[0])
for key, pricing := range s.pricingData {
keyBase := s.extractBaseName(strings.ToLower(key))
if keyBase == baseName {
@@ -422,18 +427,84 @@ func (s *PricingService) GetModelPricing(modelName string) *LiteLLMModelPricing
}
// 4. 基于模型系列匹配Claude
if pricing := s.matchByModelFamily(modelLower); pricing != nil {
if pricing := s.matchByModelFamily(lookupCandidates[0]); pricing != nil {
return pricing
}
// 5. OpenAI 模型回退策略
if strings.HasPrefix(modelLower, "gpt-") {
return s.matchOpenAIModel(modelLower)
if strings.HasPrefix(lookupCandidates[0], "gpt-") {
return s.matchOpenAIModel(lookupCandidates[0])
}
return nil
}
func (s *PricingService) buildModelLookupCandidates(modelLower string) []string {
// Prefer canonical model name first (this also improves billing compatibility with "models/xxx").
candidates := []string{
normalizeModelNameForPricing(modelLower),
modelLower,
}
for _, cand := range []string{
strings.TrimPrefix(modelLower, "models/"),
lastSegment(modelLower),
lastSegment(strings.TrimPrefix(modelLower, "models/")),
} {
candidates = append(candidates, cand)
}
seen := make(map[string]struct{}, len(candidates))
out := make([]string, 0, len(candidates))
for _, c := range candidates {
c = strings.TrimSpace(c)
if c == "" {
continue
}
if _, ok := seen[c]; ok {
continue
}
seen[c] = struct{}{}
out = append(out, c)
}
if len(out) == 0 {
return []string{modelLower}
}
return out
}
func normalizeModelNameForPricing(model string) string {
// Common Gemini/VertexAI forms:
// - models/gemini-2.0-flash-exp
// - publishers/google/models/gemini-1.5-pro
// - projects/.../locations/.../publishers/google/models/gemini-1.5-pro
model = strings.TrimSpace(model)
model = strings.TrimLeft(model, "/")
if strings.HasPrefix(model, "models/") {
model = strings.TrimPrefix(model, "models/")
}
if strings.HasPrefix(model, "publishers/google/models/") {
model = strings.TrimPrefix(model, "publishers/google/models/")
}
if idx := strings.LastIndex(model, "/publishers/google/models/"); idx != -1 {
model = model[idx+len("/publishers/google/models/"):]
}
if idx := strings.LastIndex(model, "/models/"); idx != -1 {
model = model[idx+len("/models/"):]
}
model = strings.TrimLeft(model, "/")
return model
}
func lastSegment(model string) string {
if idx := strings.LastIndex(model, "/"); idx != -1 {
return model[idx+1:]
}
return model
}
// extractBaseName 提取基础模型名称(去掉日期版本号)
func (s *PricingService) extractBaseName(model string) string {
// 移除日期后缀 (如 -20251101, -20241022)