tool_billing.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. package service
  2. import (
  3. "math"
  4. "strings"
  5. "github.com/QuantumNous/new-api/common"
  6. "github.com/QuantumNous/new-api/setting/operation_setting"
  7. )
  8. // ToolCallUsage captures all tool call counts from a single request.
  9. type ToolCallUsage struct {
  10. WebSearchCalls int
  11. WebSearchModelName string
  12. ClaudeWebSearchCalls int
  13. FileSearchCalls int
  14. ImageGenerationCall bool
  15. ImageGenerationQuality string
  16. ImageGenerationSize string
  17. }
  18. // ToolCallItem represents a single billed tool usage line.
  19. type ToolCallItem struct {
  20. Name string `json:"name"`
  21. CallCount int `json:"call_count"`
  22. PricePer1K float64 `json:"price_per_1k"`
  23. TotalPrice float64 `json:"total_price"`
  24. Quota int `json:"quota"`
  25. }
  26. // ToolCallResult holds the aggregated tool call billing for a request.
  27. type ToolCallResult struct {
  28. TotalQuota int `json:"total_quota"`
  29. Items []ToolCallItem `json:"items,omitempty"`
  30. }
  31. func getWebSearchPriceKey(modelName string) string {
  32. isNormalPrice :=
  33. strings.HasPrefix(modelName, "o3") ||
  34. strings.HasPrefix(modelName, "o4") ||
  35. strings.HasPrefix(modelName, "gpt-5")
  36. if isNormalPrice {
  37. return "web_search"
  38. }
  39. return "web_search_high"
  40. }
  41. // ComputeToolCallQuota calculates the total quota for all tool calls in a
  42. // request. All tool prices are $/1K calls (configurable via ToolCallPrices
  43. // option). groupRatio is applied. Per-call billing (UsePrice) callers should
  44. // NOT add this result — per-call price already includes everything.
  45. func ComputeToolCallQuota(usage ToolCallUsage, groupRatio float64) ToolCallResult {
  46. var items []ToolCallItem
  47. totalQuota := 0
  48. addItem := func(name string, count int, pricePer1K float64) {
  49. if count <= 0 || pricePer1K <= 0 {
  50. return
  51. }
  52. totalPrice := pricePer1K * float64(count) / 1000
  53. quota := int(math.Round(totalPrice * common.QuotaPerUnit * groupRatio))
  54. items = append(items, ToolCallItem{
  55. Name: name,
  56. CallCount: count,
  57. PricePer1K: pricePer1K,
  58. TotalPrice: totalPrice,
  59. Quota: quota,
  60. })
  61. totalQuota += quota
  62. }
  63. if usage.WebSearchCalls > 0 {
  64. priceKey := getWebSearchPriceKey(usage.WebSearchModelName)
  65. addItem("web_search", usage.WebSearchCalls, operation_setting.GetToolPrice(priceKey))
  66. }
  67. if usage.ClaudeWebSearchCalls > 0 {
  68. addItem("claude_web_search", usage.ClaudeWebSearchCalls, operation_setting.GetToolPrice("claude_web_search"))
  69. }
  70. if usage.FileSearchCalls > 0 {
  71. addItem("file_search", usage.FileSearchCalls, operation_setting.GetToolPrice("file_search"))
  72. }
  73. if usage.ImageGenerationCall {
  74. price := operation_setting.GetGPTImage1PriceOnceCall(usage.ImageGenerationQuality, usage.ImageGenerationSize)
  75. quota := int(math.Round(price * common.QuotaPerUnit * groupRatio))
  76. items = append(items, ToolCallItem{
  77. Name: "image_generation",
  78. CallCount: 1,
  79. PricePer1K: price * 1000,
  80. TotalPrice: price,
  81. Quota: quota,
  82. })
  83. totalQuota += quota
  84. }
  85. return ToolCallResult{
  86. TotalQuota: totalQuota,
  87. Items: items,
  88. }
  89. }