package service import ( "context" "fmt" commonHttp "gitea.redpowerfuture.com/red-future/common/http" "github.com/gogf/gf/v2/frame/g" ) // ====================== shop-user-trade 计价对接(独立 module,本地 JSON 对齐) ====================== // shopPricingConfig shop-user-trade config/get 响应(仅取启用标记;规则在 shop-user-trade 侧,model-gateway 不消费) type shopPricingConfig struct { Enabled int `json:"enabled"` } // shopCalcFeeRes shop-user-trade /calc 响应 type shopCalcFeeRes struct { Cost float64 `json:"cost"` } // pricingURL 组装 shop-user-trade 计价接口地址(common http.RouteRegister 推导前缀,同 ai-agent billing.go) func pricingURL(sub string) string { return "shop-user-trade/pricing/controller/" + sub } // mgMediaTypeToShop 媒体类型词汇映射:model-gateway(audio/no_video/has_video)→ shop-user-trade(text/audio/video/image) func mgMediaTypeToShop(mg string) string { switch mg { case "audio": return "audio" case "has_video": return "video" default: // no_video return "text" } } // modelBillable 调用前门禁:模型须在 shop-user-trade 已配置且启用计价,否则阻塞调用。 // 替换原 CheckTenantBalance(admin-go 租户余额门禁);未配置→config/get 返回错误,未启用→enabled!=1。 func modelBillable(ctx context.Context, modelId int64) error { var cfg shopPricingConfig err := commonHttp.Get(ctx, pricingURL("config/get"), setCtxHeader(ctx), &cfg, "subjectType", "model", "subjectId", fmt.Sprintf("%d", modelId)) if err != nil { return fmt.Errorf("模型未配置计价,无法调用: %w", err) } if cfg.Enabled != 1 { return fmt.Errorf("模型未启用计价,无法调用") } return nil } // buildModelUsage 组装算费用量 JSON 对象(ChargeUsage 形状)。mediaType 为 model-gateway 词汇(DetectMediaType/异步快照)。 func buildModelUsage(prompt, completion, cached int64, mediaType string, durationSec float64) map[string]any { if durationSec < 0 { durationSec = 0 } return map[string]any{ "promptTokens": prompt, "completionTokens": completion, "cachedTokens": cached, "mediaType": mgMediaTypeToShop(mediaType), "durationSec": durationSec, } } // calcModelCost 调 shop-user-trade /calc 按用量算费(不建单不扣费)。 // 调用前门禁已保证配置存在;此处失败(配置中途删除/网络抖动)→ 记日志返回 0,不拖垮已完成的模型调用。 func calcModelCost(ctx context.Context, modelId int64, usage map[string]any) float64 { var res shopCalcFeeRes err := commonHttp.Post(ctx, pricingURL("calc"), setCtxHeader(ctx), &res, &struct { SubjectType string `json:"subjectType"` SubjectID string `json:"subjectId"` Usage map[string]any `json:"usage"` }{ SubjectType: "model", SubjectID: fmt.Sprintf("%d", modelId), Usage: usage, }) if err != nil { g.Log().Errorf(ctx, "[算费] 调用 shop-user-trade 失败 modelId=%d: %v", modelId, err) return 0 } return res.Cost }