You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

712 lines
24 KiB

  1. package openai
  2. import (
  3. "fmt"
  4. "io"
  5. "net/http"
  6. "strings"
  7. "github.com/QuantumNous/new-api/common"
  8. "github.com/QuantumNous/new-api/constant"
  9. "github.com/QuantumNous/new-api/dto"
  10. "github.com/QuantumNous/new-api/logger"
  11. "github.com/QuantumNous/new-api/relay/channel/openrouter"
  12. relaycommon "github.com/QuantumNous/new-api/relay/common"
  13. "github.com/QuantumNous/new-api/relay/helper"
  14. "github.com/QuantumNous/new-api/service"
  15. "github.com/QuantumNous/new-api/types"
  16. "github.com/bytedance/gopkg/util/gopool"
  17. "github.com/gin-gonic/gin"
  18. "github.com/gorilla/websocket"
  19. )
  20. func sendStreamData(c *gin.Context, info *relaycommon.RelayInfo, data string, forceFormat bool, thinkToContent bool) error {
  21. if data == "" {
  22. return nil
  23. }
  24. if !forceFormat && !thinkToContent {
  25. return helper.StringData(c, data)
  26. }
  27. var lastStreamResponse dto.ChatCompletionsStreamResponse
  28. if err := common.UnmarshalJsonStr(data, &lastStreamResponse); err != nil {
  29. return err
  30. }
  31. if !thinkToContent {
  32. return helper.ObjectData(c, lastStreamResponse)
  33. }
  34. hasThinkingContent := false
  35. hasContent := false
  36. var thinkingContent strings.Builder
  37. for _, choice := range lastStreamResponse.Choices {
  38. if len(choice.Delta.GetReasoningContent()) > 0 {
  39. hasThinkingContent = true
  40. thinkingContent.WriteString(choice.Delta.GetReasoningContent())
  41. }
  42. if len(choice.Delta.GetContentString()) > 0 {
  43. hasContent = true
  44. }
  45. }
  46. // Handle think to content conversion
  47. if info.ThinkingContentInfo.IsFirstThinkingContent {
  48. if hasThinkingContent {
  49. response := lastStreamResponse.Copy()
  50. for i := range response.Choices {
  51. // send `think` tag with thinking content
  52. response.Choices[i].Delta.SetContentString("<think>\n" + thinkingContent.String())
  53. response.Choices[i].Delta.ReasoningContent = nil
  54. response.Choices[i].Delta.Reasoning = nil
  55. }
  56. info.ThinkingContentInfo.IsFirstThinkingContent = false
  57. info.ThinkingContentInfo.HasSentThinkingContent = true
  58. return helper.ObjectData(c, response)
  59. }
  60. }
  61. if lastStreamResponse.Choices == nil || len(lastStreamResponse.Choices) == 0 {
  62. return helper.ObjectData(c, lastStreamResponse)
  63. }
  64. // Process each choice
  65. for i, choice := range lastStreamResponse.Choices {
  66. // Handle transition from thinking to content
  67. // only send `</think>` tag when previous thinking content has been sent
  68. if hasContent && !info.ThinkingContentInfo.SendLastThinkingContent && info.ThinkingContentInfo.HasSentThinkingContent {
  69. response := lastStreamResponse.Copy()
  70. for j := range response.Choices {
  71. response.Choices[j].Delta.SetContentString("\n</think>\n")
  72. response.Choices[j].Delta.ReasoningContent = nil
  73. response.Choices[j].Delta.Reasoning = nil
  74. }
  75. info.ThinkingContentInfo.SendLastThinkingContent = true
  76. helper.ObjectData(c, response)
  77. }
  78. // Convert reasoning content to regular content if any
  79. if len(choice.Delta.GetReasoningContent()) > 0 {
  80. lastStreamResponse.Choices[i].Delta.SetContentString(choice.Delta.GetReasoningContent())
  81. lastStreamResponse.Choices[i].Delta.ReasoningContent = nil
  82. lastStreamResponse.Choices[i].Delta.Reasoning = nil
  83. } else if !hasThinkingContent && !hasContent {
  84. // flush thinking content
  85. lastStreamResponse.Choices[i].Delta.ReasoningContent = nil
  86. lastStreamResponse.Choices[i].Delta.Reasoning = nil
  87. }
  88. }
  89. return helper.ObjectData(c, lastStreamResponse)
  90. }
  91. func OaiStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
  92. if resp == nil || resp.Body == nil {
  93. logger.LogError(c, "invalid response or response body")
  94. return nil, types.NewOpenAIError(fmt.Errorf("invalid response"), types.ErrorCodeBadResponse, http.StatusInternalServerError)
  95. }
  96. defer service.CloseResponseBodyGracefully(resp)
  97. model := info.UpstreamModelName
  98. var responseId string
  99. var createAt int64 = 0
  100. var systemFingerprint string
  101. var containStreamUsage bool
  102. var responseTextBuilder strings.Builder
  103. var toolCount int
  104. var usage = &dto.Usage{}
  105. var streamItems []string // store stream items
  106. var lastStreamData string
  107. var streamErr *types.NewAPIError
  108. var normalStreamStarted bool
  109. var secondLastStreamData string // 存储倒数第二个stream data,用于音频模型
  110. // 检查是否为音频模型
  111. isAudioModel := strings.Contains(strings.ToLower(model), "audio")
  112. helper.StreamScannerHandler(c, resp, info, func(data string) bool {
  113. if apiErr := streamOpenAIErrorFromData(data, resp.StatusCode); apiErr != nil {
  114. if !normalStreamStarted && !helper.StreamDataWritten(c) {
  115. streamErr = apiErr
  116. } else {
  117. logger.LogError(c, "upstream stream error after normal stream started: "+apiErr.Error())
  118. }
  119. return false
  120. }
  121. normalStreamStarted = true
  122. if lastStreamData != "" {
  123. err := HandleStreamFormat(c, info, lastStreamData, info.ChannelSetting.ForceFormat, info.ChannelSetting.ThinkingToContent)
  124. if err != nil {
  125. common.SysLog("error handling stream format: " + err.Error())
  126. }
  127. }
  128. if len(data) > 0 {
  129. // 对音频模型,保存倒数第二个stream data
  130. if isAudioModel && lastStreamData != "" {
  131. secondLastStreamData = lastStreamData
  132. }
  133. lastStreamData = data
  134. streamItems = append(streamItems, data)
  135. }
  136. return true
  137. })
  138. // 对音频模型,从倒数第二个stream data中提取usage信息
  139. if streamErr != nil {
  140. helper.ClearEventStreamHeadersIfNotWritten(c)
  141. return nil, streamErr
  142. }
  143. if isAudioModel && secondLastStreamData != "" {
  144. var streamResp struct {
  145. Usage *dto.Usage `json:"usage"`
  146. }
  147. err := common.Unmarshal([]byte(secondLastStreamData), &streamResp)
  148. if err == nil && streamResp.Usage != nil && service.ValidUsage(streamResp.Usage) {
  149. usage = streamResp.Usage
  150. containStreamUsage = true
  151. if common.DebugEnabled {
  152. logger.LogDebug(c, fmt.Sprintf("Audio model usage extracted from second last SSE: PromptTokens=%d, CompletionTokens=%d, TotalTokens=%d, InputTokens=%d, OutputTokens=%d",
  153. usage.PromptTokens, usage.CompletionTokens, usage.TotalTokens,
  154. usage.InputTokens, usage.OutputTokens))
  155. }
  156. }
  157. }
  158. // 处理最后的响应
  159. shouldSendLastResp := true
  160. if err := handleLastResponse(lastStreamData, &responseId, &createAt, &systemFingerprint, &model, &usage,
  161. &containStreamUsage, info, &shouldSendLastResp); err != nil {
  162. logger.LogError(c, fmt.Sprintf("error handling last response: %s, lastStreamData: [%s]", err.Error(), lastStreamData))
  163. }
  164. if info.RelayFormat == types.RelayFormatOpenAI {
  165. if shouldSendLastResp {
  166. _ = sendStreamData(c, info, lastStreamData, info.ChannelSetting.ForceFormat, info.ChannelSetting.ThinkingToContent)
  167. }
  168. }
  169. // 处理token计算
  170. if err := processTokens(info.RelayMode, streamItems, &responseTextBuilder, &toolCount); err != nil {
  171. logger.LogError(c, "error processing tokens: "+err.Error())
  172. }
  173. if !containStreamUsage {
  174. usage = service.ResponseText2Usage(c, responseTextBuilder.String(), info.UpstreamModelName, info.GetEstimatePromptTokens())
  175. usage.CompletionTokens += toolCount * 7
  176. }
  177. applyUsagePostProcessing(info, usage, common.StringToByteSlice(lastStreamData))
  178. HandleFinalResponse(c, info, lastStreamData, responseId, createAt, model, systemFingerprint, usage, containStreamUsage)
  179. relaycommon.SetRelayChatID(c, responseId)
  180. return usage, nil
  181. }
  182. func OpenaiHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
  183. defer service.CloseResponseBodyGracefully(resp)
  184. var simpleResponse dto.OpenAITextResponse
  185. responseBody, err := io.ReadAll(resp.Body)
  186. if err != nil {
  187. return nil, types.NewOpenAIError(err, types.ErrorCodeReadResponseBodyFailed, http.StatusInternalServerError)
  188. }
  189. if common.DebugEnabled {
  190. println("upstream response body:", string(responseBody))
  191. }
  192. // Unmarshal to simpleResponse
  193. if info.ChannelType == constant.ChannelTypeOpenRouter && info.ChannelOtherSettings.IsOpenRouterEnterprise() {
  194. // 尝试解析为 openrouter enterprise
  195. var enterpriseResponse openrouter.OpenRouterEnterpriseResponse
  196. err = common.Unmarshal(responseBody, &enterpriseResponse)
  197. if err != nil {
  198. return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
  199. }
  200. if enterpriseResponse.Success {
  201. responseBody = enterpriseResponse.Data
  202. } else {
  203. logger.LogError(c, fmt.Sprintf("openrouter enterprise response success=false, data: %s", enterpriseResponse.Data))
  204. return nil, types.NewOpenAIError(fmt.Errorf("openrouter response success=false"), types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
  205. }
  206. }
  207. err = common.Unmarshal(responseBody, &simpleResponse)
  208. if err != nil {
  209. return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
  210. }
  211. relaycommon.SetRelayChatID(c, simpleResponse.Id)
  212. if oaiError := simpleResponse.GetOpenAIError(); oaiError != nil && oaiError.Type != "" {
  213. return nil, types.WithOpenAIError(*oaiError, resp.StatusCode)
  214. }
  215. for _, choice := range simpleResponse.Choices {
  216. if choice.FinishReason == constant.FinishReasonContentFilter {
  217. common.SetContextKey(c, constant.ContextKeyAdminRejectReason, "openai_finish_reason=content_filter")
  218. break
  219. }
  220. }
  221. forceFormat := false
  222. if info.ChannelSetting.ForceFormat {
  223. forceFormat = true
  224. }
  225. usageModified := false
  226. if simpleResponse.Usage.PromptTokens == 0 {
  227. completionTokens := simpleResponse.Usage.CompletionTokens
  228. if completionTokens == 0 {
  229. for _, choice := range simpleResponse.Choices {
  230. ctkm := service.CountTextToken(choice.Message.StringContent()+choice.Message.ReasoningContent+choice.Message.Reasoning, info.UpstreamModelName)
  231. completionTokens += ctkm
  232. }
  233. }
  234. simpleResponse.Usage = dto.Usage{
  235. PromptTokens: info.GetEstimatePromptTokens(),
  236. CompletionTokens: completionTokens,
  237. TotalTokens: info.GetEstimatePromptTokens() + completionTokens,
  238. }
  239. usageModified = true
  240. }
  241. applyUsagePostProcessing(info, &simpleResponse.Usage, responseBody)
  242. switch info.RelayFormat {
  243. case types.RelayFormatOpenAI:
  244. if usageModified {
  245. var bodyMap map[string]interface{}
  246. err = common.Unmarshal(responseBody, &bodyMap)
  247. if err != nil {
  248. return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
  249. }
  250. bodyMap["usage"] = simpleResponse.Usage
  251. responseBody, _ = common.Marshal(bodyMap)
  252. }
  253. if forceFormat {
  254. responseBody, err = common.Marshal(simpleResponse)
  255. if err != nil {
  256. return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
  257. }
  258. } else {
  259. break
  260. }
  261. case types.RelayFormatClaude:
  262. claudeResp := service.ResponseOpenAI2Claude(&simpleResponse, info)
  263. claudeRespStr, err := common.Marshal(claudeResp)
  264. if err != nil {
  265. return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
  266. }
  267. responseBody = claudeRespStr
  268. case types.RelayFormatGemini:
  269. geminiResp := service.ResponseOpenAI2Gemini(&simpleResponse, info)
  270. geminiRespStr, err := common.Marshal(geminiResp)
  271. if err != nil {
  272. return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
  273. }
  274. responseBody = geminiRespStr
  275. }
  276. service.IOCopyBytesGracefully(c, resp, responseBody)
  277. return &simpleResponse.Usage, nil
  278. }
  279. func streamTTSResponse(c *gin.Context, resp *http.Response) {
  280. c.Writer.WriteHeaderNow()
  281. flusher, ok := c.Writer.(http.Flusher)
  282. if !ok {
  283. logger.LogWarn(c, "streaming not supported")
  284. _, err := io.Copy(c.Writer, resp.Body)
  285. if err != nil {
  286. logger.LogWarn(c, err.Error())
  287. }
  288. return
  289. }
  290. buffer := make([]byte, 4096)
  291. for {
  292. n, err := resp.Body.Read(buffer)
  293. //logger.LogInfo(c, fmt.Sprintf("streamTTSResponse read %d bytes", n))
  294. if n > 0 {
  295. if _, writeErr := c.Writer.Write(buffer[:n]); writeErr != nil {
  296. logger.LogError(c, writeErr.Error())
  297. break
  298. }
  299. flusher.Flush()
  300. }
  301. if err != nil {
  302. if err != io.EOF {
  303. logger.LogError(c, err.Error())
  304. }
  305. break
  306. }
  307. }
  308. }
  309. func OpenaiRealtimeHandler(c *gin.Context, info *relaycommon.RelayInfo) (*types.NewAPIError, *dto.RealtimeUsage) {
  310. if info == nil || info.ClientWs == nil || info.TargetWs == nil {
  311. return types.NewError(fmt.Errorf("invalid websocket connection"), types.ErrorCodeBadResponse), nil
  312. }
  313. info.IsStream = true
  314. clientConn := info.ClientWs
  315. targetConn := info.TargetWs
  316. clientClosed := make(chan struct{})
  317. targetClosed := make(chan struct{})
  318. sendChan := make(chan []byte, 100)
  319. receiveChan := make(chan []byte, 100)
  320. errChan := make(chan error, 2)
  321. usage := &dto.RealtimeUsage{}
  322. localUsage := &dto.RealtimeUsage{}
  323. sumUsage := &dto.RealtimeUsage{}
  324. gopool.Go(func() {
  325. defer func() {
  326. if r := recover(); r != nil {
  327. errChan <- fmt.Errorf("panic in client reader: %v", r)
  328. }
  329. }()
  330. for {
  331. select {
  332. case <-c.Done():
  333. return
  334. default:
  335. _, message, err := clientConn.ReadMessage()
  336. if err != nil {
  337. if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
  338. errChan <- fmt.Errorf("error reading from client: %v", err)
  339. }
  340. close(clientClosed)
  341. return
  342. }
  343. realtimeEvent := &dto.RealtimeEvent{}
  344. err = common.Unmarshal(message, realtimeEvent)
  345. if err != nil {
  346. errChan <- fmt.Errorf("error unmarshalling message: %v", err)
  347. return
  348. }
  349. if realtimeEvent.Type == dto.RealtimeEventTypeSessionUpdate {
  350. if realtimeEvent.Session != nil {
  351. if realtimeEvent.Session.Tools != nil {
  352. info.RealtimeTools = realtimeEvent.Session.Tools
  353. }
  354. }
  355. }
  356. textToken, audioToken, err := service.CountTokenRealtime(info, *realtimeEvent, info.UpstreamModelName)
  357. if err != nil {
  358. errChan <- fmt.Errorf("error counting text token: %v", err)
  359. return
  360. }
  361. logger.LogInfo(c, fmt.Sprintf("type: %s, textToken: %d, audioToken: %d", realtimeEvent.Type, textToken, audioToken))
  362. localUsage.TotalTokens += textToken + audioToken
  363. localUsage.InputTokens += textToken + audioToken
  364. localUsage.InputTokenDetails.TextTokens += textToken
  365. localUsage.InputTokenDetails.AudioTokens += audioToken
  366. err = helper.WssString(c, targetConn, string(message))
  367. if err != nil {
  368. errChan <- fmt.Errorf("error writing to target: %v", err)
  369. return
  370. }
  371. select {
  372. case sendChan <- message:
  373. default:
  374. }
  375. }
  376. }
  377. })
  378. gopool.Go(func() {
  379. defer func() {
  380. if r := recover(); r != nil {
  381. errChan <- fmt.Errorf("panic in target reader: %v", r)
  382. }
  383. }()
  384. for {
  385. select {
  386. case <-c.Done():
  387. return
  388. default:
  389. _, message, err := targetConn.ReadMessage()
  390. if err != nil {
  391. if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
  392. errChan <- fmt.Errorf("error reading from target: %v", err)
  393. }
  394. close(targetClosed)
  395. return
  396. }
  397. info.SetFirstResponseTime()
  398. realtimeEvent := &dto.RealtimeEvent{}
  399. err = common.Unmarshal(message, realtimeEvent)
  400. if err != nil {
  401. errChan <- fmt.Errorf("error unmarshalling message: %v", err)
  402. return
  403. }
  404. if realtimeEvent.Type == dto.RealtimeEventTypeResponseDone {
  405. realtimeUsage := realtimeEvent.Response.Usage
  406. if realtimeUsage != nil {
  407. usage.TotalTokens += realtimeUsage.TotalTokens
  408. usage.InputTokens += realtimeUsage.InputTokens
  409. usage.OutputTokens += realtimeUsage.OutputTokens
  410. usage.InputTokenDetails.AudioTokens += realtimeUsage.InputTokenDetails.AudioTokens
  411. usage.InputTokenDetails.CachedTokens += realtimeUsage.InputTokenDetails.CachedTokens
  412. usage.InputTokenDetails.TextTokens += realtimeUsage.InputTokenDetails.TextTokens
  413. usage.OutputTokenDetails.AudioTokens += realtimeUsage.OutputTokenDetails.AudioTokens
  414. usage.OutputTokenDetails.TextTokens += realtimeUsage.OutputTokenDetails.TextTokens
  415. err := preConsumeUsage(c, info, usage, sumUsage)
  416. if err != nil {
  417. errChan <- fmt.Errorf("error consume usage: %v", err)
  418. return
  419. }
  420. // 本次计费完成,清除
  421. usage = &dto.RealtimeUsage{}
  422. localUsage = &dto.RealtimeUsage{}
  423. } else {
  424. textToken, audioToken, err := service.CountTokenRealtime(info, *realtimeEvent, info.UpstreamModelName)
  425. if err != nil {
  426. errChan <- fmt.Errorf("error counting text token: %v", err)
  427. return
  428. }
  429. logger.LogInfo(c, fmt.Sprintf("type: %s, textToken: %d, audioToken: %d", realtimeEvent.Type, textToken, audioToken))
  430. localUsage.TotalTokens += textToken + audioToken
  431. info.IsFirstRequest = false
  432. localUsage.InputTokens += textToken + audioToken
  433. localUsage.InputTokenDetails.TextTokens += textToken
  434. localUsage.InputTokenDetails.AudioTokens += audioToken
  435. err = preConsumeUsage(c, info, localUsage, sumUsage)
  436. if err != nil {
  437. errChan <- fmt.Errorf("error consume usage: %v", err)
  438. return
  439. }
  440. // 本次计费完成,清除
  441. localUsage = &dto.RealtimeUsage{}
  442. // print now usage
  443. }
  444. logger.LogInfo(c, fmt.Sprintf("realtime streaming sumUsage: %v", sumUsage))
  445. logger.LogInfo(c, fmt.Sprintf("realtime streaming localUsage: %v", localUsage))
  446. logger.LogInfo(c, fmt.Sprintf("realtime streaming localUsage: %v", localUsage))
  447. } else if realtimeEvent.Type == dto.RealtimeEventTypeSessionUpdated || realtimeEvent.Type == dto.RealtimeEventTypeSessionCreated {
  448. realtimeSession := realtimeEvent.Session
  449. if realtimeSession != nil {
  450. // update audio format
  451. info.InputAudioFormat = common.GetStringIfEmpty(realtimeSession.InputAudioFormat, info.InputAudioFormat)
  452. info.OutputAudioFormat = common.GetStringIfEmpty(realtimeSession.OutputAudioFormat, info.OutputAudioFormat)
  453. }
  454. } else {
  455. textToken, audioToken, err := service.CountTokenRealtime(info, *realtimeEvent, info.UpstreamModelName)
  456. if err != nil {
  457. errChan <- fmt.Errorf("error counting text token: %v", err)
  458. return
  459. }
  460. logger.LogInfo(c, fmt.Sprintf("type: %s, textToken: %d, audioToken: %d", realtimeEvent.Type, textToken, audioToken))
  461. localUsage.TotalTokens += textToken + audioToken
  462. localUsage.OutputTokens += textToken + audioToken
  463. localUsage.OutputTokenDetails.TextTokens += textToken
  464. localUsage.OutputTokenDetails.AudioTokens += audioToken
  465. }
  466. err = helper.WssString(c, clientConn, string(message))
  467. if err != nil {
  468. errChan <- fmt.Errorf("error writing to client: %v", err)
  469. return
  470. }
  471. select {
  472. case receiveChan <- message:
  473. default:
  474. }
  475. }
  476. }
  477. })
  478. select {
  479. case <-clientClosed:
  480. case <-targetClosed:
  481. case err := <-errChan:
  482. //return service.OpenAIErrorWrapper(err, "realtime_error", http.StatusInternalServerError), nil
  483. logger.LogError(c, "realtime error: "+err.Error())
  484. case <-c.Done():
  485. }
  486. if usage.TotalTokens != 0 {
  487. _ = preConsumeUsage(c, info, usage, sumUsage)
  488. }
  489. if localUsage.TotalTokens != 0 {
  490. _ = preConsumeUsage(c, info, localUsage, sumUsage)
  491. }
  492. // check usage total tokens, if 0, use local usage
  493. return nil, sumUsage
  494. }
  495. func preConsumeUsage(ctx *gin.Context, info *relaycommon.RelayInfo, usage *dto.RealtimeUsage, totalUsage *dto.RealtimeUsage) error {
  496. if usage == nil || totalUsage == nil {
  497. return fmt.Errorf("invalid usage pointer")
  498. }
  499. totalUsage.TotalTokens += usage.TotalTokens
  500. totalUsage.InputTokens += usage.InputTokens
  501. totalUsage.OutputTokens += usage.OutputTokens
  502. totalUsage.InputTokenDetails.CachedTokens += usage.InputTokenDetails.CachedTokens
  503. totalUsage.InputTokenDetails.TextTokens += usage.InputTokenDetails.TextTokens
  504. totalUsage.InputTokenDetails.AudioTokens += usage.InputTokenDetails.AudioTokens
  505. totalUsage.OutputTokenDetails.TextTokens += usage.OutputTokenDetails.TextTokens
  506. totalUsage.OutputTokenDetails.AudioTokens += usage.OutputTokenDetails.AudioTokens
  507. // clear usage
  508. err := service.PreWssConsumeQuota(ctx, info, usage)
  509. return err
  510. }
  511. func OpenaiHandlerWithUsage(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
  512. defer service.CloseResponseBodyGracefully(resp)
  513. responseBody, err := io.ReadAll(resp.Body)
  514. if err != nil {
  515. return nil, types.NewOpenAIError(err, types.ErrorCodeReadResponseBodyFailed, http.StatusInternalServerError)
  516. }
  517. var usageResp dto.SimpleResponse
  518. err = common.Unmarshal(responseBody, &usageResp)
  519. if err != nil {
  520. return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
  521. }
  522. // 写入新的 response body
  523. service.IOCopyBytesGracefully(c, resp, responseBody)
  524. // Once we've written to the client, we should not return errors anymore
  525. // because the upstream has already consumed resources and returned content
  526. // We should still perform billing even if parsing fails
  527. // format
  528. if usageResp.InputTokens > 0 {
  529. usageResp.PromptTokens += usageResp.InputTokens
  530. }
  531. if usageResp.OutputTokens > 0 {
  532. usageResp.CompletionTokens += usageResp.OutputTokens
  533. }
  534. if usageResp.InputTokensDetails != nil {
  535. usageResp.PromptTokensDetails.ImageTokens += usageResp.InputTokensDetails.ImageTokens
  536. usageResp.PromptTokensDetails.TextTokens += usageResp.InputTokensDetails.TextTokens
  537. }
  538. applyUsagePostProcessing(info, &usageResp.Usage, responseBody)
  539. return &usageResp.Usage, nil
  540. }
  541. func applyUsagePostProcessing(info *relaycommon.RelayInfo, usage *dto.Usage, responseBody []byte) {
  542. if info == nil || usage == nil {
  543. return
  544. }
  545. switch info.ChannelType {
  546. case constant.ChannelTypeDeepSeek:
  547. if usage.PromptTokensDetails.CachedTokens == 0 && usage.PromptCacheHitTokens != 0 {
  548. usage.PromptTokensDetails.CachedTokens = usage.PromptCacheHitTokens
  549. }
  550. case constant.ChannelTypeZhipu_v4:
  551. // 智普的cached_tokens在标准位置: usage.prompt_tokens_details.cached_tokens
  552. if usage.PromptTokensDetails.CachedTokens == 0 {
  553. if usage.InputTokensDetails != nil && usage.InputTokensDetails.CachedTokens > 0 {
  554. usage.PromptTokensDetails.CachedTokens = usage.InputTokensDetails.CachedTokens
  555. } else if cachedTokens, ok := extractCachedTokensFromBody(responseBody); ok {
  556. usage.PromptTokensDetails.CachedTokens = cachedTokens
  557. } else if usage.PromptCacheHitTokens > 0 {
  558. usage.PromptTokensDetails.CachedTokens = usage.PromptCacheHitTokens
  559. }
  560. }
  561. case constant.ChannelTypeMoonshot:
  562. // Moonshot的cached_tokens在非标准位置: choices[].usage.cached_tokens
  563. if usage.PromptTokensDetails.CachedTokens == 0 {
  564. if usage.InputTokensDetails != nil && usage.InputTokensDetails.CachedTokens > 0 {
  565. usage.PromptTokensDetails.CachedTokens = usage.InputTokensDetails.CachedTokens
  566. } else if cachedTokens, ok := extractMoonshotCachedTokensFromBody(responseBody); ok {
  567. usage.PromptTokensDetails.CachedTokens = cachedTokens
  568. } else if cachedTokens, ok := extractCachedTokensFromBody(responseBody); ok {
  569. usage.PromptTokensDetails.CachedTokens = cachedTokens
  570. } else if usage.PromptCacheHitTokens > 0 {
  571. usage.PromptTokensDetails.CachedTokens = usage.PromptCacheHitTokens
  572. }
  573. }
  574. }
  575. }
  576. func extractCachedTokensFromBody(body []byte) (int, bool) {
  577. if len(body) == 0 {
  578. return 0, false
  579. }
  580. var payload struct {
  581. Usage struct {
  582. PromptTokensDetails struct {
  583. CachedTokens *int `json:"cached_tokens"`
  584. } `json:"prompt_tokens_details"`
  585. CachedTokens *int `json:"cached_tokens"`
  586. PromptCacheHitTokens *int `json:"prompt_cache_hit_tokens"`
  587. } `json:"usage"`
  588. }
  589. if err := common.Unmarshal(body, &payload); err != nil {
  590. return 0, false
  591. }
  592. if payload.Usage.PromptTokensDetails.CachedTokens != nil {
  593. return *payload.Usage.PromptTokensDetails.CachedTokens, true
  594. }
  595. if payload.Usage.CachedTokens != nil {
  596. return *payload.Usage.CachedTokens, true
  597. }
  598. if payload.Usage.PromptCacheHitTokens != nil {
  599. return *payload.Usage.PromptCacheHitTokens, true
  600. }
  601. return 0, false
  602. }
  603. // extractMoonshotCachedTokensFromBody 从Moonshot的非标准位置提取cached_tokens
  604. // Moonshot的流式响应格式: {"choices":[{"usage":{"cached_tokens":111}}]}
  605. func extractMoonshotCachedTokensFromBody(body []byte) (int, bool) {
  606. if len(body) == 0 {
  607. return 0, false
  608. }
  609. var payload struct {
  610. Choices []struct {
  611. Usage struct {
  612. CachedTokens *int `json:"cached_tokens"`
  613. } `json:"usage"`
  614. } `json:"choices"`
  615. }
  616. if err := common.Unmarshal(body, &payload); err != nil {
  617. return 0, false
  618. }
  619. // 遍历choices查找cached_tokens
  620. for _, choice := range payload.Choices {
  621. if choice.Usage.CachedTokens != nil && *choice.Usage.CachedTokens > 0 {
  622. return *choice.Usage.CachedTokens, true
  623. }
  624. }
  625. return 0, false
  626. }