Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
13229a0
Fix DashScopeAPIResponse missing attribute handling
Jun 9, 2026
b3f331b
feat: add AioTextReRank async API
Jun 9, 2026
2676efd
fix: add timeout guard for async task polling
Jun 9, 2026
1514362
fix: support Windows file URI upload for multimodal inputs
Jun 9, 2026
842f110
fix: avoid passing default wait timeout to legacy wait methods
Jun 9, 2026
dbbe5b2
feat: add global trust_env config for aiohttp sessions
Jun 10, 2026
cfa1adb
fix: improve streaming interruption diagnostics
Jun 11, 2026
652f5e9
fix: improve qwen tts realtime websocket error handling
Jun 11, 2026
f3f0929
fix: handle local file URI hosts and string wait timeout
Jun 11, 2026
7f90814
fix: respect remaining wait timeout and reset SSE event type
Jun 11, 2026
d6de042
fix: preserve async task kwargs and tighten realtime connect timeout
Jun 11, 2026
82d726a
test: fix qwen tts realtime test lint issues
Jun 11, 2026
54dc3af
fix: handle aiohttp timeout as request failure
Jun 11, 2026
10b3fda
fix: avoid forwarding task kwarg to async wait
Jun 11, 2026
5ee8745
Revert "fix: avoid forwarding task kwarg to async wait"
Jun 12, 2026
f80a1b2
Revert "fix: handle aiohttp timeout as request failure"
Jun 12, 2026
1c1470d
Revert "test: fix qwen tts realtime test lint issues"
Jun 12, 2026
e1ae587
Revert "fix: preserve async task kwargs and tighten realtime connect …
Jun 12, 2026
2cf91df
Revert "fix: respect remaining wait timeout and reset SSE event type"
Jun 12, 2026
8cba414
Revert "fix: handle local file URI hosts and string wait timeout"
Jun 12, 2026
0324ff1
Revert "fix: improve qwen tts realtime websocket error handling"
Jun 12, 2026
44f334f
Revert "fix: improve streaming interruption diagnostics"
Jun 12, 2026
03a84e0
Revert "feat: add global trust_env config for aiohttp sessions"
Jun 12, 2026
6536dff
Revert "fix: add timeout guard for async task polling"
Jun 12, 2026
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
3 changes: 2 additions & 1 deletion dashscope/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,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 @@ -106,6 +106,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
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
8 changes: 7 additions & 1 deletion dashscope/embeddings/batch_text_embedding.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def wait( # type: ignore[override]
task: Union[str, BatchTextEmbeddingResponse],
api_key: str = None,
workspace: str = None,
**kwargs,
) -> BatchTextEmbeddingResponse:
"""Wait for async text embedding task to complete, and return the result. # noqa: E501

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

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

from dashscope.rerank.text_rerank import AioTextReRank, TextReRank

__all__ = ["AioTextReRank", "TextReRank"]
135 changes: 113 additions & 22 deletions dashscope/rerank/text_rerank.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,46 @@
# -*- coding: utf-8 -*-
# Copyright (c) Alibaba, Inc. and its affiliates.

from typing import List
from typing import Any, Dict, List, Tuple

from dashscope.api_entities.dashscope_response import ReRankResponse
from dashscope.client.base_api import BaseApi
from dashscope.client.base_api import BaseAioApi, BaseApi
from dashscope.common.error import InputRequired, ModelRequired
from dashscope.common.utils import _get_task_group_and_task

__all__ = ["TextReRank", "AioTextReRank"]


def _build_rerank_request(
model: str,
query: str,
documents: List[str],
return_documents: bool = None,
top_n: int = None,
instruct: str = None,
**kwargs,
) -> Tuple[str, str, Dict[str, Any], Dict[str, Any]]:
if query is None or documents is None or not documents:
raise InputRequired("query and documents are required!")
if model is None or not model:
raise ModelRequired("Model is required!")

task_group, function = _get_task_group_and_task(__name__)
rerank_input = {
"query": query,
"documents": documents,
}
parameters = {}
if return_documents is not None:
parameters["return_documents"] = return_documents
if top_n is not None:
parameters["top_n"] = top_n
if instruct is not None:
parameters["instruct"] = instruct
parameters = {**parameters, **kwargs}

return task_group, function, rerank_input, parameters


class TextReRank(BaseApi):
task = "text-rerank"
Expand Down Expand Up @@ -41,8 +74,8 @@ def call( # type: ignore[override] # pylint: disable=arguments-renamed
documents (List[str]): The documents to rank.
return_documents(bool, `optional`): enable return origin documents,
system default is false.
top_n(int, `optional`): how many documents to return, default return # noqa: E501
all the documents.
top_n(int, `optional`): how many documents to return,
default return all the documents.
api_key (str, optional): The DashScope api key. Defaults to None.
instruct (str, optional): Custom task instruction to guide
ranking strategy. English recommended.
Expand All @@ -55,31 +88,89 @@ def call( # type: ignore[override] # pylint: disable=arguments-renamed
RerankResponse: The rerank result.
"""

if query is None or documents is None or not documents:
raise InputRequired("query and documents are required!")
if model is None or not model:
raise ModelRequired("Model is required!")
task_group, function = _get_task_group_and_task(__name__)
input = { # pylint: disable=redefined-builtin
"query": query,
"documents": documents,
}
parameters = {}
if return_documents is not None:
parameters["return_documents"] = return_documents
if top_n is not None:
parameters["top_n"] = top_n
if instruct is not None:
parameters["instruct"] = instruct
parameters = {**parameters, **kwargs}
task_group, function, rerank_input, parameters = _build_rerank_request(
model=model,
query=query,
documents=documents,
return_documents=return_documents,
top_n=top_n,
instruct=instruct,
**kwargs,
)

response = super().call(
model=model,
task_group=task_group,
task=TextReRank.task,
function=function,
api_key=api_key,
input=input,
input=rerank_input,
**parameters, # type: ignore[arg-type]
)

return ReRankResponse.from_api_response(response)


class AioTextReRank(BaseAioApi):
task = "text-rerank"
"""Async API for rerank models."""

Models = TextReRank.Models

@classmethod
# pylint: disable=arguments-renamed
async def call( # type: ignore[override]
cls,
model: str,
query: str,
documents: List[str],
return_documents: bool = None,
top_n: int = None,
api_key: str = None,
workspace: str = None,
instruct: str = None,
**kwargs,
) -> ReRankResponse:
"""Calling rerank service asynchronously.

Args:
model (str): The model to use.
query (str): The query string.
documents (List[str]): The documents to rank.
return_documents(bool, `optional`): enable return origin documents,
system default is false.
top_n(int, `optional`): how many documents to return,
default return all the documents.
api_key (str, optional): The DashScope api key. Defaults to None.
workspace (str, optional): The DashScope workspace id.
instruct (str, optional): Custom task instruction to guide
ranking strategy. English recommended.

Raises:
InputRequired: The query and documents are required.
ModelRequired: The model is required.

Returns:
RerankResponse: The rerank result.
"""
task_group, function, rerank_input, parameters = _build_rerank_request(
model=model,
query=query,
documents=documents,
return_documents=return_documents,
top_n=top_n,
instruct=instruct,
**kwargs,
)

response = await super().call(
model=model,
task_group=task_group,
task=AioTextReRank.task,
function=function,
api_key=api_key,
workspace=workspace,
input=rerank_input,
**parameters, # type: ignore[arg-type]
)

Expand Down
34 changes: 22 additions & 12 deletions dashscope/utils/oss_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,31 @@ def get_upload_certificate(
return super().get(None, api_key, params=params, **kwargs) # type: ignore[return-value] # pylint: disable=line-too-long # noqa: E501


def _resolve_file_uri_path(file_uri: str):
parse_result = urlparse(file_uri)
if parse_result.netloc:
file_path = parse_result.netloc + unquote_plus(parse_result.path)
else:
file_path = unquote_plus(parse_result.path)

if (
file_path.startswith("/")
and len(file_path) > 2
and file_path[2] == ":"
):
file_path = file_path[1:]

return os.path.expanduser(file_path)


def upload_file(
model: str,
upload_path: str,
api_key: str,
upload_certificate: dict = None,
):
if upload_path.startswith(FILE_PATH_SCHEMA):
parse_result = urlparse(upload_path)
if parse_result.netloc:
file_path = parse_result.netloc + unquote_plus(parse_result.path)
else:
file_path = unquote_plus(parse_result.path)
file_path = _resolve_file_uri_path(upload_path)
if os.path.exists(file_path):
file_url, _ = OssUtils.upload(
model=model,
Expand Down Expand Up @@ -184,11 +197,7 @@ def check_and_upload_local(
is the certificate (newly obtained or passed in)
"""
if content.startswith(FILE_PATH_SCHEMA):
parse_result = urlparse(content)
if parse_result.netloc:
file_path = parse_result.netloc + unquote_plus(parse_result.path)
else:
file_path = unquote_plus(parse_result.path)
file_path = _resolve_file_uri_path(content)
if os.path.isfile(file_path):
file_url, cert = OssUtils.upload(
model=model,
Expand All @@ -201,9 +210,10 @@ def check_and_upload_local(
f"Uploading file: {content} failed",
)
return True, file_url, cert
elif content.startswith("oss://"):
raise InvalidInput(f"The file: {file_path} is not exists!")
if content.startswith("oss://"):
return True, content, upload_certificate
elif not content.startswith("http"):
if not content.startswith("http"):
content = os.path.expanduser(content)
if os.path.isfile(content):
file_url, cert = OssUtils.upload(
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/test_dashscope_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Copyright (c) Alibaba, Inc. and its affiliates.

from dashscope.api_entities.dashscope_response import DictMixin


class TestDictMixin:
def test_getattr_missing_key_raises_attribute_error(self):
response = DictMixin(existing="value")

try:
response.missing
except AttributeError:
return

raise AssertionError("Missing attribute should raise AttributeError")

def test_getattr_existing_key_returns_value(self):
response = DictMixin(existing="value")

assert response.existing == "value"
Loading
Loading