From 4583cc79d827fb1f729884bee2ac19e28f3eeabc Mon Sep 17 00:00:00 2001 From: qbc Date: Thu, 7 Mar 2024 14:36:51 +0800 Subject: [PATCH 01/21] add preprocess roles for tongyi --- src/agentscope/models/tongyi_model.py | 47 ++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/src/agentscope/models/tongyi_model.py b/src/agentscope/models/tongyi_model.py index 74712fc13..673545b64 100644 --- a/src/agentscope/models/tongyi_model.py +++ b/src/agentscope/models/tongyi_model.py @@ -16,6 +16,11 @@ from ..utils import QuotaExceededError from ..constants import _DEFAULT_API_BUDGET +# The models in this list require that the roles of messages must alternate +# between "user" and "assistant". +# TODO: add more models +SPECIAL_MODEL_LIST = ["qwen-turbo", "qwen-plus", "qwen1.5-72b-chat"] + class TongyiWrapper(ModelWrapperBase): """The model wrapper for Tongyi API.""" @@ -176,12 +181,10 @@ def __call__( "and 'content' key for Tongyi API.", ) - # For Tongyi model, the "role" value of the first and the last message - # must be "user" - if len(messages) > 0: - messages[0]["role"] = "user" - messages[-1]["role"] = "user" + messages = self._preprocess_role(messages) + print("messages after", messages) + # TODO: if user input nothing, will be an error # step3: forward to generate response response = dashscope.Generation.call( model=self.model, @@ -189,6 +192,7 @@ def __call__( result_format="message", # set the result to be "message" format. **kwargs, ) + print("response", response) # step4: record the api invocation if needed self._save_model_invocation( @@ -215,3 +219,36 @@ def __call__( text=response.output["choices"][0]["message"]["content"], raw=response, ) + + def _preprocess_role(self, messages: list) -> list: + """preprocess role rules for Tongyi""" + if self.model in SPECIAL_MODEL_LIST: + # The models in this list require that the roles of messages must + # alternate between "user" and "assistant". + message_length = len(messages) + if message_length % 2 == 1: + # messages roles will be + # ["user", "assistant", "user", "assistant", ..., "user"] + for i in range(message_length): + if i % 2 == 0: + messages[i]["role"] = "user" + else: + messages[i]["role"] = "assistant" + else: + # messages roles will be + # ["system", "user", "assistant", "user", "assistant", ... , + # "user"] + messages[0]["role"] = "system" + for i in range(1, message_length): + if i % 2 == 0: + messages[i]["role"] = "user" + else: + messages[i]["role"] = "assistant" + else: + # For other Tongyi models, the "role" value of the first and the + # last messages must be "user" + if len(messages) > 0: + messages[0]["role"] = "user" + messages[-1]["role"] = "user" + + return messages From 3c506da6fe556f6c052a6e129302d8abab35fdc2 Mon Sep 17 00:00:00 2001 From: qbc Date: Fri, 8 Mar 2024 11:23:58 +0800 Subject: [PATCH 02/21] remove monitor for tongyi --- src/agentscope/models/tongyi_model.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/agentscope/models/tongyi_model.py b/src/agentscope/models/tongyi_model.py index 673545b64..ac88e3224 100644 --- a/src/agentscope/models/tongyi_model.py +++ b/src/agentscope/models/tongyi_model.py @@ -13,7 +13,6 @@ from ..utils.monitor import MonitorFactory from ..utils.monitor import get_full_name -from ..utils import QuotaExceededError from ..constants import _DEFAULT_API_BUDGET # The models in this list require that the roles of messages must alternate @@ -192,7 +191,6 @@ def __call__( result_format="message", # set the result to be "message" format. **kwargs, ) - print("response", response) # step4: record the api invocation if needed self._save_model_invocation( @@ -204,15 +202,14 @@ def __call__( json_response=response, ) - # step5: update monitor accordingly - try: - self.monitor.update( - response.usage, - prefix=self.model, - ) - except QuotaExceededError as e: - # TODO: optimize quota exceeded error handling process - logger.error(e.message) + # TODO: Add monitor for Tongyi? step5: update monitor accordingly + # try: + # self.monitor.update( + # response.usage, + # prefix=self.model, + # ) + # except QuotaExceededError as e: + # logger.error(e.message) # step6: return response return ModelResponse( From f5b8234c20a21c498735226ba9effa559f5b3006 Mon Sep 17 00:00:00 2001 From: qbc Date: Fri, 8 Mar 2024 14:27:24 +0800 Subject: [PATCH 03/21] raise runtime error --- src/agentscope/models/tongyi_model.py | 57 ++++++++++++++++----------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/src/agentscope/models/tongyi_model.py b/src/agentscope/models/tongyi_model.py index ac88e3224..c2196bf52 100644 --- a/src/agentscope/models/tongyi_model.py +++ b/src/agentscope/models/tongyi_model.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Model wrapper for Tongyi models""" +from http import HTTPStatus from typing import Any try: @@ -181,7 +182,6 @@ def __call__( ) messages = self._preprocess_role(messages) - print("messages after", messages) # TODO: if user input nothing, will be an error # step3: forward to generate response @@ -202,7 +202,8 @@ def __call__( json_response=response, ) - # TODO: Add monitor for Tongyi? step5: update monitor accordingly + # TODO: Add monitor for Tongyi? + # step5: update monitor accordingly # try: # self.monitor.update( # response.usage, @@ -212,10 +213,20 @@ def __call__( # logger.error(e.message) # step6: return response - return ModelResponse( - text=response.output["choices"][0]["message"]["content"], - raw=response, - ) + if response.status_code == HTTPStatus.OK: + return ModelResponse( + text=response.output["choices"][0]["message"]["content"], + raw=response, + ) + else: + error_msg = ( + f"Request id: {response.request_id}," + f" Status code: {response.status_code}," + f" error code: {response.code}," + f" error message: {response.message}." + ) + + raise RuntimeError(error_msg) def _preprocess_role(self, messages: list) -> list: """preprocess role rules for Tongyi""" @@ -224,23 +235,25 @@ def _preprocess_role(self, messages: list) -> list: # alternate between "user" and "assistant". message_length = len(messages) if message_length % 2 == 1: - # messages roles will be - # ["user", "assistant", "user", "assistant", ..., "user"] - for i in range(message_length): - if i % 2 == 0: - messages[i]["role"] = "user" - else: - messages[i]["role"] = "assistant" + # If the length of the message list is odd, roles will + # alternate, starting with "user" + roles = [ + "user" if i % 2 == 0 else "assistant" + for i in range(message_length) + ] else: - # messages roles will be - # ["system", "user", "assistant", "user", "assistant", ... , - # "user"] - messages[0]["role"] = "system" - for i in range(1, message_length): - if i % 2 == 0: - messages[i]["role"] = "user" - else: - messages[i]["role"] = "assistant" + # If the length of the message list is even, the first role + # will be "system", followed by alternating "user" and + # "assistant" + roles = ["system"] + [ + "user" if i % 2 == 1 else "assistant" + for i in range(1, message_length) + ] + + # Assign the roles list to the "role" key for each message in + # the messages list + for message, role in zip(messages, roles): + message["role"] = role else: # For other Tongyi models, the "role" value of the first and the # last messages must be "user" From 8960006de8bbeca7f4ce433a0f105a3829a6680a Mon Sep 17 00:00:00 2001 From: qbc Date: Fri, 8 Mar 2024 17:54:07 +0800 Subject: [PATCH 04/21] change tongyi to qwen, and add warning for message role processing --- src/agentscope/models/__init__.py | 4 +-- src/agentscope/models/tongyi_model.py | 44 ++++++++++++++++++--------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/agentscope/models/__init__.py b/src/agentscope/models/__init__.py index e7f6e7b5d..b270ed5d4 100644 --- a/src/agentscope/models/__init__.py +++ b/src/agentscope/models/__init__.py @@ -19,7 +19,7 @@ ) from .tongyi_model import ( TongyiWrapper, - TongyiChatWrapper, + QwenChatWrapper, ) @@ -36,7 +36,7 @@ "read_model_configs", "clear_model_configs", "TongyiWrapper", - "TongyiChatWrapper", + "QwenChatWrapper", ] _MODEL_CONFIGS: dict[str, dict] = {} diff --git a/src/agentscope/models/tongyi_model.py b/src/agentscope/models/tongyi_model.py index c2196bf52..ed7a24af8 100644 --- a/src/agentscope/models/tongyi_model.py +++ b/src/agentscope/models/tongyi_model.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Model wrapper for Tongyi models""" +"""Model wrapper for Qwen chat models""" from http import HTTPStatus from typing import Any @@ -105,10 +105,10 @@ def _metric(self, metric_name: str) -> str: return get_full_name(name=metric_name, prefix=self.model) -class TongyiChatWrapper(TongyiWrapper): - """The model wrapper for Tongyi's chat API.""" +class QwenChatWrapper(TongyiWrapper): + """The model wrapper for Qwen's chat API.""" - model_type: str = "tongyi_chat" + model_type: str = "qwen_chat" def _register_default_metrics(self) -> None: # Set monitor accordingly @@ -132,22 +132,22 @@ def __call__( messages: list, **kwargs: Any, ) -> ModelResponse: - """Processes a list of messages to construct a payload for the Tongyi - API call. It then makes a request to the Tongyi API and returns the + """Processes a list of messages to construct a payload for the Qwen + API call. It then makes a request to the Qwen API and returns the response. This method also updates monitoring metrics based on the API response. Each message in the 'messages' list can contain text content and optionally an 'image_urls' key. If 'image_urls' is provided, it is expected to be a list of strings representing URLs to images. - These URLs will be transformed to a suitable format for the Tongyi + These URLs will be transformed to a suitable format for the Qwen API, which might involve converting local file paths to data URIs. Args: messages (`list`): A list of messages to process. **kwargs (`Any`): - The keyword arguments to Tongyi chat completions API, + The keyword arguments to Qwen chat completions API, e.g. `temperature`, `max_tokens`, `top_p`, etc. Please refer to for more detailed arguments. @@ -178,12 +178,9 @@ def __call__( if not all("role" in msg and "content" in msg for msg in messages): raise ValueError( "Each message in the 'messages' list must contain a 'role' " - "and 'content' key for Tongyi API.", + "and 'content' key for Qwen API.", ) - messages = self._preprocess_role(messages) - - # TODO: if user input nothing, will be an error # step3: forward to generate response response = dashscope.Generation.call( model=self.model, @@ -192,6 +189,23 @@ def __call__( **kwargs, ) + if response.status_code == 400: + logger.warning( + "Initial API call failed with status 400. Attempting role " + "preprocessing and retrying. You'd better do it yourself in " + "the prompt engineering to satisfy the model call rule.", + ) + # TODO: remove this and leave prompt engineering to user + messages = self._preprocess_role(messages) + # Retry the API call + response = dashscope.Generation.call( + model=self.model, + messages=messages, + result_format="message", + # set the result to be "message" format. + **kwargs, + ) + # step4: record the api invocation if needed self._save_model_invocation( arguments={ @@ -202,7 +216,7 @@ def __call__( json_response=response, ) - # TODO: Add monitor for Tongyi? + # TODO: Add monitor for Qwen? # step5: update monitor accordingly # try: # self.monitor.update( @@ -229,7 +243,7 @@ def __call__( raise RuntimeError(error_msg) def _preprocess_role(self, messages: list) -> list: - """preprocess role rules for Tongyi""" + """preprocess role rules for Qwen""" if self.model in SPECIAL_MODEL_LIST: # The models in this list require that the roles of messages must # alternate between "user" and "assistant". @@ -255,7 +269,7 @@ def _preprocess_role(self, messages: list) -> list: for message, role in zip(messages, roles): message["role"] = role else: - # For other Tongyi models, the "role" value of the first and the + # For other Qwen models, the "role" value of the first and the # last messages must be "user" if len(messages) > 0: messages[0]["role"] = "user" From 41dde96603fec25c090bf3b1d52cbbac5b548f59 Mon Sep 17 00:00:00 2001 From: qbc Date: Mon, 11 Mar 2024 11:47:10 +0800 Subject: [PATCH 05/21] change tongyi/qwen to dashscope --- src/agentscope/models/__init__.py | 10 +-- .../{tongyi_model.py => dashscope_model.py} | 78 ++++++------------- 2 files changed, 27 insertions(+), 61 deletions(-) rename src/agentscope/models/{tongyi_model.py => dashscope_model.py} (75%) diff --git a/src/agentscope/models/__init__.py b/src/agentscope/models/__init__.py index b270ed5d4..f2f07f897 100644 --- a/src/agentscope/models/__init__.py +++ b/src/agentscope/models/__init__.py @@ -17,9 +17,9 @@ OpenAIDALLEWrapper, OpenAIEmbeddingWrapper, ) -from .tongyi_model import ( - TongyiWrapper, - QwenChatWrapper, +from .dashscope_model import ( + DashScopeWrapper, + DashScopeChatWrapper, ) @@ -35,8 +35,8 @@ "load_model_by_config_name", "read_model_configs", "clear_model_configs", - "TongyiWrapper", - "QwenChatWrapper", + "DashScopeWrapper", + "DashScopeChatWrapper", ] _MODEL_CONFIGS: dict[str, dict] = {} diff --git a/src/agentscope/models/tongyi_model.py b/src/agentscope/models/dashscope_model.py similarity index 75% rename from src/agentscope/models/tongyi_model.py rename to src/agentscope/models/dashscope_model.py index ed7a24af8..2542e990f 100644 --- a/src/agentscope/models/tongyi_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Model wrapper for Qwen chat models""" +"""Model wrapper for DashScope models""" from http import HTTPStatus from typing import Any @@ -22,8 +22,8 @@ SPECIAL_MODEL_LIST = ["qwen-turbo", "qwen-plus", "qwen1.5-72b-chat"] -class TongyiWrapper(ModelWrapperBase): - """The model wrapper for Tongyi API.""" +class DashScopeWrapper(ModelWrapperBase): + """The model wrapper for DashScope API.""" def __init__( self, @@ -34,17 +34,17 @@ def __init__( budget: float = _DEFAULT_API_BUDGET, **kwargs: Any, ) -> None: - """Initialize the Tongyi wrapper. + """Initialize the DashScope wrapper. Args: config_name (`str`): The name of the model config. model_name (`str`, default `None`): - The name of the model to use in Tongyi API. + The name of the model to use in DashScope API. api_key (`str`, default `None`): - The API key for Tongyi API. + The API key for DashScope API. generate_args (`dict`, default `None`): - The extra keyword arguments used in Tongyi api generation, + The extra keyword arguments used in DashScope api generation, e.g. `temperature`, `seed`. budget (`float`, default `None`): The total budget using this model. Set to `None` means no @@ -105,21 +105,21 @@ def _metric(self, metric_name: str) -> str: return get_full_name(name=metric_name, prefix=self.model) -class QwenChatWrapper(TongyiWrapper): - """The model wrapper for Qwen's chat API.""" +class DashScopeChatWrapper(DashScopeWrapper): + """The model wrapper for DashScope's chat API.""" - model_type: str = "qwen_chat" + model_type: str = "dashscope_chat" def _register_default_metrics(self) -> None: # Set monitor accordingly # TODO: set quota to the following metrics self.monitor = MonitorFactory.get_monitor() self.monitor.register( - self._metric("prompt_tokens"), + self._metric("input_tokens"), metric_unit="token", ) self.monitor.register( - self._metric("completion_tokens"), + self._metric("output_tokens"), metric_unit="token", ) self.monitor.register( @@ -132,23 +132,24 @@ def __call__( messages: list, **kwargs: Any, ) -> ModelResponse: - """Processes a list of messages to construct a payload for the Qwen - API call. It then makes a request to the Qwen API and returns the - response. This method also updates monitoring metrics based on the - API response. + """Processes a list of messages to construct a payload for the + DashScope API call. It then makes a request to the DashScope API + and returns the response. This method also updates monitoring + metrics based on the API response. Each message in the 'messages' list can contain text content and optionally an 'image_urls' key. If 'image_urls' is provided, it is expected to be a list of strings representing URLs to images. - These URLs will be transformed to a suitable format for the Qwen + These URLs will be transformed to a suitable format for the DashScope API, which might involve converting local file paths to data URIs. Args: messages (`list`): A list of messages to process. **kwargs (`Any`): - The keyword arguments to Qwen chat completions API, - e.g. `temperature`, `max_tokens`, `top_p`, etc. Please refer to + The keyword arguments to DashScope chat completions API, + e.g. `temperature`, `max_tokens`, `top_p`, etc. Please + refer to for more detailed arguments. @@ -178,7 +179,7 @@ def __call__( if not all("role" in msg and "content" in msg for msg in messages): raise ValueError( "Each message in the 'messages' list must contain a 'role' " - "and 'content' key for Qwen API.", + "and 'content' key for DashScope API.", ) # step3: forward to generate response @@ -216,7 +217,7 @@ def __call__( json_response=response, ) - # TODO: Add monitor for Qwen? + # TODO: Add monitor for DashScope? # step5: update monitor accordingly # try: # self.monitor.update( @@ -241,38 +242,3 @@ def __call__( ) raise RuntimeError(error_msg) - - def _preprocess_role(self, messages: list) -> list: - """preprocess role rules for Qwen""" - if self.model in SPECIAL_MODEL_LIST: - # The models in this list require that the roles of messages must - # alternate between "user" and "assistant". - message_length = len(messages) - if message_length % 2 == 1: - # If the length of the message list is odd, roles will - # alternate, starting with "user" - roles = [ - "user" if i % 2 == 0 else "assistant" - for i in range(message_length) - ] - else: - # If the length of the message list is even, the first role - # will be "system", followed by alternating "user" and - # "assistant" - roles = ["system"] + [ - "user" if i % 2 == 1 else "assistant" - for i in range(1, message_length) - ] - - # Assign the roles list to the "role" key for each message in - # the messages list - for message, role in zip(messages, roles): - message["role"] = role - else: - # For other Qwen models, the "role" value of the first and the - # last messages must be "user" - if len(messages) > 0: - messages[0]["role"] = "user" - messages[-1]["role"] = "user" - - return messages From 95171022076758151398bd2ff7f4efa5035e1920 Mon Sep 17 00:00:00 2001 From: qbc Date: Tue, 12 Mar 2024 17:37:12 +0800 Subject: [PATCH 06/21] add dashscope wanx and embedding, raise error if failed --- src/agentscope/models/dashscope_model.py | 243 +++++++++++++++++++---- 1 file changed, 205 insertions(+), 38 deletions(-) diff --git a/src/agentscope/models/dashscope_model.py b/src/agentscope/models/dashscope_model.py index 2542e990f..e1882adc7 100644 --- a/src/agentscope/models/dashscope_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Model wrapper for DashScope models""" from http import HTTPStatus -from typing import Any +from typing import Any, Union try: import dashscope @@ -12,6 +12,7 @@ from .model import ModelWrapperBase, ModelResponse +from ..file_manager import file_manager from ..utils.monitor import MonitorFactory from ..utils.monitor import get_full_name from ..constants import _DEFAULT_API_BUDGET @@ -19,7 +20,12 @@ # The models in this list require that the roles of messages must alternate # between "user" and "assistant". # TODO: add more models -SPECIAL_MODEL_LIST = ["qwen-turbo", "qwen-plus", "qwen1.5-72b-chat"] +SPECIAL_MODEL_LIST = [ + "qwen-turbo", + "qwen-plus", + "qwen1.5-72b-chat", + "qwen-max", +] class DashScopeWrapper(ModelWrapperBase): @@ -190,45 +196,27 @@ def __call__( **kwargs, ) - if response.status_code == 400: - logger.warning( - "Initial API call failed with status 400. Attempting role " - "preprocessing and retrying. You'd better do it yourself in " - "the prompt engineering to satisfy the model call rule.", - ) - # TODO: remove this and leave prompt engineering to user - messages = self._preprocess_role(messages) - # Retry the API call - response = dashscope.Generation.call( - model=self.model, - messages=messages, - result_format="message", - # set the result to be "message" format. - **kwargs, + if response.status_code == HTTPStatus.OK: + # step4: record the api invocation if needed + self._save_model_invocation( + arguments={ + "model": self.model, + "messages": messages, + **kwargs, + }, + json_response=response, ) - # step4: record the api invocation if needed - self._save_model_invocation( - arguments={ - "model": self.model, - "messages": messages, - **kwargs, - }, - json_response=response, - ) + # step5: update monitor accordingly + try: + self.monitor.update( + response.usage, + prefix=self.model, + ) + except Exception as e: + logger.error(e) - # TODO: Add monitor for DashScope? - # step5: update monitor accordingly - # try: - # self.monitor.update( - # response.usage, - # prefix=self.model, - # ) - # except QuotaExceededError as e: - # logger.error(e.message) - - # step6: return response - if response.status_code == HTTPStatus.OK: + # step6: return response return ModelResponse( text=response.output["choices"][0]["message"]["content"], raw=response, @@ -242,3 +230,182 @@ def __call__( ) raise RuntimeError(error_msg) + + class DashScopeWanxWrapper(DashScopeWrapper): + """The model wrapper for DashScope's wanx API.""" + + model_type: str = "dashscope_wanx" + + def _register_default_metrics(self) -> None: + # Set monitor accordingly + # TODO: set quota to the following metrics + self.monitor = MonitorFactory.get_monitor() + self.monitor.register( + self._metric("image_count"), + metric_unit="image", + ) + + def __call__( + self, + prompt: str, + save_local: bool = False, + **kwargs: Any, + ) -> ModelResponse: + """ + Args: + prompt (`str`): + The prompt string to generate images from. + save_local: (`bool`, default `False`): + Whether to save the generated images locally, and replace + the returned image url with the local path. + **kwargs (`Any`): + The keyword arguments to DashScope image generation API, + e.g. `n`, `size`, etc. Please refer to + https://help.aliyun.com/zh/dashscope/developer-reference/api-details-9?spm=a2c4g.11186623.0.0.4c1e7e1cs7Lv0A + for more detailed arguments. + + Returns: + `ModelResponse`: + A list of image urls in image_urls field and the + raw response in raw field. + + Note: + `parse_func`, `fault_handler` and `max_retries` are + reserved for `_response_parse_decorator` to parse and check + the response generated by model wrapper. Their usages are + listed as follows: + - `parse_func` is a callable function used to parse and + check the response generated by the model, which takes + the response as input. + - `max_retries` is the maximum number of retries when the + `parse_func` raise an exception. + - `fault_handler` is a callable function which is called + when the response generated by the model is invalid after + `max_retries` retries. + """ + # step1: prepare keyword arguments + kwargs = {**self.generate_args, **kwargs} + + # step2: forward to generate response + response = dashscope.ImageSynthesis.call( + model=self.model, + prompt=prompt, + n=1, + **kwargs, + ) + if response.status_code != HTTPStatus.OK: + err_msg = ( + f"Failed, status_code: {response.status_code}, " + f"code: {response.code}, " + f"message: {response.message}" + ) + raise RuntimeError(err_msg) + + print("response", response) + # step3: record the model api invocation if needed + self._save_model_invocation( + arguments={ + "model": self.model, + "prompt": prompt, + **kwargs, + }, + json_response=response, + ) + + # step4: return response + raw_response = response + images = raw_response["output"]["results"] + # Get image urls as a list + urls = [_["url"] for _ in images] + + if save_local: + # Return local url if save_local is True + urls = [file_manager.save_image(_) for _ in urls] + return ModelResponse(image_urls=urls, raw=raw_response) + + class DashScopeEmbeddingWrapper(DashScopeWrapper): + """The model wrapper for DashScope embedding API.""" + + model_type: str = "dashscope_embedding" + + def _register_default_metrics(self) -> None: + # Set monitor accordingly + # TODO: set quota to the following metrics + self.monitor = MonitorFactory.get_monitor() + self.monitor.register( + self._metric("total_tokens"), + metric_unit="token", + ) + + def __call__( + self, + texts: Union[list[str], str], + **kwargs: Any, + ) -> ModelResponse: + """Embed the messages with DashScope embedding API. + + Args: + texts (`list[str]` or `str`): + The messages used to embed. + **kwargs (`Any`): + The keyword arguments to DashScope embedding API, + e.g. `encoding_format`, `user`. Please refer to + https://help.aliyun.com/zh/dashscope/developer-reference/api-details-15?spm=a2c4g.11186623.0.0.7a962a9d0tN89b + for more detailed arguments. + + Returns: + `ModelResponse`: + A list of embeddings in embedding field and the + raw response in raw field. + + Note: + `parse_func`, `fault_handler` and `max_retries` are + reserved for `_response_parse_decorator` to parse and check + the response generated by model wrapper. Their usages are + listed as follows: + - `parse_func` is a callable function used to parse and + check the response generated by the model, which takes + the response as input. + - `max_retries` is the maximum number of retries when the + `parse_func` raise an exception. + - `fault_handler` is a callable function which is called + when the response generated by the model is invalid after + `max_retries` retries. + """ + # step1: prepare keyword arguments + kwargs = {**self.generate_args, **kwargs} + + # step2: forward to generate response + response = dashscope.TextEmbedding.call( + input=texts, + model=self.model, + **kwargs, + ) + + if response.status_code != HTTPStatus.OK: + raise RuntimeError(response) + # step3: record the model api invocation if needed + self._save_model_invocation( + arguments={ + "model": self.model, + "input": texts, + **kwargs, + }, + json_response=response, + ) + + # step4: return response + response_json = response + if len(response_json["output"]["embeddings"]) == 0: + return ModelResponse( + embedding=response_json["output"]["embedding"][0], + raw=response_json, + ) + else: + return ModelResponse( + embedding=[ + _["embedding"] + for _ in response_json["output"]["embeddings"] + ], + raw=response_json, + ) From d9c67cfc107759a78d742570efc9e32140aa0280 Mon Sep 17 00:00:00 2001 From: qbc Date: Tue, 12 Mar 2024 17:51:56 +0800 Subject: [PATCH 07/21] remove print --- src/agentscope/models/dashscope_model.py | 74 ++++++++++++------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/agentscope/models/dashscope_model.py b/src/agentscope/models/dashscope_model.py index e1882adc7..cc11e524c 100644 --- a/src/agentscope/models/dashscope_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -156,7 +156,7 @@ def __call__( The keyword arguments to DashScope chat completions API, e.g. `temperature`, `max_tokens`, `top_p`, etc. Please refer to - + https://help.aliyun.com/zh/dashscope/developer-reference/api-details?spm=a2c4g.11186623.0.0.35e912b0W9wMv7 for more detailed arguments. Returns: @@ -176,6 +176,9 @@ def __call__( - `fault_handler` is a callable function which is called when the response generated by the model is invalid after `max_retries` retries. + The rule of roles in messages for DashScope is very rigid, + for more details, please refer to + https://help.aliyun.com/zh/dashscope/developer-reference/api-details?spm=a2c4g.11186623.0.0.35e912b0W9wMv7 """ # step1: prepare keyword arguments @@ -196,32 +199,7 @@ def __call__( **kwargs, ) - if response.status_code == HTTPStatus.OK: - # step4: record the api invocation if needed - self._save_model_invocation( - arguments={ - "model": self.model, - "messages": messages, - **kwargs, - }, - json_response=response, - ) - - # step5: update monitor accordingly - try: - self.monitor.update( - response.usage, - prefix=self.model, - ) - except Exception as e: - logger.error(e) - - # step6: return response - return ModelResponse( - text=response.output["choices"][0]["message"]["content"], - raw=response, - ) - else: + if response.status_code != HTTPStatus.OK: error_msg = ( f"Request id: {response.request_id}," f" Status code: {response.status_code}," @@ -231,6 +209,31 @@ def __call__( raise RuntimeError(error_msg) + # step4: record the api invocation if needed + self._save_model_invocation( + arguments={ + "model": self.model, + "messages": messages, + **kwargs, + }, + json_response=response, + ) + + # step5: update monitor accordingly + try: + self.monitor.update( + response.usage, + prefix=self.model, + ) + except Exception as e: + logger.error(e) + + # step6: return response + return ModelResponse( + text=response.output["choices"][0]["message"]["content"], + raw=response, + ) + class DashScopeWanxWrapper(DashScopeWrapper): """The model wrapper for DashScope's wanx API.""" @@ -301,7 +304,6 @@ def __call__( ) raise RuntimeError(err_msg) - print("response", response) # step3: record the model api invocation if needed self._save_model_invocation( arguments={ @@ -313,15 +315,14 @@ def __call__( ) # step4: return response - raw_response = response - images = raw_response["output"]["results"] + images = response["output"]["results"] # Get image urls as a list urls = [_["url"] for _ in images] if save_local: # Return local url if save_local is True urls = [file_manager.save_image(_) for _ in urls] - return ModelResponse(image_urls=urls, raw=raw_response) + return ModelResponse(image_urls=urls, raw=response) class DashScopeEmbeddingWrapper(DashScopeWrapper): """The model wrapper for DashScope embedding API.""" @@ -395,17 +396,16 @@ def __call__( ) # step4: return response - response_json = response - if len(response_json["output"]["embeddings"]) == 0: + if len(response["output"]["embeddings"]) == 0: return ModelResponse( - embedding=response_json["output"]["embedding"][0], - raw=response_json, + embedding=response["output"]["embedding"][0], + raw=response, ) else: return ModelResponse( embedding=[ _["embedding"] - for _ in response_json["output"]["embeddings"] + for _ in response["output"]["embeddings"] ], - raw=response_json, + raw=response, ) From 3a971f37c9aca494b1338d225aa0d939a6bdbe40 Mon Sep 17 00:00:00 2001 From: qbc Date: Tue, 12 Mar 2024 17:55:42 +0800 Subject: [PATCH 08/21] minor changes --- src/agentscope/models/__init__.py | 2 + src/agentscope/models/dashscope_model.py | 326 +++++++++++------------ 2 files changed, 165 insertions(+), 163 deletions(-) diff --git a/src/agentscope/models/__init__.py b/src/agentscope/models/__init__.py index f2f07f897..6fddf64d9 100644 --- a/src/agentscope/models/__init__.py +++ b/src/agentscope/models/__init__.py @@ -20,6 +20,8 @@ from .dashscope_model import ( DashScopeWrapper, DashScopeChatWrapper, + DashScopeWanxWrapper, + DashScopeEmbeddingWrapper, ) diff --git a/src/agentscope/models/dashscope_model.py b/src/agentscope/models/dashscope_model.py index cc11e524c..0a3340aeb 100644 --- a/src/agentscope/models/dashscope_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -234,178 +234,178 @@ def __call__( raw=response, ) - class DashScopeWanxWrapper(DashScopeWrapper): - """The model wrapper for DashScope's wanx API.""" - - model_type: str = "dashscope_wanx" - - def _register_default_metrics(self) -> None: - # Set monitor accordingly - # TODO: set quota to the following metrics - self.monitor = MonitorFactory.get_monitor() - self.monitor.register( - self._metric("image_count"), - metric_unit="image", + +class DashScopeWanxWrapper(DashScopeWrapper): + """The model wrapper for DashScope's wanx API.""" + + model_type: str = "dashscope_wanx" + + def _register_default_metrics(self) -> None: + # Set monitor accordingly + # TODO: set quota to the following metrics + self.monitor = MonitorFactory.get_monitor() + self.monitor.register( + self._metric("image_count"), + metric_unit="image", + ) + + def __call__( + self, + prompt: str, + save_local: bool = False, + **kwargs: Any, + ) -> ModelResponse: + """ + Args: + prompt (`str`): + The prompt string to generate images from. + save_local: (`bool`, default `False`): + Whether to save the generated images locally, and replace + the returned image url with the local path. + **kwargs (`Any`): + The keyword arguments to DashScope image generation API, + e.g. `n`, `size`, etc. Please refer to + https://help.aliyun.com/zh/dashscope/developer-reference/api-details-9?spm=a2c4g.11186623.0.0.4c1e7e1cs7Lv0A + for more detailed arguments. + + Returns: + `ModelResponse`: + A list of image urls in image_urls field and the + raw response in raw field. + + Note: + `parse_func`, `fault_handler` and `max_retries` are reserved + for `_response_parse_decorator` to parse and check the + response generated by model wrapper. Their usages are listed + as follows: + - `parse_func` is a callable function used to parse and + check the response generated by the model, which takes + the response as input. + - `max_retries` is the maximum number of retries when the + `parse_func` raise an exception. + - `fault_handler` is a callable function which is called + when the response generated by the model is invalid after + `max_retries` retries. + """ + # step1: prepare keyword arguments + kwargs = {**self.generate_args, **kwargs} + + # step2: forward to generate response + response = dashscope.ImageSynthesis.call( + model=self.model, + prompt=prompt, + n=1, + **kwargs, + ) + if response.status_code != HTTPStatus.OK: + err_msg = ( + f"Failed, status_code: {response.status_code}, " + f"code: {response.code}, " + f"message: {response.message}" ) + raise RuntimeError(err_msg) - def __call__( - self, - prompt: str, - save_local: bool = False, - **kwargs: Any, - ) -> ModelResponse: - """ - Args: - prompt (`str`): - The prompt string to generate images from. - save_local: (`bool`, default `False`): - Whether to save the generated images locally, and replace - the returned image url with the local path. - **kwargs (`Any`): - The keyword arguments to DashScope image generation API, - e.g. `n`, `size`, etc. Please refer to - https://help.aliyun.com/zh/dashscope/developer-reference/api-details-9?spm=a2c4g.11186623.0.0.4c1e7e1cs7Lv0A - for more detailed arguments. - - Returns: - `ModelResponse`: - A list of image urls in image_urls field and the - raw response in raw field. - - Note: - `parse_func`, `fault_handler` and `max_retries` are - reserved for `_response_parse_decorator` to parse and check - the response generated by model wrapper. Their usages are - listed as follows: - - `parse_func` is a callable function used to parse and - check the response generated by the model, which takes - the response as input. - - `max_retries` is the maximum number of retries when the - `parse_func` raise an exception. - - `fault_handler` is a callable function which is called - when the response generated by the model is invalid after - `max_retries` retries. - """ - # step1: prepare keyword arguments - kwargs = {**self.generate_args, **kwargs} - - # step2: forward to generate response - response = dashscope.ImageSynthesis.call( - model=self.model, - prompt=prompt, - n=1, + # step3: record the model api invocation if needed + self._save_model_invocation( + arguments={ + "model": self.model, + "prompt": prompt, **kwargs, - ) - if response.status_code != HTTPStatus.OK: - err_msg = ( - f"Failed, status_code: {response.status_code}, " - f"code: {response.code}, " - f"message: {response.message}" - ) - raise RuntimeError(err_msg) - - # step3: record the model api invocation if needed - self._save_model_invocation( - arguments={ - "model": self.model, - "prompt": prompt, - **kwargs, - }, - json_response=response, - ) + }, + json_response=response, + ) - # step4: return response - images = response["output"]["results"] - # Get image urls as a list - urls = [_["url"] for _ in images] + # step4: return response + images = response["output"]["results"] + # Get image urls as a list + urls = [_["url"] for _ in images] - if save_local: - # Return local url if save_local is True - urls = [file_manager.save_image(_) for _ in urls] - return ModelResponse(image_urls=urls, raw=response) + if save_local: + # Return local url if save_local is True + urls = [file_manager.save_image(_) for _ in urls] + return ModelResponse(image_urls=urls, raw=response) - class DashScopeEmbeddingWrapper(DashScopeWrapper): - """The model wrapper for DashScope embedding API.""" - model_type: str = "dashscope_embedding" +class DashScopeEmbeddingWrapper(DashScopeWrapper): + """The model wrapper for DashScope embedding API.""" - def _register_default_metrics(self) -> None: - # Set monitor accordingly - # TODO: set quota to the following metrics - self.monitor = MonitorFactory.get_monitor() - self.monitor.register( - self._metric("total_tokens"), - metric_unit="token", - ) + model_type: str = "dashscope_embedding" + + def _register_default_metrics(self) -> None: + # Set monitor accordingly + # TODO: set quota to the following metrics + self.monitor = MonitorFactory.get_monitor() + self.monitor.register( + self._metric("total_tokens"), + metric_unit="token", + ) + + def __call__( + self, + texts: Union[list[str], str], + **kwargs: Any, + ) -> ModelResponse: + """Embed the messages with DashScope embedding API. + + Args: + texts (`list[str]` or `str`): + The messages used to embed. + **kwargs (`Any`): + The keyword arguments to DashScope embedding API, + e.g. `encoding_format`, `user`. Please refer to + https://help.aliyun.com/zh/dashscope/developer-reference/api-details-15?spm=a2c4g.11186623.0.0.7a962a9d0tN89b + for more detailed arguments. - def __call__( - self, - texts: Union[list[str], str], - **kwargs: Any, - ) -> ModelResponse: - """Embed the messages with DashScope embedding API. - - Args: - texts (`list[str]` or `str`): - The messages used to embed. - **kwargs (`Any`): - The keyword arguments to DashScope embedding API, - e.g. `encoding_format`, `user`. Please refer to - https://help.aliyun.com/zh/dashscope/developer-reference/api-details-15?spm=a2c4g.11186623.0.0.7a962a9d0tN89b - for more detailed arguments. - - Returns: - `ModelResponse`: - A list of embeddings in embedding field and the - raw response in raw field. - - Note: - `parse_func`, `fault_handler` and `max_retries` are - reserved for `_response_parse_decorator` to parse and check - the response generated by model wrapper. Their usages are - listed as follows: - - `parse_func` is a callable function used to parse and - check the response generated by the model, which takes - the response as input. - - `max_retries` is the maximum number of retries when the - `parse_func` raise an exception. - - `fault_handler` is a callable function which is called - when the response generated by the model is invalid after - `max_retries` retries. - """ - # step1: prepare keyword arguments - kwargs = {**self.generate_args, **kwargs} - - # step2: forward to generate response - response = dashscope.TextEmbedding.call( - input=texts, - model=self.model, + Returns: + `ModelResponse`: + A list of embeddings in embedding field and the raw + response in raw field. + + Note: + `parse_func`, `fault_handler` and `max_retries` are reserved + for `_response_parse_decorator` to parse and check the response + generated by model wrapper. Their usages are listed as follows: + - `parse_func` is a callable function used to parse and + check the response generated by the model, which takes the + response as input. + - `max_retries` is the maximum number of retries when the + `parse_func` raise an exception. + - `fault_handler` is a callable function which is called + when the response generated by the model is invalid after + `max_retries` retries. + """ + # step1: prepare keyword arguments + kwargs = {**self.generate_args, **kwargs} + + # step2: forward to generate response + response = dashscope.TextEmbedding.call( + input=texts, + model=self.model, + **kwargs, + ) + + if response.status_code != HTTPStatus.OK: + raise RuntimeError(response) + # step3: record the model api invocation if needed + self._save_model_invocation( + arguments={ + "model": self.model, + "input": texts, **kwargs, - ) + }, + json_response=response, + ) - if response.status_code != HTTPStatus.OK: - raise RuntimeError(response) - # step3: record the model api invocation if needed - self._save_model_invocation( - arguments={ - "model": self.model, - "input": texts, - **kwargs, - }, - json_response=response, + # step4: return response + if len(response["output"]["embeddings"]) == 0: + return ModelResponse( + embedding=response["output"]["embedding"][0], + raw=response, + ) + else: + return ModelResponse( + embedding=[ + _["embedding"] for _ in response["output"]["embeddings"] + ], + raw=response, ) - - # step4: return response - if len(response["output"]["embeddings"]) == 0: - return ModelResponse( - embedding=response["output"]["embedding"][0], - raw=response, - ) - else: - return ModelResponse( - embedding=[ - _["embedding"] - for _ in response["output"]["embeddings"] - ], - raw=response, - ) From 81ae6f423359034685402d107cc0ed3e9d0cf56a Mon Sep 17 00:00:00 2001 From: qbc Date: Tue, 12 Mar 2024 17:58:08 +0800 Subject: [PATCH 09/21] remove unused variables --- src/agentscope/models/dashscope_model.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/agentscope/models/dashscope_model.py b/src/agentscope/models/dashscope_model.py index 0a3340aeb..dee436035 100644 --- a/src/agentscope/models/dashscope_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -17,16 +17,6 @@ from ..utils.monitor import get_full_name from ..constants import _DEFAULT_API_BUDGET -# The models in this list require that the roles of messages must alternate -# between "user" and "assistant". -# TODO: add more models -SPECIAL_MODEL_LIST = [ - "qwen-turbo", - "qwen-plus", - "qwen1.5-72b-chat", - "qwen-max", -] - class DashScopeWrapper(ModelWrapperBase): """The model wrapper for DashScope API.""" From 2eb0ca2a860c08f1c9a0de8b5bdbff924d6b9fb0 Mon Sep 17 00:00:00 2001 From: qbc Date: Tue, 12 Mar 2024 18:16:59 +0800 Subject: [PATCH 10/21] minor changes --- src/agentscope/models/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/agentscope/models/__init__.py b/src/agentscope/models/__init__.py index 6fddf64d9..9b17439ce 100644 --- a/src/agentscope/models/__init__.py +++ b/src/agentscope/models/__init__.py @@ -39,6 +39,8 @@ "clear_model_configs", "DashScopeWrapper", "DashScopeChatWrapper", + "DashScopeWanxWrapper", + "DashScopeEmbeddingWrapper", ] _MODEL_CONFIGS: dict[str, dict] = {} From 1cd2982b41ada2604a034ad98bf226cd7a656638 Mon Sep 17 00:00:00 2001 From: qbc Date: Tue, 12 Mar 2024 20:06:20 +0800 Subject: [PATCH 11/21] add monitor.update --- src/agentscope/models/dashscope_model.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/agentscope/models/dashscope_model.py b/src/agentscope/models/dashscope_model.py index dee436035..d895d08a5 100644 --- a/src/agentscope/models/dashscope_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -305,7 +305,16 @@ def __call__( json_response=response, ) - # step4: return response + # step4: update monitor accordingly + try: + self.monitor.update( + response.usage, + prefix=self.model, + ) + except Exception as e: + logger.error(e) + + # step5: return response images = response["output"]["results"] # Get image urls as a list urls = [_["url"] for _ in images] @@ -376,6 +385,7 @@ def __call__( if response.status_code != HTTPStatus.OK: raise RuntimeError(response) + # step3: record the model api invocation if needed self._save_model_invocation( arguments={ @@ -386,7 +396,16 @@ def __call__( json_response=response, ) - # step4: return response + # step4: update monitor accordingly + try: + self.monitor.update( + response.usage, + prefix=self.model, + ) + except Exception as e: + logger.error(e) + + # step5: return response if len(response["output"]["embeddings"]) == 0: return ModelResponse( embedding=response["output"]["embedding"][0], From 599ab31225faa63244db2ff24cdd6da314a68100 Mon Sep 17 00:00:00 2001 From: qbc Date: Tue, 12 Mar 2024 20:19:50 +0800 Subject: [PATCH 12/21] minor changes --- src/agentscope/models/dashscope_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agentscope/models/dashscope_model.py b/src/agentscope/models/dashscope_model.py index d895d08a5..be1d666e6 100644 --- a/src/agentscope/models/dashscope_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -351,7 +351,7 @@ def __call__( The messages used to embed. **kwargs (`Any`): The keyword arguments to DashScope embedding API, - e.g. `encoding_format`, `user`. Please refer to + e.g. `text_type`. Please refer to https://help.aliyun.com/zh/dashscope/developer-reference/api-details-15?spm=a2c4g.11186623.0.0.7a962a9d0tN89b for more detailed arguments. From 8a3ca305ef9d1080e12f424acdb0216b8a4418ac Mon Sep 17 00:00:00 2001 From: qbc Date: Wed, 13 Mar 2024 14:30:53 +0800 Subject: [PATCH 13/21] match dashscope chat metric to openai, fix hyperlink issues --- src/agentscope/models/dashscope_model.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/agentscope/models/dashscope_model.py b/src/agentscope/models/dashscope_model.py index be1d666e6..7061bd609 100644 --- a/src/agentscope/models/dashscope_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -5,7 +5,7 @@ try: import dashscope -except ImportError: +except ModuleNotFoundError: dashscope = None from loguru import logger @@ -111,11 +111,11 @@ def _register_default_metrics(self) -> None: # TODO: set quota to the following metrics self.monitor = MonitorFactory.get_monitor() self.monitor.register( - self._metric("input_tokens"), + self._metric("prompt_tokens"), metric_unit="token", ) self.monitor.register( - self._metric("output_tokens"), + self._metric("completion_tokens"), metric_unit="token", ) self.monitor.register( @@ -146,7 +146,7 @@ def __call__( The keyword arguments to DashScope chat completions API, e.g. `temperature`, `max_tokens`, `top_p`, etc. Please refer to - https://help.aliyun.com/zh/dashscope/developer-reference/api-details?spm=a2c4g.11186623.0.0.35e912b0W9wMv7 + https://help.aliyun.com/zh/dashscope/developer-reference/api-details for more detailed arguments. Returns: @@ -168,7 +168,7 @@ def __call__( `max_retries` retries. The rule of roles in messages for DashScope is very rigid, for more details, please refer to - https://help.aliyun.com/zh/dashscope/developer-reference/api-details?spm=a2c4g.11186623.0.0.35e912b0W9wMv7 + https://help.aliyun.com/zh/dashscope/developer-reference/api-details """ # step1: prepare keyword arguments @@ -212,7 +212,11 @@ def __call__( # step5: update monitor accordingly try: self.monitor.update( - response.usage, + { + "prompt_tokens": response.usage["input_tokens"], + "completion_tokens": response.usage["output_tokens"], + "total_tokens": response.usage["total_tokens"], + }, prefix=self.model, ) except Exception as e: @@ -255,7 +259,7 @@ def __call__( **kwargs (`Any`): The keyword arguments to DashScope image generation API, e.g. `n`, `size`, etc. Please refer to - https://help.aliyun.com/zh/dashscope/developer-reference/api-details-9?spm=a2c4g.11186623.0.0.4c1e7e1cs7Lv0A + https://help.aliyun.com/zh/dashscope/developer-reference/api-details-9 for more detailed arguments. Returns: @@ -352,7 +356,7 @@ def __call__( **kwargs (`Any`): The keyword arguments to DashScope embedding API, e.g. `text_type`. Please refer to - https://help.aliyun.com/zh/dashscope/developer-reference/api-details-15?spm=a2c4g.11186623.0.0.7a962a9d0tN89b + https://help.aliyun.com/zh/dashscope/developer-reference/api-details-15 for more detailed arguments. Returns: From 415912f0f1cc69a1ee0067fa121ce3a8b10bad16 Mon Sep 17 00:00:00 2001 From: qbc Date: Wed, 13 Mar 2024 14:37:36 +0800 Subject: [PATCH 14/21] change name of mode_type --- src/agentscope/models/dashscope_model.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/agentscope/models/dashscope_model.py b/src/agentscope/models/dashscope_model.py index 7061bd609..f8dc38421 100644 --- a/src/agentscope/models/dashscope_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -229,10 +229,10 @@ def __call__( ) -class DashScopeWanxWrapper(DashScopeWrapper): - """The model wrapper for DashScope's wanx API.""" +class DashScopeImageSynthesisWrapper(DashScopeWrapper): + """The model wrapper for DashScope Image Synthesis API.""" - model_type: str = "dashscope_wanx" + model_type: str = "dashscope_imagesynthesis" def _register_default_metrics(self) -> None: # Set monitor accordingly @@ -257,7 +257,7 @@ def __call__( Whether to save the generated images locally, and replace the returned image url with the local path. **kwargs (`Any`): - The keyword arguments to DashScope image generation API, + The keyword arguments to DashScope Image Synthesis API, e.g. `n`, `size`, etc. Please refer to https://help.aliyun.com/zh/dashscope/developer-reference/api-details-9 for more detailed arguments. @@ -329,10 +329,10 @@ def __call__( return ModelResponse(image_urls=urls, raw=response) -class DashScopeEmbeddingWrapper(DashScopeWrapper): - """The model wrapper for DashScope embedding API.""" +class DashScopeTextEmbeddingWrapper(DashScopeWrapper): + """The model wrapper for DashScope Text Embedding API.""" - model_type: str = "dashscope_embedding" + model_type: str = "dashscope_textembedding" def _register_default_metrics(self) -> None: # Set monitor accordingly @@ -348,13 +348,13 @@ def __call__( texts: Union[list[str], str], **kwargs: Any, ) -> ModelResponse: - """Embed the messages with DashScope embedding API. + """Embed the messages with DashScope Text Embedding API. Args: texts (`list[str]` or `str`): The messages used to embed. **kwargs (`Any`): - The keyword arguments to DashScope embedding API, + The keyword arguments to DashScope Text Embedding API, e.g. `text_type`. Please refer to https://help.aliyun.com/zh/dashscope/developer-reference/api-details-15 for more detailed arguments. From 2281344201f07dd538a9e858243881976e900ed3 Mon Sep 17 00:00:00 2001 From: qbc Date: Wed, 13 Mar 2024 14:40:59 +0800 Subject: [PATCH 15/21] minor changes --- src/agentscope/models/__init__.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/agentscope/models/__init__.py b/src/agentscope/models/__init__.py index 9b17439ce..c4609482a 100644 --- a/src/agentscope/models/__init__.py +++ b/src/agentscope/models/__init__.py @@ -18,10 +18,9 @@ OpenAIEmbeddingWrapper, ) from .dashscope_model import ( - DashScopeWrapper, DashScopeChatWrapper, - DashScopeWanxWrapper, - DashScopeEmbeddingWrapper, + DashScopeImageSynthesisWrapper, + DashScopeTextEmbeddingWrapper, ) @@ -37,10 +36,9 @@ "load_model_by_config_name", "read_model_configs", "clear_model_configs", - "DashScopeWrapper", "DashScopeChatWrapper", - "DashScopeWanxWrapper", - "DashScopeEmbeddingWrapper", + "DashScopeImageSynthesisWrapper", + "DashScopeTextEmbeddingWrapper", ] _MODEL_CONFIGS: dict[str, dict] = {} From 4e3d3ad96b03c9a4106552b6b8654cca3bde621c Mon Sep 17 00:00:00 2001 From: qbc Date: Wed, 13 Mar 2024 20:03:14 +0800 Subject: [PATCH 16/21] add raise error for openai --- src/agentscope/models/dashscope_model.py | 21 ++++++++++++++------- src/agentscope/models/openai_model.py | 9 +++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/agentscope/models/dashscope_model.py b/src/agentscope/models/dashscope_model.py index f8dc38421..76edc2d3e 100644 --- a/src/agentscope/models/dashscope_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -191,7 +191,7 @@ def __call__( if response.status_code != HTTPStatus.OK: error_msg = ( - f"Request id: {response.request_id}," + f" Request id: {response.request_id}," f" Status code: {response.status_code}," f" error code: {response.code}," f" error message: {response.message}." @@ -292,12 +292,13 @@ def __call__( **kwargs, ) if response.status_code != HTTPStatus.OK: - err_msg = ( - f"Failed, status_code: {response.status_code}, " - f"code: {response.code}, " - f"message: {response.message}" + error_msg = ( + f" Request id: {response.request_id}," + f" Status code: {response.status_code}," + f" error code: {response.code}," + f" error message: {response.message}." ) - raise RuntimeError(err_msg) + raise RuntimeError(error_msg) # step3: record the model api invocation if needed self._save_model_invocation( @@ -388,7 +389,13 @@ def __call__( ) if response.status_code != HTTPStatus.OK: - raise RuntimeError(response) + error_msg = ( + f" Request id: {response.request_id}," + f" Status code: {response.status_code}," + f" error code: {response.code}," + f" error message: {response.message}." + ) + raise RuntimeError(error_msg) # step3: record the model api invocation if needed self._save_model_invocation( diff --git a/src/agentscope/models/openai_model.py b/src/agentscope/models/openai_model.py index 0d8455a17..27c2e3e1c 100644 --- a/src/agentscope/models/openai_model.py +++ b/src/agentscope/models/openai_model.py @@ -206,6 +206,9 @@ def __call__( **kwargs, ) + if response.status_code != 200: + raise RuntimeError(response.json()) + # step4: record the api invocation if needed self._save_model_invocation( arguments={ @@ -309,6 +312,9 @@ def __call__( ) raise e + if response.status_code != 200: + raise RuntimeError(response.json()) + # step3: record the model api invocation if needed self._save_model_invocation( arguments={ @@ -393,6 +399,9 @@ def __call__( **kwargs, ) + if response.status_code != 200: + raise RuntimeError(response.json()) + # step3: record the model api invocation if needed self._save_model_invocation( arguments={ From f9628a965788b1295f4626c06f135e3d5e747864 Mon Sep 17 00:00:00 2001 From: qbc Date: Wed, 13 Mar 2024 20:12:15 +0800 Subject: [PATCH 17/21] reset changing openai_model --- src/agentscope/models/openai_model.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/agentscope/models/openai_model.py b/src/agentscope/models/openai_model.py index 27c2e3e1c..0d8455a17 100644 --- a/src/agentscope/models/openai_model.py +++ b/src/agentscope/models/openai_model.py @@ -206,9 +206,6 @@ def __call__( **kwargs, ) - if response.status_code != 200: - raise RuntimeError(response.json()) - # step4: record the api invocation if needed self._save_model_invocation( arguments={ @@ -312,9 +309,6 @@ def __call__( ) raise e - if response.status_code != 200: - raise RuntimeError(response.json()) - # step3: record the model api invocation if needed self._save_model_invocation( arguments={ @@ -399,9 +393,6 @@ def __call__( **kwargs, ) - if response.status_code != 200: - raise RuntimeError(response.json()) - # step3: record the model api invocation if needed self._save_model_invocation( arguments={ From f5cc6188e03a7278c34b1a288b716f34a61330ba Mon Sep 17 00:00:00 2001 From: qbc Date: Wed, 13 Mar 2024 20:14:57 +0800 Subject: [PATCH 18/21] fix test --- src/agentscope/models/openai_model.py | 9 +++++++++ tests/record_api_invocation_test.py | 1 + 2 files changed, 10 insertions(+) diff --git a/src/agentscope/models/openai_model.py b/src/agentscope/models/openai_model.py index 0d8455a17..27c2e3e1c 100644 --- a/src/agentscope/models/openai_model.py +++ b/src/agentscope/models/openai_model.py @@ -206,6 +206,9 @@ def __call__( **kwargs, ) + if response.status_code != 200: + raise RuntimeError(response.json()) + # step4: record the api invocation if needed self._save_model_invocation( arguments={ @@ -309,6 +312,9 @@ def __call__( ) raise e + if response.status_code != 200: + raise RuntimeError(response.json()) + # step3: record the model api invocation if needed self._save_model_invocation( arguments={ @@ -393,6 +399,9 @@ def __call__( **kwargs, ) + if response.status_code != 200: + raise RuntimeError(response.json()) + # step3: record the model api invocation if needed self._save_model_invocation( arguments={ diff --git a/tests/record_api_invocation_test.py b/tests/record_api_invocation_test.py index e5406003d..69309dc08 100644 --- a/tests/record_api_invocation_test.py +++ b/tests/record_api_invocation_test.py @@ -31,6 +31,7 @@ def test_record_model_invocation_with_init( """Test record model invocation with calling init function.""" # prepare mock response mock_response = MagicMock() + mock_response.status_code = 200 mock_response.model_dump.return_value = self.dummy_response mock_response.usage.model_dump.return_value = {} From a5b9648c7636eb5401deb13261ac8697596e84b7 Mon Sep 17 00:00:00 2001 From: qbc Date: Wed, 13 Mar 2024 20:46:34 +0800 Subject: [PATCH 19/21] modify model_type names --- src/agentscope/models/dashscope_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/agentscope/models/dashscope_model.py b/src/agentscope/models/dashscope_model.py index 76edc2d3e..28da562ac 100644 --- a/src/agentscope/models/dashscope_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -232,7 +232,7 @@ def __call__( class DashScopeImageSynthesisWrapper(DashScopeWrapper): """The model wrapper for DashScope Image Synthesis API.""" - model_type: str = "dashscope_imagesynthesis" + model_type: str = "dashscope_image_synthesis" def _register_default_metrics(self) -> None: # Set monitor accordingly @@ -333,7 +333,7 @@ def __call__( class DashScopeTextEmbeddingWrapper(DashScopeWrapper): """The model wrapper for DashScope Text Embedding API.""" - model_type: str = "dashscope_textembedding" + model_type: str = "dashscope_embedding" def _register_default_metrics(self) -> None: # Set monitor accordingly From 46a8df17f6d4f26abfad5b7486d0fa7ef032e655 Mon Sep 17 00:00:00 2001 From: qbc Date: Wed, 13 Mar 2024 20:48:51 +0800 Subject: [PATCH 20/21] modify model_type names --- src/agentscope/models/dashscope_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agentscope/models/dashscope_model.py b/src/agentscope/models/dashscope_model.py index 28da562ac..62c139ff6 100644 --- a/src/agentscope/models/dashscope_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -333,7 +333,7 @@ def __call__( class DashScopeTextEmbeddingWrapper(DashScopeWrapper): """The model wrapper for DashScope Text Embedding API.""" - model_type: str = "dashscope_embedding" + model_type: str = "dashscope_text_embedding" def _register_default_metrics(self) -> None: # Set monitor accordingly From 55fdaa2f244ed17266e0d2472f0a0f5827de0ce3 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Thu, 14 Mar 2024 11:12:48 +0800 Subject: [PATCH 21/21] add warning for deprecated model type --- src/agentscope/models/__init__.py | 7 +++++++ src/agentscope/models/dashscope_model.py | 2 ++ src/agentscope/models/model.py | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/src/agentscope/models/__init__.py b/src/agentscope/models/__init__.py index c4609482a..576d1d6aa 100644 --- a/src/agentscope/models/__init__.py +++ b/src/agentscope/models/__init__.py @@ -61,6 +61,13 @@ def _get_model_wrapper(model_type: str) -> Type[ModelWrapperBase]: return ModelWrapperBase.registry[ # type: ignore [return-value] model_type ] + elif model_type in ModelWrapperBase.deprecated_type_registry: + cls = ModelWrapperBase.deprecated_type_registry[model_type] + logger.warning( + f"Model type [{model_type}] will be deprecated in future releases," + f" please use [{cls.model_type}] instead.", + ) + return cls # type: ignore [return-value] else: logger.warning( f"Unsupported model_type [{model_type}]," diff --git a/src/agentscope/models/dashscope_model.py b/src/agentscope/models/dashscope_model.py index 62c139ff6..b246909c5 100644 --- a/src/agentscope/models/dashscope_model.py +++ b/src/agentscope/models/dashscope_model.py @@ -106,6 +106,8 @@ class DashScopeChatWrapper(DashScopeWrapper): model_type: str = "dashscope_chat" + deprecated_model_type: str = "tongyi_chat" + def _register_default_metrics(self) -> None: # Set monitor accordingly # TODO: set quota to the following metrics diff --git a/src/agentscope/models/model.py b/src/agentscope/models/model.py index 2496120aa..6de9ebf5f 100644 --- a/src/agentscope/models/model.py +++ b/src/agentscope/models/model.py @@ -200,10 +200,15 @@ def __init__(cls, name: Any, bases: Any, attrs: Any) -> None: if not hasattr(cls, "registry"): cls.registry = {} cls.type_registry = {} + cls.deprecated_type_registry = {} else: cls.registry[name] = cls if hasattr(cls, "model_type"): cls.type_registry[cls.model_type] = cls + if hasattr(cls, "deprecated_model_type"): + cls.deprecated_type_registry[ + cls.deprecated_model_type + ] = cls super().__init__(name, bases, attrs)