token_estimator.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. package service
  2. import (
  3. "math"
  4. "strings"
  5. "sync"
  6. "unicode"
  7. )
  8. // Provider 定义模型厂商大类
  9. type Provider string
  10. const (
  11. OpenAI Provider = "openai" // 代表 GPT-3.5, GPT-4, GPT-4o
  12. Gemini Provider = "gemini" // 代表 Gemini 1.0, 1.5 Pro/Flash
  13. Claude Provider = "claude" // 代表 Claude 3, 3.5 Sonnet
  14. Unknown Provider = "unknown" // 兜底默认
  15. )
  16. // multipliers 定义不同厂商的计费权重
  17. type multipliers struct {
  18. Word float64 // 英文单词 (每词)
  19. Number float64 // 数字 (每连续数字串)
  20. CJK float64 // 中日韩字符 (每字)
  21. Symbol float64 // 普通标点符号 (每个)
  22. MathSymbol float64 // 数学符号 (∑,∫,∂,√等,每个)
  23. URLDelim float64 // URL分隔符 (/,:,?,&,=,#,%) - tokenizer优化好
  24. AtSign float64 // @符号 - 导致单词切分,消耗较高
  25. Emoji float64 // Emoji表情 (每个)
  26. Newline float64 // 换行符/制表符 (每个)
  27. Space float64 // 空格 (每个)
  28. BasePad int // 基础起步消耗 (Start/End tokens)
  29. }
  30. var (
  31. multipliersMap = map[Provider]multipliers{
  32. Gemini: {
  33. Word: 1.15, Number: 2.8, CJK: 0.68, Symbol: 0.38, MathSymbol: 1.05, URLDelim: 1.2, AtSign: 2.5, Emoji: 1.08, Newline: 1.15, Space: 0.2, BasePad: 0,
  34. },
  35. Claude: {
  36. Word: 1.13, Number: 1.63, CJK: 1.21, Symbol: 0.4, MathSymbol: 4.52, URLDelim: 1.26, AtSign: 2.82, Emoji: 2.6, Newline: 0.89, Space: 0.39, BasePad: 0,
  37. },
  38. OpenAI: {
  39. Word: 1.02, Number: 1.55, CJK: 0.85, Symbol: 0.4, MathSymbol: 2.68, URLDelim: 1.0, AtSign: 2.0, Emoji: 2.12, Newline: 0.5, Space: 0.42, BasePad: 0,
  40. },
  41. }
  42. multipliersLock sync.RWMutex
  43. )
  44. // getMultipliers 根据厂商获取权重配置
  45. func getMultipliers(p Provider) multipliers {
  46. multipliersLock.RLock()
  47. defer multipliersLock.RUnlock()
  48. switch p {
  49. case Gemini:
  50. return multipliersMap[Gemini]
  51. case Claude:
  52. return multipliersMap[Claude]
  53. case OpenAI:
  54. return multipliersMap[OpenAI]
  55. default:
  56. // 默认兜底 (按 OpenAI 的算)
  57. return multipliersMap[OpenAI]
  58. }
  59. }
  60. // EstimateToken 计算 Token 数量
  61. func EstimateToken(provider Provider, text string) int {
  62. m := getMultipliers(provider)
  63. var count float64
  64. // 状态机变量
  65. type WordType int
  66. const (
  67. None WordType = iota
  68. Latin
  69. Number
  70. )
  71. currentWordType := None
  72. for _, r := range text {
  73. // 1. 处理空格和换行符
  74. if unicode.IsSpace(r) {
  75. currentWordType = None
  76. // 换行符和制表符使用Newline权重
  77. if r == '\n' || r == '\t' {
  78. count += m.Newline
  79. } else {
  80. // 普通空格使用Space权重
  81. count += m.Space
  82. }
  83. continue
  84. }
  85. // 2. 处理 CJK (中日韩) - 按字符计费
  86. if isCJK(r) {
  87. currentWordType = None
  88. count += m.CJK
  89. continue
  90. }
  91. // 3. 处理Emoji - 使用专门的Emoji权重
  92. if isEmoji(r) {
  93. currentWordType = None
  94. count += m.Emoji
  95. continue
  96. }
  97. // 4. 处理拉丁字母/数字 (英文单词)
  98. if isLatinOrNumber(r) {
  99. isNum := unicode.IsNumber(r)
  100. newType := Latin
  101. if isNum {
  102. newType = Number
  103. }
  104. // 如果之前不在单词中,或者类型发生变化(字母<->数字),则视为新token
  105. // 注意:对于OpenAI,通常"version 3.5"会切分,"abc123xyz"有时也会切分
  106. // 这里简单起见,字母和数字切换时增加权重
  107. if currentWordType == None || currentWordType != newType {
  108. if newType == Number {
  109. count += m.Number
  110. } else {
  111. count += m.Word
  112. }
  113. currentWordType = newType
  114. }
  115. // 单词中间的字符不额外计费
  116. continue
  117. }
  118. // 5. 处理标点符号/特殊字符 - 按类型使用不同权重
  119. currentWordType = None
  120. if isMathSymbol(r) {
  121. count += m.MathSymbol
  122. } else if r == '@' {
  123. count += m.AtSign
  124. } else if isURLDelim(r) {
  125. count += m.URLDelim
  126. } else {
  127. count += m.Symbol
  128. }
  129. }
  130. // 向上取整并加上基础 padding
  131. return int(math.Ceil(count)) + m.BasePad
  132. }
  133. // 辅助:判断是否为 CJK 字符
  134. func isCJK(r rune) bool {
  135. return unicode.Is(unicode.Han, r) ||
  136. (r >= 0x3040 && r <= 0x30FF) || // 日文
  137. (r >= 0xAC00 && r <= 0xD7A3) // 韩文
  138. }
  139. // 辅助:判断是否为单词主体 (字母或数字)
  140. func isLatinOrNumber(r rune) bool {
  141. return unicode.IsLetter(r) || unicode.IsNumber(r)
  142. }
  143. // 辅助:判断是否为Emoji字符
  144. func isEmoji(r rune) bool {
  145. // Emoji的Unicode范围
  146. // 基本范围:0x1F300-0x1F9FF (Emoticons, Symbols, Pictographs)
  147. // 补充范围:0x2600-0x26FF (Misc Symbols), 0x2700-0x27BF (Dingbats)
  148. // 表情符号:0x1F600-0x1F64F (Emoticons)
  149. // 其他:0x1F900-0x1F9FF (Supplemental Symbols and Pictographs)
  150. return (r >= 0x1F300 && r <= 0x1F9FF) ||
  151. (r >= 0x2600 && r <= 0x26FF) ||
  152. (r >= 0x2700 && r <= 0x27BF) ||
  153. (r >= 0x1F600 && r <= 0x1F64F) ||
  154. (r >= 0x1F900 && r <= 0x1F9FF) ||
  155. (r >= 0x1FA00 && r <= 0x1FAFF) // Symbols and Pictographs Extended-A
  156. }
  157. // 辅助:判断是否为数学符号
  158. func isMathSymbol(r rune) bool {
  159. // 数学运算符和符号
  160. // 基本数学符号:∑ ∫ ∂ √ ∞ ≤ ≥ ≠ ≈ ± × ÷
  161. // 上下标数字:² ³ ¹ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁰
  162. // 希腊字母等也常用于数学
  163. mathSymbols := "∑∫∂√∞≤≥≠≈±×÷∈∉∋∌⊂⊃⊆⊇∪∩∧∨¬∀∃∄∅∆∇∝∟∠∡∢°′″‴⁺⁻⁼⁽⁾ⁿ₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎²³¹⁴⁵⁶⁷⁸⁹⁰"
  164. for _, m := range mathSymbols {
  165. if r == m {
  166. return true
  167. }
  168. }
  169. // Mathematical Operators (U+2200–U+22FF)
  170. if r >= 0x2200 && r <= 0x22FF {
  171. return true
  172. }
  173. // Supplemental Mathematical Operators (U+2A00–U+2AFF)
  174. if r >= 0x2A00 && r <= 0x2AFF {
  175. return true
  176. }
  177. // Mathematical Alphanumeric Symbols (U+1D400–U+1D7FF)
  178. if r >= 0x1D400 && r <= 0x1D7FF {
  179. return true
  180. }
  181. return false
  182. }
  183. // 辅助:判断是否为URL分隔符(tokenizer对这些优化较好)
  184. func isURLDelim(r rune) bool {
  185. // URL中常见的分隔符,tokenizer通常优化处理
  186. urlDelims := "/:?&=;#%"
  187. for _, d := range urlDelims {
  188. if r == d {
  189. return true
  190. }
  191. }
  192. return false
  193. }
  194. func EstimateTokenByModel(model, text string) int {
  195. // strings.Contains(model, "gpt-4o")
  196. if text == "" {
  197. return 0
  198. }
  199. model = strings.ToLower(model)
  200. if strings.Contains(model, "gemini") {
  201. return EstimateToken(Gemini, text)
  202. } else if strings.Contains(model, "claude") {
  203. return EstimateToken(Claude, text)
  204. } else {
  205. return EstimateToken(OpenAI, text)
  206. }
  207. }