Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,18 +241,35 @@ response = TextReRank.call(

### Image Generation

Image and video generation APIs use server-side asynchronous tasks:

- `async_call()` submits a task and returns task information immediately. It is not a Python `async` coroutine.
- `call()` submits a task and blocks by polling task status until the task finishes.
- Use `fetch()` to query task status manually, or `wait()` to block until completion.
- Use `wait_timeout_seconds` with blocking calls to limit the maximum wait time.

```python
from dashscope import ImageSynthesis

# Async task pattern
# Submit a server-side async task
response = ImageSynthesis.async_call(
model="wanx-v1",
prompt="A serene mountain landscape at sunset",
)

# Wait for result
# Query task status manually
status = ImageSynthesis.fetch(response)

# Or wait for result
result = ImageSynthesis.wait(response)

# Blocking call with timeout
result = ImageSynthesis.call(
model="wanx-v1",
prompt="A serene mountain landscape at sunset",
wait_timeout_seconds=60,
)

# Sync call (for wan2.2-t2i-flash/plus)
result = ImageSynthesis.sync_call(
model="wan2.2-t2i-flash",
Expand All @@ -265,13 +282,21 @@ result = ImageSynthesis.sync_call(
```python
from dashscope import VideoSynthesis

# Text-to-video
# Submit a server-side async task
response = VideoSynthesis.async_call(
model="wan2.7-t2v",
prompt="A cat playing with a ball of yarn",
)

# Wait for result
result = VideoSynthesis.wait(response)

# Blocking call with timeout
result = VideoSynthesis.call(
model="wan2.7-t2v",
prompt="A cat playing with a ball of yarn",
wait_timeout_seconds=60,
)
```

### Speech Synthesis (TTS)
Expand Down
5 changes: 4 additions & 1 deletion dashscope/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
base_compatible_api_url,
base_http_api_url,
base_websocket_api_url,
trust_env,
)
from dashscope.finetune.deployments import Deployments
from dashscope.finetune.finetunes import FineTunes
Expand All @@ -46,7 +47,7 @@
from dashscope.files import Files
from dashscope.models import Models
from dashscope.nlp.understanding import Understanding
from dashscope.rerank.text_rerank import TextReRank
from dashscope.rerank import AioTextReRank, TextReRank
from dashscope.threads import (
MessageFile,
Messages,
Expand Down Expand Up @@ -74,6 +75,7 @@
"base_websocket_api_url",
"api_key",
"api_key_file_path",
"trust_env",
"save_api_key",
"AioGeneration",
"Conversation",
Expand Down Expand Up @@ -106,6 +108,7 @@
"list_tokenizers",
"Application",
"TextReRank",
"AioTextReRank",
"Assistants",
"Threads",
"Messages",
Expand Down
8 changes: 7 additions & 1 deletion dashscope/aigc/image_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ def wait( # type: ignore[override]
task: Union[str, ImageSynthesisResponse],
api_key: str = None,
workspace: str = None,
**kwargs,
) -> ImageSynthesisResponse:
"""Wait for image(s) synthesis task to complete, and return the result.

Expand All @@ -399,7 +400,12 @@ def wait( # type: ignore[override]
Returns:
ImageSynthesisResponse: The task result.
"""
response = super().wait(task, api_key, workspace=workspace)
response = super().wait(
task,
api_key,
workspace=workspace,
**kwargs,
)
return ImageSynthesisResponse.from_api_response(response)

@classmethod
Expand Down
8 changes: 7 additions & 1 deletion dashscope/aigc/video_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ def wait( # type: ignore[override]
task: Union[str, VideoSynthesisResponse],
api_key: str = None,
workspace: str = None,
**kwargs,
) -> VideoSynthesisResponse:
"""Wait for video synthesis task to complete, and return the result.

Expand All @@ -521,7 +522,12 @@ def wait( # type: ignore[override]
Returns:
VideoSynthesisResponse: The task result.
"""
response = super().wait(task, api_key, workspace=workspace)
response = super().wait(
task,
api_key,
workspace=workspace,
**kwargs,
)
return VideoSynthesisResponse.from_api_response(response)

@classmethod
Expand Down
76 changes: 53 additions & 23 deletions dashscope/api_entities/aiohttp_request.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) Alibaba, Inc. and its affiliates.

import asyncio
import json
from http import HTTPStatus

Expand All @@ -13,6 +14,7 @@
SSE_CONTENT_TYPE,
HTTPMethod,
)
from dashscope.common.env import get_trust_env
from dashscope.common.error import UnsupportedHTTPMethod
from dashscope.common.logging import logger
from dashscope.common.utils import async_to_sync
Expand Down Expand Up @@ -107,25 +109,38 @@ async def aio_call(self):
return result

async def _handle_stream(self, response):
# TODO define done message.
is_error = False
status_code = HTTPStatus.BAD_REQUEST
async for line in response.content:
if line:
line = line.decode("utf8")
line = line.rstrip("\n").rstrip("\r")
if line.startswith("event:error"):
is_error = True
elif line.startswith("status:"):
status_code = line[len("status:") :]
status_code = int(status_code.strip())
elif line.startswith("data:"):
line = line[len("data:") :]
yield (is_error, status_code, line)
if is_error:
break
else:
continue # ignore heartbeat...
event_type = None
try:
async for line in response.content:
if line:
line = line.decode("utf8")
line = line.rstrip("\n").rstrip("\r")
if line.startswith("event:"):
event_type = line[len("event:") :].strip()
if event_type == "error":
is_error = True
elif line.startswith("status:"):
status_code = line[len("status:") :]
status_code = int(status_code.strip())
elif line.startswith("data:"):
line = line[len("data:") :]
if event_type == "done":
continue
yield (is_error, status_code, line)
if is_error:
break
else:
continue # ignore heartbeat...
except (aiohttp.ClientError, asyncio.TimeoutError):
logger.exception(
"Stream response interrupted while reading aiohttp SSE "
"response, status_code=%s, request_id=%s",
response.status,
response.headers.get("X-Request-Id"),
)
raise

# pylint: disable=too-many-statements
async def _handle_response( # pylint: disable=too-many-branches
Expand Down Expand Up @@ -249,6 +264,7 @@ async def _handle_request(self):
async with aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=self.timeout),
headers=self.headers,
trust_env=get_trust_env(),
) as session:
logger.debug("Starting request: %s", self.url)
if self.method == HTTPMethod.POST:
Expand Down Expand Up @@ -281,9 +297,23 @@ async def _handle_request(self):
async with response:
async for rsp in self._handle_response(response):
yield rsp
except aiohttp.ClientConnectorError as e:
logger.error(e)
raise e
except Exception as e:
logger.error(e)
raise e
except (aiohttp.ClientError, asyncio.TimeoutError):
logger.exception(
"Aio HTTP request failed, url=%s, method=%s, stream=%s, "
"timeout=%s",
self.url,
self.method,
self.stream,
self.timeout,
)
raise
except Exception:
logger.exception(
"Unexpected aio HTTP request error, url=%s, method=%s, "
"stream=%s, timeout=%s",
self.url,
self.method,
self.stream,
self.timeout,
)
raise
7 changes: 6 additions & 1 deletion dashscope/api_entities/dashscope_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,12 @@ def setattr(self, attr, value):
return super().__setitem__(attr, value)

def __getattr__(self, attr):
return self[attr]
try:
return self[attr]
except KeyError:
raise AttributeError(
f"{type(self).__name__!r} object has no attribute {attr!r}",
) from None

def __setattr__(self, attr, value):
self[attr] = value
Expand Down
51 changes: 42 additions & 9 deletions dashscope/api_entities/http_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
HTTPMethod,
)
from dashscope.common.error import UnsupportedHTTPMethod
from dashscope.common.env import get_trust_env
from dashscope.common.logging import logger
from dashscope.common.utils import (
_handle_aio_stream,
Expand Down Expand Up @@ -176,6 +177,7 @@ async def _handle_aio_request(self): # pylint: disable=too-many-branches
connector=connector,
timeout=aiohttp.ClientTimeout(total=self.timeout),
headers=self.headers,
trust_env=get_trust_env(),
)
should_close = True

Expand Down Expand Up @@ -223,12 +225,26 @@ async def _handle_aio_request(self): # pylint: disable=too-many-branches
# Only close if we created the session
if should_close:
await session.close()
except aiohttp.ClientConnectorError as e:
logger.error(e)
raise e
except BaseException as e:
logger.error(e)
raise e
except aiohttp.ClientError:
logger.exception(
"Aio HTTP request failed, url=%s, method=%s, stream=%s, "
"timeout=%s",
self.url,
self.method,
self.stream,
self.timeout,
)
raise
except Exception:
logger.exception(
"Unexpected aio HTTP request error, url=%s, method=%s, "
"stream=%s, timeout=%s",
self.url,
self.method,
self.stream,
self.timeout,
)
raise

@staticmethod
def __handle_parameters(params: dict) -> dict:
Expand Down Expand Up @@ -507,6 +523,23 @@ def _handle_request(self):
# Only close if we created the session
if should_close:
session.close()
except BaseException as e:
logger.error(e)
raise e
except requests.exceptions.RequestException:
logger.exception(
"HTTP request failed, url=%s, method=%s, stream=%s, "
"timeout=%s",
self.url,
self.method,
self.stream,
self.timeout,
)
raise
except Exception:
logger.exception(
"Unexpected HTTP request error, url=%s, method=%s, "
"stream=%s, timeout=%s",
self.url,
self.method,
self.stream,
self.timeout,
)
raise
Loading
Loading