您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

243 行
6.8 KiB

  1. package service
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "math"
  9. "net/http"
  10. "strconv"
  11. "strings"
  12. "github.com/QuantumNous/new-api/common"
  13. "github.com/QuantumNous/new-api/dto"
  14. "github.com/QuantumNous/new-api/logger"
  15. "github.com/QuantumNous/new-api/types"
  16. )
  17. func MidjourneyErrorWrapper(code int, desc string) *dto.MidjourneyResponse {
  18. return &dto.MidjourneyResponse{
  19. Code: code,
  20. Description: desc,
  21. }
  22. }
  23. func MidjourneyErrorWithStatusCodeWrapper(code int, desc string, statusCode int) *dto.MidjourneyResponseWithStatusCode {
  24. return &dto.MidjourneyResponseWithStatusCode{
  25. StatusCode: statusCode,
  26. Response: *MidjourneyErrorWrapper(code, desc),
  27. }
  28. }
  29. //// OpenAIErrorWrapper wraps an error into an OpenAIErrorWithStatusCode
  30. //func OpenAIErrorWrapper(err error, code string, statusCode int) *dto.OpenAIErrorWithStatusCode {
  31. // text := err.Error()
  32. // lowerText := strings.ToLower(text)
  33. // if !strings.HasPrefix(lowerText, "get file base64 from url") && !strings.HasPrefix(lowerText, "mime type is not supported") {
  34. // if strings.Contains(lowerText, "post") || strings.Contains(lowerText, "dial") || strings.Contains(lowerText, "http") {
  35. // common.SysLog(fmt.Sprintf("error: %s", text))
  36. // text = "请求上游地址失败"
  37. // }
  38. // }
  39. // openAIError := dto.OpenAIError{
  40. // Message: text,
  41. // Type: "new_api_error",
  42. // Code: code,
  43. // }
  44. // return &dto.OpenAIErrorWithStatusCode{
  45. // Error: openAIError,
  46. // StatusCode: statusCode,
  47. // }
  48. //}
  49. //
  50. //func OpenAIErrorWrapperLocal(err error, code string, statusCode int) *dto.OpenAIErrorWithStatusCode {
  51. // openaiErr := OpenAIErrorWrapper(err, code, statusCode)
  52. // openaiErr.LocalError = true
  53. // return openaiErr
  54. //}
  55. func TruncateBody(body string) string {
  56. const maxBodyLen = 2048
  57. if len(body) > maxBodyLen {
  58. return body[:maxBodyLen] + "...(truncated)"
  59. }
  60. return body
  61. }
  62. func ClaudeErrorWrapper(err error, code string, statusCode int) *dto.ClaudeErrorWithStatusCode {
  63. text := err.Error()
  64. lowerText := strings.ToLower(text)
  65. if !strings.HasPrefix(lowerText, "get file base64 from url") {
  66. if strings.Contains(lowerText, "post") || strings.Contains(lowerText, "dial") || strings.Contains(lowerText, "http") {
  67. common.SysLog(fmt.Sprintf("error: %s", text))
  68. text = "请求上游地址失败"
  69. }
  70. }
  71. claudeError := types.ClaudeError{
  72. Message: text,
  73. Type: "new_api_error",
  74. }
  75. return &dto.ClaudeErrorWithStatusCode{
  76. Error: claudeError,
  77. StatusCode: statusCode,
  78. }
  79. }
  80. func ClaudeErrorWrapperLocal(err error, code string, statusCode int) *dto.ClaudeErrorWithStatusCode {
  81. claudeErr := ClaudeErrorWrapper(err, code, statusCode)
  82. claudeErr.LocalError = true
  83. return claudeErr
  84. }
  85. func RelayErrorHandler(ctx context.Context, resp *http.Response, showBodyWhenFail bool) (newApiErr *types.NewAPIError) {
  86. newApiErr = types.InitOpenAIError(types.ErrorCodeBadResponseStatusCode, resp.StatusCode)
  87. // Capture upstream request-id from response headers
  88. upstreamReqId := resp.Header.Get("request-id")
  89. if upstreamReqId == "" {
  90. upstreamReqId = resp.Header.Get("x-request-id")
  91. }
  92. newApiErr.UpstreamRequestId = upstreamReqId
  93. responseBody, err := io.ReadAll(resp.Body)
  94. if err != nil {
  95. return
  96. }
  97. CloseResponseBodyGracefully(resp)
  98. bodyStr := TruncateBody(string(responseBody))
  99. newApiErr.UpstreamBody = bodyStr
  100. var errResponse dto.GeneralErrorResponse
  101. buildErrWithBody := func(message string) error {
  102. if message == "" {
  103. return fmt.Errorf("bad response status code %d, body: %s", resp.StatusCode, string(responseBody))
  104. }
  105. return fmt.Errorf("bad response status code %d, message: %s, body: %s", resp.StatusCode, message, string(responseBody))
  106. }
  107. err = common.Unmarshal(responseBody, &errResponse)
  108. if err != nil {
  109. if showBodyWhenFail {
  110. newApiErr.Err = buildErrWithBody("")
  111. } else {
  112. logger.LogError(ctx, fmt.Sprintf("bad response status code %d, body: %s", resp.StatusCode, string(responseBody)))
  113. newApiErr.Err = fmt.Errorf("bad response status code %d", resp.StatusCode)
  114. }
  115. return
  116. }
  117. if common.GetJsonType(errResponse.Error) == "object" {
  118. // General format error (OpenAI, Anthropic, Gemini, etc.)
  119. oaiError := errResponse.TryToOpenAIError()
  120. if oaiError != nil {
  121. newApiErr = types.WithOpenAIError(*oaiError, resp.StatusCode)
  122. newApiErr.UpstreamRequestId = upstreamReqId
  123. newApiErr.UpstreamBody = bodyStr
  124. if showBodyWhenFail {
  125. newApiErr.Err = buildErrWithBody(newApiErr.Error())
  126. }
  127. return
  128. }
  129. }
  130. newApiErr = types.NewOpenAIError(errors.New(errResponse.ToMessage()), types.ErrorCodeBadResponseStatusCode, resp.StatusCode)
  131. newApiErr.UpstreamRequestId = upstreamReqId
  132. newApiErr.UpstreamBody = bodyStr
  133. if showBodyWhenFail {
  134. newApiErr.Err = buildErrWithBody(newApiErr.Error())
  135. }
  136. return
  137. }
  138. func ResetStatusCode(newApiErr *types.NewAPIError, statusCodeMappingStr string) {
  139. if newApiErr == nil {
  140. return
  141. }
  142. if statusCodeMappingStr == "" || statusCodeMappingStr == "{}" {
  143. return
  144. }
  145. statusCodeMapping := make(map[string]any)
  146. err := common.Unmarshal([]byte(statusCodeMappingStr), &statusCodeMapping)
  147. if err != nil {
  148. return
  149. }
  150. if newApiErr.StatusCode == http.StatusOK {
  151. return
  152. }
  153. codeStr := strconv.Itoa(newApiErr.StatusCode)
  154. if value, ok := statusCodeMapping[codeStr]; ok {
  155. intCode, ok := parseStatusCodeMappingValue(value)
  156. if !ok {
  157. return
  158. }
  159. newApiErr.StatusCode = intCode
  160. }
  161. }
  162. func parseStatusCodeMappingValue(value any) (int, bool) {
  163. switch v := value.(type) {
  164. case string:
  165. if v == "" {
  166. return 0, false
  167. }
  168. statusCode, err := strconv.Atoi(v)
  169. if err != nil {
  170. return 0, false
  171. }
  172. return statusCode, true
  173. case float64:
  174. if v != math.Trunc(v) {
  175. return 0, false
  176. }
  177. return int(v), true
  178. case int:
  179. return v, true
  180. case json.Number:
  181. statusCode, err := strconv.Atoi(v.String())
  182. if err != nil {
  183. return 0, false
  184. }
  185. return statusCode, true
  186. default:
  187. return 0, false
  188. }
  189. }
  190. func TaskErrorWrapperLocal(err error, code string, statusCode int) *dto.TaskError {
  191. openaiErr := TaskErrorWrapper(err, code, statusCode)
  192. openaiErr.LocalError = true
  193. return openaiErr
  194. }
  195. func TaskErrorWrapper(err error, code string, statusCode int) *dto.TaskError {
  196. text := err.Error()
  197. lowerText := strings.ToLower(text)
  198. if strings.Contains(lowerText, "post") || strings.Contains(lowerText, "dial") || strings.Contains(lowerText, "http") {
  199. common.SysLog(fmt.Sprintf("error: %s", text))
  200. //text = "请求上游地址失败"
  201. text = common.MaskSensitiveInfo(text)
  202. }
  203. //避免暴露内部错误
  204. taskError := &dto.TaskError{
  205. Code: code,
  206. Message: text,
  207. StatusCode: statusCode,
  208. Error: err,
  209. }
  210. return taskError
  211. }
  212. // TaskErrorFromAPIError 将 PreConsumeBilling 返回的 NewAPIError 转换为 TaskError。
  213. func TaskErrorFromAPIError(apiErr *types.NewAPIError) *dto.TaskError {
  214. if apiErr == nil {
  215. return nil
  216. }
  217. return &dto.TaskError{
  218. Code: string(apiErr.GetErrorCode()),
  219. Message: apiErr.Err.Error(),
  220. StatusCode: apiErr.StatusCode,
  221. Error: apiErr.Err,
  222. }
  223. }