@@ -16,26 +16,16 @@ import (
1616
1717 domainbilling "github.com/DEEIX-AI/DEEIX-Chat/backend/internal/domain/billing"
1818 "github.com/DEEIX-AI/DEEIX-Chat/backend/internal/repository"
19+ "github.com/DEEIX-AI/DEEIX-Chat/backend/internal/shared/nativetool"
1920)
2021
2122const (
2223 defaultPageSize = 20
2324 maxPageSize = 200
2425 publicModelPricingCacheTTL = 30 * time .Second
2526 nativeToolPricingSource = "provider_official_defaults"
26- nativeToolUSD001Nanousd = 10_000_000
27- nativeToolUSD0025Nanousd = 25_000_000
28- nativeToolUSD0005Nanousd = 5_000_000
29- nativeToolUSD00025Nanousd = 2_500_000
3027)
3128
32- // nativeToolCallPrice 描述可直接折算为按次计费的模型原生工具默认价格。
33- type nativeToolCallPrice struct {
34- provider string
35- serviceName string
36- nanousdPerCall int64
37- }
38-
3929// UserSubscriptionSnapshot 描述用户当前订阅的派生结果。
4030type UserSubscriptionSnapshot struct {
4131 UserID uint
@@ -298,23 +288,27 @@ func (s *Service) GetBillingMode(ctx context.Context) (string, error) {
298288
299289// ListNativeToolDefaultPricing 返回当前内置的原生工具默认价格目录。
300290func ListNativeToolDefaultPricing () []NativeToolPricingView {
301- return []NativeToolPricingView {
302- {Provider : "OpenAI" , ToolKey : "openaiWebSearchReasoning" , PriceNanousd : nativeToolUSD001Nanousd , Unit : "call" , Billable : true },
303- {Provider : "OpenAI" , ToolKey : "openaiWebSearchStandard" , PriceNanousd : nativeToolUSD0025Nanousd , Unit : "call" , Billable : true },
304- {Provider : "OpenAI" , ToolKey : "openaiShell" , PriceLabel : "notMetered" , Billable : false },
305- {Provider : "OpenAI" , ToolKey : "openaiImageGeneration" , PriceLabel : "notMetered" , Billable : false },
306- {Provider : "OpenAI" , ToolKey : "openaiCodeInterpreter" , PriceLabel : "notMetered" , Billable : false },
307- {Provider : "Anthropic" , ToolKey : "anthropicWebSearch" , PriceNanousd : nativeToolUSD001Nanousd , Unit : "search" , Billable : true },
308- {Provider : "Anthropic" , ToolKey : "anthropicWebFetch" , PriceLabel : "included" , Billable : false },
309- {Provider : "Anthropic" , ToolKey : "anthropicCodeExecution" , PriceLabel : "notMetered" , Billable : false },
310- {Provider : "Anthropic" , ToolKey : "anthropicAdvisor" , PriceLabel : "notMetered" , Billable : false },
311- {Provider : "Anthropic" , ToolKey : "anthropicToolSearch" , PriceLabel : "included" , Billable : false },
312- {Provider : "xAI" , ToolKey : "xaiWebSearch" , PriceNanousd : nativeToolUSD0005Nanousd , Unit : "call" , Billable : true },
313- {Provider : "xAI" , ToolKey : "xaiXSearch" , PriceNanousd : nativeToolUSD0005Nanousd , Unit : "call" , Billable : true },
314- {Provider : "xAI" , ToolKey : "xaiCodeExecution" , PriceNanousd : nativeToolUSD0005Nanousd , Unit : "call" , Billable : true },
315- {Provider : "xAI" , ToolKey : "xaiAttachmentSearch" , PriceNanousd : nativeToolUSD001Nanousd , Unit : "call" , Billable : true },
316- {Provider : "xAI" , ToolKey : "xaiCollectionsSearch" , PriceNanousd : nativeToolUSD00025Nanousd , Unit : "call" , Billable : true },
291+ return nativeToolPricingViews (nativetool .PricingDefinitions ())
292+ }
293+
294+ // ListNativeToolPricing 返回应用管理员覆盖后的原生工具计费价格目录。
295+ func ListNativeToolPricing (rawPricingJSON string ) []NativeToolPricingView {
296+ return nativeToolPricingViews (nativetool .PricingDefinitionsWithOverrides (rawPricingJSON ))
297+ }
298+
299+ func nativeToolPricingViews (items []nativetool.PricingDefinition ) []NativeToolPricingView {
300+ results := make ([]NativeToolPricingView , 0 , len (items ))
301+ for _ , item := range items {
302+ results = append (results , NativeToolPricingView {
303+ Provider : item .Provider ,
304+ ToolKey : item .ToolKey ,
305+ PriceNanousd : item .PriceNanousd ,
306+ Unit : item .Unit ,
307+ PriceLabel : item .PriceLabel ,
308+ Billable : item .Billable ,
309+ })
317310 }
311+ return results
318312}
319313
320314// ListBillingAccountSnapshots 批量查询用户按量余额。
@@ -1400,7 +1394,15 @@ func (s *Service) BuildUsageLedger(ctx context.Context, input UsagePricingInput)
14001394 if err != nil {
14011395 return nil , err
14021396 }
1403- nativeToolItems , nativeToolBilledNanousd := buildNativeToolServiceItems (input , mode , isFreeModel , nativeToolBillingEnabled )
1397+ nativeToolPricingJSON , err := s .repo .GetNativeToolPricingJSON (ctx )
1398+ if err != nil {
1399+ return nil , err
1400+ }
1401+ nativeToolPricingOverrides , err := nativetool .ParsePricingOverridesJSON (nativeToolPricingJSON )
1402+ if err != nil {
1403+ nativeToolPricingOverrides = map [string ]nativetool.PricingOverride {}
1404+ }
1405+ nativeToolItems , nativeToolBilledNanousd := buildNativeToolServiceItems (input , mode , isFreeModel , nativeToolBillingEnabled , nativeToolPricingOverrides )
14041406 if len (nativeToolItems ) > 0 {
14051407 serviceItems = append (serviceItems , nativeToolItems ... )
14061408 serviceBilledNanousd += nativeToolBilledNanousd
@@ -1460,7 +1462,7 @@ func (s *Service) BuildUsageLedger(ctx context.Context, input UsagePricingInput)
14601462 "duration_billed_nanousd" : durationBilledNanousd ,
14611463 "server_side_tool_usage" : normalizeUsageCountMap (input .ServerSideToolUsage ),
14621464 "native_tool_billing_enabled" : nativeToolBillingEnabled ,
1463- "native_tool_pricing_source" : nativeToolPricingSource ,
1465+ "native_tool_pricing_source" : nativeToolPricingSourceForSnapshot ( nativeToolPricingJSON ) ,
14641466 "native_tool_billed_nanousd" : nativeToolBilledNanousd ,
14651467 "base_service_billed_nanousd" : serviceBilledNanousd ,
14661468 "service_items" : usageServiceItemSnapshots (serviceItems ),
@@ -2684,7 +2686,7 @@ func paginateModelPricing(items []domainbilling.ModelPricing, offset int, limit
26842686}
26852687
26862688// buildNativeToolServiceItems 将原生 server-side tool 调用转换为账单服务项。
2687- func buildNativeToolServiceItems (input UsagePricingInput , billingMode string , isFreeModel bool , enabled bool ) ([]domainbilling.UsageServiceItem , int64 ) {
2689+ func buildNativeToolServiceItems (input UsagePricingInput , billingMode string , isFreeModel bool , enabled bool , pricingOverrides map [ string ]nativetool. PricingOverride ) ([]domainbilling.UsageServiceItem , int64 ) {
26882690 if billingMode == "self" || isFreeModel || ! enabled || len (input .ServerSideToolUsage ) == 0 {
26892691 return []domainbilling.UsageServiceItem {}, 0
26902692 }
@@ -2695,20 +2697,20 @@ func buildNativeToolServiceItems(input UsagePricingInput, billingMode string, is
26952697 results := make ([]domainbilling.UsageServiceItem , 0 , len (counts ))
26962698 var total int64
26972699 for toolName , count := range counts {
2698- price , ok := nativeToolDefaultCallPrice (input , toolName )
2699- if ! ok || price .nanousdPerCall <= 0 || count <= 0 {
2700+ price , ok := nativeToolDefaultCallPrice (input , toolName , pricingOverrides )
2701+ if ! ok || price .NanousdPerCall <= 0 || count <= 0 {
27002702 continue
27012703 }
2702- billed := count * price .nanousdPerCall
2704+ billed := count * price .NanousdPerCall
27032705 results = append (results , domainbilling.UsageServiceItem {
2704- ServiceCode : nativeToolServiceCode (price .provider , toolName ),
2705- ServiceName : price .serviceName ,
2706+ ServiceCode : nativeToolServiceCode (price .Provider , toolName ),
2707+ ServiceName : price .ServiceName ,
27062708 PlatformModelName : strings .TrimSpace (input .PlatformModelName ),
27072709 ProviderProtocol : strings .TrimSpace (input .ProviderProtocol ),
27082710 RateMultiplier : 1 ,
27092711 PricingMode : domainbilling .PricingModeCall ,
27102712 CallCount : count ,
2711- CallNanousdPerCall : price .nanousdPerCall ,
2713+ CallNanousdPerCall : price .NanousdPerCall ,
27122714 CallBilledNanousd : billed ,
27132715 BilledNanousd : billed ,
27142716 })
@@ -2718,44 +2720,22 @@ func buildNativeToolServiceItems(input UsagePricingInput, billingMode string, is
27182720}
27192721
27202722// nativeToolDefaultCallPrice 返回当前已适配厂商原生工具的官方默认按次价格。
2721- func nativeToolDefaultCallPrice (input UsagePricingInput , toolName string ) (nativeToolCallPrice , bool ) {
2722- tool := strings .TrimSpace (toolName )
2723- switch strings .TrimSpace (input .ProviderProtocol ) {
2724- case "anthropic_messages" :
2725- switch tool {
2726- case "web_search" :
2727- return nativeToolCallPrice {provider : "anthropic" , serviceName : "Anthropic Web search" , nanousdPerCall : nativeToolUSD001Nanousd }, true
2728- default :
2729- return nativeToolCallPrice {}, false
2730- }
2731- case "openai_responses" , "openai_chat_completions" :
2732- switch tool {
2733- case "web_search" , "web_search_preview" :
2734- if isOpenAIWebSearchReasoningModel (input ) {
2735- return nativeToolCallPrice {provider : "openai" , serviceName : "OpenAI Web search" , nanousdPerCall : nativeToolUSD001Nanousd }, true
2736- }
2737- return nativeToolCallPrice {provider : "openai" , serviceName : "OpenAI Web search" , nanousdPerCall : nativeToolUSD0025Nanousd }, true
2738- default :
2739- return nativeToolCallPrice {}, false
2740- }
2741- case "xai_responses" :
2742- switch tool {
2743- case "web_search" :
2744- return nativeToolCallPrice {provider : "xai" , serviceName : "xAI Web Search" , nanousdPerCall : nativeToolUSD0005Nanousd }, true
2745- case "x_search" :
2746- return nativeToolCallPrice {provider : "xai" , serviceName : "xAI X Search" , nanousdPerCall : nativeToolUSD0005Nanousd }, true
2747- case "code_interpreter" , "code_execution" :
2748- return nativeToolCallPrice {provider : "xai" , serviceName : "xAI Code Execution" , nanousdPerCall : nativeToolUSD0005Nanousd }, true
2749- case "attachment_search" , "file_attachment_search" :
2750- return nativeToolCallPrice {provider : "xai" , serviceName : "xAI File Attachments Search" , nanousdPerCall : nativeToolUSD001Nanousd }, true
2751- case "file_search" , "collection_search" , "collections_search" :
2752- return nativeToolCallPrice {provider : "xai" , serviceName : "xAI Collections Search / RAG" , nanousdPerCall : nativeToolUSD00025Nanousd }, true
2753- default :
2754- return nativeToolCallPrice {}, false
2755- }
2756- default :
2757- return nativeToolCallPrice {}, false
2723+ func nativeToolDefaultCallPrice (input UsagePricingInput , toolName string , pricingOverrides map [string ]nativetool.PricingOverride ) (nativetool.UsagePrice , bool ) {
2724+ key , ok := nativetool .UsagePricingKey (input .ProviderProtocol , toolName )
2725+ if ! ok {
2726+ return nativetool.UsagePrice {}, false
2727+ }
2728+ if key == "openaiWebSearchStandard" && isOpenAIWebSearchReasoningModel (input ) {
2729+ key = "openaiWebSearchReasoning"
2730+ }
2731+ return nativetool .UsagePriceByKeyWithOverrides (key , pricingOverrides )
2732+ }
2733+
2734+ func nativeToolPricingSourceForSnapshot (raw string ) string {
2735+ if nativetool .PricingOverridesUseDefaults (raw ) {
2736+ return nativeToolPricingSource
27582737 }
2738+ return "admin_configured"
27592739}
27602740
27612741// isOpenAIWebSearchReasoningModel 区分 OpenAI Web Search 的推理与非推理模型价格。
0 commit comments