| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- package operation_setting
- import (
- "sort"
- "strings"
- "sync/atomic"
- "github.com/QuantumNous/new-api/setting/config"
- )
- // ---------------------------------------------------------------------------
- // Tool call prices ($/1K calls, admin-configurable)
- // DB key: tool_price_setting.prices
- //
- // Key format:
- // - "tool_name" → default price for all models
- // - "tool_name:model_prefix*" → override for models matching the prefix
- //
- // Lookup order: longest prefix match → default → hardcoded fallback → 0
- // ---------------------------------------------------------------------------
- var defaultToolPrices = map[string]float64{
- "web_search": 10.0, // OpenAI web search (all models) / Claude web search
- "web_search_preview": 10.0, // OpenAI web search preview (default: reasoning models)
- "file_search": 2.5, // OpenAI file search (Responses API)
- "google_search": 14.0, // Gemini Grounding with Google Search
- }
- var defaultToolPriceOverrides = map[string]float64{
- "web_search_preview:gpt-4o*": 25.0, // non-reasoning models
- "web_search_preview:gpt-4.1*": 25.0,
- "web_search_preview:gpt-4o-mini*": 25.0,
- "web_search_preview:gpt-4.1-mini*": 25.0,
- }
- // ToolPriceSetting is managed by config.GlobalConfig.Register.
- type ToolPriceSetting struct {
- Prices map[string]float64 `json:"prices"`
- }
- var toolPriceSetting = ToolPriceSetting{
- Prices: func() map[string]float64 {
- m := make(map[string]float64, len(defaultToolPrices)+len(defaultToolPriceOverrides))
- for k, v := range defaultToolPrices {
- m[k] = v
- }
- for k, v := range defaultToolPriceOverrides {
- m[k] = v
- }
- return m
- }(),
- }
- func init() {
- config.GlobalConfig.Register("tool_price_setting", &toolPriceSetting)
- RebuildToolPriceIndex()
- }
- // ---------------------------------------------------------------------------
- // Precomputed price index (atomic, lock-free on read path)
- // ---------------------------------------------------------------------------
- type prefixEntry struct {
- prefix string
- price float64
- }
- type toolPriceIndex struct {
- defaults map[string]float64
- prefixes map[string][]prefixEntry
- }
- var currentIndex atomic.Pointer[toolPriceIndex]
- // RebuildToolPriceIndex rebuilds the lookup index from the current config.
- // Called on init and after config updates. Not on the billing hot path.
- func RebuildToolPriceIndex() {
- merged := make(map[string]float64, len(defaultToolPrices)+len(defaultToolPriceOverrides)+len(toolPriceSetting.Prices))
- for k, v := range defaultToolPrices {
- merged[k] = v
- }
- for k, v := range defaultToolPriceOverrides {
- merged[k] = v
- }
- for k, v := range toolPriceSetting.Prices {
- merged[k] = v
- }
- idx := &toolPriceIndex{
- defaults: make(map[string]float64),
- prefixes: make(map[string][]prefixEntry),
- }
- for key, price := range merged {
- colonIdx := strings.IndexByte(key, ':')
- if colonIdx < 0 {
- idx.defaults[key] = price
- continue
- }
- toolName := key[:colonIdx]
- modelPart := key[colonIdx+1:]
- prefix := strings.TrimSuffix(modelPart, "*")
- idx.prefixes[toolName] = append(idx.prefixes[toolName], prefixEntry{prefix: prefix, price: price})
- }
- for tool := range idx.prefixes {
- entries := idx.prefixes[tool]
- sort.Slice(entries, func(i, j int) bool {
- return len(entries[i].prefix) > len(entries[j].prefix)
- })
- idx.prefixes[tool] = entries
- }
- currentIndex.Store(idx)
- }
- // GetToolPriceForModel returns the price ($/1K calls) for a tool given a model name.
- // Lookup: longest prefix match → tool default → 0.
- func GetToolPriceForModel(toolName, modelName string) float64 {
- idx := currentIndex.Load()
- if idx == nil {
- if v, ok := defaultToolPrices[toolName]; ok {
- return v
- }
- return 0
- }
- if entries, ok := idx.prefixes[toolName]; ok && modelName != "" {
- for _, e := range entries {
- if strings.HasPrefix(modelName, e.prefix) {
- return e.price
- }
- }
- }
- if p, ok := idx.defaults[toolName]; ok {
- return p
- }
- return 0
- }
- // GetToolPrice is a convenience wrapper when no model name is needed.
- func GetToolPrice(toolName string) float64 {
- return GetToolPriceForModel(toolName, "")
- }
- // ---------------------------------------------------------------------------
- // GPT Image 1 per-call pricing (special: depends on quality + size)
- // ---------------------------------------------------------------------------
- const (
- GPTImage1Low1024x1024 = 0.011
- GPTImage1Low1024x1536 = 0.016
- GPTImage1Low1536x1024 = 0.016
- GPTImage1Medium1024x1024 = 0.042
- GPTImage1Medium1024x1536 = 0.063
- GPTImage1Medium1536x1024 = 0.063
- GPTImage1High1024x1024 = 0.167
- GPTImage1High1024x1536 = 0.25
- GPTImage1High1536x1024 = 0.25
- )
- func GetGPTImage1PriceOnceCall(quality string, size string) float64 {
- prices := map[string]map[string]float64{
- "low": {
- "1024x1024": GPTImage1Low1024x1024,
- "1024x1536": GPTImage1Low1024x1536,
- "1536x1024": GPTImage1Low1536x1024,
- },
- "medium": {
- "1024x1024": GPTImage1Medium1024x1024,
- "1024x1536": GPTImage1Medium1024x1536,
- "1536x1024": GPTImage1Medium1536x1024,
- },
- "high": {
- "1024x1024": GPTImage1High1024x1024,
- "1024x1536": GPTImage1High1024x1536,
- "1536x1024": GPTImage1High1536x1024,
- },
- }
- if qualityMap, exists := prices[quality]; exists {
- if price, exists := qualityMap[size]; exists {
- return price
- }
- }
- return GPTImage1High1024x1024
- }
- // ---------------------------------------------------------------------------
- // Gemini audio input pricing (per-million tokens, model-specific)
- // ---------------------------------------------------------------------------
- const (
- Gemini25FlashPreviewInputAudioPrice = 1.00
- Gemini25FlashProductionInputAudioPrice = 1.00
- Gemini25FlashLitePreviewInputAudioPrice = 0.50
- Gemini25FlashNativeAudioInputAudioPrice = 3.00
- Gemini20FlashInputAudioPrice = 0.70
- GeminiRoboticsER15InputAudioPrice = 1.00
- )
- func GetGeminiInputAudioPricePerMillionTokens(modelName string) float64 {
- if strings.HasPrefix(modelName, "gemini-2.5-flash-preview-native-audio") {
- return Gemini25FlashNativeAudioInputAudioPrice
- } else if strings.HasPrefix(modelName, "gemini-2.5-flash-preview-lite") {
- return Gemini25FlashLitePreviewInputAudioPrice
- } else if strings.HasPrefix(modelName, "gemini-2.5-flash-preview") {
- return Gemini25FlashPreviewInputAudioPrice
- } else if strings.HasPrefix(modelName, "gemini-2.5-flash") {
- return Gemini25FlashProductionInputAudioPrice
- } else if strings.HasPrefix(modelName, "gemini-2.0-flash") {
- return Gemini20FlashInputAudioPrice
- } else if strings.HasPrefix(modelName, "gemini-robotics-er-1.5") {
- return GeminiRoboticsER15InputAudioPrice
- }
- return 0
- }
|