From 5fdb4f317e0c6823b763f88e547fabfe00ae9ddc Mon Sep 17 00:00:00 2001 From: Koko-ry <2024104299@ruc.edu.cn> Date: Mon, 8 Sep 2025 17:01:35 +0800 Subject: [PATCH] add create model tag (#1487) --- modelscope/hub/api.py | 99 ++++++++++++++++++++++++++++++++++-- modelscope/hub/utils/aigc.py | 18 ++++--- 2 files changed, 108 insertions(+), 9 deletions(-) diff --git a/modelscope/hub/api.py b/modelscope/hub/api.py index 96c576de..8fa8732c 100644 --- a/modelscope/hub/api.py +++ b/modelscope/hub/api.py @@ -297,7 +297,8 @@ class HubApi: 'WeightsName': aigc_model.weight_filename, 'WeightsSha256': aigc_model.weight_sha256, 'WeightsSize': aigc_model.weight_size, - 'ModelPath': aigc_model.model_path + 'ModelPath': aigc_model.model_path, + 'TriggerWords': aigc_model.trigger_words }) else: @@ -309,8 +310,9 @@ class HubApi: json=body, cookies=cookies, headers=self.builder_headers(self.headers)) - handle_http_post_error(r, path, body) - raise_on_error(r.json()) + raise_for_http_status(r) + d = r.json() + raise_on_error(d) model_repo_url = f'{endpoint}/models/{model_id}' # TODO: to be aligned with the new api @@ -320,6 +322,97 @@ class HubApi: return model_repo_url + def create_model_tag(self, + model_id: str, + tag_name: str, + description: Optional[str] = None, + endpoint: Optional[str] = None, + token: Optional[str] = None, + aigc_model: Optional['AigcModel'] = None) -> str: + """Create a tag for a model at ModelScope Hub. + + Args: + model_id (str): The model id in format {owner}/{name} + tag_name (str): The tag name (e.g., "v1.0.0") + description (str): Description of this tag + endpoint: the endpoint to use, default to None to use endpoint specified in the class + token (str, optional): access token for authentication + aigc_model (AigcModel, optional): AigcModel instance for AIGC model tag creation. + If provided, will create an AIGC model tag with automatic parameters. + Refer to modelscope.hub.utils.aigc.AigcModel for details. + + Returns: + str: URL of the created tag + + Raises: + InvalidParameter: If model_id, tag_name, ref, or description is invalid. + ValueError: If not login. + + Note: + model_id = {owner}/{name} + """ + if model_id is None: + raise InvalidParameter('model_id is required!') + if tag_name is None: + raise InvalidParameter('tag_name is required!') + if tag_name.lower() in ['main', 'master']: + raise InvalidParameter( + f'tag_name "{tag_name}" is not allowed. ' + f'Please use a different tag name (e.g., "v1.0", "v1.1", "latest"). ' + f'Reserved names: main, master' + ) + if description is None: + description = f'Tag {tag_name} for model {model_id}' + + # Get cookies for authentication. + cookies = self.get_cookies(access_token=token, cookies_required=True) + if not endpoint: + endpoint = self.endpoint + + owner_or_group, name = model_id_to_group_owner_name(model_id) + + # Set path and body based on model type + if aigc_model is not None: + # Use AIGC model tag endpoint + path = f'{endpoint}/api/v1/models/aigc/repo/tag' + aigc_model.preupload_weights(cookies=cookies, headers=self.builder_headers(self.headers)) + + # Base body for AIGC model tag + body = { + 'CoverImages': aigc_model.cover_images, + 'Name': name, + 'Path': owner_or_group, + 'TagShowName': tag_name, + 'WeightsName': aigc_model.weight_filename, + 'WeightsSha256': aigc_model.weight_sha256, + 'WeightsSize': aigc_model.weight_size, + 'Description': description, + 'TriggerWords': aigc_model.trigger_words + } + + else: + # Use regular model tag endpoint + path = f'{endpoint}/api/v1/models/{model_id}/repo/tag' + revision = 'master' + body = { + 'TagName': tag_name, + 'Ref': revision, + 'Description': description + } + + r = self.session.post( + path, + json=body, + cookies=cookies, + headers=self.builder_headers(self.headers)) + + raise_for_http_status(r) + d = r.json() + raise_on_error(d) + + tag_url = f'{endpoint}/models/{model_id}/tags/{tag_name}' + return tag_url + def delete_model(self, model_id: str, endpoint: Optional[str] = None): """Delete model_id from ModelScope. diff --git a/modelscope/hub/utils/aigc.py b/modelscope/hub/utils/aigc.py index 7be6a7c9..f0b218a7 100644 --- a/modelscope/hub/utils/aigc.py +++ b/modelscope/hub/utils/aigc.py @@ -48,10 +48,12 @@ class AigcModel: # Supported base model types for reference BASE_MODEL_TYPES = { - 'SD_1_5', 'SD_XL', 'SD_3', 'FLUX_1', 'WAN_VIDEO_2_1_T2V_1_3_B', - 'WAN_VIDEO_2_1_T2V_14_B', 'WAN_VIDEO_2_1_I2V_14_B', - 'WAN_VIDEO_2_1_FLF2V_14_B', 'WAN_VIDEO_2_2_T2V_5_B', - 'WAN_VIDEO_2_2_T2V_14_B', 'WAN_VIDEO_2_2_I2V_14_B', 'QWEN_IMAGE_20B' + 'WAN_VIDEO_2_1_14_B', 'SD_XL', 'SD_2', 'SD_3', + 'WAN_VIDEO_2_1_T2V_1_3_B', 'UNKNOWN', 'WAN_VIDEO_2_2_TI2V_5_B', + 'WAN_VIDEO_2_2_I2V_A_14_B', 'WAN_VIDEO_2_1_I2V_14_B', + 'QWEN_IMAGE_20_B', 'SD_2_1', 'SD_1_5', 'FLUX_1', + 'WAN_VIDEO_2_2_T2V_A_14_B', 'WAN_VIDEO_2_1_T2V_14_B', + 'WAN_VIDEO_2_1_FLF2V_14_B' } def __init__(self, @@ -62,7 +64,8 @@ class AigcModel: revision: Optional[str] = 'v1.0', description: Optional[str] = 'this is an aigc model', cover_images: Optional[List[str]] = None, - path_in_repo: Optional[str] = ''): + path_in_repo: Optional[str] = '', + trigger_words: Optional[List[str]] = None): """ Initializes the AigcModel helper. @@ -75,6 +78,7 @@ class AigcModel: cover_images (List[str], optional): List of cover image URLs. base_model_id (str, optional): Base model name. e.g., 'AI-ModelScope/FLUX.1-dev'. path_in_repo (str, optional): Path in the repository. + trigger_words (List[str], optional): Trigger words for the AIGC Lora model. Note: Auto-upload during AIGC create is temporarily disabled by server. This parameter will not take effect at creation time. """ @@ -88,6 +92,7 @@ class AigcModel: ] self.base_model_id = base_model_id self.path_in_repo = path_in_repo + self.trigger_words = trigger_words # Validate types and provide warnings self._validate_aigc_type() @@ -315,7 +320,8 @@ class AigcModel: 'model_path': self.model_path, 'weight_filename': self.weight_filename, 'weight_sha256': self.weight_sha256, - 'weight_size': self.weight_size + 'weight_size': self.weight_size, + 'trigger_words': self.trigger_words } @classmethod