Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 

251 rinda
7.6 KiB

  1. package doubao
  2. import (
  3. "testing"
  4. "github.com/QuantumNous/new-api/dto"
  5. "github.com/QuantumNous/new-api/model"
  6. relaycommon "github.com/QuantumNous/new-api/relay/common"
  7. "github.com/stretchr/testify/require"
  8. )
  9. func TestParseTaskResult_SuccessWithUsage(t *testing.T) {
  10. adaptor := &TaskAdaptor{}
  11. taskInfo, err := adaptor.ParseTaskResult([]byte(`{
  12. "status":"succeeded",
  13. "content":{"video_url":"https://example.test/video.mp4"},
  14. "usage":{"completion_tokens":12,"total_tokens":3456}
  15. }`))
  16. require.NoError(t, err)
  17. require.Equal(t, string(model.TaskStatusSuccess), taskInfo.Status)
  18. require.Equal(t, "https://example.test/video.mp4", taskInfo.Url)
  19. require.Equal(t, 12, taskInfo.CompletionTokens)
  20. require.Equal(t, 3456, taskInfo.TotalTokens)
  21. }
  22. func TestParseTaskResult_SuccessWithoutUsage(t *testing.T) {
  23. adaptor := &TaskAdaptor{}
  24. taskInfo, err := adaptor.ParseTaskResult([]byte(`{
  25. "status":"succeeded",
  26. "content":{"video_url":"https://example.test/video.mp4"}
  27. }`))
  28. require.NoError(t, err)
  29. require.Equal(t, string(model.TaskStatusSuccess), taskInfo.Status)
  30. require.Zero(t, taskInfo.TotalTokens)
  31. }
  32. func TestParseTaskResult_NonSuccessStatusesDoNotRequireUsage(t *testing.T) {
  33. adaptor := &TaskAdaptor{}
  34. cases := map[string]model.TaskStatus{
  35. "queued": model.TaskStatusQueued,
  36. "processing": model.TaskStatusInProgress,
  37. "failed": model.TaskStatusFailure,
  38. }
  39. for status, want := range cases {
  40. taskInfo, err := adaptor.ParseTaskResult([]byte(`{"status":"` + status + `"}`))
  41. require.NoError(t, err)
  42. require.Equal(t, string(want), taskInfo.Status)
  43. require.Zero(t, taskInfo.TotalTokens)
  44. }
  45. }
  46. func TestConvertToRequestPayload_MetadataSupportsVideoAudioAndTools(t *testing.T) {
  47. adaptor := &TaskAdaptor{}
  48. req := &relaycommon.TaskSubmitReq{
  49. Model: "doubao-seedance-2-0-260128",
  50. Prompt: "make a reference video",
  51. Metadata: map[string]interface{}{
  52. "content": []interface{}{
  53. map[string]interface{}{
  54. "type": "video_url",
  55. "video_url": map[string]interface{}{
  56. "url": "https://example.test/input.mp4",
  57. },
  58. "role": "reference_video",
  59. },
  60. map[string]interface{}{
  61. "type": "audio_url",
  62. "audio_url": map[string]interface{}{
  63. "url": "https://example.test/input.wav",
  64. },
  65. "role": "reference_audio",
  66. },
  67. },
  68. "tools": []interface{}{
  69. map[string]interface{}{"type": "web_search"},
  70. },
  71. },
  72. }
  73. payload, err := adaptor.convertToRequestPayload(req)
  74. require.NoError(t, err)
  75. require.Len(t, payload.Content, 3)
  76. require.Equal(t, "video_url", payload.Content[0].Type)
  77. require.Equal(t, "https://example.test/input.mp4", payload.Content[0].VideoURL.URL)
  78. require.Equal(t, "reference_video", payload.Content[0].Role)
  79. require.Equal(t, "audio_url", payload.Content[1].Type)
  80. require.Equal(t, "https://example.test/input.wav", payload.Content[1].AudioURL.URL)
  81. require.Equal(t, "reference_audio", payload.Content[1].Role)
  82. require.Len(t, payload.Tools, 1)
  83. require.Equal(t, "web_search", payload.Tools[0].Type)
  84. }
  85. func TestConvertToRequestPayload_SecondsOverridesDuration(t *testing.T) {
  86. adaptor := &TaskAdaptor{}
  87. req := &relaycommon.TaskSubmitReq{
  88. Model: "doubao-seedance-1-5-pro-251215",
  89. Prompt: "cat yawning",
  90. Seconds: "8",
  91. Metadata: map[string]interface{}{
  92. "duration": 5,
  93. },
  94. }
  95. payload, err := adaptor.convertToRequestPayload(req)
  96. require.NoError(t, err)
  97. require.NotNil(t, payload.Duration)
  98. require.Equal(t, dto.IntValue(8), *payload.Duration)
  99. }
  100. func TestConvertToRequestPayload_PromptAppendedAfterMetadataAndReplacesMetadataText(t *testing.T) {
  101. adaptor := &TaskAdaptor{}
  102. req := &relaycommon.TaskSubmitReq{
  103. Model: "doubao-seedance-1-5-pro-251215",
  104. Prompt: "current prompt",
  105. Images: []string{"https://example.test/first.png"},
  106. Metadata: map[string]interface{}{
  107. "content": []interface{}{
  108. map[string]interface{}{
  109. "type": "text",
  110. "text": "metadata prompt",
  111. },
  112. map[string]interface{}{
  113. "type": "image_url",
  114. "image_url": map[string]interface{}{
  115. "url": "https://example.test/second.png",
  116. },
  117. "role": "last_frame",
  118. },
  119. },
  120. },
  121. }
  122. payload, err := adaptor.convertToRequestPayload(req)
  123. require.NoError(t, err)
  124. require.Len(t, payload.Content, 2)
  125. require.Equal(t, "image_url", payload.Content[0].Type)
  126. require.Equal(t, "https://example.test/second.png", payload.Content[0].ImageURL.URL)
  127. require.Equal(t, "last_frame", payload.Content[0].Role)
  128. require.Equal(t, "text", payload.Content[1].Type)
  129. require.Equal(t, "current prompt", payload.Content[1].Text)
  130. }
  131. // Native /api/v3/contents/generations/tasks requests store the prompt inside a
  132. // content text item while req.Prompt stays empty; such text items must survive.
  133. func TestConvertToRequestPayload_EmptyPromptKeepsNativeContentText(t *testing.T) {
  134. adaptor := &TaskAdaptor{}
  135. req := &relaycommon.TaskSubmitReq{
  136. Model: "doubao-seedance-2-0-260128",
  137. Metadata: map[string]interface{}{
  138. "content": []interface{}{
  139. map[string]interface{}{
  140. "type": "text",
  141. "text": "一只猫在打哈欠",
  142. },
  143. map[string]interface{}{
  144. "type": "image_url",
  145. "image_url": map[string]interface{}{
  146. "url": "https://example.test/cat.png",
  147. },
  148. },
  149. },
  150. "resolution": "1080p",
  151. "ratio": "16:9",
  152. },
  153. }
  154. payload, err := adaptor.convertToRequestPayload(req)
  155. require.NoError(t, err)
  156. require.Len(t, payload.Content, 2)
  157. require.Equal(t, "text", payload.Content[0].Type)
  158. require.Equal(t, "一只猫在打哈欠", payload.Content[0].Text)
  159. require.Equal(t, "image_url", payload.Content[1].Type)
  160. require.Equal(t, "https://example.test/cat.png", payload.Content[1].ImageURL.URL)
  161. require.Equal(t, "1080p", payload.Resolution)
  162. require.Equal(t, "16:9", payload.Ratio)
  163. }
  164. // Image-to-video without any text item must not produce an empty text item.
  165. func TestConvertToRequestPayload_EmptyPromptWithoutTextContentAppendsNothing(t *testing.T) {
  166. adaptor := &TaskAdaptor{}
  167. req := &relaycommon.TaskSubmitReq{
  168. Model: "doubao-seedance-2-0-260128",
  169. Metadata: map[string]interface{}{
  170. "content": []interface{}{
  171. map[string]interface{}{
  172. "type": "image_url",
  173. "image_url": map[string]interface{}{
  174. "url": "https://example.test/cat.png",
  175. },
  176. },
  177. },
  178. },
  179. }
  180. payload, err := adaptor.convertToRequestPayload(req)
  181. require.NoError(t, err)
  182. require.Len(t, payload.Content, 1)
  183. require.Equal(t, "image_url", payload.Content[0].Type)
  184. }
  185. func TestParseTaskResult_FailedUsesUpstreamErrorMessage(t *testing.T) {
  186. adaptor := &TaskAdaptor{}
  187. taskInfo, err := adaptor.ParseTaskResult([]byte(`{
  188. "status":"failed",
  189. "error":{"code":"InvalidParameter","message":"duration is invalid"}
  190. }`))
  191. require.NoError(t, err)
  192. require.Equal(t, string(model.TaskStatusFailure), taskInfo.Status)
  193. require.Equal(t, "duration is invalid", taskInfo.Reason)
  194. }
  195. func TestConvertToOpenAIVideo_FailedUsesUpstreamError(t *testing.T) {
  196. adaptor := &TaskAdaptor{}
  197. task := &model.Task{
  198. TaskID: "task_public",
  199. Status: model.TaskStatusFailure,
  200. Progress: "100%",
  201. CreatedAt: 100,
  202. UpdatedAt: 200,
  203. Properties: model.Properties{
  204. OriginModelName: "doubao-seedance-1-5-pro-251215",
  205. },
  206. Data: []byte(`{
  207. "status":"failed",
  208. "error":{"code":"InvalidParameter","message":"duration is invalid"}
  209. }`),
  210. }
  211. data, err := adaptor.ConvertToOpenAIVideo(task)
  212. require.NoError(t, err)
  213. require.Contains(t, string(data), `"message":"duration is invalid"`)
  214. require.Contains(t, string(data), `"code":"InvalidParameter"`)
  215. }
  216. func TestGetModelList_IncludesSeedance20Models(t *testing.T) {
  217. adaptor := &TaskAdaptor{}
  218. models := adaptor.GetModelList()
  219. require.Contains(t, models, "doubao-seedance-2-0-260128")
  220. require.Contains(t, models, "doubao-seedance-2-0-fast-260128")
  221. }