Procházet zdrojové kódy

fix(doubao-video): parse camelCase fields from Volcengine task responses

The taskdoubao adaptor expected snake_case JSON keys (video_url,
completion_tokens, total_tokens, framespersecond, service_tier,
created_at, updated_at) but Volcengine returns camelCase (videoUrl,
completionTokens, totalTokens, framesPerSecond, serviceTier,
createdAt, updatedAt). As a result the video URL was never parsed:
successful tasks fell back to the platform proxy URL
(BuildProxyURL) as result_url, which broke the video proxy in
containerized deployments (localhost:port not reachable inside the
container) and zeroed the usage tokens used for billing.

Align the responseTask JSON tags with the real Volcengine response
shape and add a regression test with the exact production payload.

Co-Authored-By: ZCode <noreply@anthropic.com>
master
fengsilin před 1 týdnem
rodič
revize
b2d43b6c29
2 změnil soubory, kde provedl 42 přidání a 10 odebrání
  1. +7
    -7
      relay/channel/task/doubao/adaptor.go
  2. +35
    -3
      relay/channel/task/doubao/adaptor_test.go

+ 7
- 7
relay/channel/task/doubao/adaptor.go Zobrazit soubor

@@ -71,24 +71,24 @@ type responseTask struct {
Model string `json:"model"`
Status string `json:"status"`
Content struct {
VideoURL string `json:"video_url"`
VideoURL string `json:"videoUrl"`
} `json:"content"`
Seed int `json:"seed"`
Resolution string `json:"resolution"`
Duration int `json:"duration"`
Ratio string `json:"ratio"`
FramesPerSecond int `json:"framespersecond"`
ServiceTier string `json:"service_tier"`
FramesPerSecond int `json:"framesPerSecond"`
ServiceTier string `json:"serviceTier"`
Usage struct {
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
CompletionTokens int `json:"completionTokens"`
TotalTokens int `json:"totalTokens"`
} `json:"usage"`
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}

// ============================


+ 35
- 3
relay/channel/task/doubao/adaptor_test.go Zobrazit soubor

@@ -13,8 +13,8 @@ func TestParseTaskResult_SuccessWithUsage(t *testing.T) {
adaptor := &TaskAdaptor{}
taskInfo, err := adaptor.ParseTaskResult([]byte(`{
"status":"succeeded",
"content":{"video_url":"https://example.test/video.mp4"},
"usage":{"completion_tokens":12,"total_tokens":3456}
"content":{"videoUrl":"https://example.test/video.mp4"},
"usage":{"completionTokens":12,"totalTokens":3456}
}`))

require.NoError(t, err)
@@ -28,7 +28,7 @@ func TestParseTaskResult_SuccessWithoutUsage(t *testing.T) {
adaptor := &TaskAdaptor{}
taskInfo, err := adaptor.ParseTaskResult([]byte(`{
"status":"succeeded",
"content":{"video_url":"https://example.test/video.mp4"}
"content":{"videoUrl":"https://example.test/video.mp4"}
}`))

require.NoError(t, err)
@@ -241,6 +241,38 @@ func TestConvertToOpenAIVideo_FailedUsesUpstreamError(t *testing.T) {
require.Contains(t, string(data), `"code":"InvalidParameter"`)
}

// Parses the exact Volcengine response shape observed in production:
// camelCase videoUrl / usage tokens, not snake_case.
func TestParseTaskResult_RealVolcengineResponse(t *testing.T) {
adaptor := &TaskAdaptor{}
taskInfo, err := adaptor.ParseTaskResult([]byte(`{
"id":"cgt-20260819170015-glgnd",
"seed":46303,
"draft":false,
"model":"doubao-seedance-2-0-260128",
"ratio":"16:9",
"usage":{"totalTokens":216900,"completionTokens":216900},
"status":"succeeded",
"content":{"videoUrl":"https://ark-acg-cn-beijing.tos-cn-beijing.volces.com/output.mp4"},
"duration":10,
"priority":0,
"createdAt":1787130015,
"updatedAt":1787130344,
"resolution":"720p",
"serviceTier":"default",
"outputFormat":"mp4",
"generateAudio":false,
"framesPerSecond":24,
"executionExpiresAfter":172800
}`))

require.NoError(t, err)
require.Equal(t, string(model.TaskStatusSuccess), taskInfo.Status)
require.Equal(t, "https://ark-acg-cn-beijing.tos-cn-beijing.volces.com/output.mp4", taskInfo.Url)
require.Equal(t, 216900, taskInfo.CompletionTokens)
require.Equal(t, 216900, taskInfo.TotalTokens)
}

func TestGetModelList_IncludesSeedance20Models(t *testing.T) {
adaptor := &TaskAdaptor{}
models := adaptor.GetModelList()


Načítá se…
Zrušit
Uložit