Skip to content

Commit 5bd0ea6

Browse files
authored
feat: support seedance 1.5 pro and sleep with max_wait_seconds (#406)
* chore: support seedance 1.5 pro * chore: video generate sleep with `max_wait_seconds` * fix: timeout of image generate * fix: config yaml * fix: consts
1 parent 4dde9f9 commit 5bd0ea6

File tree

5 files changed

+83
-15
lines changed

5 files changed

+83
-15
lines changed

config.yaml.full

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ model:
2020
api_base: https://ark.cn-beijing.volces.com/api/v3/
2121
api_key:
2222
video:
23-
name: doubao-seedance-1-0-pro-250528
23+
name: doubao-seedance-1-5-pro-251215
2424
api_base: https://ark.cn-beijing.volces.com/api/v3/
2525
api_key:
2626
image:

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ dependencies = [
2222
"opentelemetry-instrumentation-logging>=0.56b0",
2323
"wrapt==1.17.2", # For patching built-in functions
2424
"openai<1.100", # For fix https://github.com/BerriAI/litellm/issues/13710
25-
"volcengine-python-sdk==4.0.33", # For Volcengine API
26-
"volcengine==1.0.193", # For Volcengine sign
25+
"volcengine-python-sdk>=5.0.1", # For Volcengine API
26+
"volcengine>=1.0.193", # For Volcengine sign
2727
"agent-pilot-sdk==0.1.2", # Prompt optimization by Volcengine AgentPilot/PromptPilot toolkits
2828
"fastmcp==2.12.3", # For running MCP
2929
"trustedmcp==0.0.5", # For running TrustedMCP

veadk/consts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
DEFAULT_IMAGE_EDIT_MODEL_NAME = "doubao-seededit-3-0-i2i-250628"
6666
DEFAULT_IMAGE_EDIT_MODEL_API_BASE = "https://ark.cn-beijing.volces.com/api/v3/"
6767

68-
DEFAULT_VIDEO_MODEL_NAME = "doubao-seedance-1-0-pro-250528"
68+
DEFAULT_VIDEO_MODEL_NAME = "doubao-seedance-1-5-pro-251215"
6969
DEFAULT_VIDEO_MODEL_API_BASE = "https://ark.cn-beijing.volces.com/api/v3/"
7070

7171
DEFAULT_IMAGE_GENERATE_MODEL_NAME = "doubao-seedream-4-5-251128"

veadk/tools/builtin_tools/image_generate.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ def _build_input_parts(item: dict, task_type: str, image_field):
8383

8484

8585
def handle_single_task_sync(
86-
idx: int, item: dict, tool_context
86+
idx: int,
87+
item: dict,
88+
timeout: int,
89+
tool_context,
8790
) -> tuple[list[dict], list[str]]:
8891
logger.debug(f"handle_single_task_sync item {idx}: {item}")
8992
success_list: list[dict] = []
@@ -139,6 +142,7 @@ def handle_single_task_sync(
139142
"MODEL_AGENT_CLIENT_REQ_ID", f"veadk/{VERSION}"
140143
),
141144
},
145+
timeout=timeout,
142146
)
143147
else:
144148
response = client.images.generate(
@@ -152,6 +156,7 @@ def handle_single_task_sync(
152156
"MODEL_AGENT_CLIENT_REQ_ID", f"veadk/{VERSION}"
153157
),
154158
},
159+
timeout=timeout,
155160
)
156161

157162
if not response.error:
@@ -228,14 +233,16 @@ def handle_single_task_sync(
228233
return success_list, error_list
229234

230235

231-
async def image_generate(tasks: list[dict], tool_context) -> Dict:
232-
"""Generate images with Seedream 4.0.
236+
async def image_generate(tasks: list[dict], tool_context, timeout: int = 600) -> Dict:
237+
"""Generate images with Seedream 4.0 / 4.5
233238
234239
Commit batch image generation requests via tasks.
235240
236241
Args:
237242
tasks (list[dict]):
238243
A list of image-generation tasks. Each task is a dict.
244+
timeout (int)
245+
The timeout limit for the image generation task request, in seconds, with a default value of 600 seconds.
239246
Per-task schema
240247
---------------
241248
Required:
@@ -336,7 +343,9 @@ async def image_generate(tasks: list[dict], tool_context) -> Dict:
336343

337344
def make_task(idx, item):
338345
ctx = base_ctx.copy()
339-
return lambda: ctx.run(handle_single_task_sync, idx, item, tool_context)
346+
return lambda: ctx.run(
347+
handle_single_task_sync, idx, item, timeout, tool_context
348+
)
340349

341350
loop = asyncio.get_event_loop()
342351
futures = [

veadk/tools/builtin_tools/video_generate.py

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414

1515
import json
16-
import time
16+
import asyncio
1717
import traceback
1818
from typing import Dict, cast
1919

@@ -41,14 +41,27 @@
4141
)
4242

4343

44-
async def generate(prompt, first_frame_image=None, last_frame_image=None):
44+
async def generate(
45+
prompt, first_frame_image=None, last_frame_image=None, generate_audio=None
46+
):
4547
try:
48+
if generate_audio is False:
49+
generate_audio = None
50+
model_name = getenv("MODEL_VIDEO_NAME", DEFAULT_VIDEO_MODEL_NAME)
51+
52+
if model_name.startswith("doubao-seedance-1-0") and generate_audio:
53+
logger.warning(
54+
"The `doubao-seedance-1-0` series models do not support enabling the audio field. "
55+
"Please upgrade to the doubao-seedance-1-5 series of you want to generate video with audio."
56+
)
57+
generate_audio = None
4658
if first_frame_image is None:
4759
response = client.content_generation.tasks.create(
4860
model=getenv("MODEL_VIDEO_NAME", DEFAULT_VIDEO_MODEL_NAME),
4961
content=[
5062
{"type": "text", "text": prompt},
5163
],
64+
generate_audio=generate_audio,
5265
extra_headers={
5366
"veadk-source": "veadk",
5467
"veadk-version": VERSION,
@@ -112,7 +125,10 @@ async def generate(prompt, first_frame_image=None, last_frame_image=None):
112125

113126

114127
async def video_generate(
115-
params: list, tool_context: ToolContext, batch_size: int = 10
128+
params: list,
129+
tool_context: ToolContext,
130+
batch_size: int = 10,
131+
max_wait_seconds: int = 1200,
116132
) -> Dict:
117133
"""
118134
Generate videos in **batch** from text prompts, optionally guided by a first/last frame,
@@ -126,6 +142,10 @@ async def video_generate(
126142
A list of video generation requests. Each item supports the fields below.
127143
batch_size (int):
128144
The number of videos to generate in a batch. Defaults to 10.
145+
max_wait_seconds (int):
146+
Maximum time in seconds to wait for all video tasks in each batch.
147+
Default is 20 minutes (1200 seconds). When the timeout is reached,
148+
unfinished tasks will be marked as timeout errors.
129149
130150
Required per item:
131151
- video_name (str):
@@ -148,6 +168,12 @@ async def video_generate(
148168
URL or Base64 string (data URL) for the **last frame** (role = `last_frame`).
149169
Use when you want the clip to end on a specific image.
150170
171+
- generate_audio (bool | None):
172+
Boolean value, used to determine whether the generated video should have sound.
173+
If this field is not configured (None) or its value is `False`, no sound will be generated.
174+
If it is configured as `True`, sound can be generated.
175+
If you want to describe the sound content in detail, you can do so in the `prompt` field.
176+
151177
Notes on first/last frame:
152178
* When both frames are provided, **match width/height** to avoid cropping; if they differ,
153179
the tail frame may be auto-cropped to fit.
@@ -222,6 +248,7 @@ async def video_generate(
222248
"""
223249
success_list = []
224250
error_list = []
251+
timeout_tasks = []
225252
logger.debug(f"Using model: {getenv('MODEL_VIDEO_NAME', DEFAULT_VIDEO_MODEL_NAME)}")
226253
logger.debug(f"video_generate params: {params}")
227254

@@ -243,22 +270,32 @@ async def video_generate(
243270
prompt = item["prompt"]
244271
first_frame = item.get("first_frame", None)
245272
last_frame = item.get("last_frame", None)
273+
generate_audio = item.get("generate_audio", None)
246274
try:
247275
if not first_frame:
248276
logger.debug(
249277
f"video_generate task_{idx} text generation: prompt={prompt}"
250278
)
251-
response = await generate(prompt)
279+
response = await generate(prompt, generate_audio=generate_audio)
252280
elif not last_frame:
253281
logger.debug(
254282
f"video_generate task_{idx} first frame generation: prompt={prompt}, first_frame={first_frame}"
255283
)
256-
response = await generate(prompt, first_frame)
284+
response = await generate(
285+
prompt,
286+
first_frame_image=first_frame,
287+
generate_audio=generate_audio,
288+
)
257289
else:
258290
logger.debug(
259291
f"video_generate task_{idx} first and last frame generation: prompt={prompt}, first_frame={first_frame}, last_frame={last_frame}"
260292
)
261-
response = await generate(prompt, first_frame, last_frame)
293+
response = await generate(
294+
prompt,
295+
first_frame_image=first_frame,
296+
last_frame_image=last_frame,
297+
generate_audio=generate_audio,
298+
)
262299
logger.debug(
263300
f"batch_{start_idx // batch_size} video_generate task_{idx} response: {response}"
264301
)
@@ -270,6 +307,10 @@ async def video_generate(
270307

271308
logger.debug("begin query video_generate task status...")
272309

310+
sleep_interval = 10
311+
max_sleep_times = max_wait_seconds // sleep_interval
312+
sleep_times = 0
313+
273314
while True:
274315
task_list = list(task_dict.keys())
275316
if len(task_list) == 0:
@@ -303,7 +344,23 @@ async def video_generate(
303344
logger.debug(
304345
f"{task_dict[task_id]} video_generate current status: {status}, Retrying after 10 seconds..."
305346
)
306-
time.sleep(10)
347+
if sleep_times >= max_sleep_times:
348+
logger.error(
349+
f"video_generate polling timed out after {max_wait_seconds} seconds; remaining tasks: {task_dict}"
350+
)
351+
for task_id, video_name in task_dict.items():
352+
timeout_tasks.append(
353+
{
354+
"task_id": task_id,
355+
"video_name": video_name,
356+
}
357+
)
358+
error_list.append(video_name)
359+
task_dict.clear()
360+
break
361+
362+
await asyncio.sleep(sleep_interval)
363+
sleep_times += 1
307364

308365
add_span_attributes(
309366
span,
@@ -324,6 +381,7 @@ async def video_generate(
324381
"status": "error",
325382
"success_list": success_list,
326383
"error_list": error_list,
384+
"timeout_tasks": timeout_tasks,
327385
}
328386
else:
329387
logger.debug(
@@ -333,6 +391,7 @@ async def video_generate(
333391
"status": "success",
334392
"success_list": success_list,
335393
"error_list": error_list,
394+
"timeout_tasks": timeout_tasks,
336395
}
337396

338397

0 commit comments

Comments
 (0)