image_output_accounting.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. package service
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "strings"
  6. "github.com/tidwall/gjson"
  7. )
  8. type openAIImageOutputCounter struct {
  9. seen map[string]struct{}
  10. seenSizes map[string]string
  11. seenOrder []string
  12. dataSizes []string
  13. count int
  14. maxDataCount int
  15. }
  16. func newOpenAIImageOutputCounter() *openAIImageOutputCounter {
  17. return &openAIImageOutputCounter{
  18. seen: make(map[string]struct{}),
  19. seenSizes: make(map[string]string),
  20. }
  21. }
  22. func (c *openAIImageOutputCounter) Count() int {
  23. if c == nil {
  24. return 0
  25. }
  26. if c.maxDataCount > c.count {
  27. return c.maxDataCount
  28. }
  29. return c.count
  30. }
  31. func (c *openAIImageOutputCounter) Sizes() []string {
  32. if c == nil {
  33. return nil
  34. }
  35. sizes := make([]string, 0, len(c.seenOrder)+len(c.dataSizes))
  36. for _, key := range c.seenOrder {
  37. if size := strings.TrimSpace(c.seenSizes[key]); size != "" {
  38. sizes = append(sizes, size)
  39. }
  40. }
  41. if len(sizes) == 0 && len(c.dataSizes) > 0 {
  42. sizes = append(sizes, c.dataSizes...)
  43. }
  44. if len(sizes) == 0 {
  45. return nil
  46. }
  47. return sizes
  48. }
  49. func (c *openAIImageOutputCounter) AddJSONResponse(body []byte) {
  50. if c == nil || len(body) == 0 || !gjson.ValidBytes(body) {
  51. return
  52. }
  53. c.addDataArray(gjson.GetBytes(body, "data"))
  54. c.addOutputArray(gjson.GetBytes(body, "output"))
  55. c.addOutputArray(gjson.GetBytes(body, "response.output"))
  56. }
  57. func (c *openAIImageOutputCounter) AddSSEData(data []byte) {
  58. if c == nil || len(data) == 0 || strings.TrimSpace(string(data)) == "[DONE]" || !gjson.ValidBytes(data) {
  59. return
  60. }
  61. root := gjson.ParseBytes(data)
  62. c.addDataArray(root.Get("data"))
  63. eventType := strings.TrimSpace(root.Get("type").String())
  64. switch eventType {
  65. case "response.output_item.done":
  66. c.addImageOutputItem(root.Get("item"))
  67. case "response.completed", "response.done":
  68. c.addOutputArray(root.Get("response.output"))
  69. case "image_generation.completed":
  70. if item := root.Get("item"); item.Exists() {
  71. c.addImageOutputItem(item)
  72. return
  73. }
  74. if output := root.Get("output"); output.Exists() {
  75. c.addImageOutputItem(output)
  76. return
  77. }
  78. c.addImageOutputItem(root)
  79. }
  80. }
  81. func (c *openAIImageOutputCounter) AddSSEBody(body string) {
  82. if c == nil || strings.TrimSpace(body) == "" {
  83. return
  84. }
  85. forEachOpenAISSEDataPayload(body, c.AddSSEData)
  86. }
  87. func (c *openAIImageOutputCounter) addDataArray(data gjson.Result) {
  88. if !data.IsArray() {
  89. return
  90. }
  91. items := data.Array()
  92. imageCount := 0
  93. sizes := make([]string, 0, len(items))
  94. for _, item := range items {
  95. if !item.IsObject() {
  96. continue
  97. }
  98. hasImageOutput := strings.TrimSpace(item.Get("url").String()) != "" ||
  99. strings.TrimSpace(item.Get("b64_json").String()) != ""
  100. if !hasImageOutput {
  101. continue
  102. }
  103. imageCount++
  104. if size := strings.TrimSpace(item.Get("size").String()); size != "" {
  105. sizes = append(sizes, size)
  106. }
  107. }
  108. if imageCount > c.maxDataCount {
  109. c.maxDataCount = imageCount
  110. }
  111. if len(sizes) > 0 {
  112. c.dataSizes = sizes
  113. }
  114. }
  115. func (c *openAIImageOutputCounter) addOutputArray(output gjson.Result) {
  116. if !output.IsArray() {
  117. return
  118. }
  119. output.ForEach(func(_, item gjson.Result) bool {
  120. c.addImageOutputItem(item)
  121. return true
  122. })
  123. }
  124. func (c *openAIImageOutputCounter) addImageOutputItem(item gjson.Result) {
  125. if !item.Exists() || !item.IsObject() {
  126. return
  127. }
  128. itemType := strings.TrimSpace(item.Get("type").String())
  129. if itemType != "" && itemType != "image_generation_call" && itemType != "image_generation.completed" {
  130. return
  131. }
  132. if strings.Contains(strings.ToLower(item.Raw), "partial_image") {
  133. return
  134. }
  135. result := strings.TrimSpace(item.Get("result").String())
  136. if result == "" {
  137. result = strings.TrimSpace(item.Get("b64_json").String())
  138. }
  139. if result == "" {
  140. result = strings.TrimSpace(item.Get("url").String())
  141. }
  142. if result == "" {
  143. return
  144. }
  145. key := strings.TrimSpace(item.Get("id").String())
  146. if key == "" {
  147. key = strings.TrimSpace(item.Get("call_id").String())
  148. }
  149. if key == "" {
  150. key = hashOpenAIImageOutputResult(result)
  151. }
  152. if key == "" {
  153. return
  154. }
  155. size := strings.TrimSpace(item.Get("size").String())
  156. if _, exists := c.seen[key]; exists {
  157. if size != "" && strings.TrimSpace(c.seenSizes[key]) == "" {
  158. c.seenSizes[key] = size
  159. }
  160. return
  161. }
  162. c.seen[key] = struct{}{}
  163. c.seenOrder = append(c.seenOrder, key)
  164. if size != "" {
  165. c.seenSizes[key] = size
  166. }
  167. c.count++
  168. }
  169. func hashOpenAIImageOutputResult(result string) string {
  170. result = strings.TrimSpace(result)
  171. if result == "" {
  172. return ""
  173. }
  174. sum := sha256.Sum256([]byte(result))
  175. return hex.EncodeToString(sum[:])
  176. }
  177. func countOpenAIResponseImageOutputsFromJSONBytes(body []byte) int {
  178. counter := newOpenAIImageOutputCounter()
  179. counter.AddJSONResponse(body)
  180. return counter.Count()
  181. }
  182. func collectOpenAIResponseImageOutputSizesFromJSONBytes(body []byte) []string {
  183. counter := newOpenAIImageOutputCounter()
  184. counter.AddJSONResponse(body)
  185. return counter.Sizes()
  186. }
  187. func countOpenAIImageOutputsFromSSEBody(body string) int {
  188. counter := newOpenAIImageOutputCounter()
  189. counter.AddSSEBody(body)
  190. return counter.Count()
  191. }
  192. func collectOpenAIImageOutputSizesFromSSEBody(body string) []string {
  193. counter := newOpenAIImageOutputCounter()
  194. counter.AddSSEBody(body)
  195. return counter.Sizes()
  196. }