@@ -28,7 +28,7 @@ class Client:
2828 client = Client(api_key="your-api-key")
2929 output = client.run("wavespeed-ai/z-image/turbo", {"prompt": "Cat"})
3030
31- # With sync mode (single request, waits for result)
31+ # With sync mode (best-effort single request, waits for result)
3232 output = client.run("wavespeed-ai/z-image/turbo", {"prompt": "Cat"}, enable_sync_mode=True)
3333
3434 # With retry
@@ -334,6 +334,25 @@ def _is_retryable_error(self, error: Exception) -> bool:
334334
335335 return False
336336
337+ @staticmethod
338+ def _format_sync_mode_error (data : dict [str , Any ]) -> str :
339+ """Build an actionable error for a non-completed sync-mode response."""
340+ request_id = data .get ("id" ) or "unknown"
341+ error = data .get ("error" ) or "Unknown error"
342+ urls = data .get ("urls" ) or {}
343+ result_url = urls .get ("get" ) if isinstance (urls , dict ) else None
344+
345+ is_sync_timeout = data .get ("code" ) == 5004 or (
346+ data .get ("status" ) == "processing" and "Sync mode timed out" in error
347+ )
348+ if is_sync_timeout :
349+ message = f"Sync mode timed out (task_id: { request_id } ): { error } "
350+ if result_url and result_url not in message :
351+ message += f" Query the result later at: { result_url } "
352+ return message
353+
354+ return f"Prediction failed (task_id: { request_id } ): { error } "
355+
337356 def run (
338357 self ,
339358 model : str ,
@@ -351,9 +370,9 @@ def run(
351370 input: Input parameters for the model.
352371 timeout: Maximum time to wait for completion (None = no timeout).
353372 poll_interval: Interval between status checks in seconds.
354- enable_sync_mode: If True, use synchronous mode (single request).
355- If sync mode fails with a gateway timeout (HTTP 502/504),
356- the SDK automatically falls back to async mode (submit + poll) .
373+ enable_sync_mode: If True, use synchronous mode (best-effort single
374+ request). If the server-side sync wait times out, the SDK raises
375+ an error with the task ID so the result can be queried later .
357376 max_retries: Maximum task-level retries (overrides client setting).
358377
359378 Returns:
@@ -366,28 +385,19 @@ def run(
366385 """
367386 task_retries = max_retries if max_retries is not None else self .max_retries
368387 last_error = None
369- # Track whether we should fall back from sync to async mode.
370- # This happens when sync mode hits a gateway timeout (502/504) after
371- # exhausting connection-level retries — the gateway cannot hold the
372- # connection long enough, but the backend may still be healthy.
373- use_sync = enable_sync_mode
374388
375389 for attempt in range (task_retries + 1 ):
376390 try :
377391 request_id , sync_result = self ._submit (
378- model , input , enable_sync_mode = use_sync , timeout = timeout
392+ model , input , enable_sync_mode = enable_sync_mode , timeout = timeout
379393 )
380394
381- if use_sync :
395+ if enable_sync_mode :
382396 # In sync mode, extract outputs from the result
383397 status = sync_result .get ("data" , {}).get ("status" )
384398 if status != "completed" :
385- error = (
386- sync_result .get ("data" , {}).get ("error" ) or "Unknown error"
387- )
388- request_id = sync_result .get ("data" , {}).get ("id" , "unknown" )
389399 raise RuntimeError (
390- f"Prediction failed (task_id: { request_id } ): { error } "
400+ self . _format_sync_mode_error ( sync_result . get ( "data" , {}))
391401 )
392402 data = sync_result .get ("data" , {})
393403 return {"outputs" : data .get ("outputs" , [])}
@@ -397,17 +407,6 @@ def run(
397407 except Exception as e :
398408 last_error = e
399409
400- # Sync-to-async fallback: if sync mode got a gateway timeout
401- # (502/504) after all connection retries, switch to async mode
402- # and retry immediately without consuming a task-level retry.
403- if use_sync and self ._is_gateway_timeout (e ):
404- print (
405- "Sync mode hit gateway timeout, "
406- "falling back to async mode (submit + poll)..."
407- )
408- use_sync = False
409- continue
410-
411410 is_retryable = self ._is_retryable_error (e )
412411
413412 if not is_retryable or attempt >= task_retries :
@@ -423,21 +422,6 @@ def run(
423422 raise last_error
424423 raise RuntimeError (f"All { task_retries + 1 } attempts failed" )
425424
426- @staticmethod
427- def _is_gateway_timeout (error : Exception ) -> bool :
428- """Check if an error is a gateway timeout (HTTP 502 or 504).
429-
430- Args:
431- error: The exception to check.
432-
433- Returns:
434- True if the error indicates a gateway timeout.
435- """
436- if isinstance (error , RuntimeError ):
437- error_str = str (error )
438- return "HTTP 502" in error_str or "HTTP 504" in error_str
439- return False
440-
441425 def upload (self , file : str | BinaryIO , * , timeout : float | None = None ) -> str :
442426 """Upload a file to WaveSpeed.
443427
0 commit comments