Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

495 linhas
14 KiB

  1. package chinamobile_seedance
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/QuantumNous/new-api/common"
  11. "github.com/QuantumNous/new-api/constant"
  12. "github.com/QuantumNous/new-api/dto"
  13. "github.com/QuantumNous/new-api/model"
  14. taskcommon "github.com/QuantumNous/new-api/relay/channel/task/taskcommon"
  15. relaycommon "github.com/QuantumNous/new-api/relay/common"
  16. "github.com/QuantumNous/new-api/service"
  17. relaytypes "github.com/QuantumNous/new-api/types"
  18. "github.com/gin-gonic/gin"
  19. "github.com/pkg/errors"
  20. )
  21. type responsePayload struct {
  22. ID string `json:"id"`
  23. }
  24. type upstreamError struct {
  25. Code string `json:"code"`
  26. Message string `json:"message"`
  27. }
  28. func (e *upstreamError) UnmarshalJSON(data []byte) error {
  29. if len(bytes.TrimSpace(data)) == 0 || string(bytes.TrimSpace(data)) == "null" {
  30. return nil
  31. }
  32. var message string
  33. if err := common.Unmarshal(data, &message); err == nil {
  34. e.Message = message
  35. return nil
  36. }
  37. type alias upstreamError
  38. var parsed alias
  39. if err := common.Unmarshal(data, &parsed); err != nil {
  40. return err
  41. }
  42. *e = upstreamError(parsed)
  43. return nil
  44. }
  45. type responseTask struct {
  46. ID string `json:"id"`
  47. Model string `json:"model"`
  48. Status string `json:"status"`
  49. Content struct {
  50. VideoURL string `json:"video_url"`
  51. } `json:"content"`
  52. Usage struct {
  53. CompletionTokens int `json:"completion_tokens"`
  54. TotalTokens int `json:"total_tokens"`
  55. } `json:"usage"`
  56. Error upstreamError `json:"error"`
  57. Message string `json:"message"`
  58. CreatedAt int64 `json:"created_at"`
  59. UpdatedAt int64 `json:"updated_at"`
  60. }
  61. type TaskAdaptor struct {
  62. taskcommon.BaseBilling
  63. ChannelType int
  64. apiKey string
  65. baseURL string
  66. newSDK seedanceSDKFactory
  67. submitTimeout time.Duration
  68. queryTimeout time.Duration
  69. }
  70. func (a *TaskAdaptor) Init(info *relaycommon.RelayInfo) {
  71. if info != nil && info.ChannelMeta != nil {
  72. a.ChannelType = info.ChannelType
  73. a.baseURL = strings.TrimRight(info.ChannelBaseUrl, "/")
  74. a.apiKey = info.ApiKey
  75. }
  76. if a.newSDK == nil {
  77. a.newSDK = newRealSDK
  78. }
  79. if a.submitTimeout == 0 {
  80. a.submitTimeout = defaultSubmitTimeout
  81. }
  82. if a.queryTimeout == 0 {
  83. a.queryTimeout = defaultQueryTimeout
  84. }
  85. }
  86. func (a *TaskAdaptor) ValidateRequestAndSetAction(c *gin.Context, info *relaycommon.RelayInfo) (taskErr *dto.TaskError) {
  87. if _, err := relaycommon.GetTaskRequest(c); err == nil {
  88. info.Action = constant.TaskActionGenerate
  89. return nil
  90. }
  91. return relaycommon.ValidateBasicTaskRequest(c, info, constant.TaskActionGenerate)
  92. }
  93. func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, error) {
  94. baseURL := a.baseURL
  95. if strings.TrimSpace(baseURL) == "" && info != nil {
  96. baseURL = strings.TrimRight(info.ChannelBaseUrl, "/")
  97. }
  98. if strings.TrimSpace(baseURL) == "" {
  99. baseURL = defaultBaseURL
  100. }
  101. return fmt.Sprintf("%s/contents/generations/tasks", strings.TrimRight(baseURL, "/")), nil
  102. }
  103. func (a *TaskAdaptor) BuildRequestHeader(c *gin.Context, req *http.Request, info *relaycommon.RelayInfo) error {
  104. req.Header.Set("Content-Type", "application/json")
  105. req.Header.Set("Accept", "application/json")
  106. return nil
  107. }
  108. func (a *TaskAdaptor) BuildRequestBody(c *gin.Context, info *relaycommon.RelayInfo) (io.Reader, error) {
  109. req, err := relaycommon.GetTaskRequest(c)
  110. if err != nil {
  111. return nil, err
  112. }
  113. body, err := a.convertToRequestPayload(&req)
  114. if err != nil {
  115. return nil, errors.Wrap(err, "convert request payload failed")
  116. }
  117. if info != nil {
  118. if info.IsModelMapped {
  119. body["model"] = info.UpstreamModelName
  120. } else if modelName, _ := body["model"].(string); modelName != "" {
  121. info.UpstreamModelName = modelName
  122. }
  123. }
  124. data, err := common.Marshal(body)
  125. if err != nil {
  126. return nil, err
  127. }
  128. return bytes.NewReader(data), nil
  129. }
  130. func (a *TaskAdaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (*http.Response, error) {
  131. data, err := io.ReadAll(requestBody)
  132. if err != nil {
  133. return nil, err
  134. }
  135. payload := map[string]interface{}{}
  136. if err := common.Unmarshal(data, &payload); err != nil {
  137. return nil, err
  138. }
  139. modelName := chinaMobileSeedanceModel(modelFromPayloadOrInfo(payload, info))
  140. payload["model"] = modelName
  141. if info != nil {
  142. info.UpstreamModelName = modelName
  143. }
  144. client, err := a.createSDK(info, modelName)
  145. if err != nil {
  146. return nil, err
  147. }
  148. submitTimeout := a.submitTimeout
  149. if submitTimeout == 0 {
  150. submitTimeout = defaultSubmitTimeout
  151. }
  152. taskID, err := runSDKCall[string](submitTimeout, func() (string, error) {
  153. return client.CreateVideoGenerationTask(payload)
  154. })
  155. if err != nil {
  156. return chinaMobileSeedanceErrorResponse(err), nil
  157. }
  158. respBody, err := common.Marshal(map[string]any{"id": taskID})
  159. if err != nil {
  160. return nil, err
  161. }
  162. return &http.Response{
  163. StatusCode: http.StatusOK,
  164. Header: make(http.Header),
  165. Body: io.NopCloser(bytes.NewReader(respBody)),
  166. }, nil
  167. }
  168. func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (taskID string, taskData []byte, taskErr *dto.TaskError) {
  169. responseBody, err := io.ReadAll(resp.Body)
  170. if err != nil {
  171. return "", nil, service.TaskErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError)
  172. }
  173. _ = resp.Body.Close()
  174. var dResp responsePayload
  175. if err := common.Unmarshal(responseBody, &dResp); err != nil {
  176. return "", nil, service.TaskErrorWrapper(errors.Wrapf(err, "body: %s", responseBody), "unmarshal_response_body_failed", http.StatusInternalServerError)
  177. }
  178. if strings.TrimSpace(dResp.ID) == "" {
  179. return "", nil, service.TaskErrorWrapper(fmt.Errorf("task_id is empty"), "invalid_response", http.StatusInternalServerError)
  180. }
  181. clientPayload := map[string]any{}
  182. if err := common.Unmarshal(responseBody, &clientPayload); err != nil {
  183. return "", nil, service.TaskErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError)
  184. }
  185. clientPayload = relaytypes.CloneMapAny(clientPayload)
  186. if info.PublicTaskID != "" {
  187. clientPayload["id"] = info.PublicTaskID
  188. }
  189. if _, ok := clientPayload["created_at"]; !ok {
  190. clientPayload["created_at"] = time.Now().Unix()
  191. }
  192. if _, ok := clientPayload["model"]; !ok {
  193. clientPayload["model"] = info.OriginModelName
  194. }
  195. c.JSON(http.StatusOK, clientPayload)
  196. return dResp.ID, responseBody, nil
  197. }
  198. func (a *TaskAdaptor) FetchTask(baseUrl, key string, body map[string]any, proxy string) (*http.Response, error) {
  199. taskID, _ := body["task_id"].(string)
  200. if strings.TrimSpace(taskID) == "" {
  201. return nil, fmt.Errorf("invalid task_id")
  202. }
  203. modelName, _ := body["model"].(string)
  204. client, err := a.createSDKFromValues(baseUrl, key, modelName)
  205. if err != nil {
  206. return nil, err
  207. }
  208. queryTimeout := a.queryTimeout
  209. if queryTimeout == 0 {
  210. queryTimeout = defaultQueryTimeout
  211. }
  212. result, err := runSDKCall[map[string]interface{}](queryTimeout, func() (map[string]interface{}, error) {
  213. return client.QueryVideoGenerationTask(taskID)
  214. })
  215. if err != nil {
  216. return nil, err
  217. }
  218. respBody, err := common.Marshal(result)
  219. if err != nil {
  220. return nil, err
  221. }
  222. return &http.Response{
  223. StatusCode: http.StatusOK,
  224. Header: make(http.Header),
  225. Body: io.NopCloser(bytes.NewReader(respBody)),
  226. }, nil
  227. }
  228. func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, error) {
  229. resTask := responseTask{}
  230. if err := common.Unmarshal(respBody, &resTask); err != nil {
  231. return nil, errors.Wrap(err, "unmarshal task result failed")
  232. }
  233. taskResult := relaycommon.TaskInfo{Code: 0}
  234. switch strings.ToLower(resTask.Status) {
  235. case "pending", "queued":
  236. taskResult.Status = model.TaskStatusQueued
  237. taskResult.Progress = "10%"
  238. case "processing", "running":
  239. taskResult.Status = model.TaskStatusInProgress
  240. taskResult.Progress = "50%"
  241. case "succeeded", "success":
  242. taskResult.Status = model.TaskStatusSuccess
  243. taskResult.Progress = "100%"
  244. taskResult.Url = resTask.Content.VideoURL
  245. taskResult.CompletionTokens = resTask.Usage.CompletionTokens
  246. taskResult.TotalTokens = resTask.Usage.TotalTokens
  247. case "failed", "expired", "cancelled":
  248. taskResult.Status = model.TaskStatusFailure
  249. taskResult.Progress = "100%"
  250. taskResult.Reason = upstreamReason(resTask)
  251. default:
  252. if resTask.Error.Message != "" || resTask.Message != "" {
  253. taskResult.Status = model.TaskStatusFailure
  254. taskResult.Progress = "100%"
  255. taskResult.Reason = upstreamReason(resTask)
  256. } else {
  257. taskResult.Status = model.TaskStatusInProgress
  258. taskResult.Progress = "30%"
  259. }
  260. }
  261. return &taskResult, nil
  262. }
  263. func (a *TaskAdaptor) ConvertToOpenAIVideo(originTask *model.Task) ([]byte, error) {
  264. var dResp responseTask
  265. if err := common.Unmarshal(originTask.Data, &dResp); err != nil {
  266. return nil, errors.Wrap(err, "unmarshal chinamobile seedance task data failed")
  267. }
  268. openAIVideo := dto.NewOpenAIVideo()
  269. openAIVideo.ID = originTask.TaskID
  270. openAIVideo.TaskID = originTask.TaskID
  271. openAIVideo.Status = originTask.Status.ToVideoStatus()
  272. openAIVideo.SetProgressStr(originTask.Progress)
  273. openAIVideo.SetMetadata("url", dResp.Content.VideoURL)
  274. openAIVideo.CreatedAt = originTask.CreatedAt
  275. openAIVideo.CompletedAt = originTask.UpdatedAt
  276. openAIVideo.Model = originTask.Properties.OriginModelName
  277. if originTask.Status == model.TaskStatusFailure || dResp.Status == "failed" {
  278. message := upstreamReason(dResp)
  279. if message == "" {
  280. message = "task failed"
  281. }
  282. code := dResp.Error.Code
  283. if code == "" {
  284. code = "failed"
  285. }
  286. openAIVideo.Error = &dto.OpenAIVideoError{Message: message, Code: code}
  287. }
  288. return common.Marshal(openAIVideo)
  289. }
  290. func (a *TaskAdaptor) GetModelList() []string {
  291. return ModelList
  292. }
  293. func (a *TaskAdaptor) GetChannelName() string {
  294. return ChannelName
  295. }
  296. func (a *TaskAdaptor) createSDK(info *relaycommon.RelayInfo, modelName string) (seedanceSDK, error) {
  297. baseURL := a.baseURL
  298. apiKey := a.apiKey
  299. if info != nil && info.ChannelMeta != nil {
  300. baseURL = info.ChannelBaseUrl
  301. apiKey = info.ApiKey
  302. }
  303. return a.createSDKFromValues(baseURL, apiKey, modelName)
  304. }
  305. func (a *TaskAdaptor) createSDKFromValues(baseURL, apiKey, modelName string) (seedanceSDK, error) {
  306. factory := a.newSDK
  307. if factory == nil {
  308. factory = newRealSDK
  309. }
  310. return factory(strings.TrimRight(baseURL, "/"), apiKey, modelName)
  311. }
  312. func (a *TaskAdaptor) convertToRequestPayload(req *relaycommon.TaskSubmitReq) (map[string]interface{}, error) {
  313. payload := map[string]interface{}{
  314. "model": req.Model,
  315. }
  316. for k, v := range req.Metadata {
  317. payload[k] = v
  318. }
  319. if sec, _ := strconv.Atoi(req.Seconds); sec > 0 {
  320. if _, ok := payload["duration"]; !ok {
  321. payload["duration"] = sec
  322. }
  323. }
  324. content, err := normalizeContent(payload["content"])
  325. if err != nil {
  326. return nil, err
  327. }
  328. if len(content) == 0 && req.HasImage() {
  329. for _, imgURL := range req.Images {
  330. content = append(content, map[string]interface{}{
  331. "type": "image_url",
  332. "image_url": map[string]interface{}{"url": imgURL},
  333. })
  334. }
  335. }
  336. if strings.TrimSpace(req.Prompt) != "" && !hasTextContent(content) {
  337. content = append(content, map[string]interface{}{
  338. "type": "text",
  339. "text": req.Prompt,
  340. })
  341. }
  342. if len(content) > 0 {
  343. payload["content"] = content
  344. }
  345. return payload, nil
  346. }
  347. func normalizeContent(input any) ([]interface{}, error) {
  348. if input == nil {
  349. return nil, nil
  350. }
  351. data, err := common.Marshal(input)
  352. if err != nil {
  353. return nil, err
  354. }
  355. var content []interface{}
  356. if err := common.Unmarshal(data, &content); err != nil {
  357. return nil, err
  358. }
  359. return content, nil
  360. }
  361. func hasTextContent(content []interface{}) bool {
  362. for _, item := range content {
  363. m, ok := item.(map[string]interface{})
  364. if !ok {
  365. continue
  366. }
  367. if m["type"] == "text" && strings.TrimSpace(fmt.Sprint(m["text"])) != "" {
  368. return true
  369. }
  370. }
  371. return false
  372. }
  373. func modelFromPayloadOrInfo(payload map[string]interface{}, info *relaycommon.RelayInfo) string {
  374. if modelName, _ := payload["model"].(string); strings.TrimSpace(modelName) != "" {
  375. return modelName
  376. }
  377. if info != nil {
  378. if strings.TrimSpace(info.UpstreamModelName) != "" {
  379. return info.UpstreamModelName
  380. }
  381. return info.OriginModelName
  382. }
  383. return defaultModel
  384. }
  385. func chinaMobileSeedanceModel(modelName string) string {
  386. switch strings.TrimSpace(modelName) {
  387. case "", "doubao-seedance-2-0-260128", "doubao-seedance-2-0-fast-260128":
  388. return defaultModel
  389. default:
  390. return modelName
  391. }
  392. }
  393. type chinaMobileSeedanceUpstreamError struct {
  394. ErrorCode string `json:"ErrorCode"`
  395. ErrorMessage string `json:"ErrorMessage"`
  396. }
  397. func chinaMobileSeedanceErrorResponse(err error) *http.Response {
  398. statusCode := http.StatusBadGateway
  399. body := map[string]any{
  400. "code": "upstream_error",
  401. "message": err.Error(),
  402. }
  403. if upstreamErr, ok := parseChinaMobileSeedanceError(err); ok {
  404. statusCode = chinaMobileSeedanceStatusCode(upstreamErr.ErrorCode)
  405. body["code"] = upstreamErr.ErrorCode
  406. body["message"] = upstreamErr.ErrorMessage
  407. }
  408. respBody, marshalErr := common.Marshal(body)
  409. if marshalErr != nil {
  410. respBody = []byte(`{"code":"upstream_error","message":"upstream error"}`)
  411. }
  412. return &http.Response{
  413. StatusCode: statusCode,
  414. Header: make(http.Header),
  415. Body: io.NopCloser(bytes.NewReader(respBody)),
  416. }
  417. }
  418. func parseChinaMobileSeedanceError(err error) (chinaMobileSeedanceUpstreamError, bool) {
  419. if err == nil {
  420. return chinaMobileSeedanceUpstreamError{}, false
  421. }
  422. text := err.Error()
  423. start := strings.Index(text, "{")
  424. end := strings.LastIndex(text, "}")
  425. if start < 0 || end < start {
  426. return chinaMobileSeedanceUpstreamError{}, false
  427. }
  428. var upstreamErr chinaMobileSeedanceUpstreamError
  429. if err := common.Unmarshal([]byte(text[start:end+1]), &upstreamErr); err != nil {
  430. return chinaMobileSeedanceUpstreamError{}, false
  431. }
  432. return upstreamErr, upstreamErr.ErrorCode != "" || upstreamErr.ErrorMessage != ""
  433. }
  434. func chinaMobileSeedanceStatusCode(code string) int {
  435. upperCode := strings.ToUpper(strings.TrimSpace(code))
  436. switch {
  437. case strings.Contains(upperCode, "PERMISSION") || strings.Contains(upperCode, "UNAUTHORIZED"):
  438. return http.StatusForbidden
  439. case strings.Contains(upperCode, "SENSITIVE") || strings.Contains(upperCode, "INVALID") || strings.Contains(upperCode, "BAD"):
  440. return http.StatusBadRequest
  441. case strings.Contains(upperCode, "RATE") || strings.Contains(upperCode, "LIMIT"):
  442. return http.StatusTooManyRequests
  443. default:
  444. return http.StatusBadGateway
  445. }
  446. }
  447. func upstreamReason(resTask responseTask) string {
  448. if resTask.Error.Message != "" {
  449. return resTask.Error.Message
  450. }
  451. if resTask.Message != "" {
  452. return resTask.Message
  453. }
  454. if resTask.Status != "" {
  455. return "task " + resTask.Status
  456. }
  457. return "upstream error"
  458. }