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.
 
 
 

240 rivejä
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 ClaudeErrorWrapper(err error, code string, statusCode int) *dto.ClaudeErrorWithStatusCode {
  56. text := err.Error()
  57. lowerText := strings.ToLower(text)
  58. if !strings.HasPrefix(lowerText, "get file base64 from url") {
  59. if strings.Contains(lowerText, "post") || strings.Contains(lowerText, "dial") || strings.Contains(lowerText, "http") {
  60. common.SysLog(fmt.Sprintf("error: %s", text))
  61. text = "请求上游地址失败"
  62. }
  63. }
  64. claudeError := types.ClaudeError{
  65. Message: text,
  66. Type: "new_api_error",
  67. }
  68. return &dto.ClaudeErrorWithStatusCode{
  69. Error: claudeError,
  70. StatusCode: statusCode,
  71. }
  72. }
  73. func ClaudeErrorWrapperLocal(err error, code string, statusCode int) *dto.ClaudeErrorWithStatusCode {
  74. claudeErr := ClaudeErrorWrapper(err, code, statusCode)
  75. claudeErr.LocalError = true
  76. return claudeErr
  77. }
  78. func RelayErrorHandler(ctx context.Context, resp *http.Response, showBodyWhenFail bool) (newApiErr *types.NewAPIError) {
  79. newApiErr = types.InitOpenAIError(types.ErrorCodeBadResponseStatusCode, resp.StatusCode)
  80. // Capture upstream request-id from response headers
  81. upstreamReqId := resp.Header.Get("request-id")
  82. if upstreamReqId == "" {
  83. upstreamReqId = resp.Header.Get("x-request-id")
  84. }
  85. newApiErr.UpstreamRequestId = upstreamReqId
  86. responseBody, err := io.ReadAll(resp.Body)
  87. if err != nil {
  88. return
  89. }
  90. CloseResponseBodyGracefully(resp)
  91. // Capture upstream response body (truncate to 2KB to avoid oversized logs)
  92. const maxBodyLen = 2048
  93. bodyStr := string(responseBody)
  94. if len(bodyStr) > maxBodyLen {
  95. bodyStr = bodyStr[:maxBodyLen] + "...(truncated)"
  96. }
  97. newApiErr.UpstreamBody = bodyStr
  98. var errResponse dto.GeneralErrorResponse
  99. buildErrWithBody := func(message string) error {
  100. if message == "" {
  101. return fmt.Errorf("bad response status code %d, body: %s", resp.StatusCode, string(responseBody))
  102. }
  103. return fmt.Errorf("bad response status code %d, message: %s, body: %s", resp.StatusCode, message, string(responseBody))
  104. }
  105. err = common.Unmarshal(responseBody, &errResponse)
  106. if err != nil {
  107. if showBodyWhenFail {
  108. newApiErr.Err = buildErrWithBody("")
  109. } else {
  110. logger.LogError(ctx, fmt.Sprintf("bad response status code %d, body: %s", resp.StatusCode, string(responseBody)))
  111. newApiErr.Err = fmt.Errorf("bad response status code %d", resp.StatusCode)
  112. }
  113. return
  114. }
  115. if common.GetJsonType(errResponse.Error) == "object" {
  116. // General format error (OpenAI, Anthropic, Gemini, etc.)
  117. oaiError := errResponse.TryToOpenAIError()
  118. if oaiError != nil {
  119. newApiErr = types.WithOpenAIError(*oaiError, resp.StatusCode)
  120. newApiErr.UpstreamRequestId = upstreamReqId
  121. newApiErr.UpstreamBody = bodyStr
  122. if showBodyWhenFail {
  123. newApiErr.Err = buildErrWithBody(newApiErr.Error())
  124. }
  125. return
  126. }
  127. }
  128. newApiErr = types.NewOpenAIError(errors.New(errResponse.ToMessage()), types.ErrorCodeBadResponseStatusCode, resp.StatusCode)
  129. newApiErr.UpstreamRequestId = upstreamReqId
  130. newApiErr.UpstreamBody = bodyStr
  131. if showBodyWhenFail {
  132. newApiErr.Err = buildErrWithBody(newApiErr.Error())
  133. }
  134. return
  135. }
  136. func ResetStatusCode(newApiErr *types.NewAPIError, statusCodeMappingStr string) {
  137. if newApiErr == nil {
  138. return
  139. }
  140. if statusCodeMappingStr == "" || statusCodeMappingStr == "{}" {
  141. return
  142. }
  143. statusCodeMapping := make(map[string]any)
  144. err := common.Unmarshal([]byte(statusCodeMappingStr), &statusCodeMapping)
  145. if err != nil {
  146. return
  147. }
  148. if newApiErr.StatusCode == http.StatusOK {
  149. return
  150. }
  151. codeStr := strconv.Itoa(newApiErr.StatusCode)
  152. if value, ok := statusCodeMapping[codeStr]; ok {
  153. intCode, ok := parseStatusCodeMappingValue(value)
  154. if !ok {
  155. return
  156. }
  157. newApiErr.StatusCode = intCode
  158. }
  159. }
  160. func parseStatusCodeMappingValue(value any) (int, bool) {
  161. switch v := value.(type) {
  162. case string:
  163. if v == "" {
  164. return 0, false
  165. }
  166. statusCode, err := strconv.Atoi(v)
  167. if err != nil {
  168. return 0, false
  169. }
  170. return statusCode, true
  171. case float64:
  172. if v != math.Trunc(v) {
  173. return 0, false
  174. }
  175. return int(v), true
  176. case int:
  177. return v, true
  178. case json.Number:
  179. statusCode, err := strconv.Atoi(v.String())
  180. if err != nil {
  181. return 0, false
  182. }
  183. return statusCode, true
  184. default:
  185. return 0, false
  186. }
  187. }
  188. func TaskErrorWrapperLocal(err error, code string, statusCode int) *dto.TaskError {
  189. openaiErr := TaskErrorWrapper(err, code, statusCode)
  190. openaiErr.LocalError = true
  191. return openaiErr
  192. }
  193. func TaskErrorWrapper(err error, code string, statusCode int) *dto.TaskError {
  194. text := err.Error()
  195. lowerText := strings.ToLower(text)
  196. if strings.Contains(lowerText, "post") || strings.Contains(lowerText, "dial") || strings.Contains(lowerText, "http") {
  197. common.SysLog(fmt.Sprintf("error: %s", text))
  198. //text = "请求上游地址失败"
  199. text = common.MaskSensitiveInfo(text)
  200. }
  201. //避免暴露内部错误
  202. taskError := &dto.TaskError{
  203. Code: code,
  204. Message: text,
  205. StatusCode: statusCode,
  206. Error: err,
  207. }
  208. return taskError
  209. }
  210. // TaskErrorFromAPIError 将 PreConsumeBilling 返回的 NewAPIError 转换为 TaskError。
  211. func TaskErrorFromAPIError(apiErr *types.NewAPIError) *dto.TaskError {
  212. if apiErr == nil {
  213. return nil
  214. }
  215. return &dto.TaskError{
  216. Code: string(apiErr.GetErrorCode()),
  217. Message: apiErr.Err.Error(),
  218. StatusCode: apiErr.StatusCode,
  219. Error: apiErr.Err,
  220. }
  221. }