選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 

854 行
28 KiB

  1. package controller
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "log"
  7. "net/http"
  8. "strconv"
  9. "strings"
  10. "time"
  11. "github.com/QuantumNous/new-api/common"
  12. "github.com/QuantumNous/new-api/common/metrics"
  13. "github.com/QuantumNous/new-api/constant"
  14. "github.com/QuantumNous/new-api/dto"
  15. "github.com/QuantumNous/new-api/logger"
  16. "github.com/QuantumNous/new-api/middleware"
  17. "github.com/QuantumNous/new-api/model"
  18. "github.com/QuantumNous/new-api/relay"
  19. relaycommon "github.com/QuantumNous/new-api/relay/common"
  20. relayconstant "github.com/QuantumNous/new-api/relay/constant"
  21. "github.com/QuantumNous/new-api/relay/helper"
  22. "github.com/QuantumNous/new-api/service"
  23. "github.com/QuantumNous/new-api/setting"
  24. "github.com/QuantumNous/new-api/setting/operation_setting"
  25. "github.com/QuantumNous/new-api/setting/ratio_setting"
  26. "github.com/QuantumNous/new-api/types"
  27. "github.com/bytedance/gopkg/util/gopool"
  28. "github.com/gin-gonic/gin"
  29. "github.com/gorilla/websocket"
  30. )
  31. func relayHandler(c *gin.Context, info *relaycommon.RelayInfo) *types.NewAPIError {
  32. var err *types.NewAPIError
  33. switch info.RelayMode {
  34. case relayconstant.RelayModeImagesGenerations, relayconstant.RelayModeImagesEdits:
  35. err = relay.ImageHelper(c, info)
  36. case relayconstant.RelayModeAudioSpeech:
  37. fallthrough
  38. case relayconstant.RelayModeAudioTranslation:
  39. fallthrough
  40. case relayconstant.RelayModeAudioTranscription:
  41. err = relay.AudioHelper(c, info)
  42. case relayconstant.RelayModeRerank:
  43. err = relay.RerankHelper(c, info)
  44. case relayconstant.RelayModeEmbeddings:
  45. err = relay.EmbeddingHelper(c, info)
  46. case relayconstant.RelayModeResponses, relayconstant.RelayModeResponsesCompact:
  47. err = relay.ResponsesHelper(c, info)
  48. default:
  49. err = relay.TextHelper(c, info)
  50. }
  51. return err
  52. }
  53. func geminiRelayHandler(c *gin.Context, info *relaycommon.RelayInfo) *types.NewAPIError {
  54. var err *types.NewAPIError
  55. if strings.Contains(c.Request.URL.Path, "embed") {
  56. err = relay.GeminiEmbeddingHandler(c, info)
  57. } else {
  58. err = relay.GeminiHelper(c, info)
  59. }
  60. return err
  61. }
  62. func Relay(c *gin.Context, relayFormat types.RelayFormat) {
  63. requestId := c.GetString(common.RequestIdKey)
  64. //group := common.GetContextKeyString(c, constant.ContextKeyUsingGroup)
  65. //originalModel := common.GetContextKeyString(c, constant.ContextKeyOriginalModel)
  66. var (
  67. newAPIError *types.NewAPIError
  68. ws *websocket.Conn
  69. )
  70. if relayFormat == types.RelayFormatOpenAIRealtime {
  71. var err error
  72. ws, err = upgrader.Upgrade(c.Writer, c.Request, nil)
  73. if err != nil {
  74. helper.WssError(c, ws, types.NewError(err, types.ErrorCodeGetChannelFailed, types.ErrOptionWithSkipRetry()).ToOpenAIError())
  75. return
  76. }
  77. defer ws.Close()
  78. }
  79. defer func() {
  80. if newAPIError != nil {
  81. logger.LogError(c, fmt.Sprintf("relay error: %s", newAPIError.Error()))
  82. // Prometheus: 错误计数
  83. if metrics.IsEnabled() {
  84. channelId := strconv.Itoa(c.GetInt("channel_id"))
  85. modelName := c.GetString("original_model")
  86. errorType, errorCode := metrics.ClassifyError(newAPIError.StatusCode, newAPIError.Error())
  87. metrics.GetMetrics().RequestErrorsTotal.WithLabelValues(channelId, modelName, errorType, errorCode).Inc()
  88. }
  89. newAPIError.SetMessage(common.MessageWithRequestId(newAPIError.Error(), requestId))
  90. switch relayFormat {
  91. case types.RelayFormatOpenAIRealtime:
  92. helper.WssError(c, ws, newAPIError.ToOpenAIError())
  93. case types.RelayFormatClaude:
  94. c.JSON(newAPIError.StatusCode, gin.H{
  95. "type": "error",
  96. "error": newAPIError.ToClaudeError(),
  97. })
  98. default:
  99. c.JSON(newAPIError.StatusCode, gin.H{
  100. "error": newAPIError.ToOpenAIError(),
  101. })
  102. }
  103. }
  104. }()
  105. request, err := helper.GetAndValidateRequest(c, relayFormat)
  106. if err != nil {
  107. // Map "request body too large" to 413 so clients can handle it correctly
  108. if common.IsRequestBodyTooLargeError(err) || errors.Is(err, common.ErrRequestBodyTooLarge) {
  109. newAPIError = types.NewErrorWithStatusCode(err, types.ErrorCodeReadRequestBodyFailed, http.StatusRequestEntityTooLarge, types.ErrOptionWithSkipRetry())
  110. } else {
  111. newAPIError = types.NewError(err, types.ErrorCodeInvalidRequest)
  112. }
  113. return
  114. }
  115. relayInfo, err := relaycommon.GenRelayInfo(c, relayFormat, request, ws)
  116. if err != nil {
  117. newAPIError = types.NewError(err, types.ErrorCodeGenRelayInfoFailed)
  118. return
  119. }
  120. needSensitiveCheck := setting.ShouldCheckPromptSensitive()
  121. needCountToken := constant.CountToken
  122. // Avoid building huge CombineText (strings.Join) when token counting and sensitive check are both disabled.
  123. var meta *types.TokenCountMeta
  124. if needSensitiveCheck || needCountToken {
  125. meta = request.GetTokenCountMeta()
  126. } else {
  127. meta = fastTokenCountMetaForPricing(request)
  128. }
  129. if needSensitiveCheck && meta != nil {
  130. contains, words := service.CheckSensitiveText(meta.CombineText)
  131. if contains {
  132. logger.LogWarn(c, fmt.Sprintf("user sensitive words detected: %s", strings.Join(words, ", ")))
  133. newAPIError = types.NewError(err, types.ErrorCodeSensitiveWordsDetected)
  134. return
  135. }
  136. }
  137. tokens, err := service.EstimateRequestToken(c, meta, relayInfo)
  138. if err != nil {
  139. newAPIError = types.NewError(err, types.ErrorCodeCountTokenFailed)
  140. return
  141. }
  142. relayInfo.SetEstimatePromptTokens(tokens)
  143. priceData, err := helper.ModelPriceHelper(c, relayInfo, tokens, meta)
  144. if err != nil {
  145. newAPIError = types.NewError(err, types.ErrorCodeModelPriceError)
  146. return
  147. }
  148. // common.SetContextKey(c, constant.ContextKeyTokenCountMeta, meta)
  149. if priceData.FreeModel {
  150. logger.LogInfo(c, fmt.Sprintf("模型 %s 免费,跳过预扣费", relayInfo.OriginModelName))
  151. } else {
  152. common.SysLog(fmt.Sprintf("[RegionSync] relay PreConsumeBilling: userId=%d, QuotaToPreConsume=%d, FreeModel=%v, UsePrice=%v, ModelRatio=%.4f, GroupRatio=%.4f, ModelPrice=%.4f, model=%s",
  153. relayInfo.UserId, priceData.QuotaToPreConsume, priceData.FreeModel, priceData.UsePrice, priceData.ModelRatio, priceData.GroupRatioInfo.GroupRatio, priceData.ModelPrice, relayInfo.OriginModelName))
  154. newAPIError = service.PreConsumeBilling(c, priceData.QuotaToPreConsume, relayInfo)
  155. if newAPIError != nil {
  156. return
  157. }
  158. }
  159. defer func() {
  160. // Only return quota if downstream failed and quota was actually pre-consumed
  161. if newAPIError != nil {
  162. newAPIError = service.NormalizeViolationFeeError(newAPIError)
  163. if relayInfo.Billing != nil {
  164. relayInfo.Billing.Refund(c)
  165. }
  166. service.ChargeViolationFeeIfNeeded(c, relayInfo, newAPIError)
  167. }
  168. }()
  169. retryParam := &service.RetryParam{
  170. Ctx: c,
  171. TokenGroup: relayInfo.TokenGroup,
  172. ModelName: relayInfo.OriginModelName,
  173. Retry: common.GetPointer(0),
  174. RequireMatrixUsageBilling: relayInfo.RequireMatrixUsageBilling,
  175. }
  176. for ; retryParam.GetRetry() <= common.RetryTimes; retryParam.IncreaseRetry() {
  177. channel, _, channelErr := getChannel(c, relayInfo, retryParam)
  178. if channelErr != nil {
  179. logger.LogError(c, channelErr.Error())
  180. newAPIError = channelErr
  181. break
  182. }
  183. addUsedChannel(c, channel.Id)
  184. bodyStorage, bodyErr := common.GetBodyStorage(c)
  185. if bodyErr != nil {
  186. // Ensure consistent 413 for oversized bodies even when error occurs later (e.g., retry path)
  187. if common.IsRequestBodyTooLargeError(bodyErr) || errors.Is(bodyErr, common.ErrRequestBodyTooLarge) {
  188. newAPIError = types.NewErrorWithStatusCode(bodyErr, types.ErrorCodeReadRequestBodyFailed, http.StatusRequestEntityTooLarge, types.ErrOptionWithSkipRetry())
  189. } else {
  190. newAPIError = types.NewErrorWithStatusCode(bodyErr, types.ErrorCodeReadRequestBodyFailed, http.StatusBadRequest, types.ErrOptionWithSkipRetry())
  191. }
  192. break
  193. }
  194. c.Request.Body = io.NopCloser(bodyStorage)
  195. switch relayFormat {
  196. case types.RelayFormatOpenAIRealtime:
  197. newAPIError = relay.WssHelper(c, relayInfo)
  198. case types.RelayFormatClaude:
  199. newAPIError = relay.ClaudeHelper(c, relayInfo)
  200. case types.RelayFormatGemini:
  201. newAPIError = geminiRelayHandler(c, relayInfo)
  202. default:
  203. newAPIError = relayHandler(c, relayInfo)
  204. }
  205. if newAPIError == nil {
  206. return
  207. }
  208. newAPIError = service.NormalizeViolationFeeError(newAPIError)
  209. processChannelError(c, *types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey, common.GetContextKeyString(c, constant.ContextKeyChannelKey), channel.GetAutoBan()), newAPIError)
  210. if !shouldRetry(c, newAPIError, common.RetryTimes-retryParam.GetRetry()) {
  211. break
  212. }
  213. // Prometheus: 重试计数
  214. if metrics.IsEnabled() {
  215. channelIdStr := strconv.Itoa(channel.Id)
  216. metrics.GetMetrics().RequestRetriesTotal.WithLabelValues(channelIdStr, relayInfo.OriginModelName).Inc()
  217. }
  218. }
  219. useChannel := c.GetStringSlice("use_channel")
  220. if len(useChannel) > 1 {
  221. retryLogStr := fmt.Sprintf("重试:%s", strings.Trim(strings.Join(strings.Fields(fmt.Sprint(useChannel)), "->"), "[]"))
  222. logger.LogInfo(c, retryLogStr)
  223. }
  224. }
  225. var upgrader = websocket.Upgrader{
  226. Subprotocols: []string{"realtime"}, // WS 握手支持的协议,如果有使用 Sec-WebSocket-Protocol,则必须在此声明对应的 Protocol TODO add other protocol
  227. CheckOrigin: func(r *http.Request) bool {
  228. return true // 允许跨域
  229. },
  230. }
  231. func addUsedChannel(c *gin.Context, channelId int) {
  232. useChannel := c.GetStringSlice("use_channel")
  233. useChannel = append(useChannel, fmt.Sprintf("%d", channelId))
  234. c.Set("use_channel", useChannel)
  235. }
  236. func fastTokenCountMetaForPricing(request dto.Request) *types.TokenCountMeta {
  237. if request == nil {
  238. return &types.TokenCountMeta{}
  239. }
  240. meta := &types.TokenCountMeta{
  241. TokenType: types.TokenTypeTokenizer,
  242. }
  243. switch r := request.(type) {
  244. case *dto.GeneralOpenAIRequest:
  245. if r.MaxCompletionTokens > r.MaxTokens {
  246. meta.MaxTokens = int(r.MaxCompletionTokens)
  247. } else {
  248. meta.MaxTokens = int(r.MaxTokens)
  249. }
  250. case *dto.OpenAIResponsesRequest:
  251. meta.MaxTokens = int(r.MaxOutputTokens)
  252. case *dto.ClaudeRequest:
  253. meta.MaxTokens = int(r.MaxTokens)
  254. case *dto.ImageRequest:
  255. // Pricing for image requests depends on ImagePriceRatio; safe to compute even when CountToken is disabled.
  256. return r.GetTokenCountMeta()
  257. default:
  258. // Best-effort: leave CombineText empty to avoid large allocations.
  259. }
  260. return meta
  261. }
  262. func concreteTaskVideoBindingGroup(c *gin.Context, group string) string {
  263. group = strings.TrimSpace(group)
  264. if group == "" {
  265. group = strings.TrimSpace(common.GetContextKeyString(c, constant.ContextKeyTokenGroup))
  266. }
  267. if group == "auto" {
  268. autoGroup := strings.TrimSpace(common.GetContextKeyString(c, constant.ContextKeyAutoGroup))
  269. if autoGroup == "" || autoGroup == "auto" {
  270. return ""
  271. }
  272. return autoGroup
  273. }
  274. return group
  275. }
  276. func persistTaskVideoBindingIfNeeded(userId int, group string, channel *model.Channel) error {
  277. group = strings.TrimSpace(group)
  278. if group == "" || group == "auto" || channel == nil {
  279. return nil
  280. }
  281. switch channel.Type {
  282. case service.DoubaoAssetChannelType, constant.ChannelTypeDoubaoVideoCompatibleTianyiYun:
  283. if !service.IsUsableDoubaoAssetChannel(channel, group) {
  284. return nil
  285. }
  286. return model.BindUserAssetChannel(userId, channel.Type, group, channel.Id)
  287. case constant.ChannelTypeKlingAiping:
  288. if !service.IsUsableKlingAssetChannel(channel, group) {
  289. return nil
  290. }
  291. return model.BindUserAssetChannel(userId, constant.ChannelTypeKlingAiping, group, channel.Id)
  292. }
  293. return nil
  294. }
  295. func getChannel(c *gin.Context, info *relaycommon.RelayInfo, retryParam *service.RetryParam) (*model.Channel, string, *types.NewAPIError) {
  296. if info.ChannelMeta == nil {
  297. autoBan := c.GetBool("auto_ban")
  298. autoBanInt := 1
  299. if !autoBan {
  300. autoBanInt = 0
  301. }
  302. return &model.Channel{
  303. Id: c.GetInt("channel_id"),
  304. Type: c.GetInt("channel_type"),
  305. Name: c.GetString("channel_name"),
  306. AutoBan: &autoBanInt,
  307. }, concreteTaskVideoBindingGroup(c, info.TokenGroup), nil
  308. }
  309. channel, selectGroup, err := service.CacheGetRandomSatisfiedChannel(retryParam)
  310. info.PriceData.GroupRatioInfo = helper.HandleGroupRatio(c, info)
  311. if err != nil {
  312. if apiErr, ok := err.(*types.NewAPIError); ok {
  313. return nil, selectGroup, apiErr
  314. }
  315. return nil, selectGroup, types.NewError(fmt.Errorf("获取分组 %s 下模型 %s 的可用渠道失败(retry): %s", selectGroup, info.OriginModelName, err.Error()), types.ErrorCodeGetChannelFailed, types.ErrOptionWithSkipRetry())
  316. }
  317. if channel == nil {
  318. return nil, selectGroup, types.NewError(fmt.Errorf("分组 %s 下模型 %s 的可用渠道不存在(retry)", selectGroup, info.OriginModelName), types.ErrorCodeGetChannelFailed, types.ErrOptionWithSkipRetry())
  319. }
  320. newAPIError := middleware.SetupContextForSelectedChannel(c, channel, info.OriginModelName)
  321. if newAPIError != nil {
  322. return nil, selectGroup, newAPIError
  323. }
  324. return channel, selectGroup, nil
  325. }
  326. func requiredTaskChannelTypeForRequest(c *gin.Context) int {
  327. if strings.HasPrefix(c.Request.URL.Path, "/api/v3/contents/generations/tasks") {
  328. return service.DoubaoAssetChannelTypeForModel(taskModelNameForRequiredChannelType(c))
  329. }
  330. if isKlingAipingNativePath(c.Request.URL.Path) {
  331. return constant.ChannelTypeKlingAiping
  332. }
  333. return 0
  334. }
  335. func taskModelNameForRequiredChannelType(c *gin.Context) string {
  336. if req, err := relaycommon.GetTaskRequest(c); err == nil {
  337. return req.Model
  338. }
  339. bodyStorage, err := common.GetBodyStorage(c)
  340. if err != nil {
  341. return ""
  342. }
  343. body, err := bodyStorage.Bytes()
  344. if err != nil {
  345. return ""
  346. }
  347. var raw struct {
  348. Model string `json:"model"`
  349. }
  350. if err := common.Unmarshal(body, &raw); err != nil {
  351. return ""
  352. }
  353. return raw.Model
  354. }
  355. func shouldRetry(c *gin.Context, openaiErr *types.NewAPIError, retryTimes int) bool {
  356. if openaiErr == nil {
  357. return false
  358. }
  359. if service.ShouldSkipRetryAfterChannelAffinityFailure(c) {
  360. return false
  361. }
  362. if types.IsChannelError(openaiErr) {
  363. return true
  364. }
  365. if types.IsSkipRetryError(openaiErr) {
  366. return false
  367. }
  368. if retryTimes <= 0 {
  369. return false
  370. }
  371. code := openaiErr.StatusCode
  372. if code >= 200 && code < 300 {
  373. return false
  374. }
  375. if code < 100 || code > 599 {
  376. return true
  377. }
  378. return operation_setting.ShouldRetryByStatusCode(code)
  379. }
  380. func processChannelError(c *gin.Context, channelError types.ChannelError, err *types.NewAPIError) {
  381. logger.LogError(c, fmt.Sprintf("channel error (channel #%d, status code: %d): %s", channelError.ChannelId, err.StatusCode, err.Error()))
  382. // 不要使用context获取渠道信息,异步处理时可能会出现渠道信息不一致的情况
  383. // do not use context to get channel info, there may be inconsistent channel info when processing asynchronously
  384. if service.ShouldDisableChannel(channelError.ChannelType, err) && channelError.AutoBan {
  385. gopool.Go(func() {
  386. service.DisableChannel(channelError, err.ErrorWithStatusCode())
  387. })
  388. }
  389. if constant.ErrorLogEnabled && types.IsRecordErrorLog(err) {
  390. // 保存错误日志到mysql中
  391. userId := c.GetInt("id")
  392. tokenName := c.GetString("token_name")
  393. modelName := c.GetString("original_model")
  394. tokenId := c.GetInt("token_id")
  395. userGroup := c.GetString("group")
  396. channelId := c.GetInt("channel_id")
  397. other := make(map[string]interface{})
  398. if c.Request != nil && c.Request.URL != nil {
  399. other["request_path"] = c.Request.URL.Path
  400. }
  401. other["error_type"] = err.GetErrorType()
  402. other["error_code"] = err.GetErrorCode()
  403. other["status_code"] = err.StatusCode
  404. other["channel_id"] = channelId
  405. other["channel_name"] = c.GetString("channel_name")
  406. other["channel_type"] = c.GetInt("channel_type")
  407. if err.UpstreamRequestId != "" {
  408. other["upstream_request_id"] = err.UpstreamRequestId
  409. }
  410. if err.UpstreamBody != "" {
  411. other["upstream_body"] = err.UpstreamBody
  412. }
  413. adminInfo := make(map[string]interface{})
  414. adminInfo["use_channel"] = c.GetStringSlice("use_channel")
  415. isMultiKey := common.GetContextKeyBool(c, constant.ContextKeyChannelIsMultiKey)
  416. if isMultiKey {
  417. adminInfo["is_multi_key"] = true
  418. adminInfo["multi_key_index"] = common.GetContextKeyInt(c, constant.ContextKeyChannelMultiKeyIndex)
  419. }
  420. service.AppendChannelAffinityAdminInfo(c, adminInfo)
  421. other["admin_info"] = adminInfo
  422. startTime := common.GetContextKeyTime(c, constant.ContextKeyRequestStartTime)
  423. if startTime.IsZero() {
  424. startTime = time.Now()
  425. }
  426. useTimeSeconds := int(time.Since(startTime).Seconds())
  427. model.RecordErrorLog(c, userId, channelId, modelName, tokenName, err.MaskSensitiveErrorWithStatusCode(), tokenId, useTimeSeconds, false, userGroup, other)
  428. }
  429. }
  430. func RelayMidjourney(c *gin.Context) {
  431. relayInfo, err := relaycommon.GenRelayInfo(c, types.RelayFormatMjProxy, nil, nil)
  432. if err != nil {
  433. c.JSON(http.StatusInternalServerError, gin.H{
  434. "description": fmt.Sprintf("failed to generate relay info: %s", err.Error()),
  435. "type": "upstream_error",
  436. "code": 4,
  437. })
  438. return
  439. }
  440. var mjErr *dto.MidjourneyResponse
  441. switch relayInfo.RelayMode {
  442. case relayconstant.RelayModeMidjourneyNotify:
  443. mjErr = relay.RelayMidjourneyNotify(c)
  444. case relayconstant.RelayModeMidjourneyTaskFetch, relayconstant.RelayModeMidjourneyTaskFetchByCondition:
  445. mjErr = relay.RelayMidjourneyTask(c, relayInfo.RelayMode)
  446. case relayconstant.RelayModeMidjourneyTaskImageSeed:
  447. mjErr = relay.RelayMidjourneyTaskImageSeed(c)
  448. case relayconstant.RelayModeSwapFace:
  449. mjErr = relay.RelaySwapFace(c, relayInfo)
  450. default:
  451. mjErr = relay.RelayMidjourneySubmit(c, relayInfo)
  452. }
  453. //err = relayMidjourneySubmit(c, relayMode)
  454. log.Println(mjErr)
  455. if mjErr != nil {
  456. statusCode := http.StatusBadRequest
  457. if mjErr.Code == 30 {
  458. mjErr.Result = "当前分组负载已饱和,请稍后再试,或升级账户以提升服务质量。"
  459. statusCode = http.StatusTooManyRequests
  460. }
  461. c.JSON(statusCode, gin.H{
  462. "description": fmt.Sprintf("%s %s", mjErr.Description, mjErr.Result),
  463. "type": "upstream_error",
  464. "code": mjErr.Code,
  465. })
  466. channelId := c.GetInt("channel_id")
  467. logger.LogError(c, fmt.Sprintf("relay error (channel #%d, status code %d): %s", channelId, statusCode, fmt.Sprintf("%s %s", mjErr.Description, mjErr.Result)))
  468. }
  469. }
  470. func RelayNotImplemented(c *gin.Context) {
  471. err := types.OpenAIError{
  472. Message: "API not implemented",
  473. Type: "new_api_error",
  474. Param: "",
  475. Code: "api_not_implemented",
  476. }
  477. c.JSON(http.StatusNotImplemented, gin.H{
  478. "error": err,
  479. })
  480. }
  481. func RelayNotFound(c *gin.Context) {
  482. err := types.OpenAIError{
  483. Message: fmt.Sprintf("Invalid URL (%s %s)", c.Request.Method, c.Request.URL.Path),
  484. Type: "invalid_request_error",
  485. Param: "",
  486. Code: "",
  487. }
  488. c.JSON(http.StatusNotFound, gin.H{
  489. "error": err,
  490. })
  491. }
  492. func RelayTaskFetch(c *gin.Context) {
  493. relayInfo, err := relaycommon.GenRelayInfo(c, types.RelayFormatTask, nil, nil)
  494. if err != nil {
  495. c.JSON(http.StatusInternalServerError, &dto.TaskError{
  496. Code: "gen_relay_info_failed",
  497. Message: err.Error(),
  498. StatusCode: http.StatusInternalServerError,
  499. })
  500. return
  501. }
  502. if taskErr := relay.RelayTaskFetch(c, relayInfo.RelayMode); taskErr != nil {
  503. respondTaskError(c, taskErr)
  504. }
  505. }
  506. func preloadTaskPricingConfig(c *gin.Context, info *relaycommon.RelayInfo) *dto.TaskError {
  507. modelName := strings.TrimSpace(info.OriginModelName)
  508. action := strings.TrimSpace(info.Action)
  509. contentType := c.Request.Header.Get("Content-Type")
  510. storage, err := common.GetBodyStorage(c)
  511. if err != nil {
  512. status := http.StatusBadRequest
  513. if common.IsRequestBodyTooLargeError(err) || errors.Is(err, common.ErrRequestBodyTooLarge) {
  514. status = http.StatusRequestEntityTooLarge
  515. }
  516. return service.TaskErrorWrapperLocal(err, "read_request_body_failed", status)
  517. }
  518. defer func() {
  519. _, _ = storage.Seek(0, io.SeekStart)
  520. c.Request.Body = io.NopCloser(storage)
  521. }()
  522. switch {
  523. case strings.HasPrefix(contentType, "application/json"):
  524. body, err := storage.Bytes()
  525. if err != nil {
  526. return service.TaskErrorWrapperLocal(err, "read_request_body_failed", http.StatusBadRequest)
  527. }
  528. if strings.TrimSpace(string(body)) != "" {
  529. var payload map[string]any
  530. if err := common.Unmarshal(body, &payload); err != nil {
  531. return service.TaskErrorWrapperLocal(err, "invalid_json", http.StatusBadRequest)
  532. }
  533. if raw, ok := payload["model_name"].(string); ok && strings.TrimSpace(raw) != "" {
  534. modelName = strings.TrimSpace(raw)
  535. } else if raw, ok := payload["model"].(string); ok && strings.TrimSpace(raw) != "" {
  536. modelName = strings.TrimSpace(raw)
  537. }
  538. if raw, ok := payload["action"].(string); ok && strings.TrimSpace(raw) != "" {
  539. action = strings.TrimSpace(raw)
  540. }
  541. }
  542. case strings.Contains(contentType, gin.MIMEMultipartPOSTForm):
  543. form, err := common.ParseMultipartFormReusable(c)
  544. if err != nil {
  545. return service.TaskErrorWrapperLocal(err, "invalid_multipart_form", http.StatusBadRequest)
  546. }
  547. if vals := form.Value["model"]; len(vals) > 0 && strings.TrimSpace(vals[0]) != "" {
  548. modelName = strings.TrimSpace(vals[0])
  549. }
  550. if vals := form.Value["action"]; len(vals) > 0 && strings.TrimSpace(vals[0]) != "" {
  551. action = strings.TrimSpace(vals[0])
  552. }
  553. }
  554. if modelName == "" && action != "" {
  555. platform := constant.TaskPlatform(c.GetString("platform"))
  556. modelName = service.CoverTaskActionToModelName(platform, action)
  557. }
  558. if modelName != "" {
  559. info.OriginModelName = modelName
  560. }
  561. if action != "" {
  562. info.Action = action
  563. }
  564. info.PricingConfigSnapshotLoaded = true
  565. info.PricingConfigSnapshot = ratio_setting.GetPricingConfig(info.OriginModelName)
  566. info.RequireMatrixUsageBilling = info.PricingConfigSnapshot != nil &&
  567. info.PricingConfigSnapshot.BillingUnit == types.BillingUnitPer1MTokens
  568. return nil
  569. }
  570. func buildTaskBillingContext(info *relaycommon.RelayInfo) *model.TaskBillingContext {
  571. bc := &model.TaskBillingContext{
  572. ModelPrice: info.PriceData.ModelPrice,
  573. GroupRatio: info.PriceData.GroupRatioInfo.GroupRatio,
  574. ModelRatio: info.PriceData.ModelRatio,
  575. OtherRatios: info.PriceData.OtherRatios,
  576. OriginModelName: info.OriginModelName,
  577. PerCallBilling: common.StringsContains(constant.TaskPricePatches, info.OriginModelName),
  578. }
  579. if decision := info.PricingDecisionFrozen; decision != nil && decision.BillingMode == types.BillingModeMatrix {
  580. bc.BillingMode = decision.BillingMode
  581. bc.PricingSnapshot = types.CloneMapAny(decision.Snapshot)
  582. bc.BillingUnit = decision.BillingUnit
  583. bc.TokenUnitPriceUSD = decision.TokenUnitPriceUSD
  584. bc.PerCallBilling = decision.PerCallBilling
  585. if decision.BillingUnit == types.BillingUnitPer1MTokens {
  586. bc.ModelPrice = decision.TokenUnitPriceUSD
  587. } else {
  588. bc.ModelPrice = decision.PriceUSD
  589. }
  590. bc.GroupRatio = decision.GroupRatioInfo.GroupRatio
  591. bc.OtherRatios = types.CloneRatios(decision.OtherRatios)
  592. }
  593. return bc
  594. }
  595. func RelayTask(c *gin.Context) {
  596. relayInfo, err := relaycommon.GenRelayInfo(c, types.RelayFormatTask, nil, nil)
  597. if err != nil {
  598. c.JSON(http.StatusInternalServerError, &dto.TaskError{
  599. Code: "gen_relay_info_failed",
  600. Message: err.Error(),
  601. StatusCode: http.StatusInternalServerError,
  602. })
  603. return
  604. }
  605. relayTaskWithInfo(c, relayInfo)
  606. }
  607. func relayTaskWithInfo(c *gin.Context, relayInfo *relaycommon.RelayInfo) {
  608. if taskErr := relay.ResolveOriginTask(c, relayInfo); taskErr != nil {
  609. respondTaskError(c, taskErr)
  610. return
  611. }
  612. if taskErr := preloadTaskPricingConfig(c, relayInfo); taskErr != nil {
  613. respondTaskError(c, taskErr)
  614. return
  615. }
  616. var result *relay.TaskSubmitResult
  617. var taskErr *dto.TaskError
  618. defer func() {
  619. if taskErr != nil && relayInfo.Billing != nil {
  620. relayInfo.Billing.Refund(c)
  621. }
  622. }()
  623. retryParam := &service.RetryParam{
  624. Ctx: c,
  625. TokenGroup: relayInfo.TokenGroup,
  626. ModelName: relayInfo.OriginModelName,
  627. Retry: common.GetPointer(0),
  628. RequiredChannelType: requiredTaskChannelTypeForRequest(c),
  629. }
  630. for ; retryParam.GetRetry() <= common.RetryTimes; retryParam.IncreaseRetry() {
  631. var channel *model.Channel
  632. var selectedGroup string
  633. if lockedCh, ok := relayInfo.LockedChannel.(*model.Channel); ok && lockedCh != nil {
  634. channel = lockedCh
  635. selectedGroup = concreteTaskVideoBindingGroup(c, relayInfo.TokenGroup)
  636. if setupErr := middleware.SetupContextForSelectedChannel(c, channel, relayInfo.OriginModelName); setupErr != nil {
  637. taskErr = service.TaskErrorWrapperLocal(setupErr.Err, "setup_locked_channel_failed", http.StatusInternalServerError)
  638. break
  639. }
  640. } else {
  641. var channelErr *types.NewAPIError
  642. channel, selectedGroup, channelErr = getChannel(c, relayInfo, retryParam)
  643. if channelErr != nil {
  644. logger.LogError(c, channelErr.Error())
  645. taskErr = service.TaskErrorWrapperLocal(channelErr.Err, "get_channel_failed", http.StatusInternalServerError)
  646. break
  647. }
  648. }
  649. if bindErr := persistTaskVideoBindingIfNeeded(c.GetInt("id"), selectedGroup, channel); bindErr != nil {
  650. taskErr = service.TaskErrorWrapperLocal(bindErr, "bind_task_video_channel_failed", http.StatusServiceUnavailable)
  651. break
  652. }
  653. addUsedChannel(c, channel.Id)
  654. bodyStorage, bodyErr := common.GetBodyStorage(c)
  655. if bodyErr != nil {
  656. if common.IsRequestBodyTooLargeError(bodyErr) || errors.Is(bodyErr, common.ErrRequestBodyTooLarge) {
  657. taskErr = service.TaskErrorWrapperLocal(bodyErr, "read_request_body_failed", http.StatusRequestEntityTooLarge)
  658. } else {
  659. taskErr = service.TaskErrorWrapperLocal(bodyErr, "read_request_body_failed", http.StatusBadRequest)
  660. }
  661. break
  662. }
  663. c.Request.Body = io.NopCloser(bodyStorage)
  664. result, taskErr = relay.RelayTaskSubmit(c, relayInfo)
  665. if taskErr == nil {
  666. break
  667. }
  668. if !taskErr.LocalError {
  669. processChannelError(c,
  670. *types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey,
  671. common.GetContextKeyString(c, constant.ContextKeyChannelKey), channel.GetAutoBan()),
  672. types.NewOpenAIError(taskErr.Error, types.ErrorCodeBadResponseStatusCode, taskErr.StatusCode))
  673. }
  674. if !shouldRetryTaskRelay(c, channel.Id, taskErr, common.RetryTimes-retryParam.GetRetry()) {
  675. break
  676. }
  677. }
  678. useChannel := c.GetStringSlice("use_channel")
  679. if len(useChannel) > 1 {
  680. retryLogStr := fmt.Sprintf("重试:%s", strings.Trim(strings.Join(strings.Fields(fmt.Sprint(useChannel)), "->"), "[]"))
  681. logger.LogInfo(c, retryLogStr)
  682. }
  683. // ── 成功:结算 + 日志 + 插入任务 ──
  684. if taskErr == nil {
  685. if settleErr := service.SettleBilling(c, relayInfo, result.Quota); settleErr != nil {
  686. common.SysError("settle task billing error: " + settleErr.Error())
  687. }
  688. service.LogTaskConsumption(c, relayInfo)
  689. task := model.InitTask(result.Platform, relayInfo)
  690. task.PrivateData.UpstreamTaskID = result.UpstreamTaskID
  691. task.PrivateData.BillingSource = relayInfo.BillingSource
  692. task.PrivateData.SubscriptionId = relayInfo.SubscriptionId
  693. task.PrivateData.TokenId = relayInfo.TokenId
  694. task.PrivateData.BillingContext = buildTaskBillingContext(relayInfo)
  695. task.PrivateData.UpstreamRequest = relay.BuildUpstreamRequestSnapshotForTask(result.UpstreamReqJSON)
  696. task.Quota = result.Quota
  697. task.Data = result.TaskData
  698. task.Action = relayInfo.Action
  699. if insertErr := task.Insert(); insertErr != nil {
  700. common.SysError("insert task error: " + insertErr.Error())
  701. }
  702. }
  703. if taskErr != nil {
  704. respondTaskError(c, taskErr)
  705. }
  706. }
  707. // respondTaskError 统一输出 Task 错误响应(含 429 限流提示改写)
  708. func respondTaskError(c *gin.Context, taskErr *dto.TaskError) {
  709. if taskErr.StatusCode == http.StatusTooManyRequests {
  710. taskErr.Message = "当前分组上游负载已饱和,请稍后再试"
  711. }
  712. if isKlingAipingNativePath(c.Request.URL.Path) {
  713. normalizeKlingAipingTaskError(taskErr)
  714. }
  715. c.JSON(taskErr.StatusCode, taskErr)
  716. }
  717. func isKlingAipingNativePath(path string) bool {
  718. return pathMatchesAnyKlingAipingNativePrefix(path,
  719. "/v1/videos/text2video",
  720. "/v1/videos/image2video",
  721. "/v1/videos/motion-control",
  722. "/v1/videos/omni-video",
  723. "/v1/videos/multi-image2video",
  724. "/v1/videos/video-extend",
  725. "/v1/general/advanced-custom-elements",
  726. "/v1/general/custom-voices",
  727. )
  728. }
  729. func pathMatchesAnyKlingAipingNativePrefix(path string, prefixes ...string) bool {
  730. for _, prefix := range prefixes {
  731. if path == prefix || strings.HasPrefix(path, prefix+"/") {
  732. return true
  733. }
  734. }
  735. return false
  736. }
  737. func shouldRetryTaskRelay(c *gin.Context, channelId int, taskErr *dto.TaskError, retryTimes int) bool {
  738. if taskErr == nil {
  739. return false
  740. }
  741. if service.ShouldSkipRetryAfterChannelAffinityFailure(c) {
  742. return false
  743. }
  744. if retryTimes <= 0 {
  745. return false
  746. }
  747. if taskErr.StatusCode == http.StatusTooManyRequests {
  748. return true
  749. }
  750. if taskErr.StatusCode == 307 {
  751. return true
  752. }
  753. if taskErr.StatusCode/100 == 5 {
  754. // 超时不重试
  755. if operation_setting.IsAlwaysSkipRetryStatusCode(taskErr.StatusCode) {
  756. return false
  757. }
  758. return true
  759. }
  760. if taskErr.StatusCode == http.StatusBadRequest {
  761. return false
  762. }
  763. if taskErr.StatusCode == 408 {
  764. // azure处理超时不重试
  765. return false
  766. }
  767. if taskErr.LocalError {
  768. return false
  769. }
  770. if taskErr.StatusCode/100 == 2 {
  771. return false
  772. }
  773. return true
  774. }