feat(region_pricing): 扩展非 token 统一计费字段,支持语音按字符/秒计费
Some checks failed
CI / go-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / docker-build (push) Has been cancelled

- 新增 region_pricing.pricing_mode / price_unit / flat_price 字段
- 新增 migration 016_region_pricing_non_token_units.sql
- officialPricingRecord 新增 PricingMode/PriceUnit/FlatPrice 字段
- detectModality 新增 audio 模态检测(voice/audio/speech)
- providerMetadata 新增 BAAI/ByteDance/China Mobile 元数据
- import_mobile_cloud_pricing.go: 解析语音计费表(CosyVoice/SenseVoice)
  - CosyVoice: 2元/万字符 → pricingMode=flat, priceUnit=10k_characters
  - SenseVoice: 0.0007元/秒 → pricingMode=flat, priceUnit=second
- mobileCloudProviderName 新增 cosyvoice/sensevoice → Alibaba 映射
- cmd/server: modelResponse 新增 pricingMode/priceUnit/flatPrice,API 字段说明同步更新
- 新增 TestModelsHandlerReturnsFlatPricingFields 测试
This commit is contained in:
phamnazage-jpg
2026-05-22 14:51:38 +08:00
parent 1db813cb6b
commit 5c5578a19b
7 changed files with 776 additions and 2 deletions

View File

@@ -22,6 +22,9 @@ type modelResponse struct {
ProviderCN string `json:"providerCN"`
Modality string `json:"modality"`
ContextLength int `json:"contextLength"`
PricingMode string `json:"pricingMode,omitempty"`
PriceUnit string `json:"priceUnit,omitempty"`
FlatPrice float64 `json:"flatPrice,omitempty"`
InputPrice float64 `json:"inputPrice"`
OutputPrice float64 `json:"outputPrice"`
Currency string `json:"currency"`
@@ -171,6 +174,9 @@ func fetchModels(ctx context.Context, db *sql.DB) ([]modelResponse, error) {
WITH latest_prices AS (
SELECT
rp.model_id,
rp.pricing_mode,
rp.price_unit,
rp.flat_price,
rp.input_price_per_mtok,
rp.output_price_per_mtok,
rp.currency,
@@ -188,6 +194,9 @@ func fetchModels(ctx context.Context, db *sql.DB) ([]modelResponse, error) {
COALESCE(mp.name, split_part(m.external_id, '/', 1)),
COALESCE(m.modality, 'text'),
COALESCE(m.context_length, 0),
COALESCE(lp.pricing_mode, 'input_output'),
COALESCE(lp.price_unit, 'million_tokens'),
COALESCE(lp.flat_price, 0),
lp.input_price_per_mtok,
lp.output_price_per_mtok,
COALESCE(lp.currency, 'USD'),
@@ -207,6 +216,7 @@ func fetchModels(ctx context.Context, db *sql.DB) ([]modelResponse, error) {
var models []modelResponse
for rows.Next() {
var model modelResponse
var flatPrice sql.NullFloat64
var inputPrice sql.NullFloat64
var outputPrice sql.NullFloat64
if err := rows.Scan(
@@ -216,6 +226,9 @@ func fetchModels(ctx context.Context, db *sql.DB) ([]modelResponse, error) {
&model.Provider,
&model.Modality,
&model.ContextLength,
&model.PricingMode,
&model.PriceUnit,
&flatPrice,
&inputPrice,
&outputPrice,
&model.Currency,
@@ -232,6 +245,9 @@ func fetchModels(ctx context.Context, db *sql.DB) ([]modelResponse, error) {
if outputPrice.Valid {
model.OutputPrice = outputPrice.Float64
}
if flatPrice.Valid {
model.FlatPrice = flatPrice.Float64
}
model.Stale = model.DataConfidence == "stale"
models = append(models, model)
}

View File

@@ -10,6 +10,55 @@ import (
"testing"
)
func TestModelsHandlerReturnsFlatPricingFields(t *testing.T) {
mux := newMux(
&sql.DB{},
func(context.Context, *sql.DB) ([]modelResponse, error) {
return []modelResponse{{
ID: "mobile-cloud-huabei-huhehaote-cosyvoice",
Name: "CosyVoice",
Provider: "Alibaba",
ProviderCN: "阿里云",
Modality: "audio",
PricingMode: "flat",
PriceUnit: "10k_characters",
FlatPrice: 2,
Currency: "CNY",
IsFree: false,
DataConfidence: "official",
}}, nil
},
func(context.Context, *sql.DB) ([]subscriptionPlanResponse, error) {
return nil, nil
},
func(context.Context, *sql.DB) (*latestReportResponse, error) {
return nil, sql.ErrNoRows
},
)
req := httptest.NewRequest(http.MethodGet, "/api/v1/models", nil)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
var payload struct {
Data []modelResponse `json:"data"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
if len(payload.Data) != 1 {
t.Fatalf("expected 1 model, got %d", len(payload.Data))
}
got := payload.Data[0]
if got.PricingMode != "flat" || got.PriceUnit != "10k_characters" || got.FlatPrice != 2 {
t.Fatalf("unexpected flat pricing payload: %+v", got)
}
}
func TestSubscriptionPlansHandlerReturnsEnvelope(t *testing.T) {
mux := newMux(
&sql.DB{},