Files
new-api/service/tool_billing.go
T
CaIon c5405b2a12 feat: add billing expression system documentation and enhance tiered billing logic
- Introduced a new rule for the Billing Expression System, emphasizing the importance of reading `pkg/billingexpr/expr.md` for dynamic billing.
- Updated the billing expression logic to support new variables and improved handling of image and audio tokens.
- Enhanced the tiered billing functionality with versioning support for expressions and refined quota calculations.
- Added tests to validate the new billing expression features and ensure correctness in pricing calculations.
2026-03-17 16:59:25 +08:00

103 lines
2.9 KiB
Go

package service
import (
"math"
"strings"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/setting/operation_setting"
)
// ToolCallUsage captures all tool call counts from a single request.
type ToolCallUsage struct {
WebSearchCalls int
WebSearchModelName string
ClaudeWebSearchCalls int
FileSearchCalls int
ImageGenerationCall bool
ImageGenerationQuality string
ImageGenerationSize string
}
// ToolCallItem represents a single billed tool usage line.
type ToolCallItem struct {
Name string `json:"name"`
CallCount int `json:"call_count"`
PricePer1K float64 `json:"price_per_1k"`
TotalPrice float64 `json:"total_price"`
Quota int `json:"quota"`
}
// ToolCallResult holds the aggregated tool call billing for a request.
type ToolCallResult struct {
TotalQuota int `json:"total_quota"`
Items []ToolCallItem `json:"items,omitempty"`
}
func getWebSearchPriceKey(modelName string) string {
isNormalPrice :=
strings.HasPrefix(modelName, "o3") ||
strings.HasPrefix(modelName, "o4") ||
strings.HasPrefix(modelName, "gpt-5")
if isNormalPrice {
return "web_search"
}
return "web_search_high"
}
// ComputeToolCallQuota calculates the total quota for all tool calls in a
// request. All tool prices are $/1K calls (configurable via ToolCallPrices
// option). groupRatio is applied. Per-call billing (UsePrice) callers should
// NOT add this result — per-call price already includes everything.
func ComputeToolCallQuota(usage ToolCallUsage, groupRatio float64) ToolCallResult {
var items []ToolCallItem
totalQuota := 0
addItem := func(name string, count int, pricePer1K float64) {
if count <= 0 || pricePer1K <= 0 {
return
}
totalPrice := pricePer1K * float64(count) / 1000
quota := int(math.Round(totalPrice * common.QuotaPerUnit * groupRatio))
items = append(items, ToolCallItem{
Name: name,
CallCount: count,
PricePer1K: pricePer1K,
TotalPrice: totalPrice,
Quota: quota,
})
totalQuota += quota
}
if usage.WebSearchCalls > 0 {
priceKey := getWebSearchPriceKey(usage.WebSearchModelName)
addItem("web_search", usage.WebSearchCalls, operation_setting.GetToolPrice(priceKey))
}
if usage.ClaudeWebSearchCalls > 0 {
addItem("claude_web_search", usage.ClaudeWebSearchCalls, operation_setting.GetToolPrice("claude_web_search"))
}
if usage.FileSearchCalls > 0 {
addItem("file_search", usage.FileSearchCalls, operation_setting.GetToolPrice("file_search"))
}
if usage.ImageGenerationCall {
price := operation_setting.GetGPTImage1PriceOnceCall(usage.ImageGenerationQuality, usage.ImageGenerationSize)
quota := int(math.Round(price * common.QuotaPerUnit * groupRatio))
items = append(items, ToolCallItem{
Name: "image_generation",
CallCount: 1,
PricePer1K: price * 1000,
TotalPrice: price,
Quota: quota,
})
totalQuota += quota
}
return ToolCallResult{
TotalQuota: totalQuota,
Items: items,
}
}