From 4e4b25f31c41ff98236acbe7afaaa7ddf35b8b9f Mon Sep 17 00:00:00 2001 From: kevin Date: Wed, 17 Jun 2026 16:35:28 +0800 Subject: [PATCH 1/8] feat: support env proxy and merge incremental stream output for Application API --- dashscope/api_entities/aio_session.py | 2 +- dashscope/api_entities/websocket_request.py | 1 + dashscope/app/application.py | 57 +++++++++++++++++-- .../finetune/reinforcement/common/utils.py | 1 + 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/dashscope/api_entities/aio_session.py b/dashscope/api_entities/aio_session.py index d2365db..2757f3c 100644 --- a/dashscope/api_entities/aio_session.py +++ b/dashscope/api_entities/aio_session.py @@ -45,7 +45,7 @@ async def get_shared_aio_session() -> aiohttp.ClientSession: return session connector = aiohttp.TCPConnector(ssl=get_ssl_context()) - session = aiohttp.ClientSession(connector=connector) + session = aiohttp.ClientSession(connector=connector, trust_env=True) _aio_sessions[loop] = session return session diff --git a/dashscope/api_entities/websocket_request.py b/dashscope/api_entities/websocket_request.py index 0473709..249b8d8 100644 --- a/dashscope/api_entities/websocket_request.py +++ b/dashscope/api_entities/websocket_request.py @@ -119,6 +119,7 @@ async def connection_handler(self): # pylint: disable=too-many-branches timeout=aiohttp.ClientTimeout( total=self.timeout, ), + trust_env=True, ) as session: async with session.ws_connect( self.url, diff --git a/dashscope/app/application.py b/dashscope/app/application.py index 78a77a0..08b612e 100644 --- a/dashscope/app/application.py +++ b/dashscope/app/application.py @@ -21,6 +21,7 @@ ) from dashscope.common.error import InputRequired, InvalidInput from dashscope.common.logging import logger +from dashscope.utils.message_utils import merge_single_response class Application(BaseApi): @@ -149,6 +150,26 @@ def call( # type: ignore[override] headers["X-DashScope-WorkSpace"] = workspace kwargs["headers"] = headers + # Check if we need to merge incremental output (compute once) + is_stream = kwargs.get("stream", False) + is_incremental_output = kwargs.get("incremental_output", None) + to_merge_incremental_output = ( + is_stream and is_incremental_output is False + ) + + if to_merge_incremental_output: + kwargs["incremental_output"] = True + + # Pass incremental_to_full flag via user-agent (append) + if "headers" not in kwargs: + kwargs["headers"] = {} + flag = "1" if to_merge_incremental_output else "0" + existing_ua = kwargs["headers"].get("user-agent", "") + new_ua = f"incremental_to_full/{flag}" + kwargs["headers"]["user-agent"] = ( + f"{existing_ua} {new_ua}".strip() if existing_ua else new_ua + ) + ( input, # pylint: disable=redefined-builtin parameters, @@ -171,12 +192,15 @@ def call( # type: ignore[override] ) # call request service. response = request.call() - is_stream = kwargs.get("stream", False) if is_stream: - return ( - ApplicationResponse.from_api_response(rsp) for rsp in response - ) + if to_merge_incremental_output: + return cls._merge_application_response(response) + else: + return ( + ApplicationResponse.from_api_response(rsp) + for rsp in response + ) else: return ApplicationResponse.from_api_response(response) @@ -238,3 +262,28 @@ def _build_input_parameters( # pylint: disable=too-many-branches input_param["file_list"] = file_list return input_param, {**parameters, **kwargs} + + @classmethod + def _merge_application_response(cls, response): + """Merge incremental application response chunks. + + Simulate non-incremental output by accumulating text. + """ + accumulated_data = {} + + for rsp in response: + parsed_response = ApplicationResponse.from_api_response(rsp) + result = merge_single_response( + parsed_response, + accumulated_data, + ) + if result is True: + yield parsed_response + elif isinstance(result, list): + for resp in result: + yield resp + else: + logger.warning( + "Unexpected merge result type: %s, skipping", + type(result).__name__, + ) diff --git a/dashscope/finetune/reinforcement/common/utils.py b/dashscope/finetune/reinforcement/common/utils.py index 3757460..27da3d9 100644 --- a/dashscope/finetune/reinforcement/common/utils.py +++ b/dashscope/finetune/reinforcement/common/utils.py @@ -74,6 +74,7 @@ async def _make_request() -> Dict[str, Any]: async with aiohttp.ClientSession( headers=headers, timeout=aiohttp.ClientTimeout(total=timeout), + trust_env=True, ) as session: method_upper = method.upper() From ff0cb758dd9dbe6a12598bd6169a83818eb32138 Mon Sep 17 00:00:00 2001 From: "zhansheng.lzs" Date: Wed, 17 Jun 2026 15:28:42 +0800 Subject: [PATCH 2/8] Fix tiktoken StackOverflow on long text inputs tiktoken's recursive BPE merging overflows the call stack on very long inputs, causing pyo3_runtime.PanicException: StackOverflow. Split text into chunks (<= 100k chars) at line boundaries before encoding. Short text takes the original fast path unchanged. Fixes #116 --- dashscope/tokenizers/qwen_tokenizer.py | 47 +++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/dashscope/tokenizers/qwen_tokenizer.py b/dashscope/tokenizers/qwen_tokenizer.py index bfa1119..d06d755 100644 --- a/dashscope/tokenizers/qwen_tokenizer.py +++ b/dashscope/tokenizers/qwen_tokenizer.py @@ -33,6 +33,11 @@ ) SPECIAL_TOKENS_SET = set(t for i, t in SPECIAL_TOKENS) +# tiktoken's BPE merges tokens recursively in Rust, which can overflow the +# call stack on very long inputs (pyo3_runtime.PanicException: StackOverflow). +# Split text into chunks below this threshold before encoding. +_CHUNK_SIZE = 100_000 + class QwenTokenizer(Tokenizer): @staticmethod @@ -102,11 +107,43 @@ def encode( # type: ignore[override] disallowed_special: Union[Collection, str] = (), ) -> Union[List[List], List]: text = unicodedata.normalize("NFC", text) - return self._tokenizer.encode( - text, - allowed_special=allowed_special, - disallowed_special=disallowed_special, - ) + if len(text) <= _CHUNK_SIZE: + return self._tokenizer.encode( + text, + allowed_special=allowed_special, + disallowed_special=disallowed_special, + ) + + result = [] + for chunk in self._split_text(text): + result.extend( + self._tokenizer.encode( + chunk, + allowed_special=allowed_special, + disallowed_special=disallowed_special, + ), + ) + return result + + @staticmethod + def _split_text(text: str, chunk_size: int = _CHUNK_SIZE) -> List[str]: + """Split text into chunks at safe tokenization boundaries.""" + parts: List[str] = [] + for i, line in enumerate(text.split("\n")): + piece = line if i == 0 else "\n" + line + if len(piece) <= chunk_size: + parts.append(piece) + else: + for j in range(0, len(piece), chunk_size): + parts.append(piece[j : j + chunk_size]) + + chunks: List[str] = [] + for part in parts: + if chunks and len(chunks[-1]) + len(part) <= chunk_size: + chunks[-1] += part + else: + chunks.append(part) + return chunks def decode( self, From 5070eedb973dbafb06d0f81a35f7d6683529335b Mon Sep 17 00:00:00 2001 From: "zhansheng.lzs" Date: Wed, 17 Jun 2026 15:01:50 +0800 Subject: [PATCH 3/8] Fix end_session() to handle server errors immediately The server currently does not support session.finish event for qwen3.5-omni-flash-realtime, returning an error instead of session.finished. Previously end_session() would wait the full timeout duration (20s) before falling back to ws.close(). Now the method detects server error events immediately and closes the connection without waiting, reducing wait time from 20s to <0.1s when the server rejects session.finish. Related to issue #140 --- dashscope/audio/qwen_omni/omni_realtime.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/dashscope/audio/qwen_omni/omni_realtime.py b/dashscope/audio/qwen_omni/omni_realtime.py index 9aeabdd..ec4a1ab 100644 --- a/dashscope/audio/qwen_omni/omni_realtime.py +++ b/dashscope/audio/qwen_omni/omni_realtime.py @@ -153,6 +153,7 @@ def __init__( self.metrics = [] # Add event for synchronously waiting on connection close self.disconnect_event = None + self._disconnect_error = None def _generate_event_id(self): """ @@ -350,6 +351,7 @@ def end_session(self, timeout: int = 20) -> None: # create the event self.disconnect_event = threading.Event() + self._disconnect_error = None self.__send_str( json.dumps( @@ -362,10 +364,14 @@ def end_session(self, timeout: int = 20) -> None: # wait for the event to be set finish_success = self.disconnect_event.wait(timeout) - # clear the event + error = self._disconnect_error self.disconnect_event = None + self._disconnect_error = None - # if the event is not set, close the connection + # if the server returned an error or timed out, close the connection + if error is not None: + self.close() + return if not finish_success: self.close() raise TimeoutError( @@ -536,6 +542,14 @@ def _on_message( # pylint: disable=unused-argument,too-many-branches logger.info("[omni realtime] session finished") if self.disconnect_event is not None: self.disconnect_event.set() + elif "error" == json_data.get("type"): + if self.disconnect_event is not None: + self._disconnect_error = json_data.get("error") + logger.warning( + "[omni realtime] error during end_session: %s", + self._disconnect_error, + ) + self.disconnect_event.set() if "response.created" == json_data["type"]: self.last_response_id = json_data["response"]["id"] self.last_response_create_time = time.time() * 1000 From ee294c2eebf51930a043d0ee0157f2c31ac25845 Mon Sep 17 00:00:00 2001 From: kevin Date: Wed, 17 Jun 2026 18:27:15 +0800 Subject: [PATCH 4/8] refactor: migrate incremental_to_full flag from headers to user_agent parameter --- dashscope/aigc/generation.py | 20 ++++++++++++-------- dashscope/aigc/image_generation.py | 22 ++++++++++++---------- dashscope/aigc/multimodal_conversation.py | 10 ++++++---- dashscope/app/application.py | 16 ++++++++++------ dashscope/audio/qwen_omni/omni_realtime.py | 2 +- dashscope/tokenizers/qwen_tokenizer.py | 14 +++++++++++--- tests/unit/test_conversation.py | 2 +- 7 files changed, 53 insertions(+), 33 deletions(-) diff --git a/dashscope/aigc/generation.py b/dashscope/aigc/generation.py index 6ec2a39..054a4f4 100644 --- a/dashscope/aigc/generation.py +++ b/dashscope/aigc/generation.py @@ -179,11 +179,13 @@ def call( # pylint: disable=arguments-renamed,too-many-branches,too-many-statem to_merge_incremental_output = True parameters["incremental_output"] = True - # Pass incremental_to_full flag via headers user-agent - if "headers" not in parameters: - parameters["headers"] = {} + # Pass incremental_to_full flag via user_agent parameter flag = "1" if to_merge_incremental_output else "0" - parameters["headers"]["user-agent"] = f"incremental_to_full/{flag}" + existing_ua = parameters.get("user_agent", "") + new_ua = f"incremental_to_full/{flag}" + parameters["user_agent"] = ( + f"{existing_ua}; {new_ua}".strip() if existing_ua else new_ua + ) response = super().call( model=model, @@ -434,11 +436,13 @@ async def call( # type: ignore[override] # pylint: disable=arguments-renamed,to to_merge_incremental_output = True parameters["incremental_output"] = True - # Pass incremental_to_full flag via headers user-agent - if "headers" not in parameters: - parameters["headers"] = {} + # Pass incremental_to_full flag via user_agent parameter flag = "1" if to_merge_incremental_output else "0" - parameters["headers"]["user-agent"] = f"incremental_to_full/{flag}" + existing_ua = parameters.get("user_agent", "") + new_ua = f"incremental_to_full/{flag}" + parameters["user_agent"] = ( + f"{existing_ua}; {new_ua}".strip() if existing_ua else new_ua + ) response = await super().call( model=model, diff --git a/dashscope/aigc/image_generation.py b/dashscope/aigc/image_generation.py index 6973108..d627708 100644 --- a/dashscope/aigc/image_generation.py +++ b/dashscope/aigc/image_generation.py @@ -99,12 +99,13 @@ def call( # type: ignore[override] to_merge_incremental_output = True kwargs["incremental_output"] = True - # Pass incremental_to_full flag via headers user-agent - if "headers" not in kwargs: - kwargs["headers"] = {} - + # Pass incremental_to_full flag via user_agent parameter flag = "1" if to_merge_incremental_output else "0" - kwargs["headers"]["user-agent"] = f"incremental_to_full/{flag}" + existing_ua = kwargs.get("user_agent", "") + new_ua = f"incremental_to_full/{flag}" + kwargs["user_agent"] = ( + f"{existing_ua}; {new_ua}".strip() if existing_ua else new_ua + ) if kwargs.get("is_async", False): kwargs["headers"]["X-DashScope-Async"] = "enable" task = cls.async_task @@ -416,12 +417,13 @@ async def call( # type: ignore[override] to_merge_incremental_output = True kwargs["incremental_output"] = True - # Pass incremental_to_full flag via headers user-agent - if "headers" not in kwargs: - kwargs["headers"] = {} - + # Pass incremental_to_full flag via user_agent parameter flag = "1" if to_merge_incremental_output else "0" - kwargs["headers"]["user-agent"] = f"incremental_to_full/{flag}" + existing_ua = kwargs.get("user_agent", "") + new_ua = f"incremental_to_full/{flag}" + kwargs["user_agent"] = ( + f"{existing_ua}; {new_ua}".strip() if existing_ua else new_ua + ) if kwargs.get("is_async", False): kwargs["headers"]["X-DashScope-Async"] = "enable" task = cls.async_task diff --git a/dashscope/aigc/multimodal_conversation.py b/dashscope/aigc/multimodal_conversation.py index cac2141..8243e77 100644 --- a/dashscope/aigc/multimodal_conversation.py +++ b/dashscope/aigc/multimodal_conversation.py @@ -163,11 +163,13 @@ def call( # pylint: disable=arguments-renamed,too-many-branches,too-many-statem to_merge_incremental_output = True kwargs["incremental_output"] = True - # Pass incremental_to_full flag via headers user-agent - if "headers" not in kwargs: - kwargs["headers"] = {} + # Pass incremental_to_full flag via user_agent parameter flag = "1" if to_merge_incremental_output else "0" - kwargs["headers"]["user-agent"] = f"incremental_to_full/{flag}" + existing_ua = kwargs.get("user_agent", "") + new_ua = f"incremental_to_full/{flag}" + kwargs["user_agent"] = ( + f"{existing_ua}; {new_ua}".strip() if existing_ua else new_ua + ) response = super().call( model=model, diff --git a/dashscope/app/application.py b/dashscope/app/application.py index 08b612e..0926cbd 100644 --- a/dashscope/app/application.py +++ b/dashscope/app/application.py @@ -160,14 +160,13 @@ def call( # type: ignore[override] if to_merge_incremental_output: kwargs["incremental_output"] = True - # Pass incremental_to_full flag via user-agent (append) - if "headers" not in kwargs: - kwargs["headers"] = {} + # Pass incremental_to_full flag via user_agent parameter to avoid + # overwriting the default SDK user-agent flag = "1" if to_merge_incremental_output else "0" - existing_ua = kwargs["headers"].get("user-agent", "") + existing_ua = kwargs.get("user_agent", "") new_ua = f"incremental_to_full/{flag}" - kwargs["headers"]["user-agent"] = ( - f"{existing_ua} {new_ua}".strip() if existing_ua else new_ua + kwargs["user_agent"] = ( + f"{existing_ua}; {new_ua}".strip() if existing_ua else new_ua ) ( @@ -273,6 +272,11 @@ def _merge_application_response(cls, response): for rsp in response: parsed_response = ApplicationResponse.from_api_response(rsp) + if parsed_response.output and not hasattr( + parsed_response.output, + "choices", + ): + parsed_response.output.choices = None result = merge_single_response( parsed_response, accumulated_data, diff --git a/dashscope/audio/qwen_omni/omni_realtime.py b/dashscope/audio/qwen_omni/omni_realtime.py index ec4a1ab..4f15735 100644 --- a/dashscope/audio/qwen_omni/omni_realtime.py +++ b/dashscope/audio/qwen_omni/omni_realtime.py @@ -371,7 +371,7 @@ def end_session(self, timeout: int = 20) -> None: # if the server returned an error or timed out, close the connection if error is not None: self.close() - return + raise RuntimeError(f"Session ended with error: {error}") if not finish_success: self.close() raise TimeoutError( diff --git a/dashscope/tokenizers/qwen_tokenizer.py b/dashscope/tokenizers/qwen_tokenizer.py index d06d755..9a538f9 100644 --- a/dashscope/tokenizers/qwen_tokenizer.py +++ b/dashscope/tokenizers/qwen_tokenizer.py @@ -138,11 +138,19 @@ def _split_text(text: str, chunk_size: int = _CHUNK_SIZE) -> List[str]: parts.append(piece[j : j + chunk_size]) chunks: List[str] = [] + current_chunk: List[str] = [] + current_len = 0 for part in parts: - if chunks and len(chunks[-1]) + len(part) <= chunk_size: - chunks[-1] += part + if current_len + len(part) <= chunk_size: + current_chunk.append(part) + current_len += len(part) else: - chunks.append(part) + if current_chunk: + chunks.append("".join(current_chunk)) + current_chunk = [part] + current_len = len(part) + if current_chunk: + chunks.append("".join(current_chunk)) return chunks def decode( diff --git a/tests/unit/test_conversation.py b/tests/unit/test_conversation.py index 52c734d..7a63c1a 100644 --- a/tests/unit/test_conversation.py +++ b/tests/unit/test_conversation.py @@ -234,5 +234,5 @@ def test_not_qwen(self, mock_server: MockServer): assert response.output.finish_reason == "stop" req = mock_server.requests.get(block=True) assert req["model"] == Generation.Models.dolly_12b_v2 - assert req["parameters"] == {} + assert "user_agent" in req["parameters"] assert req["input"] == {"prompt": prompt} From 7a841b8d700a1ee85988cb990983686343d03da1 Mon Sep 17 00:00:00 2001 From: kevin Date: Thu, 18 Jun 2026 10:16:56 +0800 Subject: [PATCH 5/8] fix: resolve KeyError in ImageGeneration.async_call when headers is missing --- dashscope/aigc/image_generation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dashscope/aigc/image_generation.py b/dashscope/aigc/image_generation.py index d627708..b830495 100644 --- a/dashscope/aigc/image_generation.py +++ b/dashscope/aigc/image_generation.py @@ -107,7 +107,7 @@ def call( # type: ignore[override] f"{existing_ua}; {new_ua}".strip() if existing_ua else new_ua ) if kwargs.get("is_async", False): - kwargs["headers"]["X-DashScope-Async"] = "enable" + kwargs.setdefault("headers", {})["X-DashScope-Async"] = "enable" task = cls.async_task else: task = cls.sync_task @@ -425,7 +425,7 @@ async def call( # type: ignore[override] f"{existing_ua}; {new_ua}".strip() if existing_ua else new_ua ) if kwargs.get("is_async", False): - kwargs["headers"]["X-DashScope-Async"] = "enable" + kwargs.setdefault("headers", {})["X-DashScope-Async"] = "enable" task = cls.async_task else: task = cls.sync_task From dcff9cdf6c3ed3e9c833d97cc2028e8dc53919b7 Mon Sep 17 00:00:00 2001 From: "zhansheng.lzs" Date: Thu, 18 Jun 2026 10:53:12 +0800 Subject: [PATCH 6/8] Fix proxy support and user_agent header extraction - Add trust_env=True to base_api.py async task polling (issue #112) - Extract user_agent from kwargs in api_request_factory to ensure incremental_to_full flag reaches HTTP header (issue #4) - Update test to verify user_agent doesn't leak into request body Co-Authored-By: Claude Opus 4.7 --- dashscope/api_entities/api_request_factory.py | 10 +++++++--- dashscope/client/base_api.py | 2 +- tests/unit/test_conversation.py | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/dashscope/api_entities/api_request_factory.py b/dashscope/api_entities/api_request_factory.py index cc58479..dbf0f52 100644 --- a/dashscope/api_entities/api_request_factory.py +++ b/dashscope/api_entities/api_request_factory.py @@ -38,10 +38,14 @@ def _get_protocol_params(kwargs): extra_url_parameters = kwargs.pop("extra_url_parameters", None) session = kwargs.pop("session", None) - # Extract user-agent from headers if present - user_agent = "" + # Extract user_agent from kwargs (preferred) or from headers["user-agent"] + user_agent = kwargs.pop("user_agent", "") if headers and "user-agent" in headers: - user_agent = headers.pop("user-agent") + header_ua = headers.pop("user-agent") + if user_agent: + user_agent = f"{header_ua}; {user_agent}" if header_ua else user_agent + else: + user_agent = header_ua return ( api_protocol, diff --git a/dashscope/client/base_api.py b/dashscope/client/base_api.py index 68f682a..d67149b 100644 --- a/dashscope/client/base_api.py +++ b/dashscope/client/base_api.py @@ -374,7 +374,7 @@ async def list( **_workspace_header(workspace), **default_headers(api_key), } - async with aiohttp.ClientSession() as session: + async with aiohttp.ClientSession(trust_env=True) as session: response = await session.get( url, params=params, diff --git a/tests/unit/test_conversation.py b/tests/unit/test_conversation.py index 7a63c1a..8dedcc0 100644 --- a/tests/unit/test_conversation.py +++ b/tests/unit/test_conversation.py @@ -234,5 +234,5 @@ def test_not_qwen(self, mock_server: MockServer): assert response.output.finish_reason == "stop" req = mock_server.requests.get(block=True) assert req["model"] == Generation.Models.dolly_12b_v2 - assert "user_agent" in req["parameters"] + assert "user_agent" not in req.get("parameters", {}) assert req["input"] == {"prompt": prompt} From 01c0b57f424fe3db1d644ee10ea110e59939775b Mon Sep 17 00:00:00 2001 From: "zhansheng.lzs" Date: Thu, 18 Jun 2026 10:59:20 +0800 Subject: [PATCH 7/8] Bump version to 1.25.23 and fix pre-commit lint issues Co-Authored-By: Claude Opus 4.7 --- dashscope/version.py | 2 +- tests/unit/test_tokenizer.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/dashscope/version.py b/dashscope/version.py index 117bf62..a4c4f3f 100644 --- a/dashscope/version.py +++ b/dashscope/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- # Copyright (c) Alibaba, Inc. and its affiliates. -__version__ = "1.25.22" +__version__ = "1.25.23" diff --git a/tests/unit/test_tokenizer.py b/tests/unit/test_tokenizer.py index 903e1f6..682c6a1 100644 --- a/tests/unit/test_tokenizer.py +++ b/tests/unit/test_tokenizer.py @@ -4,6 +4,7 @@ import os from dashscope.tokenizers.tokenizer import get_tokenizer +from dashscope.tokenizers.qwen_tokenizer import _CHUNK_SIZE class TestTokenization: @@ -47,3 +48,33 @@ def test_encode_decode(self): "<|endoftext|>", disallowed_special=set(), ) + + def test_encode_chunk_size_exceed(self): + # Test encoding functionality when text length exceeds _CHUNK_SIZE + tokenizer = get_tokenizer("qwen-7b-chat") + + # Create a long text that exceeds _CHUNK_SIZE + long_text = "Hello world! " * ( + _CHUNK_SIZE // 12 + 10 + ) # Ensure it exceeds the threshold + + # Encode long text, should not raise an exception + tokens = tokenizer.encode(long_text) + + # Decoded text should match the original string + decoded_str = tokenizer.decode(tokens) + assert decoded_str == long_text + + # Ensure the return type is a list + assert isinstance(tokens, list) + + # Test long text with special characters + long_text_with_special = ( + "<|extra_0|> " * (_CHUNK_SIZE // 12 + 5) + ) + "Normal text here." + tokens_with_special = tokenizer.encode( + long_text_with_special, + allowed_special="all", + ) + decoded_with_special = tokenizer.decode(tokens_with_special) + assert decoded_with_special == long_text_with_special From 687834cf5c46f3fa0d8e9abdc8b88d3b6ebd351e Mon Sep 17 00:00:00 2001 From: "zhansheng.lzs" Date: Thu, 18 Jun 2026 11:05:14 +0800 Subject: [PATCH 8/8] Fix pre-commit lint issues Co-Authored-By: Claude Opus 4.7 --- dashscope/api_entities/api_request_factory.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dashscope/api_entities/api_request_factory.py b/dashscope/api_entities/api_request_factory.py index dbf0f52..c231287 100644 --- a/dashscope/api_entities/api_request_factory.py +++ b/dashscope/api_entities/api_request_factory.py @@ -43,7 +43,9 @@ def _get_protocol_params(kwargs): if headers and "user-agent" in headers: header_ua = headers.pop("user-agent") if user_agent: - user_agent = f"{header_ua}; {user_agent}" if header_ua else user_agent + user_agent = ( + f"{header_ua}; {user_agent}" if header_ua else user_agent + ) else: user_agent = header_ua